WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

二度押し防止

by WebSurfer 12. December 2010 12:20

UpdatePanel を利用した非同期ポストバックを行う際に、最初の部分ページ更新が完了するまで 2 回目以降のリクエストをキャンセルする(いわゆる、ボタンの二度押し防止)方法の紹介です。

二度押し防止

その前に、ちょっと前置きが長くなりますが、それを実現するために利用する PageRequestManager クライアントオブジェクトについて説明します。

ASP.NET AJAX Extensions の ScriptManager と UpdatePanel コントロールを使用して部分ページ更新を行う場合、自力でクライアントスクリプトを書く必要はありません。

ただし、以下ような操作を行う場合は、クライアント側で適切なタイミングで発生するイベントを利用して、クライアントスクリプトで処置する必要があります。

  1. 複数の非同期ポストバックの処理方法を制御する。
  2. 部分ページ更新のリクエストをキャンセルする。
  3. 部分ページ更新のプログレスを表示する。
  4. 部分ページ更新時にデータを受け渡す。
  5. クライアントスクリプトでエラーを処理する。

開発者が 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 追記 ------------

この記事で紹介したコードを実際に動かして試せるよう 実験室 にアップしました。興味のある方は試してみてください。

Tags: ,

AJAX

UpdatePanel 内の TextBox に focus

by WebSurfer 21. November 2010 17:22

IE を使った場合、UpdatePanel 内に配置した TextBox に、ボタンクリックで非同期ポストバックした後、JavaScript でフォーカスを当てても無視されるという問題があります。

UpdatePanel 内の TextBox に focus

上の画像は以下のコードで描いたものです。UpdatePanel の外のボタンを操作した場合は TextBox にフォーカスを当てられますが、UpdatePanel の中のボタンを操作した場合は無視されます。

ちなみに、Firefox 3.6.12, Chrome 7.0.517.44, Opera 10.63, Safari 5.0.3 は、UpdatePanel の中/外どちらのボタンを操作しても期待通り TextBox にフォーカスが当たります。

解決策は、IE8 の場合のみですが、$('#TextBox1').focus().focus(); のように focus を 2 回かけることによって、UpdatePanel の中のボタンを操作した場合でもフォーカスが当たるようになります。ただし、IETester で試した限り、IE6, IE7 ではうまくいきません。IE6, IE7 の場合の解決策を検討中です。

