WebSurfer's Home

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

ModalPopup で編集・更新操作

by WebSurfer 2010年10月14日 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

customErrors と requestFiltering

by WebSurfer 2010年10月12日 12:17

先の記事 アプリケーションレベルの例外処理 で書きました、(1) Button 6 クリックおよび (2) 存在しない静的ファイルを要求した場合は、web.config の customErrors 要素にカスタムエラーページを設定してもそれが表示されない(下の画像のような標準エラーページが表示される)件につき理由を調べてみました。

標準エラーページ

調査結果をまとめると以下のとおりです(自信度 99% ぐらい。残り 1% の不安要素は後述します)。

(1) Button 6 クリック(Global-NoCatch.asax へリダイレクト)

IIS7 で導入された 要求のフィルタリング で、特定の拡張子(asa, asax, ascx など)のファイルに対する要求がブロックされています。

Global-NoCatch.asax へリダイレクトした場合は以下のようになります。拡張子でチェックしているので Global.asax でも結果は同じです。

要求フィルタモジュール (RequestFilteringModule) が拡張子 asax をチェックして拒否。 → 静的ファイルハンドラー (StaticFile) によって 404.7 エラーとして処理される。 → デフォルト(httpErrors 設定なし)では標準エラーページを返す。

先に applicationHost.config の設定を変えて(fileExtension=".asax" allowed="false" を true にして)試したときも、やはり標準エラーページになったのは、asax が静的ファイルハンドラー (StaticFile) によって処理されたためと思われます。

注意:
2014/4/19 時点の最新のサンプルでは、Button 6 クリックでのリダイレクト先が Global-NoCatch.asax から NonexistentPage-NoCatch.aspx/xxx...(xxx... は x が 500 文字)に変わっています。(遅くとも 2011/4/23 には変わっていたはず) 結果、HTTP/1.1 400 Bad Request が返ってきます。詳しくは下の「2011/4/23 追記」と「2011/8/22 追記」を見てください。

(2) 存在しない静的ファイルを要求

統合パイプラインモードでも既存のハンドラーマッピングはすべて機能したままなので、静的ファイルは依然として IIS のネイティブの静的ファイルハンドラー (StaticFile) によって処理される。 → デフォルト(httpErrors 設定なし)では標準エラーページを返す。

以上から、要求フィルタリングで拒否設定されているファイルや存在しない静的ファイルを要求されて、カスタムエラーページを返には、TechNet のページ HTTP エラー <httpErrors> の中の「カスタムエラーページを追加する方法」で述べられている手順を取るほかなさそうです。

なお、開発サーバーで検証すると動作が異なる件は、今後二度と開発サーバーを検証に利用しないことにするということで、気にしないことにしました。(笑) 開発サーバーには要求フィルタリングはなく、例外はすべて ASP.NET で処置されるということなのかもしれません(想像です)。

------ 2011/4/23 追記(2014/4/19 一部訂正) ------

上に書いたとおり、MSDN ライブラリの英語版 Complete Example for Error Handlers にコメントしましたが、それを受けてコードが一部変更されています。(日本語の方は 2011/4/23 現在以前のままですが)

具体的には、Button6 クリックでのリダイレクト先を Global-NoCatch.asax から NonexistentPage-NoCatch.aspx/xxx...(xxx... は x が 500 文字)に変えています。

でも、依然としてダメです。DefaultRedirectErrorPage.aspx にはリダイレクトされません。サーバーは HTTP/1.1 400 Bad Request を返します。


------ 2011/8/22 追記(2014/4/19 一部訂正) ------

MSDN ライブラリの英語版 Complete Example for Error Handlers に書いたコメントはすでに消されていました。また、「日本語の方は 2011/4/23 現在以前のまま」と書きましたが、英語版と同様に修正されていました。

ただし、コードの内容は 2011/4/23 時点の英語版のままです。Button 6 の説明に "Click this button to create an HTTP 400 (invalid url) error. Application_Error will catch this but will not take any action on it, and ASP.NET will redirect to DefaultRedirectErrorPage.aspx." と書いてありますが、そうはなりません。

NonexistentPage-NoCatch.aspx/xxx...(xxx... は x が 500 文字)を要求すると、サーバーは HTTP/1.1 400 Bad Request を返します。web.config の defaultRedirect に指定した DefaultRedirectErrorPage.aspx にはリダイレクトされません。

(HTTP/1.1 400 Bad Request が返ってくると、IE に表示されるのはサーバーから帰ってきた html コードではなく IE が差し替えたものになるので注意してください。Firefox の場合はサーバーから帰ってきた html コードがそのまま表示されます)

