Shoeisha Technology Media

CodeZine(コードジン)

特集ページ一覧

JavaScriptを利用してフォームの二重送信を防止する

クリックしたボタンの無効化と画面の擬似フリーズ表示

  • LINEで送る
  • このエントリーをはてなブックマークに追加
2006/12/15 00:00

ユーザーがフォームの送信ボタンをクリックした際に、Webサーバー側の応答が遅いと、再度ボタンをクリックして、フォームが二重に送信されてしまうことが考えられます。本稿では、ボタンの無効化と、画面のフリーズ化という二つの解決アプローチを紹介します。

はじめに

 ユーザーがフォームの送信ボタンをクリックすると、ブラウザは指定された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」に設定します。この動作をデモで実際に確認してみてください。

無効になったボタンと、フォームの送信時に送信されるデータ
 警告:ブラウザはフォームの送信時に、無効に設定されているコントロールのデータを送信しません。この例の送信ボタンは、クリックされた時点でJavaScriptによって無効に設定されるため、ブラウザは送信ボタンがクリックされたという情報をフォームの送信に含めません。このため、ASP.NETを使用している場合は、対応するButton WebコントロールのClickイベントが起動しなくなります。送信ボタンのクリックによってポストバックが発生したということがASP.NETエンジンに通知されないからです。これが問題になる場合は、次のどちらかの方法を使用します。
  • この後で説明する、画面を「フリーズ」させる方法を使います。
  • DisableButton(button)のJavaScriptに手を加え、ボタンを無効にする代わりに、ユーザーがフォームを二重に送信できないようにします。例えば、無効状態を表すダミーのボタンを用意し、初期設定で非表示にしておくという方法が考えられます。本物の送信ボタンがクリックされたら、CSSルールで送信ボタンを非表示にし、このダミーのボタンを表示します。

フォームの送信時にページを「フリーズ」させる

 商用のWebアプリケーション(FogBugzなど)には、フォームの送信時に画面を「フリーズ」させて、フォームの二重送信を防いでいるものがあります。具体的には、ページ上の他のすべての要素の前面に、絶対位置で指定された要素(通常は<div>要素)をフルスクリーンで配置することによって画面をフリーズさせています。これにより、要素の背後にあるコンテンツ全体が覆われて、ユーザーはフォームの要素を操作できなくなります。

 最初に、フリーズした画面の外観を指定するいくつかのカスケードスタイルシート(CSS)クラスを定義します。次の3つのCSSクラスを使います。

  • FreezePaneOff
  • <div>要素を「非表示」にして、ページを操作できるように(つまりフリーズしていない状態に)します。
  • FreezePaneOn
  • フリーズした画面を表す要素は、実際にはフルスクリーンの<div>要素であり、その中にメッセージ(「要求を処理しています」など)を表示する<div>要素が含まれています。このCSSクラスは、外側の<div>の設定を定義します。
  • InnerFreezePane
  • 内側の<div>要素のスタイル設定を指定します。この要素は、ユーザーにメッセージ(「要求を処理しています」など)を表示します。

 さらに、内側と外側の<div>要素を定義し、これらの要素を操作して画面をフリーズさせるJavaScript関数を作成する必要があります。この関数では、これらの<div>要素のclass属性を、デフォルトのCSSクラス(FreezePaneOff)から、フリーズしたフレームを表すCSSクラス(FreezePaneOnInnerFreezePane)へと変更します。

 まず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>

 FreezePaneOffCSSクラスは、非表示の要素(visibility: hidden; display: none;)を定義します。一方、FreezePaneOnCSSクラスは要素を表示状態にし、それをページの上部に配置して幅と高さを最大に設定します。z-index値は、ページ上の要素の「レイヤ」を指定します。ここでは要素がページ上の他の要素の前面に表示されるように、値を999に設定しています。filter:alpha(opacity=85)-moz-opacity:0.85は、それぞれInternet ExplorerとMozilla FireFoxで背景のペインを半透明にします。最後に、InnerFreezePaneCSSクラスで内側のメッセージのスタイル設定を定義します。

 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(半透明の設定を除く)では機能しますが、古いブラウザに対応しているかどうかは分かりません。

 このようにいくつか欠点はあるものの、私のプロジェクトではこの方法がうまく機能しており、読者にもある程度役に立つことと思います。

 では、ハッピープログラミング!



  • LINEで送る
  • このエントリーをはてなブックマークに追加

著者プロフィール

バックナンバー

連載:japan.internet.com翻訳記事

もっと読む

All contents copyright © 2005-2018 Shoeisha Co., Ltd. All rights reserved. ver.1.5