UpdatePanel を利用した非同期ポストバックを行う際に、最初の部分ページ更新が完了するまで 2 回目以降のリクエストをキャンセルする(いわゆる、ボタンの二度押し防止)方法の紹介です。
その前に、ちょっと前置きが長くなりますが、それを実現するために利用する PageRequestManager クライアントオブジェクトについて説明します。
ASP.NET AJAX Extensions の ScriptManager と UpdatePanel コントロールを使用して部分ページ更新を行う場合、自力でクライアントスクリプトを書く必要はありません。
ただし、以下ような操作を行う場合は、クライアント側で適切なタイミングで発生するイベントを利用して、クライアントスクリプトで処置する必要があります。
-
複数の非同期ポストバックの処理方法を制御する。
-
部分ページ更新のリクエストをキャンセルする。
-
部分ページ更新のプログレスを表示する。
-
部分ページ更新時にデータを受け渡す。
-
クライアントスクリプトでエラーを処理する。
開発者が XMLHttpRequest オブジェクトを直接管理している場合は、リクエストとレスポンスを完全に制御して、上記のような操作ができるらしいです。
しかしながら、自分には「XMLHttpRequest オブジェクトを直接管理」などということは無理です。だから ASP.NET AJAX Extensions を利用しているわけですし。
従って、XMLHttpRequest オブジェクトを管理している ASP.NET AJAX Extensions がイベントモデルを提供してくれない限り、自分には何もできません。
ScriptManager と UpdatePanel コントロールを使用しての非同期ポストバックは、内部で XMLHttpRequest オブジェクトを呼び出す PageRequestManager クライアントオブジェクトが関係しています。
PageRequestManager が提供している以下のイベントを利用して、開発者が部分ページ更新のリクエストとレスポンスの処理をカスタマイズできます。
-
initializeRequest
-
beginRequest
-
pageLoading
-
pageLoaded
-
endRequest
詳しくは MSDN ライブラリの ASP.NET PageRequestManager クラスの概要 および PageRequestManager のイベントの処理 を参照してください。
前置きが長くなりましたが、ここでは、上記 1 項の「複数の非同期ポストバックの処理方法を制御する」でボタンの二度押しを防止する方法と、上記 2 項の「部分ページ更新のリクエストをキャンセルする」で未完了のリクエストをユーザーがキャンセルできる方法の例を示したコードを紹介します。
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
protected void Button_Click(object sender, EventArgs e)
{
System.Threading.Thread.Sleep(5000);
}
</script>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>二度押し防止</title>
<style type="text/css">
#UpdatePanel1, #UpdatePanel2, #UpdatePanel3 {
width: 200px;
height: 250px;
border: gray 1px solid;
position: relative;
float: left;
margin-left: 10px;
margin-top: 10px;
}
#UpdateProgress1 {
width: 400px;
background-color: #FFC080;
border: gray 1px solid;
bottom: 0%;
left: 0px;
position: absolute;
}
.labalposition {
bottom: 0%;
left: 0px;
position: absolute;
}
</style>
<script type="text/javascript">
<!--
var manager;
var initiatingButtonId;
function pageLoad(sender, args) {
if (args.get_isPartialLoad() === false) {
manager = Sys.WebForms.PageRequestManager.getInstance();
manager.add_initializeRequest(OnInitializeRequest);
manager.add_endRequest(OnEndRequest);
}
}
function OnInitializeRequest(sender, args) {
if (manager.get_isInAsyncPostBack()) {
var buttonId = args.get_postBackElement().id.toLowerCase();
if (buttonId == "button1" &&
initiatingButtonId == "button1") {
$get("Label1").innerHTML = "このパネルの更新中";
}
else if (buttonId == "button1" &&
initiatingButtonId == "button2") {
$get("Label1").innerHTML = "Panel B 更新中";
}
else if (buttonId == "button2" &&
initiatingButtonId == "button1") {
$get("Label2").innerHTML = "Panel A 更新中";
}
else if (buttonId == "button2" &&
initiatingButtonId == "button2") {
$get("Label2").innerHTML = "このパネルの更新中";
}
args.set_cancel(true);
}
else {
initiatingButtonId =
args.get_postBackElement().id.toLowerCase();
}
}
function OnEndRequest(sender, args) {
$get("Label1").innerHTML = "";
$get("Label2").innerHTML = "";
}
function AbortPostBack() {
if (manager.get_isInAsyncPostBack()) {
manager.abortPostBack();
}
}
//-->
</script>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<asp:UpdatePanel ID="UpdatePanel1"
UpdateMode="Conditional"
runat="server">
<ContentTemplate>
Panel A
<hr />
<%=DateTime.Now.ToString() %> <br />
<asp:Button ID="Button1"
runat="server"
Text="Refresh Panel"
OnClick="Button_Click" />
<br />
前の非同期ポストバック処理が終了しないと、
次の非同期ポストバックはできません。
ボタンをクリックしてもキャンセルされます。
<br />
<asp:Label ID="Label1"
runat="server"
CssClass="labalposition" />
</ContentTemplate>
</asp:UpdatePanel>
<asp:UpdatePanel ID="UpdatePanel2"
UpdateMode="Conditional"
runat="server">
<ContentTemplate>
Panel B
<hr />
<%=DateTime.Now.ToString() %> <br />
<asp:Button ID="Button2"
runat="server"
Text="Refresh Panel"
OnClick="Button_Click"/>
<br />
こちらも、左のパネルと同様、
前の非同期ポストバック処理が終了しないと、
ボタンをクリックしてもポストバックはキャンセルされます。
<asp:Label ID="Label2"
runat="server"
CssClass="labalposition" />
</ContentTemplate>
</asp:UpdatePanel>
<asp:UpdateProgress ID="UpdateProgress1"
runat="server">
<ProgressTemplate>
非同期ポストバックで更新中です・・・
<input type="button"
value="stop"
onclick="AbortPostBack()" />
</ProgressTemplate>
</asp:UpdateProgress>
</form>
</body>
</html>
通常のポストバックにおける二度押し防止では、制御対象のボタンがクリックされた時に document.readyState が complete になっていない場合を通信中と判断し、通信処理を行わないように制御する手段が取られることがあります。しかしながら、UpdatePanel を利用した非同期ポストバックでは document.readyState は即 complete となるため、その方法は使えませんので注意してください。
他に、上記 4 項の「部分ページ更新時にデータを受け渡す」例が、先の記事 ModalPopup で編集・更新操作 に書いてありますので、興味があればそちらも見てください。pageLoaded イベントと ScriptManager の RegisterDataItem メソッドを使っています。
------------ 2010/4/14 追記 ------------
この記事で紹介したコードを実際に動かして試せるよう 実験室 にアップしました。興味のある方は試してみてください。