HTML FORMでEnterキーによるsubmitを抑止する方法

幾つかの入力フィールドが存在するFORMで、text fieldやpassword fieldでEnterを誤って入力した際にsubmitされてしまうのはいささか不便です。入力フィールドが一つしかないformの場合など、便利なケースもありますが、多くのユーザーが入力ミスをする事が予測できるので、この挙動をdisableに出来ると良いと思います。 残念ながら、HTMLにはこの挙動を制御する方法が無く、各ブラウザの実装に任されています。W3CのHTML5の仕様にある4.10.22.2 Implicit submissionに基いていると思われます。

この仕様の元で暗黙的submitをされないようにする方法について検討してみました。

そもそもImplicit submissionとは?

前述のドキュメントを要約すると、次のとおりです。

HTMLの標準動作で暗黙的サブミットを抑止する方法

サブミットされないケースを意図的に作れば良いわけですが、あまり見栄えの良い方法は見付かりません。

type=textの入力フィールドが1つの場合はsubmitされる仕様になっているブラウザが多いようです。

Java script(jQuery)を使う方法

ネットで検索すると、(2016/4/15時点の)定石は次の方法のようで、キー入力をフックして不要なフィールドにはEnter(key code=13)が入力されないようにするということらしい。しかしながら、このコードを実行するにはjQueryが必要です。

$(function() {
  $("input:not(.allow_submit)").on("keypress", function(){
    return event.which !== 13;
  });
});

Java script(ネイティブ)での実装

今時、jQueryとか使わなくてもできるんじゃないの?
と思ったので少し調べてみたところ、 Document Object Model(DOM) APIでイベントをフックしたり、内容を調べてコントロールすればブラウザの機能だけで実現できそうなことがわかりました。

onKeyPressで関数をcallする

まず、ドキュメントに手を入れて構わないのであれば、onKeyPressアトリビュートを使った方法が簡単です。returnでfalseを返すと、イベントが無視されますので、ここにEnter(key cole=13)ならfalseになる様な関数をセットすればよいです。勿論、js内で定義した関数を呼び出して、より複雑な処理を行うこともできます。

<form method="post" action="nosubmit.html" enctype="multipart/form-data" class="index">
    <input type = "text" name = "text" onKeyPress="return(event.keyCode !==13)"/>
    <input type="submit" name=".submit"/>
</form>

submitイベントを拾って、暗黙的サブミットなら無視する事はできるか?

次に、ドキュメントに手をつけず、全てのsubmitイベントを拾って、Enter入力によるものを排除する方法について検討しましたが、メカニズム上、ブラウザ側でsubmitボタンを押したのと同一のイベントを発生させているようで、event.targetの情報などから区別する事はできませんでした。
よって、submitを一様に禁止して、ボタンのクリックを拾うしかありません。

イベントハンドラを用いた方法

イベントハンドラの登録はaddEventListenerを使います。デフォルトの処理を停止するにはpreventDefault()関数を使います。よって、submitの禁止は次のように行えます。
document.addEventListener("submit", function(e){e.preventDefault();}, false);

同じように、mousedownイベントのハンドラを特定のボタンに対して設定します。
イベントハンドラの設定対象は例えばつぎのようにしてエレメントを指定して行うことができます。
document.getElementById("i3").addEventListener("mousedown", submit_func, false);

関数submit_funcがボタンを押すと呼ばれるので、この中でsubmitを発生させます。
submitには、formエレメントのsubmit()メソッドを使うので、予めドキュメント内のformエレメントを特定しておく必要があります。
例えば、nameやidアトリビュートなどを用います。

以上で示した例は、何れもidやnameが決っている場合ですが、idが無い場合や動的なドキュメントにも対応するためには、ドキュメント内のエレメントを検索して、submitボタンを見付けてイベントハンドラを設定する必要があります。

余談ですが、ドキュメントが読み込まれてからでないとドキュメントの評価ができないので、DOMContentLoadedイベントでイベントハンドラの設定を行う関数を呼ぶ必要があります。

ここまでの説明をふまえて作成したのが次のプログラムです。

<html>
<head>
<script>
function load(){
    // submitイベントの阻止
    document.addEventListener("submit", function(e){e.preventDefault();}, false);
    // ドキュメント内のinputエレメントを探す
    var el = document.getElementsByTagName('input');
    for(var i=0; i<el.length; i++){
       if(el[i].type != 'submit'){      // type = submitのものがsubmitボタン
           continue;
       }
       var input = el[i];
       var form = input.parentNode;     // submitボタンの親のformエレメントの取得
       input.addEventListener("mousedown", form.submit(), false);
    }
}
// ドキュメントの読み込み完了を待ってイベントハンドラの登録開始
document.addEventListener("DOMContentLoaded", load, false);
</script>
</head>
<body>
  <form>
    <input type=text name='name1'>
    <input type=text name='name2'>
    <input type=submit name=".submit">
   </form>
  <form>
    <input type=text name='name3'>
    <input type=text name='name4'>
    <input type=submit name=".submit">
   </form>
</body>
</html>

残念ながら、このページはブラウザに読み込ませると無限ループします。イベントハンドラを登録する際、form.submit()が評価されてしまうためです。
書式的には、次のようにすれば実行されませんが、これだと、無名関数が上書きされるためか、formがハンドラ実行時に評価されるためか、最後に定義したハンドラが、全てのボタンに設定されてしまいます。

       input.addEventListener("mousedown", function (){form.submit();}, false);

最終的には、次のように引数でformエレメントを渡すようにしたところ正しく動作するようになりました。

function form_submit(e){
    return function() {
        e.submit();
    }
}
function load(){
    document.addEventListener("submit", function(e){e.preventDefault();}, false);
    var el = document.getElementsByTagName('input');
    for(var i=0; i<el.length; i++){
       if(el[i].type != 'submit'){
           continue;
       }
       var input = el[i];
       var form = input.parentNode;
       input.addEventListener("mousedown", form_submit(form), false);
    }
}
document.addEventListener("DOMContentLoaded", load, false);

こちらがこのプログラムを用いたサンプルです。

もう一工夫

前述の例では、submitボタンにnameやvalueを持たせていなかったので問題は無かったのですが、受信側にこれらのパラメータが入ってこない事がわかりました。仕様だか不具合だかよくわかりませんが、イベントをfookしてしまうと、そのサブミットボタンは存在しないものとして扱われるようです。

つまり次のようなformだと問題が生じます。
  <form>
    <input type=text name='name1'>
    <input type=text name='name2'>
    <input type=submit name=action value=A>
   </form>
  <form>
    <input type=text name='name3'>
    <input type=text name='name4'>
    <input type=submit name=action value=B>
   </form>

これを避けるためには、自動的にhiddenフィールドを追加して、submitボタンに該当するnameやvalueを設定しておく必要があります。
問題動作の確認サンプルがこちら、イベントハンドラの登録の際に、DOMに追加するようにしたものがこちらになります。