※このサンプルのコードに一部不具合(リンクのIDが設定されていない)がありましたので2007年6月17日に一部修正しました。
※このサンプルのコードに一部不具合(ソースの赤字部:戻り値が不正)がありましたので2007年6月21日に一部修正しました。
※ImageButtonには対応していませんでしたので、一部ソースを修正しました。(2007年6月21日)
※現在、このサンプルはIEのみの対応となっています。
※ImageButton対応を2007年6月21に実施しましたが、_enableCtrlメソッド部の対応が漏れていたので追加修正しました。(2007年8月17日)
このサンプルでは通常のPostBack通信時と非同期通信時(UpdatePanel利用)の両方に対応した二度押し防止方法を説明します。
まず通常のPostBack通信時における二度押し制御方法ですが、制御対象のコントロールが押下された時に通信状態(document.readyState)が'complete'になっていない場合は通信中と判断し、通信処理を行わないように制御します。
以下のボタンはクリック後、サーバ側で5秒間sleepし、その後現在時刻をテキストエリアに表示します。
sleep中にボタンを押下した場合、メッセージボックスが表示され、且つ時刻は1度しか更新されない事がわかると思います。
上記二度押し制御処理は以下の方法で実装しています。(実装はJavaScript)
- ページのロード時にページ内のリンクやボタンを列挙します。(これらが二度押し制御対象コントロールになります)
- 列挙したコントロールのonclick処理を退避し、別途制御用メソッドを定義します。
- 制御用メソッドにて通信状態(document.readyState)をチェックし、通信中の場合はalertを表示し、処理を終了します。通信中でない場合は退避した処理を実行します。
次にUpdatePanel内のコントロールを実行された時の制御方法を記します。
UpdatePanel内の処理は非同期通信として実行される為、上記に記したdocument.readyStateをチェックし、制御するという方法を利用できません。
※非同期通信では即座にdocument.readyStateが'complete'になります。サーバサイドでの処理終了後にコールバックされます。
非同期通信をXMLHttpRequestを利用して実装した事がある方は、通信状態が変更時にコールバックされる事を知っていると思いますが、UpdatePanel利用時も同様に状態変化時にイベントが発生します。
ここでUpdatePanelの利用方法ですが、UpdatePanel利用するには、ScriptManagerを実装する必要があります。
またUpdatePanelの部分更新処理(非同期通信)を利用する場合、ScriptManagerのEnablePartialRenderingプロパティをtrueに設定する必要があります。
EnablePartialRenderingプロパティをtrueに設定する事により、実行時にPageRequestManagerクラスのインスタンスが生成され、このPageRequestManagerが非同期通信を管理し、以下のイベントも管理します。
※PageRequestManagerのインスタンスは、Sys.WebForms.PageRequestManager.getInstance()にて取得できます。
- beginRequestイベント
- endRequestイベント
- initializeRequestイベント
- pageLoadedイベント
- pageLoadingイベント
これらイベントの内、beginRequestとendRequestイベントを利用する事により二度押し防止が可能となります。
例えば、beginRequest時に二度押し制御対象のコントロールをdisableに設定し、endRequest時にenableにするという方法です。
以下のサンプルでは、UpdatePanel内にテキストボックスとボタンを配置し、ボタン押下時にサーバサイドにて5秒sleepします。
クライアントサイドではボタンが押下できないようにdisableに設定し、通信終了時にenableにします。
ボタンクリック時に画面内の他のコントロール(リンクやボタン)も利用不可能になっている事がわかります。
上記で説明した内容を実装したJavaScriptを以下に記します。
尚、以下のクラス(JavaScriptコード)はASP.NET AJAX環境で利用可能です。
二度押し制御クラス(DoublePostManager.js)
/****************2度押しを制御するクラスです***********************/
var DoublePostManager = "";
$addHandler(window, 'load', function(){
DoublePostManager = $create(CodeSample.DoublePostManager, {}, null, null, null);
});
Type.registerNamespace("CodeSample");
CodeSample.DoublePostManager = function(){
CodeSample.DoublePostManager.initializeBase(this);
this._prman = null; //PageRequestManagerのインスタンス
this._beginRequestHandler = null; //非同期通信開始時に実行するハンドラー
this._endRequestHandler = null; //非同期通信終了時に実行するハンドラー
this._onclickList = null; //既存のclick処理を退避するリスト
this._submitHandler = null; //クリック処理をフックするハンドラー
}
CodeSample.DoublePostManager.prototype ={
initialize:function() {
CodeSample.DoublePostManager.callBaseMethod(this,'initialize');
this._setObserveCtrl();
//UpdatePanel内の非同期通信時に制御するイベントを登録する。
//ScriptManagerのEnablePartialRenderingプロパティ(部分更新処理)がtrueに設定されている場合、
//実行時にPageRequestManagerが生成されます。
//PageRequestManagerが生成されている場合、非同期通信用の制御メソッドを登録します。
this._prman = Sys.WebForms.PageRequestManager.getInstance();
if(null!=this._prman){
this._beginRequestHandler = Function.createDelegate(this, this._onBeginRequest);
this._endRequestHandler = Function.createDelegate(this, this._onEndRequest);
this._prman.add_beginRequest(this._beginRequestHandler);
this._prman.add_endRequest(this._endRequestHandler);
}
},
/*********二度押し制御対象コントロールのonclick処理に制御メソッドを定義します。*********
*/
_setObserveCtrl : function(){
if(null==this._submitHandler){
this._submitHandler = Function.createDelegate(this, this._submitCtrl);
}
this._onclickList = new Array();
//全てのリンクのクリックイベントに_submitCtrlメソッドを登録する。
for(var i = 0; i < document.links.length; i ++) {
if(null!=document.links[i].onclick){
this._onclickList[document.links[i].id] = document.links[i].onclick;
}
document.links[i].onclick = this._submitHandler;
}
//全てのボタンのクリックイベントを_submitCtrlメソッドを登録する。
/*
for(var i = 0; i < document.forms[0].elements.length; i ++) {
var elm = document.forms[0].elements[i];
if (elm.type == "button" ||
elm.type == "submit" ||
elm.type == "reset" ||
elm.type == "file") {
if(null!=elm.onclick){
this._onclickList[elm.id] = elm.onclick;
}
elm.onclick = this._submitHandler;
}
}
*/
var inputElmlist = document.getElementsByTagName("input");
if(inputElmlist!=null){
  for(var i = 0; i < inputElmlist.length; i ++) {
  var elm = inputElmlist[i];
  if (elm.type == "button" ||
  elm.type == "submit" ||
  elm.type == "reset" ||
  elm.type == "file" ||
  elm.type == "image") {
  if(null!=elm.onclick){
  this._onclickList[elm.id] = elm.onclick;
  }
  elm.onclick = this._submitHandler;
}
}
}
},
/*********2度押し制御コントロールのクリック時処理(アクセス中は処理が中断されます)
2度押しでない場合は登録されていた処理を実行します**********/
_submitCtrl : function(e){
if (DoublePostManager._isAccessing()){
alert("処理中です。暫くお待ち下さい。");
return false;
}
var id = null;
if(Sys.Browser.agent === Sys.Browser.InternetExplorer){
//IEは引数が飛んでこないのでeventより取得する。
id = event.srcElement.id;
}else{
//Firefoxでは引数のtarget.idに格納されている。
try{
id = e.target.id;
}catch(err){
}
}
if(null!=this._onclickList){
var func = this._onclickList[id];
if(null!=func && typeof(func) != "undefined"){
var retValue = func();
//元々設定されていたスクリプトに戻り値がある場合はそれを返却する。
if(retValue!=null){
//return false;//不具合修正
return retValue;
}
}
}
return true;
},
/*********アクセス中か判定します。**********/
_isAccessing : function(){
return (document.readyState != null && document.readyState != "complete");
},
/*********非同期通信開始時処理*******************/
_onBeginRequest : function(sender,args){
//全てのリンクボタンとボタンの利用不可にする。
this._enableCtrl(false);
},
/*********非同期通信終了時処理*******************/
_onEndRequest : function(sender,args){
//全てのリンクボタンとボタンの利用可能にする。
this._enableCtrl(true);
},
/*********全てのリンクとボタンの利用可否設定を行う*******************/
_enableCtrl : function(bEnable){
for(var i = 0; i < document.links.length; i ++) {
document.links[i].disabled = !bEnable;
}
/*
//ImageButton対応漏れの修正対応(2007年8月17日修正)
for(var i = 0; i < document.forms[0].elements.length; i ++) {
if (document.forms[0].elements[i].type == "button" ||
document.forms[0].elements[i].type == "submit" ||
document.forms[0].elements[i].type == "reset" ||
document.forms[0].elements[i].type == "file") {
document.forms[0].elements[i].disabled = !bEnable;;
}
}
*/
//以下修正コード(2007年8月17日)
var inputElmlist = document.getElementsByTagName("input");
for(var i=0;i<inputElmlist.length;i++){
var elm = inputElmlist[i];
if (elm.type == "button" ||
elm.type == "submit" ||
elm.type == "reset" ||
elm.type == "file" ||
elm.type == "image") {
elm.disabled = !bEnable;
}
},
dispose: function() {
CodeSample.DoublePostManager.callBaseMethod(this, 'dispose');
if(null!=this._prman){
if(null!=this._beginRequestHandler){
this._prman.remove_beginRequest(this._beginRequestHandler);
}
if(null!=this._endRequestHandler){
this._prman.remove_endRequest(this._endRequestHandler);
}
}
}
}
CodeSample.DoublePostManager.registerClass('CodeSample.DoublePostManager', Sys.Component);
if (typeof(Sys) !== 'undefined')
Sys.Application.notifyScriptLoaded();