WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

ModalPopup でプログレス表示

by WebSurfer 2011年5月29日 17:43

AJAX による部分ページ更新のプログレスを表示するには、普通、UpdateProgress コントロールを利用しますが、代わりに ModalPopup を使った例を書いてみました。

ModalPopup はクライアントのコードで表示/非表示にできますので、リクエストの開始と完了のイベントがクライアント側で取得できれば、そのイベントハンドラでコントロールできます。

イベントは PageRequestManager クライアントオブジェクトに用意されている beginRequest と endRequest を使っています。PageRequestManager クラスの詳しい説明は MSDN ライブラリ を参照してください。

以下のコードは、実際に動かして試せるよう 実験室 にアップしましたので、興味のある方は試してみてください。

<%@ Page Language="C#" %>
<%@ Register Assembly="AjaxControlToolkit" 
    Namespace="AjaxControlToolkit" 
    TagPrefix="asp" %>

<!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 id="Head1" runat="server">
  <title>WebSurfer's Page - 実験室</title>
  <script type="text/javascript">
  //<![CDATA[
      var manager;

      function pageLoad(sender, args) {
          if (args.get_isPartialLoad() === false) {
              manager =
            Sys.WebForms.PageRequestManager.getInstance();
              manager.add_beginRequest(OnBeginRequest);
              manager.add_endRequest(OnEndRequest);
          }
      }

      function OnBeginRequest(sender, args) {
          var modalPopupBehavior =
          $find('programmaticModalPopupBehavior');
          modalPopupBehavior.show();
      }

      function OnEndRequest(sender, args) {
          var modalPopupBehavior =
          $find('programmaticModalPopupBehavior');
          modalPopupBehavior.hide();
      }

      function AbortPostBack() {
          if (manager.get_isInAsyncPostBack()) {
              manager.abortPostBack();
          }
      }
  //]]>
  </script>

  <style type="text/css">
    /*Modal Popup*/
    .modalBackground {
      background-color: Gray;
      filter: alpha(opacity=70);
      opacity: 0.7;
    }

    #UpdatePanel1 { 
      width: 200px; 
      height: 200px; 
      border: gray 2px solid;
      position: relative;
      float: left; 
      margin-left: 10px; 
      margin-top: 10px;
    }

    .progress {
      height: 50px;
      width: 400px;
      background-color: White;
    }
  </style>
</head>
<body>
  <form id="form1" runat="server">
    <asp:ToolkitScriptManager 
      ID="ToolkitScriptManager1" 
      runat="server">
    </asp:ToolkitScriptManager>
    <asp:UpdatePanel ID="UpdatePanel1"  
      runat="server">
      <ContentTemplate>
        UpdatePanel
        <hr />            
        <%=DateTime.Now.ToString() %> <br />
        <asp:Button ID="Button1" 
          runat="server" 
          Text="Refresh Panel" 
          OnClick="Button_Click" />    
        <br /><br />
        [Refresh Panel]ボタンをクリックすると、
        5 秒後にこのパネル内が更新されます。
        その間 ModalPopup が表示されます。
      </ContentTemplate>
    </asp:UpdatePanel>

    <asp:Button ID="DummyButton" 
      runat="server" 
      style="display: none;" />

    <asp:Panel ID="Panel1" 
      runat="server" 
      CssClass="progress">
      <asp:Image ID="Image1" 
        runat="server" 
        ImageUrl="~/img/grid-loading.gif" />
      非同期ポストバックで更新中です・・・
      <input type="button" 
        value="中止" 
        onclick="AbortPostBack()" />
    </asp:Panel>
    <asp:ModalPopupExtender ID="ModalPopupExtender1" 
      runat="server" 
      TargetControlID="DummyButton"
      BehaviorID="programmaticModalPopupBehavior"
      PopupControlID="Panel1" 
      BackgroundCssClass="modalBackground" />
  </form>
</body>
</html>

Tags: , ,

AJAX

UpdatePanel と半角スペース

by WebSurfer 2011年5月24日 22:40

下の画像のように、複数連続していた半角スペースが、非同期ポストバックで再描画されると 1 文字になってしまうという話です。