<%@ 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 FocusTextBox1_Click(object sender, EventArgs e)
  {
    ScriptManager.RegisterHiddenField(Page, "Focus", "TextBox1");
  }

  protected void FocusTextBox2_Click(object sender, EventArgs e)
  {
    ScriptManager.RegisterHiddenField(Page, "Focus", "TextBox2");
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Focus TextBox in ControlPanel</title>
  <script src="Scripts/jquery-1.4.1.js" type="text/javascript"></script>
  <script type="text/javascript">
  <!--
    $(function () {
      var manager = Sys.WebForms.PageRequestManager.getInstance();
      manager.add_pageLoaded(OnPageLoaded);
    });

    function OnPageLoaded(sender, args) {
      var target = $('#Focus').val();
      if (target == 'TextBox1') {
        $('#TextBox1').focus();
      }
      else if (target == 'TextBox2') {
        $('#TextBox2').focus();
      }
    }
  //-->
  </script>
</head>
<body>
  <form id="form1" runat="server">
  <asp:ScriptManager ID="ScriptManager1" runat="server">
  </asp:ScriptManager>
  <h3>Focus TextBox in ControlPanel</h3>    
  <asp:Button ID="Button1" 
    runat="server" 
    Text="Focus TextBox1" 
    OnClick="FocusTextBox1_Click" />
  <asp:Button ID="Button2" 
    runat="server" 
    Text="Focus TextBox2" 
    OnClick="FocusTextBox2_Click" />
  <div style="border: solid 1px Silver; padding: 2px 5px;">
    UpdatePanel<hr />
    <asp:UpdatePanel ID="UpdatePanel1" runat="server">
      <ContentTemplate>
        TextBox1: 
        <asp:TextBox ID="TextBox1" runat="server" />
        <asp:Button ID="Button3" 
          runat="server" 
          Text="Focus TextBox1" 
          OnClick="FocusTextBox1_Click" />
        <br />
        TextBox2: 
        <asp:TextBox ID="TextBox2" runat="server" />
        <asp:Button ID="Button4" 
          runat="server" 
          Text="Focus TextBox2" 
          OnClick="FocusTextBox2_Click" />
      </ContentTemplate>
    </asp:UpdatePanel>
  </div>
  </form>
</body>
</html>

------------ 2010/4/24 追記 ------------

この記事で紹介したコードを実際に動かして試せるよう 実験室 にアップしました。興味のある方は試してみてください。

Tags: , ,

AJAX

UpdatePanel へのトリガ追加(改版)

by WebSurfer 8. November 2010 21:19

先の記事 UpdatePanel へのトリガ追加 で、AsyncPostBackTrigger をプログラムで追加する話を書きましたが、MSDN ライブラリの AsyncPostBackTrigger クラス の説明によると、それはサポートされていないのだそうです。無知でした。(汗)

MSDN ライブラリの説明に書かれているように、RegisterAsyncPostBackControl メソッド を使って書き換えました。

コードは以下のとおりです。先の記事のサンプルから C# のコードの部分を書き換えただけです。こんなに簡単だったとは、以前の苦労はなんだったんだろうという感じです。(笑)

<%@ 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 Page_Load(object sender, EventArgs e)
  {
    LinkButton lb = 
      (LinkButton)DetailsView1.FindControl("LinkButton1");
    if (lb != null)
    {
      ScriptManager1.RegisterAsyncPostBackControl(lb);
    }        
  }    

  protected void LinkButton1_Click(object sender, EventArgs e)
  {
    Label1.Text = DateTime.Now.ToString();
    UpdatePanel2.Update();
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>AsyncPostBackTrigger を動的に設定</title>
</head>
<body>
  <form id="form1" runat="server">
  <asp:ScriptManager ID="ScriptManager1" runat="server">
  </asp:ScriptManager>

  <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
    ConnectionString="<%$ ConnectionStrings:MyDB %>" 
    SelectCommand="SELECT [id], [name], [price] FROM [table]" >
  </asp:SqlDataSource>

  <asp:Button ID="DummyButton" 
    runat="server" 
    style="display: none;" />
  <h2>AsyncPostBackTrigger を動的に設定</h2>
  <h3>DetailsView</h3>
  <asp:UpdatePanel ID="UpdatePanel1" runat="server">
    <ContentTemplate>
      <asp:DetailsView ID="DetailsView1" 
        runat="server" 
        AutoGenerateRows="False" 
        DataKeyNames="id" 
        DataSourceID="SqlDataSource1" 
        EnableModelValidation="True" 
        AllowPaging="True" Width="250px">
        <Fields>
          <asp:BoundField DataField="id" 
            HeaderText="id" 
            InsertVisible="False" 
            ReadOnly="True" 
            SortExpression="id" />
          <asp:BoundField DataField="name" 
            HeaderText="name" 
            SortExpression="name" />
          <asp:BoundField DataField="price" 
            HeaderText="price" 
            SortExpression="price" />
          <asp:TemplateField ShowHeader="False">
            <ItemTemplate>
              <asp:LinkButton ID="LinkButton1" 
                runat="server" 
                CausesValidation="False" 
                Text="Show Current Time" 
                OnClick="LinkButton1_Click">
              </asp:LinkButton>
            </ItemTemplate>
          </asp:TemplateField>
        </Fields>
      </asp:DetailsView>            
    </ContentTemplate>
  </asp:UpdatePanel>
  <hr />
  <h3>Time</h3>
  <asp:UpdatePanel ID="UpdatePanel2" 
    runat="server" 
    UpdateMode="Conditional">
    <ContentTemplate>
      <asp:Label ID="Label1" 
        runat="server" 
        Text="[time]" />
    </ContentTemplate>
  </asp:UpdatePanel>
  </form>
</body>
</html>

------------ 2010/3/26 追記 ------------

どうも上記の例は適切ではなかったです。

RegisterAsyncPostBackControl メソッドは「同期ポストバックではなく非同期ポストバックを実行できるように、Web サーバーコントロールをトリガとして登録」するものであって、ある特定の UpdatePanel の Trigger コレクションに AsyncPostPackTrigger を登録するものではないです。従って、部分更新を行うには、LinkButton.Click のイベントハンドラで当該 UpdatePanel の Update メソッドを実行する必要があります。

上記の例では、LinkButton が配置された DetailsView は UpdatePanel1 に含まれているので、RegisterAsyncPostBackControl で非同期ポストバックのトリガとして登録する必要はなく、単純に Click のイベントのハンドラ(LinkButton1_Click メソッド)で UpdatePanel2 の Update メソッドを実行すれば済みました。

これは 先のシナリオ でも同じです。LinkButton が UpdatePanel 内にない場合を例にすべきでした。(汗)

Tags: ,

AJAX

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  January 2020  >>
MoTuWeThFrSaSu
303112345
6789101112
13141516171819
20212223242526
272829303112
3456789

View posts in large calendar