WebSurfer's Home

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

UpdatePanel へのトリガ追加

by WebSurfer 2010年11月6日 19:08

2010/11/8 追記

MSDN ライブラリの AsyncPostBackTrigger クラス の説明によると、"AsyncPostBackTrigger コントロールのプログラムによる追加はサポートされていません。" とのことです。というわけで、以下の方法はボツです。(汗) 別の方法で書き換えたものを UpdatePanel へのトリガ追加(改版) にポストしましたのでそちらを見てください。

AsyncPostBackTrigger を動的に設定

ASP.NET AJAX Extensions の UpdatePanel コントロールのトリガに AsyncPostBackTrigger クラスの ControlID として Button などのコントロールを動的に設定する話です。

大体のケースでは静的に設定できると思いますが、UniqueID を設定しなければならない場合は話が別です。

UniqueID を設定する必要があるのは、トリガとなる Button などのコントロールが名前付けコンテナに含まれ、ID とは異なる一意の UniqueID がサーバー側で設定される場合です。

名前付けコンテナとは、例えば、Repeater, DataList, DetailsView, FormView, GridView などやマスタページの ContentPlaceHolder です。

そのような場合に、AsyncPostBackTrigger クラスの ControlID プロパティに(UniqueID ではなく)ID を設定すると、「UpdatePanel 'UpdatePanel1' のトリガ用の ID 'Button1' のコントロールが見つかりませんでした。」というようなエラーが出るはずです。

というわけで本来は UniqueID を設定しなければなりませんが、UniqueID はサーバー側で生成されますので、サーバー側で動的に設定した方が簡単そうです。

ただし、タイミングが問題です。自分が試した限りですが、Page_Load ではうまくいかず、Page_Init でないとダメでした。(Page_Load で設定すると UpdateMode="Conditional" ではスクリプトエラー、デフォルトの Always にすればスクリプトエラーは出ないものの偶数回目のクリックで全画面が更新されてしまうというように、期待した動きにはなりません)

しかしながら、目的の(トリガとする)コントロールの参照が Page_Init では取得できない場合が困ります。例えば DetailesView の Template 内に配置したコントロールを FindControl メソッドで取得しようとしても、Page_Init では取得できません。

で、どうするかと言えば、ちょっとハック的なやり方ですが、Page_Init ではダミーのコントロールを設定し、Page_Load で目的のコントロールを設定するとうまくいきました。

DetailsView の Template に配置した Button を、AsyncPostBackTrigger クラスの ControlID プロパティに設定するサンプルを以下にアップしておきます。

<%@ 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_Init(object sender, EventArgs e)
  {
    // ここでは LinkButton1 取得できない。以下は常に null。
    // それはよしとして、不可解なのは、以下の一文を入れて
    // おくと LinkButton1 クリックで、DetailsView が消えて
    // しまうこと。何故???
    //LinkButton lb =
    //  (LinkButton)DetailsView1.FindControl("LinkButton1");

    // この時点でダミーでもいいから以下のように Trigger を追
    // 加しておかないとうまくいかない。これなしでページロード
    // でトリガを追加すると、LinkButton1 クリックでスクリプト
    // エラーとなる。
    AsyncPostBackTrigger trigger = new AsyncPostBackTrigger();
    trigger.ControlID = DummyButton.ID;
    UpdatePanel2.Triggers.Add(trigger);
  }

  protected void Page_Load(object sender, EventArgs e)
  {
    LinkButton lb = 
      (LinkButton)DetailsView1.FindControl("LinkButton1");
    if (lb != null)
    {
      AsyncPostBackTrigger trigger = new AsyncPostBackTrigger();

      // DetailsView に配置したボタンは UniqueID を持つ。
      // それを ControlID に設定しないとエラーになる。
      trigger.ControlID = lb.UniqueID;
      UpdatePanel2.Triggers.Add(trigger);
    }        
  }    

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

<html xmlns="http://www.w3.org/1999/xhtml">
<head 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>

Tags: , ,

AJAX

ACT の TabContainer

by WebSurfer 2010年11月5日 21:04
ACT の TabContainer と Calendar の問題

AJAX Control Toolkit の TabContainer や Calendar を使用する際、head 要素内にコードブロック(即ち、<% ... %>)を定義すると例外がスローされるという問題と回避策の紹介です。なお、この問題は .NET 4 でも同様に発生します。

Calendar や TabContainer は AjaxControlToolkit.ScriptObjectBuilder クラスの RegisterCssReferences メソッドを起動し、head タグ内に HTML link 要素(CSS ファイルの定義)を追加しようとします。その時に head タグ内にコードブロックがあると、画像に示したように例外をスローします。

この問題を再現する具体的なコード例を下に載せておきます。この例のように JavaScript を定義する際に ClientID を取得するため、コードブロックを埋め込むケースはよくあるのではないでしょうか。

回避策は書くまでもないですが、コードブロックを head タグの外に移動するだけです。

なお、'<%=TextBox1.ClientID%>' の代わりに、実際の ClientID をプログラマがハードコーディングするのは避けたほうがよさそうです。何故なら、コントロールを配置する場所によって Client ID は変化しますし、コードを変更しなくても将来の ASP.NET の仕様の変更で変わるかもしれませんので。