複数連続していた半角スペースが 1 文字になってしまう

Label に表示する文字列の中の連続する半角スペースをブラウザ上でそのまま表示したいので、Label コントロールの CssClass に white-space: pre; と設定していたとします。さらに、その Label コントロール を UpdatePanel に配置していたとします。

初期画面では、半角スペースは設定したとおり連続してブラウザに表示されます(white-space: pre; の設定がないと、複数連続した半角スペースを Label に設定しても、ブラウザに表示された時は一個になってしまいます)。

ところが、非同期ポストバックをかけて UpdatePanel 内を部分更新すると、初期画面と同様に複数連続した半角スペースを Label に設定しても、ブラウザに表示された時は一個になってしまいます。もちろん CssClass の white-space: pre; の設定は有効な状態でです。

なお、これはブラウザに依存する問題で、IE6 と IE7 で発生します(それ以前のバージョンは未検証)。IE8, IE9, Firefox, Chrome, Safari は期待通り UpdatePanel 内を部分更新しても問題ありません。

何故でしょう?

最初は ASP.NET AJAX に関係する問題と思っていたのですが、そうではなかったです。ヒントはブラウザに依存するというところです。

原因は AJAX の部分レンダリングの際、JavaScript によって innerHTML を書き換えるとき、JavaScript が複数スペースを一個にしてしまうところにありました。

書き換えられた後は一個しかスペースがないので、いくら white-space: pre; としても複数のスペースは表示されないというわけでした。

という訳で、ASP.NET AJAX とは関係ありませんでした。IE6, IE7 で JavaScript を使って innerHTML を書き換えてやるだけで、この問題は再現できます。例えば、以下のコードのように。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Rewrite innerHTML with multiple white space</title>
    <script type="text/javascript">
    //<![CDATA[
        var str = "<h1>半角スペース>     <5 文字??</h1>";

        function RewriteInnerHTML(id, innerHtml){
            document.getElementById(id).innerHTML = innerHtml;
        }
    //]]>
    </script>
</head>
<body>
    <div id="div1" style="white-space: pre;">
        <h1>半角スペース>     <5 文字</h1>
    </div>
    <input type="button" 
        value="innerHTML 書き換え" 
        id="button1" 
        onclick="RewriteInnerHTML('div1', str);" />
</body>
</html>

上記のコードを IE6 で実行し、[innerHTML 書き換え]ボタンをクリックした時のものがこの記事の一番上に示した画像です。

Tags:

AJAX

クライアントコールバック

by WebSurfer 2011年4月2日 19:29

クライアント側のスクリプトによって、現在ブラウザに表示されているのと同じ URL に対して帯域外呼び出しを行い、その要求に従ってサーバー側で必要な処置を実行して、結果をクライアントに返すという、クライアントコールバックの話です。

クライアントコールバックの実行

今は、帯域外呼び出しの機能を自動化している ASP.NET AJAX Extensions の UpdatePanel などが利用できるので、この記事に書いたクライアントコールバックを実装する機会はないかもしれませんが、せっかくいろいろ調べて分かったことがありますので、備忘録として書いておきます。

ASP.NET クライアントコールバックは、XMLHttpRequest の必要な呼び出しをラッピングして独自の JavaScript API を提供してくれますので、開発者が帯域外呼び出しを実行するための JavaScript コードを自力で書く必要はありません。

代わりに、Page.ClientScript オブジェクトの GetCallbackEventReference メソッドを利用して、帯域外呼び出しを実行するために使用する JavaScript コードを生成します。このコードは、クライアント側のイベントハンドラに設定して利用します。

サーバー側では、ICallbackEventHandler インターフェイスの RaiseCallbackEvent が呼び出されて必要なサーバー側での処置が実行され、次にブラウザへのレスポンスが生成されるときに GetCallbackResult メソッドが呼び出されます。

上の画像を表示したコードをアップしておきます。画像は Callback ボタンをクリックしてコールバック前にクライアント側で実行されるスクリプト(例では alert)が実行されたところです。

alert ダイアログの OK ボタンをクリックするとコールバックがかかり、表示されている「コールバック前」という文字列が「クライアントからサーバーに渡した文字列 + サーバー側で追加した文字列」に書き換えられます。

