2005-08-08 (Mon)

onsubmit で disable にするやつ

フォームを送信した時に、submit ボタンを disabled にして二重送信を防ぎましょう、というのがナウなヤングにバカウケ?

そんでもって、ここに書いてあるやつ なら、そこの JavaScript を読み込ませるだけで、自動的に全てのフォームに対して設定が行なわれるので便利。中止ボタン対策もしてあってグッド。

ただ、Deer Park な Firefox で試した所、戻るボタンで戻った場合は disabled が解除されないし、中止ボタンでの復活も効かない様子。

なので、書き直してみました。

var DisableSubmit = {
   init: function() {
      this.addEvent(window, 'load', this.set());
   },

   set: function() {
      var self = this;
      return function() {
         for (var i = 0; i < document.forms.length; ++i) {
            if(document.forms[i].onsubmit) continue;
            document.forms[i].onsubmit = function() {
               self.setDisable(this.getElementsByTagName('input'));
            };
         }
      }
   },

   setDisable: function(elms) {
      for (var i = 0, elm; elm = elms[i]; i++) {
         if ((elm.type == 'submit' || elm.type == 'image') && !elm.disabled) {
            Set(elm);
            unSet(elm);
         }
      }

      function Set(button) {
         window.setTimeout(function() { button.disabled = true; }, 1);
      }
      function unSet(button) {
         window.setTimeout(function() { button.disabled = false; }, 1000);
      }
   },

   addEvent: function(elm, type, event) {
      if(elm.addEventListener) {
         elm.addEventListener(type, event, false);
      } else if(elm.attachEvent) {
         elm.attachEvent('on'+type, event);
      } else {
         elm['on'+type] = event;
      }
   }
}

DisableSubmit.init();

普通に読み込んでもらえれば、勝手に機能が追加されます。

あとは、 submit のパラメータを hidden に指定する 機能を入れればかなり良さそうかな?導入した。

disabled にして欲しくないフォームへの対応も欲しいかなー。

* 2006-07-08 追記

miau's blog? で指摘されていた通り、type='image' で動作しないのと、window.onload を上書きしている問題があったため、そのままコピペして使っても問題が生じる場合があったので、この問題の修正を行ないました。

特に onload に関しては DisableSubmit の中に組み込んでおいたので、今後は本当にそのままコピペしても何も気にせず使えるようになりました。

* 更新履歴

  • 2005-08-10 - 少しだけシンプルにしました。
  • 2005-08-16 - submit フォームの値も送信出来るように。
  • 2006-07-08 - Asarimaさん の指摘通りの修正。type='image' も動作するように。window.onload を上書きしないように

Posted by Kyosuke Takayama at 2005-08-08 (Mon) 23:30 printable version

この記事へのコメント

1) Asarima (2005-08-20 (Sat) 21:56)

setDisableですが、元々disabledだったボタンまで1秒後に復活してしまって困りました。
そこで、「if (elm.type == 'submit') {」の後に、「if (!elm.disabled) {」を追加するといい感じになりました。

2) takayama (2005-09-01 (Thu) 15:58)

なるほど、その方が良いのかもしれませんね。

3) miau (2006-07-08 (Sat) 12:52)

早速対応してくださったようでありがとうございます。
elm.type の条件は追加しましたが、基本的にそのままコピペでいけました。

4) PoohKid (2007-03-07 (Wed) 17:25)

いつも参考にさせていただいています。ありがとうございます。
既存のonsubmitが存在するとスキップされてしまうのですね。
Validatorと併用したかったので次のようにしてみました。

var fnc = document.forms[i].onsubmit;
document.forms[i].onsubmit = function() {
if(fnc() == false) return false;
self.setDisable(this.getElementsByTagName('input'));
};

これなら先に入力チェックが有効になるかな、と。
実際は既存のonsubmitが無い場合も考慮しないといけないですね。

5) takayama (2007-03-07 (Wed) 23:43)

そうなんですよ、eventListener で登録したイベントにも対応しないといけないから
ちゃんと対応しようとすると最初から書き直したくなるので、放置してました。
Validator かわいがってやって下さい。

6) sano (2007-07-03 (Tue) 17:38)

はじめまして。
あるスクリプトに埋め込んでみたら、フォームの「戻る」ボタン(ブラウザのじゃなくてフォーム内の)が効かなくなってしまいました。
ソースを見たところどうやら、「戻る」ボタンもsubmitで(つまりsubmitが2つ)、name属性でどちらが押されたのか判別しているようで、disabledにするとこのnameが取得できなくなってしまうようです(IEとfirefox)。
ようするに、無効にする⇒フォーム送信、という順番に問題があるようで…。
仕方がないのでとりあえず、送信してからdisabledさせるため、イベントハンドラからタイマーで0.1秒後に本処理を呼び出す、というようにして対応しました。

7) takayama (2007-07-03 (Tue) 19:51)

具体的にどのような対応をしたのか教えてもらえませんか?

上のスクリプトは、既に遅延実行型の動作(1ミリ秒後に実行)になってると思うので、何か別の不具合の気がします。

8) sano (2007-07-06 (Fri) 00:33)