<%@ 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">

</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title></title>
    <script type="text/javascript">   
    <!--
        function ShowText() {
            var txtbx = $get('<%=TextBox1.ClientID%>');
            txtbx.value = 'これはテストです。';
        }
    //-->
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:ToolkitScriptManager ID="ToolkitScriptManager1" 
            runat="server">
        </asp:ToolkitScriptManager>
        <asp:TextBox ID="TextBox1" 
            runat="server" 
            Text="Test" />
        <asp:Button ID="Button1" 
            runat="server" 
            Text="Button" 
            OnClientClick="ShowText(); return false;" />
        <asp:TabContainer ID="TabContainer1" 
            runat="server" 
            ActiveTabIndex="0" 
            Height="160px" 
            Width="360px">
            <asp:TabPanel runat="server" 
                HeaderText="TabPanel1" 
                ID="TabPanel1">
                <ContentTemplate>ABCDE</ContentTemplate>
            </asp:TabPanel>
            <asp:TabPanel ID="TabPanel2" 
                runat="server" 
                HeaderText="TabPanel2">
                <ContentTemplate>FGHIJ</ContentTemplate>
            </asp:TabPanel>
        </asp:TabContainer>
    </div>
    </form>
</body>
</html>

Tags: , ,

AJAX

window.opener を使って PostBack

by WebSurfer 2010年11月4日 21:47
window.opener を使って PostBack

JavaScript の window.opener を利用すると、例えばページ A から window.open でページ B を開いたとすると、ページ B からページ A にポストバックをかけることが可能です。

先の記事 ModalPopup で編集・更新操作 で書いた、「一覧画面にレコード一覧を表示してユーザーに選択させ、一覧画面は開いたまま別に編集画面を開いてそこでレコードを編集してもらい、編集画面の[更新]ボタンクリックで DB を更新すると共に編集画面は閉じて、更新結果を一覧画面に反映する」というシナリオに使えます。

それを知らなかったので、先の記事では「開いたままの一覧画面に、編集画面による更新結果を反映するのは難しいです(というよりほとんど無理と思います)。」と書きましたが、そんなことはなかったです。(汗)

以下に、備忘録として例を書いておきます。

検証用に、一覧表示のための GridView を持つ親ページ (RecordList.aspx) と、編集・更新のための DetailsView を持つ子ページ (EditAndUpdate.aspx) を用いてアプリを作ってみました。手順は以下のようになります。

  1. まず親ページを要求する。
  2. 親ページは、DB からデータを取得し、GirdView にレコード一覧を表示。
  3. GridView 上のレコードを選択すると、親ページは開いたまま別ウィンドウを開いて子ページを要求。
  4. 子ページは、選択されたレコードを DB から取得し DetailsView に Edit モードで表示。
  5. 子ページでレコードを編集し、ボタンクリックでポストバックして DB を更新。同時に、親ページにポストバックをかけるための JavaScript を追加する。
  6. 子ページが再描画された時、追加した JavaScript が働いて親ページにポストバックをかける。
  7. 親ページはサーバー側で更新後のレコードを取得し GridView に表示する。
  8. 子ページを自動的に閉じる。

コードは以下のとおりです。

RecordList.aspx(親ページ)

<%@ Page Language="C#" %>
<%@ import namespace="System.Data" %>

<!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 GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
  {
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
      LinkButton lb = (LinkButton)e.Row.FindControl("LinkButton1");
      DataRowView rowView = (DataRowView)e.Row.DataItem;
      string script = String.Format(
        "javascript:window.open('EditAndUpdate.aspx?customerId={0}', " +
        "null, 'width=400, height=300'); return false;", 
        rowView["CustomerID"]);
      lb.OnClientClick = script;
    }
  }

  protected void Page_Load(object sender, EventArgs e)
  {
      if (Page.IsPostBack)
      {
          GridView1.DataBind();
      }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>一覧画面</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h1>一覧画面</h1>
    <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
      ConnectionString="<%$ ConnectionStrings:Northwind %>" 
      SelectCommand=
        "SELECT [CustomerID], [CompanyName], [ContactName], 
          [ContactTitle], [Phone] 
        FROM [Customers]">
    </asp:SqlDataSource>
    <asp:GridView ID="GridView1" 
      runat="server" 
      AutoGenerateColumns="False" 
      DataKeyNames="CustomerID" 
      DataSourceID="SqlDataSource1" 
      EnableModelValidation="True" 
      OnRowDataBound="GridView1_RowDataBound"
      AllowPaging="True">
      <Columns>
        <asp:BoundField DataField="CustomerID" 
          HeaderText="CustomerID" 
          ReadOnly="True" 
          SortExpression="CustomerID" />
        <asp:BoundField DataField="CompanyName" 
          HeaderText="CompanyName" 
          SortExpression="CompanyName" />
        <asp:BoundField DataField="ContactName" 
          HeaderText="ContactName" 
          SortExpression="ContactName" />
        <asp:BoundField DataField="ContactTitle" 
          HeaderText="ContactTitle" 
          SortExpression="ContactTitle" />
        <asp:BoundField DataField="Phone" 
          HeaderText="Phone" 
          SortExpression="Phone" />
        <asp:TemplateField ShowHeader="False">
          <ItemTemplate>
            <asp:LinkButton ID="LinkButton1" 
              runat="server" 
              CausesValidation="False" 
              CommandName="Select" 
              Text="編集">
            </asp:LinkButton>
          </ItemTemplate>
        </asp:TemplateField>
      </Columns>
    </asp:GridView>
  </div>
  </form>
</body>
</html>

EditAndUpdate.aspx(子ページ)

<%@ 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)
  {
    string customerid = Request.QueryString["customerId"];
    if (!String.IsNullOrEmpty(customerid))
    {
      DetailsView1.DefaultMode = DetailsViewMode.Edit;
      Panel1.Visible = true;
      Panel2.Visible = false;
      Label1.Text = customerid;
    }
    else
    {
      Panel1.Visible = false;
      Panel2.Visible = true;
    }
  }

  protected void  LinkButton1_Click(object sender, EventArgs e)
  {
    DetailsView1.UpdateItem(false);
    string csname = "PostBackSourceWindowScript";
    Type cstype = this.GetType();
    ClientScriptManager cs = Page.ClientScript;
    if (!cs.IsStartupScriptRegistered(cstype, csname))
    {
      String cstext =
      @"window.onload = function() {
        if (!window.opener || window.opener.closed) {
          self.close();
        }
        else {
          window.opener.document.getElementById('form1').submit();
          self.close();
        }
      };";
      cs.RegisterStartupScript(cstype, csname, cstext, true);
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>編集画面</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:Panel ID="Panel1" runat="server">
      <h1>編集画面</h1>
      <span>
        選択された Customer ID: <asp:Label ID="Label1" runat="server" />
      </span>

      <asp:SqlDataSource ID="SqlDataSource2" runat="server" 
        ConnectionString="<%$ ConnectionStrings:Northwind %>" 
        SelectCommand=
          "SELECT [CustomerID], [CompanyName], [ContactName], 
             [ContactTitle], [Phone] 
          FROM [Customers] 
          WHERE ([CustomerID] = @CustomerID)" 
        UpdateCommand=
          "UPDATE [Customers] 
          SET [CompanyName]=@CompanyName, [ContactName]=@ContactName, 
            [ContactTitle]=@ContactTitle, [Phone]=@Phone 
          WHERE [CustomerID] = @CustomerID">
        <SelectParameters>
          <asp:ControlParameter ControlID="Label1" 
            Name="CustomerID" 
            PropertyName="Text" 
            Type="String" />
        </SelectParameters>
        <UpdateParameters>
          <asp:Parameter Name="CompanyName" Type="String" />
          <asp:Parameter Name="ContactName" Type="String" />
          <asp:Parameter Name="ContactTitle" Type="String" />
          <asp:Parameter Name="Phone" Type="String" />
          <asp:Parameter Name="CustomerID" Type="String" />
        </UpdateParameters>
      </asp:SqlDataSource>

      <asp:DetailsView ID="DetailsView1" 
        runat="server" 
        AutoGenerateRows="False" 
        DataKeyNames="CustomerID" 
        DataSourceID="SqlDataSource2" 
        EnableModelValidation="True">
        <Fields>
          <asp:BoundField DataField="CustomerID" 
            HeaderText="CustomerID" 
            ReadOnly="True" 
            SortExpression="CustomerID" />
          <asp:BoundField DataField="CompanyName" 
            HeaderText="CompanyName" 
            SortExpression="CompanyName" />
          <asp:BoundField DataField="ContactName" 
            HeaderText="ContactName" 
            SortExpression="ContactName" />
          <asp:BoundField DataField="ContactTitle" 
            HeaderText="ContactTitle" 
            SortExpression="ContactTitle" />
          <asp:BoundField DataField="Phone" 
            HeaderText="Phone" 
            SortExpression="Phone" />
        </Fields>
      </asp:DetailsView>
      <div>
        <asp:LinkButton ID="LinkButton1" 
          runat="server" 
          onclick="LinkButton1_Click" 
          Text="Save" />
        <asp:LinkButton ID="LinkButton2" 
          runat="server"
          CausesValidation="False" 
          Text="Cancel"
          OnClientClick="self.close(); return false;" />
      </div>
    </asp:Panel>
    <asp:Panel ID="Panel2" runat="server">
      <p>クエリ文字列 customerId が指定されていません。</p>
    </asp:Panel>
  </div>
  </form>
</body>
</html>

先の記事 ModalPopup で編集・更新操作 で紹介したアプリと比べると、一覧画面の[編集]ボタンの二度押し防止と、更新された行を GirdView 上でハイライトする操作が実装できていません。解決策がないか考えてみましたが、思いつきません。というわけで、先の記事の例の方がよさそうです。

Tags: ,

ASP.NET

About this blog

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

Calendar

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

View posts in large calendar