ちなみに、以前のように Global-NoCatch.asax にリダイレクトすると、「HTTP エラー 404.7 - Not Found 要求フィルタ モジュールが、ファイル拡張子を拒否するように構成されています。」という標準エラーページが返ってきます。

Tags: ,

Exception Handling

GridView 内の全 CheckBox をオン(jQuery)

by WebSurfer 2010年10月8日 22:11

GirdView の各行に配置した複数の CheckBox に、一度に全部チェックを入れたり外したりする操作を、クライアントサイドのスクリプトで実行する方法の jQuery バージョンです。

普通の JavaScript を使ったバージョンは、先の記事 GridView 内の全 CheckBox をオン にありますので、見比べてみてください。

ネタは先の記事 jQuery の本を買いました で紹介した本の中のサンプルです。

コードは以下の通りです。説明はコードの中にコメントとして入れましたので、そちらを参照してください。

jQuery を使うと、普通の JavaScript を使った場合と比べて、かなりコード量を減らすことができるのは確かのようです。でも、考え方がかなり違うようで、コードの書き方も自分の常識を超えてました。jQuery のセレクタのパワーを垣間見ることができたような気もしますが、正直言って、まだよく分かってないです。(笑)

<%@ 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">
  DataTable CreateDataTable()
  {
    DataTable dt = new DataTable();
    DataRow dr;

    dt.Columns.Add(new DataColumn("Item", typeof(Int32)));
    dt.Columns.Add(new DataColumn("Name", typeof(string)));
    dt.Columns.Add(new DataColumn("Price", typeof(Int32)));

    for (int i = 0; i < 10; i++)
    {
      dr = dt.NewRow();
      dr["Item"] = i;
      dr["Name"] = "Item " + i.ToString();
      dr["Price"] = 123000 * (i + 1);
      dt.Rows.Add(dr);
    }
    return dt;
  }

  void Page_Load(Object sender, EventArgs e)
  {
    if (!IsPostBack)
    {
      GridView1.DataSource = CreateDataTable();
      GridView1.DataBind();
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>Check/Uncheck All CheckBoxes in a GridView</title>
  <script src="Scripts/jquery-1.4.1.js" type="text/javascript"></script>
  <script type="text/javascript">
    <!--
    $(function () {
      // ヘッダーの CheckBox にチェックを入れる/外すのと連
      // 動して、各行に配置した CheckBox すべてにチェックを
      // 入れる/外す。
      $('table tr th input:checkbox').click(function () {
        $('table tr td input:checkbox')
          .attr('checked', $(this).is(':checked'));
      });

      // 各行に配置した CheckBox のすべてにチェックが入った
      // 時、ヘッダーの CheckBox にもチェックを入れる。
      // 各行に配置した CheckBox の一つでもチェックを外すと、
      // ヘッダーの CheckBox のチェックが外れる。
      $('table tr td input:checkbox').click(function () {
        var chkAll =
          $('table tr td input:checkbox:not(:checked)')
            .length == 0 ? true : false;
        $('table tr th input:checkbox')
          .attr('checked', chkAll);
      });

      // [Check All]ボタンクリックで、すべての CheckBox
      // にチェックを入れる。
      $('#checkAll').click(function () {
        $('table input:checkbox').attr('checked', true);
      });

      // [Uncheck All]ボタンクリックで、すべての CheckBox 
      // のチェックを外す。
      $('#uncheckAll').click(function () {
        $('table input:checkbox').attr('checked', false);
      });
    });            
  //-->
  </script>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h2>Check/Uncheck All CheckBoxes</h2>
    <input type="button" 
      id="checkAll"
      value="Check All" /> 
    <input type="button" 
      id="uncheckAll"
      value="Uncheck All" />        
    <asp:GridView ID="GridView1" 
      runat="server" 
      AutoGenerateColumns="False">
      <Columns>
        <asp:TemplateField>
          <HeaderTemplate>
            <input id="Checkbox1" type="checkbox" />
          </HeaderTemplate>
          <ItemTemplate>
            <input id="Checkbox2" type="checkbox" />
          </ItemTemplate>
        </asp:TemplateField>
        <asp:BoundField HeaderText="Item" DataField="Item" />
        <asp:BoundField HeaderText="Name" DataField="Name" />
        <asp:BoundField HeaderText="Price" DataField="Price" />
      </Columns>
    </asp:GridView>    
  </div>
  </form>
</body>
</html>

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

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

Tags: , ,

JavaScript

About this blog

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

Calendar

<<  2024年5月  >>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar