ページへ戻る

− Links

 印刷 

[jQuery] Web ページのフォーム投稿時問題のあれこれを上手いことするやつ :: XOOPS マニア

UsersWiki:nao-pon/blog/2015-04-03


RSS of nao-pon/blog[5]
2015 4月 3 (金)
 

[jQuery] Web ページのフォーム投稿時問題のあれこれを上手いことするやつ anchor.png[6]

Tag: プログラミング[7] jQuery[8] JavaScript[9]

長文の投稿を書いている時に、間違ってブラウザ閉じてしまったり、ページ移動してしまったり、送信したらサーバーとの接続が切れてしまった!

OH MY GOD!

会心の出来だ!と喜んだのも束の間、奈落の底に突き落とされるような思いをすることが、たまにありますよね。

このような事態にみまわれると、投稿分の出来がよければよいほど、立ち直るのにしばらく時間が掛かってしまいます。

そんな、思いをしないため、させないために、ちょっとゴリゴリ jQuery で書いてみました。

Textarea復原ボタン.png[10]

移動してもいい?.png[11]
テキストエリアに入力中にページ移動しようとしたりしてページが閉じられる前にブラウザが閉じていいのかを聞いてくれます。
さらに、万が一閉じてしまったり送信時にサーバーと接続できなかったりした時でも、次に開いた時にテキストエリアの下に「復元」ボタンが表示されるので、それをクリックすれば、ほらっ元通り!

<script type="text/javascript" charset="utf-8" src="hyp_form_rescue.js"></script>

などと、UTF-8 指定で <head> 内の jQuery を読み込んだ後に置くだけで OK です。 :ok:

filehyp_form_rescue.js[12]
Everything is expanded.Everything is shortened.
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
-
|
|
|
|
|
!
-
|
-
-
|
|
!
!
-
-
-
-
|
!
!
!
|
|
|
!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
!
|
|
-
|
!
-
|
!
|
|
-
|
|
-
|
|
|
!
|
|
!
-
|
!
-
|
!
-
|
|
-
|
|
|
|
-
-
|
!
!
-
|
|
-
|
|
!
|
!
-
|
|
!
|
|
-
|
|
|
-
|
-
-
|
|
|
|
!
-
-
|
|
|
!
!
!
|
|
|
|
-
-
|
|
!
!
-
-
-
|
|
!
!
!
!
!
!
-
!
-
-
|
|
-
|
-
|
|
|
!
!
!
|
!
!
/*!
 * Web ページのフォーム投稿時問題のあれこれを上手いことするやつ
 * 
 * Copyright (c) 2015 Naoki Sawada hypweb.net
 * Released under the MIT license
 * http://opensource.org/licenses/mit-license.php[13]
 */
jQuery(function($){
    /** 二重投稿防止用 **/
    $('form:not([target])').each(function(){
        if (this.onsubmit) {
            $(this).data('hyp_onsubmit', this.onsubmit.bind(this));
            this.onsubmit = null;
        }
    });
    $(window).on('submit', 'form:not([target])', function(e){
        if ($(this).data('hyp_onsubmit')) {
            try {
                if (!$(this).data('hyp_onsubmit')()) {
                    return false;
                }
            } catch(e){}
        }
        var btn = $(this).find('input[type="submit"]');
        setTimeout(function(){btn.prop('disabled', true)},100);
        setTimeout(function(){btn.prop('disabled', false);},20000);
    });
 
    /** 入力途中のページ移動防止とテキストエリアに「復元」ボタン追加 **/
    var
    /* ページを閉じるときのアラートメッセージ(Firefox は指定メッセージは表示されない) */
    msg   = 'テキストエリアに入力中のデータがあります。\n\n※ 次回、同様のページを開いた時にテキストエリアの内容は[復元]ボタンで復元できます。',
    /* ボタンテキスト */
    btnTx = '復元',
    /* ボタンの title 属性値 */
    btnTi = '最後に入力したデータを復元します。\nテキストエリアが未入力の場合に利用できます。'
    /* ボタン CSS クラス */
    btnCl = 'hypRescueBtn',
    /* 自動保存(秒): 初回2分後、その後30秒毎 (及びページ移動時) */
    autoS = [120000, 30000],
    /* textarea 入力中フラグ */
    flgC  = 'hyp-changed',
    /* localStorage の保存キー */
    dataK = 'hyp-textarea-rescue',
    /* CSS */
    css   = (function () {/*
form div.hypRescueBtn {
	float: right;
	width: auto;
	font-size: 11px;
	margin-top: 2px;
	margin-right: 1px;
	padding: 1px 5px;
	border: 1px solid gray;
	-moz-border-radius: 4px;
	-webkit-border-radius: 4px;
	border-radius: 4px;
	cursor: pointer;
	background: #fcfff4;
	background: -moz-linear-gradient(top,  #fcfff4 27%, #dfe5d7 93%, #b3bead 100%);
	background: -webkit-gradient(linear, left top, left bottom, color-stop(27%,#fcfff4), color-stop(93%,#dfe5d7), color-stop(100%,#b3bead));
	background: -webkit-linear-gradient(top,  #fcfff4 27%,#dfe5d7 93%,#b3bead 100%);
	background: -o-linear-gradient(top,  #fcfff4 27%,#dfe5d7 93%,#b3bead 100%);
	background: -ms-linear-gradient(top,  #fcfff4 27%,#dfe5d7 93%,#b3bead 100%);
	background: linear-gradient(to bottom,  #fcfff4 27%,#dfe5d7 93%,#b3bead 100%);
	filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fcfff4', endColorstr='#b3bead',GradientType=0 );
}
form div.hypRescueBtn:hover {
	padding: 0px 4px;
	border: 2px solid orange;
	border-top-color: gold;
	border-left-color: gold;
}
form div.hypRescueBtn.disabled {
	color: #aaa;
	cursor: default;
}
form div.hypRescueBtn.disabled:hover {
	border: 1px solid #808080;
	padding: 1px 5px;
}
*/}).toString().match(/\/\*([^]*)\*\//)[1];
 
    var htr = {},
    isEmpty = function(val) {
        return val.match(/^\s*$/);
    }
    try{
        htr = JSON.parse(localStorage.getItem(dataK));
    }catch(e){}
    if (!htr || typeof htr != 'object') htr = {};
    $('<style>').append(css).appendTo($('head')[0]);
    $(window).on('beforeunload', function(){
        if (typeof localStorage == 'undefined') return;
        var ret;
        $('form textarea').each(function(){
            var tArea = $(this);
            if (tArea.data(flgC) && !isEmpty(tArea.val())) ret = true;
            if (!isEmpty(tArea.val())) htr[this.name] = tArea.val();
        });
        localStorage.setItem(dataK, JSON.stringify(htr));
        if (ret) return msg;
    });
    $('body').on('change', 'form textarea', function(){
        $(this).val() && $(this).data(flgC, true);
    });
    $('body').on('submit', 'form', function(){
        $(this).find('textarea').data(flgC, false);
    });
    $('body').one('focus', 'textarea', function(){
        var tArea = $(this),
            flg   = 'hyp_rescue';
        if (!tArea.data(flg)) {
            var res,cur,ck,
            ckArea = $('#cke_' + tArea.attr('id'))[0],
            name = this.name,
            bef  = null,
            tgl  = function(on){
                if (res) {
                    on? res.removeClass('disabled') : res.addClass('disabled');
                }
            },
            save = function(){
                ck = ckArea? CKEDITOR.instances[tArea.attr('id')] : null;
                ck && tArea.val(ck.getData());
                if (!isEmpty(tArea.val())) {
                    htr[name] = tArea.val();
                    localStorage.setItem(dataK, JSON.stringify(htr));
                }
                setTimeout(save, autoS[1]);
            };
            if (ckArea || typeof CKEDITOR == 'object') {
                ckArea = $(ckArea);
                ck = CKEDITOR.instances[tArea.attr('id')];
            }
            tArea.data(flg, true).change();
            setTimeout(save, autoS[1]);
            if (htr[name]) {
                res = $('<div>')
                .addClass(btnCl)
                .attr('title', btnTi)
                .on('click', function(){
                    ck = ckArea? CKEDITOR.instances[tArea.attr('id')] : null;
                    if (ck) {
                        if (isEmpty(ck.getData())) {
                            (tArea.data('editor') == 'html')? ck.insertHtml(htr[name]) : ck.insertText(htr[name]);
                            tArea.data(flgC, true);
                            ck.focus();
                            tgl(false);
                        }
                    } else {
                        if (isEmpty(tArea.val())) {
                            tArea.val(htr[name]).data(flgC, true);
                            tArea.focus();
                            tgl(false);
                        }
                    }
                })
                .append(btnTx)
                .insertAfter(ckArea|| tArea);
                if (!(bef = isEmpty(tArea.val()))) res.addClass('disabled');
                res.fadeIn(750).fadeOut(250).fadeIn(750);
                tArea.on('keyup change', function(){
                    if (bef !== (cur = isEmpty(tArea.val()))) {
                        bef = cur;
                        tgl(bef);
                    }
                });
                if (ck) {
                    (tArea.data('ckon') || ck.on)('change', function(){
                        if (bef !== (cur = isEmpty(ck.getData()))) {
                            bef = cur;
                            tgl(bef);
                        }
                    });
                }
            }
        }
    });
    // for Ajax post
    var oldSend = XMLHttpRequest.prototype.send;
    XMLHttpRequest.prototype.send = function(){
        if (arguments[0] && arguments[0].split) {
            var q = arguments[0].split('&'),
            i = 0, t;
            for(i; i < q.length; i ++) {
                t = q[i].split('=');
                $('form textarea[name=\''+t[0]+'\']').each(function(){
                    var tArea = $(this);
                    tArea.data(flgC, false);
                    if (!isEmpty(tArea.val())) htr[t[0]] = tArea.val();
                });
            }
        }
        oldSend.apply(this, arguments);
    };
});
Page Top

更新履歴 anchor.png[14]

  • 2015-04-03 初版
  • 2015-04-04 Ajax 送信に対応
  • 2015-05-11 ckeditor4 モジュール 0.61 以上との組み合わせで CKEditor での投稿フォームに対応
  • 2015-05-11 onsubmit 属性が指定された <FORM> で submit がキャンセルされた場合にフォームボタンが一定時間押せなくなって困ってしまう :hammer: 問題を修正
  • 2015-05-12 CKEditor で編集中でも定期的な自動保存に対応
  • 2015-05-18 Ajax 通信時に不具合がでることがある問題を修正
    • 具体的には、name 属性に英数文字以外の文字が含まれる場合に正常に送信できていませんでした。XOOPS[15] X だと、X-update[16] での一括処理で送信エラーになっていました。


Last-modified: 2015-05-18 (月) 20:31:08 (JST) (3259d) by nao-pon