はじめに
ユーザーがフォームの送信ボタンをクリックすると、ブラウザは指定されたURLのWebサーバーに要求を送ります。すると、Webサーバー側でユーザーの入力が処理され、入力に基づいて何らかのアクションが実行されます。例えばデータベースレコードが追加されたり、電子メールが送信されたりします。問題は、ユーザーがフォームを送信しても、サーバーからの応答がなかなか返らない場合です。ユーザーはクリックが「効かなかった」と考えて、何度も送信ボタンを押してしまうことがあります。その結果、フォームが二重に送信されて、2つのデータベースレコードが追加されたり、2通の電子メールが送信されたりする可能性があります。
先日、ASP101.comに掲載されているある豆知識が目にとまりました。「Stop Users from Double Clicking(ユーザーによる二重クリックを防止する)」と題するその豆知識では、フォームの送信ボタンにちょっとしたJavaScriptを追加し、ユーザーがフォームを2回以上送信できないようにする方法が紹介されていました。この方法は、ボタンのクライアント側onclick
イベントで、ボタンのdisabled
プロパティをtrue
に設定するという実に単純なものです。
私のプロジェクトでは、いつももう少し複雑な別の方法を使っています。単に送信ボタンを無効にするのではなく、画面を「フリーズ」させて、データが処理中であることを示すメッセージを表示するという方法です。個人的には、フリーズした画面のルックアンドフィールの方が気に入っています。この方が、データがサーバーに送信されて処理中であることがよく分かるからです。さらに、ユーザーがページ上の他の送信ボタンをクリックするのを防ぐこともできます。本稿では、この2種類の方法について説明します。
クリックされたボタンを無効にする
多くのWebサイトでは、クリックされた時点で送信ボタンを無効にすることによって、ユーザーがフォームを二重に送信できないようにしています。通常は送信ボタンを無効にすると共に、ボタンのラベルを初期値(「送信」など)から別の値(「送信済み」など)に変更して、アクションが実行されたことを示します。ラベルを変更してボタンを無効にするには、ボタンのクライアント側onclick
イベントハンドラで次のようなJavaScriptを使います。
<script> function DisableButton(b) { b.disabled = true; b.value = 'Submitting'; b.form.submit(); } </script> <p> Click the button to see how it can be disabled and have its text changed when clicked... </p> <p> <input type="submit" name="SubmitButton" id="SubmitButton" value="Submit" onclick="DisableButton(this);" /> </p>
このonclick
イベントハンドラでは、DisableButton(button)
関数を呼び出し、ボタンへの参照(this
)を渡しています。DisableButton(button)
では、ボタンのdisabled
プロパティをtrue
に設定し、value
(ボタンに表示されるラベル)を「Submitting」に設定します。この動作をデモで実際に確認してみてください。
Click
イベントが起動しなくなります。送信ボタンのクリックによってポストバックが発生したということがASP.NETエンジンに通知されないからです。これが問題になる場合は、次のどちらかの方法を使用します。- この後で説明する、画面を「フリーズ」させる方法を使います。
DisableButton(button)
のJavaScriptに手を加え、ボタンを無効にする代わりに、ユーザーがフォームを二重に送信できないようにします。例えば、無効状態を表すダミーのボタンを用意し、初期設定で非表示にしておくという方法が考えられます。本物の送信ボタンがクリックされたら、CSSルールで送信ボタンを非表示にし、このダミーのボタンを表示します。
フォームの送信時にページを「フリーズ」させる
商用のWebアプリケーション(FogBugzなど)には、フォームの送信時に画面を「フリーズ」させて、フォームの二重送信を防いでいるものがあります。具体的には、ページ上の他のすべての要素の前面に、絶対位置で指定された要素(通常は<div>
要素)をフルスクリーンで配置することによって画面をフリーズさせています。これにより、要素の背後にあるコンテンツ全体が覆われて、ユーザーはフォームの要素を操作できなくなります。
最初に、フリーズした画面の外観を指定するいくつかのカスケードスタイルシート(CSS)クラスを定義します。次の3つのCSS
クラスを使います。
FreezePaneOff
FreezePaneOn
InnerFreezePane
<div>
要素を「非表示」にして、ページを操作できるように(つまりフリーズしていない状態に)します。<div>
要素であり、その中にメッセージ(「要求を処理しています」など)を表示する<div>
要素が含まれています。このCSS
クラスは、外側の<div>
の設定を定義します。<div>
要素のスタイル設定を指定します。この要素は、ユーザーにメッセージ(「要求を処理しています」など)を表示します。 さらに、内側と外側の<div>
要素を定義し、これらの要素を操作して画面をフリーズさせるJavaScript関数を作成する必要があります。この関数では、これらの<div>
要素のclass
属性を、デフォルトのCSSクラス(FreezePaneOff
)から、フリーズしたフレームを表すCSSクラス(FreezePaneOn
とInnerFreezePane
)へと変更します。
まずCSSクラスから始めましょう。
<style type="text/css"> .FreezePaneOff { visibility: hidden; display: none; position: absolute; top: -100px; left: -100px; } .FreezePaneOn { position: absolute; top: 0px; left: 0px; visibility: visible; display: block; width: 100%; height: 100%; background-color: #666; z-index: 999; filter:alpha(opacity=85); -moz-opacity:0.85; padding-top: 20%; } .InnerFreezePane { text-align: center; width: 66%; background-color: #171; color: White; font-size: large; border: dashed 2px #111; padding: 9px; } </style>
FreezePaneOff
CSSクラスは、非表示の要素(visibility: hidden; display: none;
)を定義します。一方、FreezePaneOn
CSSクラスは要素を表示状態にし、それをページの上部に配置して幅と高さを最大に設定します。z-index
値は、ページ上の要素の「レイヤ」を指定します。ここでは要素がページ上の他の要素の前面に表示されるように、値を999に設定しています。filter:alpha(opacity=85)
と-moz-opacity:0.85
は、それぞれInternet ExplorerとMozilla FireFoxで背景のペインを半透明にします。最後に、InnerFreezePane
CSSクラスで内側のメッセージのスタイル設定を定義します。
CSSクラスに加えて、ページに<div>
要素を追加する必要があります。初期状態では、外側の<div>
のclass
属性はFreezePaneOff
に設定されています。フォームが送信されると、この属性がJavaScriptによってFreezePaneOn
に変更されます。
<div align="center" id="FreezePane" class="FreezePaneOff"> <div id="InnerFreezePane" class="InnerFreezePane"> </div> </div>
次に、ブラウザをウィンドウの上部にスクロールさせ(<div>
はページの上部に配置されるため)、外側の<div>
のCSSクラスを更新するJavaScript関数を定義します。フォームが送信されるときに、例えば送信ボタンのonclick
イベントでこのJavaScript関数を起動する必要があります。
<script language="JavaScript"> function FreezeScreen(msg) { scroll(0,0); var outerPane = document.getElementById('FreezePane'); var innerPane = document.getElementById('InnerFreezePane'); if (outerPane) outerPane.className = 'FreezePaneOn'; if (innerPane) innerPane.innerHTML = msg; } </script> <p> <input type="submit" name="SubmitButton" id="SubmitButton" value="Submit" onclick="FreezeScreen('Your Data is Being Processed...');" /> </p>
FreezeScreen(msg)
関数は、内側の<div>
に表示されるメッセージを入力として受け取ります。このスクリプトの動作をデモで実際に確認してみてください。
画面をフリーズさせるこの方法には、いくつかの欠点や積み残しがあります。例えば、画面は実際にはフリーズしていません。ユーザーは画面をスクロールしたり、外側の<div>
で覆われていない部分のHTML要素をクリックしたり、[戻る]ボタンを押したり、ページを更新したり、さまざまな操作を実行できてしまいます。
また、この方法は多種多様なブラウザで十分にテストされているわけではありません。IE 6、FireFox 1.5、およびOpera 9.0(半透明の設定を除く)では機能しますが、古いブラウザに対応しているかどうかは分かりません。
このようにいくつか欠点はあるものの、私のプロジェクトではこの方法がうまく機能しており、読者にもある程度役に立つことと思います。
では、ハッピープログラミング!