<%@ Page Language="C#" %>
<%@ Implements Interface="System.Web.UI.ICallbackEventHandler" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
  // ICallbackEventHandler インターフェイスを実装し、
  // RaiseCallbackEvent メソッドを提供している必要がある。 
    
  private string returnValue;

  // サーバー側でコールバックを実行するためにこのメソッド
  // が呼び出される。
  public void RaiseCallbackEvent(string arg)
  {
    // 引数 arg にはクライアントスクリプト CallServer 
    // 関数の第一引数に設定した文字列が渡される。
    returnValue = arg + " + サーバー側で追加した文字列";
  }

  // コールバック結果をクライアントに返すメソッド。
  public string GetCallbackResult()
  {
    return returnValue;
  }

  protected void Page_Load(object sender, EventArgs e)
  {
    ClientScriptManager cm = Page.ClientScript;

    // サーバーに実際に要求を行うヘルパー関数。
    // この例では cbReference は以下のようになる。
    // WebForm_DoCallback('__Page',arg,
    //        ReceiveServerData,"",null,false);
    string cbReference = 
      cm.GetCallbackEventReference(this, "arg", 
        "ReceiveServerData", "");

    // サーバーに要求を行うクライアントスクリプト。
    // arg は RaiseCallbackEventメソッドの引数に渡さ
    // れる文字列。context はコールバック前にクライ
    // アント側で実行されるスクリプト。
    String callbackScript = 
      "function CallServer(arg, context) {"
          + cbReference + "; }";

    // この例では以下のクライアントスクリプトが登録
    // される。
    // function CallServer(arg, context) {
    //     WebForm_DoCallback('__Page',arg,
    //         ReceiveServerData,"",null,false);
    // } 
    cm.RegisterClientScriptBlock(this.GetType(),
        "CallServer", callbackScript, true);
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>Client Callback</title>
  <script type="text/javascript">
  //<![CDATA[
    // クライアント コールバック関数
    // コールバック イベントを処理したサーバーコード
    // から呼び出され、結果を表す文字列を受け取る。
    function ReceiveServerData(arg, context) {
      // 2011/11/19 修正(下の追記参照)
      var element = document.getElementById("Message");
      if (typeof element.textContent != "undefined") {
        element.textContent = arg;
      } else {
        element.innerText = arg;
      }
    }

    var str = "クライアントからサーバーに渡した文字列";

    // コールバック前に実行するクライアントスクリプト
    function DoBeforeCallback() {
        alert('コールバック前に実行された alert');
    }
  //]]>
  </script>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h1>Client Callback</h1>
    <input type="button" value="Callback" 
      onclick="CallServer(str, DoBeforeCallback());"/>
    <br />
    <span id="Message">コールバック前</span>
  </div>
  </form>
</body>
</html>

クライアントコールバックの問題は、クライアントとサーバー間の受け渡しが文字列に限定されていることです。文字列の内容は自由ですが、カスタムオブジェクトを受け渡したい場合はシリアル化と逆シリアル化は開発者が自力でコードを書かなければなりません。

というわけで、今後この方法を使うことはなさそうです。

------------ 2010/4/14 追記 ------------

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

------------ 2010/11/19 追記 ------------

Firefox では span id="Message" の「コールバック前」の文字列が書き換わらなかったので修正しました。修正したのは ReceiveServerData 関数の中身のコードです。

最初は、getElementById を使わず Message.innerText = arg; というようにしていたのが原因と思っていましたが、それだけが原因ではなくて、Firefox では innerText は使えないのでした。

Firefox では textContent を使うらしいです。ただし、これは IE では使えないのでtextContent を使えないブラウザがあるらしいので(IE では使えないという話がありますが IE9 は問題なかったです)、使えるか否かを判定して innerText と使い分ける必要があります。

加えて、コメントに「ここで定義せず GetCallbackEventReference メソッドの第 4 引数に設定することも可能。」と書いてあったのですが、これは間違っていたので削除しました。正しくは、第 4 引数はコールバック前に実行するクライアントスクリプトの文字列だったのです。(汗)

Tags:

AJAX

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar