WebSurfer's Home

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

ユーザーコントロールから ClientID 取得

by WebSurfer 2015年10月24日 14:18

ユーザーコントロール(.ascx)の中に配置した子コントロールの ClientID を、そのユーザーコントロールを使う .aspx ページで取得するにはどうしたらいいかという話を書きます。

ユーザーコントロール内の TextBox のイベント

上の画像は、ユーザーコントロール(子に TextBox を持つ)の html ソースがブラウザにレンダリングされた後、クライアント側でその TextBox(html では input 要素)を JavaScript の getElementById メソッドで取得し、input 要素の change イベントにリスナをアタッチして入力された値を取得して alert で表示したものです。(ブラウザは Chrome です)

input 要素 <input type="text" id="xxx" ... /> を getElementById メソッドで取得するためには id の値 xxx が必要です。サーバー側のコードでは ClientID が xxx になりますので TextBox.Client プロパティで容易に取得できますが、クライアント側ではどのようにするのでしょうか?

一旦 html ソースをレンダリングし、その中の xxx を調べてそれを JavaScript のコードにハードコーディングする・・・というのは避けた方がよさそうです。将来 ClientID の命名規則が変わると動かなくなりますし、コードの可読性もよくないので保守の問題もありそうです。

なので、getElementById("<%=TextBox.ClientID%>") のようにコードブロック(即ち、<% ... %>)で取得するのがよさそうですが、TextBox はユーザーコントロールの中の子コントロールとして配置されているのでそうはいきません。

いろいろ解決法はあると思いますが、ユーザーコントロールのコードビハインドに TextBox の ClientID を取得するためのパブリックプロパティを追加し、.aspx ページではそのパブリックプロパティをコードブロックで埋め込んでやればいいはずです。

ユーザーコントロール(.ascx)

ユーザーコントロール(.ascx)のコードは具体的には以下のようになります。本題とは関係ないコードがいろいろと入っていますが、TextBoxClientID プロパティの定義に注目してください。

<%@ Control Language="C#" 
    ClassName="_0014_ClientIDInWebUserControl" %>

<%@ Register Assembly="AjaxControlToolkit" 
    Namespace="AjaxControlToolkit" 
    TagPrefix="Ajax" %>

<script runat="server">

    public string TextBoxClientID
    {
        get { return Control_Date.ClientID; }
    }
</script>

<asp:TextBox ID="Control_Date" 
    runat="server" 
    Width="130px" 
    MaxLength="1" 
    CssClass="imeDisabled" 
    style="text-align:justify" 
    autocomplete="on" 
    ValidationGroup="MKE" />
 <Ajax:MaskedEditExtender 
    ID="Control_DateMaskEditExtender" 
    runat="server"
    TargetControlID="Control_Date"
    Mask="9999/99/99"
    MessageValidatorTip="true"
    OnFocusCssClass="MaskedEditFocus"
    OnInvalidCssClass="MaskedEditError"
    MaskType="Date"
    DisplayMoney="Left"
    AcceptNegative="Left"
    ErrorTooltipEnabled="True" />
 <Ajax:MaskedEditValidator 
    ID="Control_DateMaskEditValidator" 
    runat="server"
    ControlExtender="Control_DateMaskEditExtender"
    ControlToValidate="Control_Date"
    Display="Dynamic"
    EmptyValueBlurredText="*"
    ValidationGroup="MKE" />
 <Ajax:CalendarExtender 
    ID="Control_CalendarExtender" 
    Format="yyyy/MM/dd" 
    runat="server" 
    TargetControlID="Control_Date" 
    PopupButtonID="ImgBntCalc" /> 

.aspx ページ

上記のユーザーコントロールを使用する .aspx ページは以下のようになります。下の方のインラインの JavaScript のコードで、コートブロックによって thismonthBegin.TextBoxClientID から id を取得しているところに注目してください。

<%@ Page Language="C#" %>
<%@ Register TagPrefix="Ctrl" TagName="DateCtrl" 
    Src="~/0014-ClientIDInWebUserControl.ascx" %>

<%@ 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 runat="server">
    <title></title>    
</head>
<body>
    <form id="form1" runat="server">
    <asp:ToolkitScriptManager 
        ID="ToolkitScriptManager1" runat="server">
    </asp:ToolkitScriptManager>
    <div>
        <Ctrl:DateCtrl ID="thismonthBegin" runat="server" />
    </div>
    </form>    
</body>
</html>
<script type="text/javascript">
//<![CDATA[
    window.onload = function () {
        var element = document.getElementById(
                    "<%=thismonthBegin.TextBoxClientID%>");
        element.onchange = function () {
            alert(element.value);
        }
    }
//]]>
</script>
注意:
AJAX Control Toolkit の TabContainer や Calendar を使用する際、head 要素内にコードブロック(<% ... %>)を定義すると、"System.Web.HttpException: コントロールにコード ブロック (<% ... %>) が含まれているため、コントロールのコレクションを変更できません。" という例外がスローされるので注意してください。その理由等は先の記事 ACT の TabContainer を見てください。回避策は、上のコード例のように、コードブロックを head タグの外に移動するだけです。

Tags: ,

ASP.NET

DropDownList での NULL の処置

by WebSurfer 2015年10月23日 17:19

下の画像のように、GridView などでデータベースの更新操作を行う際 DropDownList を利用してユーザー入力に便宜を図ることがあると思います。その時、NULL を DropDownList でどう対処するかという話を書きます。

GridView 上の DropDownList

ここで紹介する例には Microsoft が提供している Northwind サンプルデータベースの Products テーブルと Categories テーブルを使用しています。

Products テーブルの中の ProductName, CategoryID フィールドを GridView 上で更新する際、CategoryID の列に DropDownList を表示するようにします。

DropDownList には、ユーザーが見ても何だか分からない数字 (CategoryID) を表示するのでははなくて、ユーザーが読んで理解できる名前 (CategoryName) を Categories テーブルから取得して表示します。

Products テーブルの CategoryID には以下のような外部キー制約がかかっていますので、Categories テーブルの CategoryID の値(Northwind サンプルデータベースでは 1 から 8)以外のものは入力できません。

ALTER TABLE [dbo].[Products]  
  ADD CONSTRAINT [FK_Products_Categories] 
  FOREIGN KEY([CategoryID])
  REFERENCES [dbo].[Categories] 
  ([CategoryID])

ただし、NULL は許可されていますので、1 から 8 以外に NULL が入っていることがあります。また、更新の際に NULL を入力したいというケースもあるかもしれません。

先の記事 GridView 上の DropDownList に ToolTip でも同様に DropDownList を使用していますが、そこでは NULL 対応は考えていませんでした。それを以下ように修正して NULL に対応します。

  1. SELECT クエリで INNER JOIN 句に替えて LEFT OUTER JOIN 句を使い CategoryID が NULL のレコードも抽出する。
  2. DropDownList で AppendDataBoundItems="True" とし、以下のような NULL 用の ListItem を追加する。

    <asp:ListItem Value="">NULL</asp:ListItem>

    Value は空白("")にしてください。DropDownList に表示される文字列 NULL は任意のものに変えていいです。データベース上での NULL と ASP.NET コントロール上での空白("")の変換は、ASP.NET の組み込み機能を利用します。その機能についての説明は MSDN ライブラリの記事データ ソース コントロールを使用した、データベースの Null 値の処理を見てください。

具体的なコードは以下の通りです。(分かりやすくするため、先の記事 GridView 上の DropDownList に ToolTip のコードの ToolTip を設定する部分は省いています)

<%@ 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">
    
</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:SqlDataSource ID="SqlDataSource1" 
      runat="server" 
      ConnectionString="<%$ ConnectionStrings:Northwind2 %>" 
      SelectCommand=
        "SELECT TOP 10 
        p.[ProductID], p.[ProductName], 
        p.[CategoryID], c.[CategoryName] 
        FROM [Products] AS p 
        LEFT OUTER JOIN [Categories] AS c 
        ON p.[CategoryID] = c.[CategoryID]
        ORDER BY p.[ProductID]" 
      UpdateCommand=
        "UPDATE [Products] 
        SET [ProductName] = @ProductName, 
        [CategoryID] = @CategoryID 
        WHERE [ProductID] = @ProductID">
      <UpdateParameters>
        <asp:Parameter Name="ProductID" Type="Int32" />
        <asp:Parameter Name="ProductName" Type="String" />
        <asp:Parameter Name="CategoryID" Type="Int32" />
      </UpdateParameters>
    </asp:SqlDataSource>

    <asp:SqlDataSource ID="SqlDataSource2" 
      runat="server" 
      ConnectionString="<%$ ConnectionStrings:Northwind2 %>" 
      SelectCommand=
        "SELECT [CategoryID], [CategoryName], [Description] 
        FROM [Categories]">
    </asp:SqlDataSource>
        
    <asp:GridView ID="GridView1" 
      runat="server" 
      AutoGenerateColumns="False" 
      DataKeyNames="ProductID" 
      DataSourceID="SqlDataSource1">
      <Columns>
        <asp:CommandField ShowEditButton="True" />
        <asp:BoundField DataField="ProductID" 
          HeaderText="ID" 
          InsertVisible="False" 
          ReadOnly="True" 
          SortExpression="ProductID" />
        <asp:BoundField DataField="ProductName" 
          HeaderText="Product Name" 
          SortExpression="ProductName" />
        <asp:TemplateField HeaderText="Category" 
          SortExpression="CategoryName">
          <EditItemTemplate>                        
            <asp:DropDownList ID="DropDownList1" 
              runat="server" 
              DataSourceID="SqlDataSource2" 
              DataTextField="CategoryName" 
              DataValueField="CategoryID" 
              SelectedValue='<%# Bind("CategoryID") %>' 
              AppendDataBoundItems="True">
              <asp:ListItem Value="">NULL</asp:ListItem>
            </asp:DropDownList>
          </EditItemTemplate>
         <ItemTemplate>
            <asp:Label ID="Label1" 
              runat="server" 
              Text='<%# Bind("CategoryName") %>'>
            </asp:Label>
          </ItemTemplate>
        </asp:TemplateField>
      </Columns>
    </asp:GridView>
  </div>
  </form>
</body>
</html>

Tags: , ,

ASP.NET

カスタム SiteMapPath

by WebSurfer 2015年10月22日 19:49

各要素に LinkButton(デフォルトではハイパーリンク)を使ったカスタム SiteMapPath をユーザーコントロールとして作ってみました。

注意:
自力でユーザーコントロールを作らなくても、標準の SiteMapPath コントロールのテンプレートを使ってそれに LinkButton を実装すれば以下に紹介するユーザーコントロールと同等の機能が得られ、その方がはるかに簡単かつスマートでした。詳しくはこの記事の下の方の「2015/10/25 追記」を見てください。

SiteMapPath

SiteMapPath コントロールは、web.sitemap ファイルがきちんと書かれていれば(特に url)、それを Page に配置するだけで上の画像のようなパンくずリストが表示されます。

その個々の要素は <a title="Home" href="/default.aspx">Home</a> というようなハイパーリンクになっています。なので、これをクリックするとブラウザは /default.aspx を GET 要求しますので、単純に要求したページに遷移するだけです。

そうではなくて、クリックすると一旦表示しているページにポストバックして、HttpResponse.Redirect メソッドで /default.aspx にリダイレクトするというカスタム SiteMapPath をユーザーコントロールとして作ってみました。

その理由は、ポストバックした際にサーバー側で何らかの処置をしたいということです。

サイトのパス情報を取得するのは SiteMap オブジェクトを利用するのが便利です。SiteMap オブジェクトは ASP.NET によって自動的に生成されるサイトのナビゲーション構造のインメモリ表現で、単純に SiteMap として参照が取得できます。

それを使って、ユーザーコントロールとして、Panel に LinkButton を動的に追加していきカスタム SiteMapPath を作ってみました。コードは以下のようになります。要点はコメントとして記載しましたのでそれを見てください。

<%@ Control Language="C#" ClassName="SiteMapPathUserControl" %>

<script runat="server">

  protected void Page_Init(object sender, EventArgs e)
  {
    List<SiteMapNode> nodeList = new List<SiteMapNode>();

    // CurrentNode → RootNode の順で nodeList に Add
    CreateNodeList(SiteMap.CurrentNode, nodeList);

    // RootNode → CurrentNode の順に LinkButton を並べる
    for (int i = nodeList.Count - 1; i >= 0; i--)
    {
      if (i == 0)
      {
        // CurrentNode は LinkButton でなくLiteral
        Literal literal = new Literal();
        literal.Text = nodeList[i].Title;
        siteMapPathContainer.Controls.Add(literal);
      }
      else
      {
        // CurrentNode の親から RootNode まで LinkButton
        LinkButton button = new LinkButton();
        button.Text = nodeList[i].Title;
        button.ToolTip = nodeList[i].Description;
        button.CommandName = "navigate";
        button.CommandArgument = nodeList[i].Url;
        button.Click += new EventHandler(button_Click);
        siteMapPathContainer.Controls.Add(button);
                
        // 各要素間を " > " でつなぐ
        Literal literal = new Literal();
        literal.Text = " &gt; ";
        siteMapPathContainer.Controls.Add(literal);
      }
    }
  }

  // ルートまでたどって List<SiteMapNode> を作るヘルパ。
  // web.sitemap ですべてのノードに url が設定済みの条件。
  protected void CreateNodeList(
        SiteMapNode node, List<SiteMapNode> nodeList)
  {
    nodeList.Add(node);

    if (node.Url == SiteMap.RootNode.Url)
    {
      return;
    }
    else
    {
      CreateNodeList(node.ParentNode, nodeList);
    }
  }

  protected void button_Click(object sender, EventArgs e)
  {
    if (((LinkButton)sender).CommandName == "navigate")
    {

      // ここでリダイレクトする前に必要な処置
            
      Response.Redirect(((LinkButton)sender).CommandArgument);
    }
  }
</script>

<asp:Panel ID="siteMapPathContainer" runat="server">
</asp:Panel>

あまり需要はないかもしれませんが、せっかく作ったので忘れないように記事を書いておきました。

------ 2015/10/25 追記 ------

この記事の上の方の「注記」で書きましたように、標準 SiteMapPath コントロールのテンプレートを使ってそれに LinkButton を実装する方法が簡単でした。具体例は以下のコードの通りです。

<%@ 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 LinkButton1_Click(object sender, EventArgs e)
  {
    if (((LinkButton)sender).CommandName == "navigate")
    {
            
      // ここでリダイレクトする前に必要な処置を行う

      Response.Redirect(((LinkButton)sender).CommandArgument);
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">

    <asp:SiteMapPath ID="SiteMapPath2" runat="server">
        <NodeTemplate>
            <asp:LinkButton 
                ID="LinkButton1" 
                runat="server"
                Text='<%# Eval("Title") %>'
                ToolTip='<%# Eval("Description") %>'
                CommandName="navigate" 
                CommandArgument='<%# Eval("Url") %>' 
                OnClick="LinkButton1_Click">
            </asp:LinkButton>
        </NodeTemplate>
        <CurrentNodeTemplate>
            <asp:Literal 
                ID="Literal1" 
                runat="server" Text='<%# Eval("Title") %>'>
            </asp:Literal>
        </CurrentNodeTemplate>
    </asp:SiteMapPath>

    </form>
</body>
</html>

Title, Url, Descriptin が正しく定義されている Web.sitemap ファイルがアプリケーションルート直下に存在することが条件ですので注意してください。

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