二度押し防止の onsubmit で disable にするやつ

もう2年ほど前に話題になったアレなんですけど、今更ながらあるサービスでこの仕組みの導入を検討しています。

img02.jpg

- スポンサーリンク -

onsubmit の disable 化ですが既に議論が終わっているように、onsubmit disable の実装方法として、

  1. onsubmit イベント発生時に submit 要素を disable にして値をサーバへ渡すための hidden 要素を生成する方法
  2. setTimeout() でボタン押下後(=サーバへデータがとんだ直後)に disable を仕掛ける方法

がありますが、次の2点の問題が考えられます。

  1. 中止ボタンを押された際に disable のままになる
  2. history.back() やブラウザの戻るボタンで戻ったときにボタンが disable になったまま表示されるブラウザ(firefox/safari)がある。

いま導入を検討しているサービスでは history.back() で戻るボタンが簡易的に実装されている部分があったり、ブラウザの戻るボタンで戻ることも可能な実装になっている部分もあったりで上記2点を解消しなければなりません。


中止ボタン押下時にボタンの問題を解決する方法は onsubmit で disable にするやつ - 鷹の島 の実装方法の通り setTimeout() で一定時間後に disable = false; を仕掛けておく方法しかなさそうです。もっともタイマーを何秒後に設定するかはサービス毎にチューニングしないとアレですし、中止ボタンを押下直後に有効になるわけでもないので、これまたちょっとアレですが一定時間後にはリロードしなくてもボタンの disable が解除されるので再度 submit をすることが可能になるってか。

history.back() の問題を解決する方法はonload を使ってページアウト時に各種フォームを元の状態に戻しておくことで解決可能です。

ってことで、既出の onsubmit disable の方法で上記2つを満たすものはなかったので自分でアレコレ検証しながら実装していました。で、ふと思い返して現行のはてブは history.back() ってかブラウザの戻るボタンを押しても disable が解除されてるなぁ〜と思い、どうやって解決してるんだろう〜とコードを追っていったらこんな感じでした。

<script type="text/javascript" src="/js/utility.js"></script>
<script type="text/javascript" src="/js/prototype-1.4.0.js"></script>
<script type="text/javascript" src="/js/keybind.js"></script>
<script type="text/javascript" src="/js/pin.js"></script>
<script type="text/javascript" src="/js/bookmark.js"></script>

onsubmit disable の処理は utility.js が担当しています。

function disableSubmit(form) {
  var elements = form.elements;
  for (var i = 0; i < elements.length; i++) {
    if (elements[i].type == 'submit' || elements[i].type == 'button') {
      elements[i].disabled = true;
    }
  }
}

function setHiddenValue(button) {
    if (button.name) {
    var q = document.createElement('input');
    q.type = 'hidden';
    q.name = button.name;
    q.value = button.value;
    button.form.appendChild(q);
    }
}

でもって、form タグで onsubmit="disableSubmit(this)" として submit を disable しているのですが unload 処理での初期化部分が見あたりません。上記 JS を1つずつ削っていったら prototype.js が初期化をしてくれていました。おぉ〜 prototype.js 賢いよぉ〜

ver 1.4.0 でいえば 1550 行目付近になるわけですが、

/* prevent memory leaks in IE */
Event.observe(window, 'unload', Event.unloadCache, false);

ってコードがあって Event.unloadCache って部分が元の状態に戻す処理をしています。試しに上記コードをコメントアウトしてみたら、しっかりと submit ボタンが disable されたままになりました。


さて、そんなこととは別に僕はこんなコードを書いて実装を完了していました。二度押し対策、中止ボタン対策、history.back() 対策を全て実装したつもりです。いろいろな謎が解明されたので頭がスッキリしました。

var DisableSubmit ={
    arg: {},
    timer: 1000,

    init: function(arg, timer){
        if (typeof(arg) == "object") this.arg = arg;
        if (timer) this.timer = timer;
        this.addEvent(window, 'load', this.setEvent(), false);
        this.addEvent(window, 'unload', this.setEnable(), false);
    },

    addEvent: function(elem, eventType, fn, useCapture){
        if (elem.addEventListener){
            elem.addEventListener(eventType, fn, useCapture);
            return true;
        }
        else if (elem.attachEvent){
            var r = elem.attachEvent('on' + eventType, fn);
            return r;
        }
        else{
            elem['on'+eventType] = fn;
        }
    },

    setEvent: function(){
        var self = this;
        return function(){
            if (self.arg.name){
                for (var i = 0; i < self.arg.name.length; i++){
                    var elem = document.getElementsByName(self.arg.name[i]);
                    if (elem){
                        for (var j = 0; j < elem.length; j++){
                            self.addEvent(elem[j], 'click', self.setDisable(elem[j]), false);
                        }
                    }
                }
            }
            if (self.arg.id){
                for (var i = 0; i < self.arg.id.length; i++){
                    var elem = document.getElementById(self.arg.id[i]);
                    if (elem){
                        self.addEvent(elem, 'click', self.setDisable(elem), false);
                    }
                }
            }
        }
    },

    // history.back() 対策でページアウトするするときにボタンを再度enable化しておく
    setEnable: function(){
        var self = this;
        return function(){
            if (self.arg.name){
                for (var i = 0; i < self.arg.name.length; i++){
                    var elem = document.getElementsByName(self.arg.name[i]);
                    if (elem){
                        for (var j = 0; j < elem.length; j++){
                            elem[j].disabled = false;
                        }
                    }
                }
            }
            if (self.arg.id){
                for (var i = 0; i < self.arg.id.length; i++){
                    var elem = document.getElementById(self.arg.id[i]);
                    if (elem){
                        elem.disabled = false;
                    }
                }
            }
        }
    },

    // 二度押し防止対策でボタンを 1mm-sec 後にdisable化しておく。これで hidden データ作成しなくても get/post が正常処理される
    setDisable: function(elem){
        var self = this;
        var func = elem.onclick;
        elem.onclick = "";
        return function(evt){
            var elem = evt.srcElement || evt.target;
            window.setTimeout(function(){ elem.disabled = true; }, 1);
            if (func) window.setTimeout(function(){ func(); }, 2);
            window.setTimeout(function(){ elem.disabled = false; }, self.timer);
        }
    }
}

僕的な使い方で全自動に submit disable を効かせたくなかったので、name または id で disable するヶ所を指定できる作りにしています。

name 値が "back_button" と "submit_button" のボタンと id="submit_id" のボタンに対してイベントを設定したい場合はこんな感じ。disable を解除するのは3秒後。

DisableSubmit.init( { "name":["back_button","submit_button"], "id":["submit_id"] }, 3000 );

id だけで指定したい場合はこんな感じ。

DisableSubmit.init( { "id":["submit_id"] }, 3000 );

name だけで指定したい場合はこんな感じ。

DisableSubmit.init( { "name":["back_button","submit_button"] }, 3000 );

ちなみに unload ではなくて beforeunload で処理すると unload よりも早いタイミングで処理できるのですが beforeunload が実装されていない Opera とかがあるので unload 処理としています。

一応テストプログラムも置いておきます。

1. 戻るボタンで再表示できないヤツ
2. 戻るボタンで再表示できなるヤツ
3. 1秒後に再表示されるヤツ


- スポンサーリンク -