WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

UpdatePanel へのトリガ追加

by WebSurfer 6. November 2010 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 5. November 2010 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

ModalPopup で編集・更新操作

by WebSurfer 14. October 2010 23:18

DB の更新などで、まず一覧画面にレコード一覧を表示してユーザーに選択させ、一覧画面は開いたまま別に編集画面を開いてそこでレコードを編集してもらい、編集画面の[更新]ボタンクリックで DB を更新すると共に編集画面は閉じて、更新結果を一覧画面に反映したいといった要求を時々聞きます。

1 ページで行うか、2 ページ使う場合は一覧画面 → 編集画面 → 一覧画面と遷移していけば難しくありませんが、上記のように開いたままの一覧画面に、編集画面による更新結果を反映するのは難しいです(というよりほとんど無理と思います)。

という訳で、実際は 1 ページで行って、見かけは 2 画面で行うようにする方法を紹介します。

2 画面で選択・編集・更新操作

具体的にどうするかというと、一覧を表示する GridView と、レコードの編集を行う DetailsView を 1 ページに配置し、DetailsView を ASP.NET AJAX Control Toolkit の ModalPopup で表示するというものです。

GridView の各行に配置した[編集]ボタンをクリックすると、ModalPopup の中の DetailsView が表示され、当該行のレコードを編集できます。その間、バックグラウンドは暗く表示され、GridView の操作はできません。

DetailsView の編集が終わったら、[更新]ボタン(上の画像、下のコードでは[Save]ボタン)をクリックすると DB の当該レコードが更新され、ModalPopup が非表示になります。

そして GridView 上で更新された行がハイライトされます。(以下のコードでは、バックグラウンドが黄色に変わり、6 秒間で3 回点滅した後、元のスタイルに戻ります)。

コードは以下のとおりです。Microsoft 提供のサンプルデータベース Northwind の Customers テーブルを使用しています。

なお、Opera 10.63 では[Save]ボタンをクリックしたとき ModalPopup が消えないという問題があります。IE8, Firefox 3.6.10, Safari 5.0.2 は問題なしでした。

<%@ 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 GridView1_SelectedIndexChanged(object sender, EventArgs e)
  {
    DetailsView1.DefaultMode = DetailsViewMode.Edit;
    UpdatePanel2.Update();
    ModalPopupExtender1.Show();
  }

  protected void  LinkButton1_Click(object sender, EventArgs e)
  {
    DetailsView1.UpdateItem(false);
    GridView1.DataBind();
    UpdatePanel1.Update();
    ModalPopupExtender1.Hide();

    if (ScriptManager.GetCurrent(this).IsInAsyncPostBack)
    {
      ScriptManager.GetCurrent(this).RegisterDataItem(GridView1, 
        GridView1.SelectedIndex.ToString());
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
  <script src="Scripts/jquery-1.4.1.js" type="text/javascript"></script>
  <style type="text/css">
    /*Modal Popup*/
    .modalBackground {
      background-color: Gray;
      filter: alpha(opacity=70);
      opacity: 0.7;
    }

    tr.updated td {
      background-color: yellow;
    }

    .detail {
      background-color: #ffffff;
    }
  </style>
</head>
<body>
  <form id="form1" runat="server">
  <asp:ToolkitScriptManager ID="ToolkitScriptManager1" runat="server">
  </asp:ToolkitScriptManager>
  <script type="text/javascript">
  <!--
    Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(pageLoaded);

    function pageLoaded(sender, args) {
      var updateRowIndex = args.get_dataItems()["GridView1"];
      if (updateRowIndex) {
        var tr = $get("GridView1").rows[parseInt(updateRowIndex) + 1];
        $(tr).addClass('updated').children('td')
          .fadeTo(1000, 0.33).fadeTo(1000, 1.0)
          .fadeTo(1000, 0.33).fadeTo(1000, 1.0)
          .fadeTo(1000, 0.33).fadeTo(1000, 1.0);
        window.setTimeout(function () {
          $(tr).removeClass('updated');
        }, 6000);
      }
    }
  //-->
  </script>
  <div>
    <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
      ConnectionString="<%$ ConnectionStrings:Northwind %>" 
      SelectCommand=
        "SELECT [CustomerID], [CompanyName], [ContactName], 
          [ContactTitle], [Phone] 
        FROM [Customers]">
    </asp:SqlDataSource>
    <asp:UpdatePanel ID="UpdatePanel1" 
      runat="server" 
      UpdateMode="Conditional">
      <ContentTemplate>
        <asp:GridView ID="GridView1" 
          runat="server" 
          AutoGenerateColumns="False" 
          DataKeyNames="CustomerID" 
          DataSourceID="SqlDataSource1" 
          EnableModelValidation="True" 
          OnSelectedIndexChanged="GridView1_SelectedIndexChanged" 
          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:CommandField SelectText="編集" 
              ShowSelectButton="True" />
          </Columns>
        </asp:GridView>
      </ContentTemplate>
    </asp:UpdatePanel>
  </div>
  <div>
    <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="GridView1" Name="CustomerID" 
          PropertyName="SelectedValue" 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:Panel ID="Panel1" runat="server" CssClass="detail">
      <asp:UpdatePanel ID="UpdatePanel2" 
        runat="server" 
        UpdateMode="Conditional">
        <ContentTemplate>
          <asp:Button ID="DummyButton" 
            runat="server" 
            style="display: none;" />
          <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" />
          </div>
          <asp:ModalPopupExtender ID="ModalPopupExtender1" 
            runat="server" 
            PopupControlID="Panel1" 
            TargetControlID="DummyButton" 
            CancelControlID="LinkButton2" 
            BackgroundCssClass="modalBackground">
          </asp:ModalPopupExtender>
        </ContentTemplate>
      </asp:UpdatePanel> 
    </asp:Panel> 
  </div>
  </form>
</body>
</html>

ネタは先の記事 jQuery の本を買いました で紹介した本のサンプルです(サンプルそのままではなく、不要な部分の削除、誤りの修正を行っています)。

jQuery の本なのに、jQuery を使っているのは GridView 上で更新された行がハイライトされるところだけです。もっと使えるのかと思っていたのですが、更新操作のサーバーとの連携が難しいようで、ちょっと期待はずれでした。

---------------- 2011/5/29 追記 ----------------

Opera では[Save]ボタンをクリックしたとき ModalPopup が消えないという問題があると書きましたが、UpdatePanel で囲うのを GridView と DetailsView に分けず一つにまとめるとその問題が回避できます。

その場合、GridView1_SelectedIndexChanged メソッドの UpdatePanel2.Update(); および LinkButton1_Click メソッドの UpdatePanel1.Update(); は不要です。

Tags: , , ,

AJAX

About this blog

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

Calendar

<<  October 2024  >>
MoTuWeThFrSaSu
30123456
78910111213
14151617181920
21222324252627
28293031123
45678910

View posts in large calendar