どうも元からあるハンドラが入力データチェックをしていて、その処理のためにタイミングがずれたようです。
まぁこんなのはレアなケースだと思います。
それより話は変わるのですが、二重送信してしまうケースは、おそらく2パターンだと思います。
1.連続してクリック、ダブルクリックしてしまう。
2.なかなか結果が表示されず、待ちきれずにもう一度クリックしてしまう。
この1に対しては、ご提示されている1秒間ボタンを無効にする方法が有効ですが、
2のほうに対しては、逆効果になってしまうこともあって、やはり難しいですね…。
つまり、一度グレイになってからまた元に戻るのを見たら、「何か処理をしてそれが終わった」かのようにも見えてしまうようで、それでも画面全体の様子には変化がないため、何か失敗したのかな?と、もう一度クリックしてしまう、というケースが多いようです。
無効にしておく時間を長くすればいいのか、でも具体的な間隔は不定だし…、IEのreadyStateのような、送信を中止したことが判る方法があればいいんですよねぇ…

9) sano (2007-07-06 (Fri) 00:51)

すみません、上の「IEのreadyStateのような」って意味不明でしたね。
ようするに、送信後の待ち状態なのか、キャンセルしたのか、などが判る方法、と言いたかったのでした^^;

10) takayama (2007-07-07 (Sat) 09:06)

そうですね、(2) が問題なんでよね。その辺がうまく処理出来ればユーザを混乱させずに済みそうです。

戻るボタンに関しては、onload 時に disabled を解除するようにするような処理を入れると出来そうな気がします。
中止ボタン系も、イベントの取得が出来ればうまく出来そうですね。

11) sano (2007-07-08 (Sun) 15:55)

なんどもすみません。
ここ数日「しいたけ問題」に取り組んでいて、
サブミット時に別途画像の読み込みを開始させて、中止ボタンが押されたら「画像の読み込みキャンセル」でイベントをつかまえよう、とか、中止ボタンが押されるとアニメーションが止まるので定期的にチェックしてはどうか、などなど試してみましたが、お察しのとおりどれもダメ…、画像読み込みなんてそもそも画像のサイズに依存するので愚策もいいところ…。
という具合でしたが、いろいろ踏まえて一つの結論に達しました。

そもそも私の目的は「二重送信を防止すること」だったわけです。
中止ボタンが押されたときの状況とは、2つあると思います。

1.「読み込み」の中止
おそらくほとんどすべての状況で、ごく普通のフォームでサブミットボタンを押すと(よほどの通信障害とかない限り)フォームデータの「送信」自体は一瞬にして完了し、そしてその後サーバーからの応答を待つ状態になります。
中止ボタンが押されるのは、この待機中のことで、つまり送信はすでに完了しているわけで(通常httpdは処理している)、この時点で中止ボタンを押してから復活したサブミットボタンを再び押されてしまうということは、これは二重送信に他ならない、ということなんですね。
ですから、このケースでは、たとえ中止ボタンを押そうが、もう一度サブミットボタンを押されては困る、したがってボタンを復活させてはいけないのでは、と考えました。
その場合困ることといえば、画面遷移ですよね。本来見るはずの「結果画面」が表示されない、そこで、何らかの方法で代替の、たとえば結果を確認できるページへのリンクを用意しておくとか、対応はできると思います。
若干の不自然さや面倒はあるものの、中止ボタンが押されることはあまり多くないことと、二重送信されてしまうことの弊害に比べたらマシ、とも考えられます。

問題はもうひとつのほう。
2.「送信」の中止
中止ボタンによって、本当に送信自体が中止されるケースももちろんあると思います。ただし、ファイルアップロードなど何らかの巨大データを含むフォームに限られます。
このケースは、中止されたらやはり復活させる必要がありますよね。しかしずっと話されているようにこれが難しいわけで…。

これらを踏まえて、フォームの種類によって変える、というのが今のところ最善策と考えました。
1の通常フォームのほうが多いでしょうから、この場合はボタンを復活させない、
2のようなケースでは、もし完全を求めるならもはやクライアント側だけでは不可能なので、もしやるなら、たとえばフォーム表示時にユニークなIDをhiddenフィールドに入れておいて、サーバー側でこれを見て二重送信を判別する、など…。

と、こんな風に考えてみたのですが、何かツッコミなどあればお願いいたします。

コメントはお気軽にどうぞ

Cookie に保存しますか?


・スパム対策のため、http:// を含むコメントの投稿は出来ません。
・スパムチェックのため、投稿がすぐに反映されない場合があります。
・メールアドレス入力欄には何も入力しないでください。

トラックバック

トラックバックURL: http://espion.just-size.jp/mt-tb.cgi/519

鷹の島: onsubmit => ボタン disable の機能追加とか (2005-08-16 (Tue) 21:26) 0%

onsubmit で disable にするやつ ですが、submit のパラメータが送信出来る機能が必要になったので、この機能を導入したものに代えました。 submit ボタン disable 技の罠のコメント欄 に書いてある方法がシンプルに導入出来そうだったので、このアイディアをパクってきま...


鷹の島: JavaScript ミニライブラリ (2006-07-17 (Mon) 18:24) 0%

ちょっと前から「なくても不便じゃないけど、あったらちょっと便利かもね」っていう JavaScript を書いたりして発表していましたが、一覧とか作ってないからどんなのがあるのかわかりにくかったのでまとめてみました。 onsubmit で disable 以外のスクリプトは全てパブリ...



この記事のリンク元

 
Copyright (C) Kyosuke Takayama, All rights reserved.