WebSurfer's Home

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

GridView と jQuery UI DatePicker

by WebSurfer 2014年10月26日 15:47

ASP.NET の GridView で jQuery UI の Datepicker を使用する例の紹介です。

GridView に jQuery UI の Datepicker

GridView と組み合わせるなら Ajax Control Toolkit の CalendarExtender を使う方が簡単で、かつ 日付表示の日本語対応 もやりやすいと思いますが、外観の統一を図るなどの理由で jQuery UI の Datapicker を使用したい場合があると思います。

その場合、どのように GridView 内で Datapicker を表示する TextBox を探して、その jQuery オブジェクトを取得し、それに datepicker() メソッドを適用するかを書きます。

通常、GridView では編集モードになった行に TextBox を表示してユーザー入力を受け付けるようにします。その時に datepicker() メソッドを当該 TextBox に適用すればいいわけです。

編集モードになった行は GridView.EditIndex プロパティで判定できますが、Page.Load のタイミングでは早すぎて判定できません。

GridView.RowEditing のタイミングでは、ユーザー入力用の TextBox が生成されていないので FindControl では取得できず、このタイミングでもダメです。

GridView.RowCreated のタイミングでは、最終的な TextBox.ClientID が取得できないので、これまたダメです。(このタイミングでは TextBox.ClientID にはまだ名前つきコンテナの id が付与されておらず TextBox1_x のようになります。最終的な TextBox.ClientID が取得できないと、クライアントスクリプトでその jQuery オブジェクトを取得できません)

GridView.PreLender のタイミングなら、編集モードになっているか否かおよび編集モードになっている行のインデックスは正しく判定できます。最終的な TextBox.ClientID も取得できます。

GridView の[編集]ボタンクリックでポストバックされた際に、GridView.PreLender イベントのハンドラで編集モードになっている行の中の Datapicker を適用する TextBox を探し、その ClientID を取得し、RegisterClientScriptBlock メソッド を使って当該 TextBox に datepicker() メソッドを適用するスクリプトを登録してやることで Datapicker を表示することができます。

以下のコードの GridView1_PreRender イベントハンドラののコードような感じです。GridView で[編集]ボタンをクリックして表示される TextBox にフォーカスを当てると、上の画像のように Datapicker が表示されます。

<%@ 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 GridView1_PreRender(object sender, EventArgs e)
  {
    int index = GridView1.EditIndex;

    if (index >= 0)
    {
      TextBox tb = 
        (TextBox)GridView1.Rows[index].FindControl("TextBox1");

      if (tb != null)
      {
        ClientScriptManager cs = Page.ClientScript;
        Type cstype = this.GetType();
        string csname = "jQueryUiDatepicker";

        if (!cs.IsClientScriptBlockRegistered(cstype, csname))
        {
          string cstext = "$(function () { $('#" 
              + tb.ClientID + "').datepicker(); });";

          cs.RegisterClientScriptBlock(
              cstype, 
              csname, 
              cstext, 
              true);
        }
      }
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>jQuery UI Datepicker in GridView</title>
  <script src="Scripts/jquery.js" type="text/javascript">
  </script>
  <script src="Scripts/jquery-ui.js" type="text/javascript">
  </script>
  <link href="css/jquery-ui.css" rel="stylesheet" type="text/css" />
</head>
<body>
  <form id="form1" runat="server">
  <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
    ConnectionString="<%$ ConnectionStrings:Northwind %>" 
    SelectCommand="SELECT TOP 10 
      [OrderID], [CustomerID], [ShippedDate] 
      FROM [Orders]" 
    UpdateCommand="UPDATE [Orders] 
      SET [CustomerID] = @CustomerID, 
          [ShippedDate] = @ShippedDate 
      WHERE [OrderID] = @OrderID">
    <UpdateParameters>
      <asp:Parameter Name="CustomerID" Type="String" />
      <asp:Parameter Name="ShippedDate" Type="DateTime" />
      <asp:Parameter Name="OrderID" Type="Int32" />
    </UpdateParameters>
  </asp:SqlDataSource>

  <asp:GridView ID="GridView1" 
    runat="server" 
    AutoGenerateColumns="False" 
    DataKeyNames="OrderID" 
    DataSourceID="SqlDataSource1" 
    OnPreRender="GridView1_PreRender">
    <Columns>
      <asp:CommandField ShowEditButton="True" />
      <asp:BoundField DataField="OrderID" 
        HeaderText="OrderID" 
        InsertVisible="False" 
        ReadOnly="True" 
        SortExpression="OrderID" />
      <asp:BoundField DataField="CustomerID" 
        HeaderText="CustomerID" 
        SortExpression="CustomerID" />
      <asp:TemplateField HeaderText="ShippedDate" 
        SortExpression="ShippedDate">
        <EditItemTemplate>
          <asp:TextBox ID="TextBox1" 
            runat="server" 
            Text='<%# Bind("ShippedDate") %>'>
          </asp:TextBox>
        </EditItemTemplate>
        <ItemTemplate>
          <asp:Label ID="Label1" 
            runat="server" 
            Text='<%# Bind("ShippedDate") %>'>
          </asp:Label>
        </ItemTemplate>
      </asp:TemplateField>
    </Columns>
  </asp:GridView>
  </form>
</body>
</html>

サーバー側のコードで TextBox.ClientID を取得してピンポイントなスクリプトを設定しなくても、クライアント側で jQuery のセレクタで当該 TextBox を探して、それに datepicker() 適用する方が簡単ですが、ClientID の命名規則が変わるとうまくいかなくなる可能性があります。

そのリスクが許容できるのであれば、GridView1_PreRender イベントハンドラに代えて、以下のようなクライアントスクリプトを使っても同等なことが可能です。

<script type="text/javascript">
//<![CDATA[
  $(function () { 
    $('#<%=GridView1.ClientID%> input:text[id*=TextBox1]')
      .datepicker(); 
  });
//]]>
</script>

Tags: , ,

JavaScript

TreeView の SelectedNodeStyle-ImageUrl

by WebSurfer 2014年10月20日 17:21

TreeView の SelectedNodeStyle-ImageUrl(正確には TreeView.SelectedNodeStyle プロパティで取得できる TreeNodeStyle オブジェクトの ImageUrl プロパティ)にイメージの URL を設定しても無視されます。

TreeView の SelectedNodeStyle-ImageUrl

例えば、上の画像のように、選択されたノード(上の画像の例では Topic 1.0.1)の左隣に表示される三角形は青、それ以外は白抜きの三角形にしたいとします。

その場合、NodeStyle-ImageUrl には白抜きの三角形、SelectedNodeStyle-ImageUrl に青の三角形のイメージを設定するとよさそうですが、実際にそのように設定して試すと SelectedNodeStyle-ImageUrl の方は無視されます(ノードを選択しても白抜きの三角形のまま変わりません)。

これは stackoverflow のページ およびそのページにあるリンク先に書いてあるようにバグのようです。ASP.NET 4.5 でも修正されていません。

では、どうすれば選択されたノードの左隣に表示されるイメージを、期待した通り上の画像のような青三角のイメージにできるかですが、その方法を以下に書きます。

サンプルとして、MSDN ライブラリの TreeView.SelectedNodeStyle プロパティ にあるコードに手を加えてみました。

まず、選択されてないノード用のイメージですが、各 TreeNodeStyle の ImageUrl プロパティに白抜き三角のイメージの URL を静的に設定します。

SelectedNodeStyle-ImageUrl は静的に設定しても無視されるので、TreeView の SelectedNodeChanged イベントのハンドラで SelectedNode の ImageUrl に青三角のイメージの URL を動的に設定します。

ただし、設定しっぱなしではノードの選択を変更した時に前に選択したノードのイメージが青三角のまま元に戻りません。ノードの選択が変更された場合は、前に選択されたノードのイメージを白抜き三角イメージの URL に書き戻すコードが必要になります。

そのため、選択したノードの ValuePath を ViewState に保持しておき、ノードの選択が変更になった場合は ViewState に保持したパス情報を元に前に選択されたノードを探し、その ImageUrl を書き換えます。その後、新たに選択されたノードの ValuePath を ViewState に保存します。

ちなみに、ValuePath はルートノードから選択されたノードまでの Value をつなげた文字列になります。例えば Topic 1.0.1 を選択した場合は "Table of Contents/Chapter One/Section 1.0/Topic 1.0.1" になります(サンプルコードでは Value プロパティを明示的に設定してないので Text プロパティの値が使用されます)。

修正後のコードは以下の通りです。実際に動かして試すことができるよう 実験室 にアップしましたので、興味がありましたら試してみてください。

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

    void Select_Change(Object sender, EventArgs e)
    {
        Message.Text = 
            "You selected: " + LinksTreeView.SelectedNode.Text;
        
        // これだけではダメ。イメージは元に戻らない。
        LinksTreeView.SelectedNode.ImageUrl = 
            "~/Images/right_selected.gif";

        // 前に選択されたノードのイメージを元に戻す。
        string valuePath = 
            (string)ViewState["TreeNodeValuePath"];
        
        if (!String.IsNullOrEmpty(valuePath))
        {
            LinksTreeView.FindNode(valuePath).ImageUrl = 
                "~/Images/right.gif";            
        }

        ViewState["TreeNodeValuePath"] = 
            LinksTreeView.SelectedNode.ValuePath;
    }
    
</script>

<html xmlns="http://www.w3.org/1999/xhtml" >

<head id="Head1" runat="server">
    <title>TreeView SelectedNodeStyle Example</title>    
</head>
<body>
    <form id="form1" runat="server">

    <h3>TreeView SelectedNodeStyle Example</h3>

    <asp:TreeView id="LinksTreeView"
        Font-Names= "Arial"
        ForeColor="Blue"
        SelectedNodeStyle-ForeColor="Green"
        SelectedNodeStyle-VerticalPadding="0"                
        OnSelectedNodeChanged="Select_Change"   
        runat="server">

        <LevelStyles>
            <asp:TreeNodeStyle ChildNodesPadding="10" 
                ImageUrl="~/Images/right.gif"
                Font-Bold="true" 
                Font-Size="12pt" 
                ForeColor="DarkGreen"/>
            <asp:TreeNodeStyle ChildNodesPadding="5" 
                ImageUrl="~/Images/right.gif"
                Font-Bold="true" 
                Font-Size="10pt"/>
            <asp:TreeNodeStyle ChildNodesPadding="5" 
                ImageUrl="~/Images/right.gif"
                Font-UnderLine="true" 
                Font-Size="10pt"/>
            <asp:TreeNodeStyle ChildNodesPadding="10" 
                ImageUrl="~/Images/right.gif"
                Font-Size="8pt"/>
        </LevelStyles>

        <Nodes>
            <asp:TreeNode Text="Table of Contents" 
                SelectAction="None">
                <asp:TreeNode Text="Chapter One">
                    <asp:TreeNode Text="Section 1.0">
                        <asp:TreeNode Text="Topic 1.0.1" />
                        <asp:TreeNode Text="Topic 1.0.2" />
                        <asp:TreeNode Text="Topic 1.0.3" />
                    </asp:TreeNode>

                    <asp:TreeNode Text="Section 1.1">
                        <asp:TreeNode Text="Topic 1.1.1"/>
                        <asp:TreeNode Text="Topic 1.1.2" />
                        <asp:TreeNode Text="Topic 1.1.3" />
                        <asp:TreeNode Text="Topic 1.1.4" />
                    </asp:TreeNode>
                </asp:TreeNode>

                <asp:TreeNode Text="Chapter Two">
                    <asp:TreeNode Text="Section 2.0">
                        <asp:TreeNode Text="Topic 2.0.1" />
                        <asp:TreeNode Text="Topic 2.0.2" />
                    </asp:TreeNode>
                </asp:TreeNode>
            </asp:TreeNode>
            <asp:TreeNode Text="Appendix A" />
            <asp:TreeNode Text="Appendix B" />
            <asp:TreeNode Text="Appendix C" />
        </Nodes>
    </asp:TreeView>

    <br /><br />

    <asp:Label id="Message" runat="server"/>

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

Tags: ,

ASP.NET

データアノテーション検証の多言語対応

by WebSurfer 2014年9月11日 20:36

ASP.NET MVC Web アプリケーションのデータアノテーション属性を使用する際、リソースファイル(.resx)を使って表示されるメッセージを多言語化する方法を書きます。

エラーメッセージの多言語化

先の記事 コレクションのデータアノテーション検証 では、モデルのソースコードに中でメッセージをハードコーディングしました。

この記事では、メッセージをリソースファイルに格納し、そこからメッセージを取得して表示します。さらに、日本語と英語のリソースファイルを追加し、ブラウザの言語設定によって言語を切り替える例も紹介します。

さて、まずリソースファイルの格納場所ですが、どこが適当でしょうか?

ASP.NET には App_GlobalResources, App_LocalResources というリソースファイルを格納する専用のフォルダがあります。しかし、そのフォルダにリソースファイルを置くとユニットテストの際に問題があるそうです。(詳しくは Resource Files and ASP.NET MVC Projects の記事を参照)

ということで、この記事では App_GlobalResources フォルダは使わないで、とりあえずアプリケーションルート直下に直接置きました。(フォルダに入れる場合、名前空間にそのフォルダ名が追加になるので注意)

まず、Visual Studio のソリューションエクスプローラーを操作して[新しい項目の追加]ダイアログを表示し、[アセンブリ リソースファイル]を選んでリソースファイルを追加します。下の画像の例ではリソースファイル名を Resources1.resx という名前にしました。そうすると自動生成される Resource1.Designer.cs に定義される「厳密に型指定されたリソースクラス」において Resources1 がクラス名になります。

アセンブリ リソースファイルの追加

リソースファイルを追加したら、下の画像のように、Visual Studio の左側のウィンドウでその内容を設定します。表示するメッセージを[値]欄に、そのメッセージを取得するキー名を[名前]欄に記入します。ここで設定したものがデフォルトのメッセージとなります。

デフォルトのメッセージの設定

上の画像の例では、データアノテーション属性として DisplayAttribute, RequiredAttribute, StringLengthAttribute の 3 つを使用するという前提で、それぞれ Name, Required, StringLength というキー名(名前は任意です)でメッセージを設定しています。上のメッセージは説明用にテキトー書いたものです。実際にはデフォルトにふさわしいメッセージにしてください。

アクセス修飾子の設定を Public にするのを忘れないでください(上の画像で赤枠で囲んだ部分)。デフォルトでは Internal です。Public にするのを忘れると、後でユニットテストをする際に困ることになるはずです。(ちなみに、Internal ⇒ Public に変更すると、プロパティウィンドウで見て、カスタムツールが ResXFileCodeGenerator ⇒ PublicResXFileCodeGenerator に変わります)

Resources1.resx の設定が終わったら、日本語用に Resources1.ja-JP.resx、英語用に Resources1.en-US.resx という名前のリソースファイルを追加します。下の画像は日本語用の Resources1.ja-JP.resx の設定です。英語用も同様に(もちろんメッセージは英語で)設定します。

日本語リソースの設定

ja-JP, en-US というカルチャ名をリソースファイル名に追加するところがミソです。これによってブラウザの言語設定を ASP.NET が検出して(=要求ヘッダ情報を見て)自動的に使用するリソースファイルが切り替わります。

設定が完成すると以下の画像のようなリソースファイルができているはずです。(赤枠で囲った部分)

リソースファイル

次に、モデルでデータアノテーション属性を以下のように設定します。この例では Parent2 クラスの Name プロパティのみにリソースファイルからメッセージを取得して表示するようにしています。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

namespace Mvc4App.Models
{
  public class Parent2
  {
    public Parent2()
    {
      Children = new List<Child2>();
    }

    public int Id { get; set; }
        
    [Required(
        ErrorMessageResourceType=typeof(Mvc4App.Resource1),
        ErrorMessageResourceName="Required")]
    [StringLength(
        5, 
        ErrorMessageResourceType = typeof(Mvc4App.Resource1), 
        ErrorMessageResourceName = "StringLength")]
    [Display(
        Name = "Name", 
        ResourceType = typeof(Mvc4App.Resource1))]
    public string Name { get; set; }

    public virtual IList<Child2> Children { get; set; }
  }

  public class Child2
  {
    public int Id { get; set; }
        
    [Required(ErrorMessage = "{0} は必須")]
    [StringLength(5, ErrorMessage = "{0} は {1} 文字以内")]
    [Display(Name = "Child Name")]
    public string Name { get; set; }

    public virtual Parent2 Parent { get; set; }
  }
}

RequiredAttribute, StringLengthAttirbute は ErrorMessageResourceType プロパティに Resource1.Designer.cs で定義される「厳密に型指定されたリソースクラス」の型を指定します(この例では typeof(Mvc4App.Resource1) です。Mvc4App が名前空間名、Resource1 がクラス名)。ErrorMessageResourceName プロパティにはリソースファイルに設定したキー名を設定します。

DisplayAttribute は、上の 2 つの属性とは設定するプロパティが異なり、ResourceType プロパ��ィに「厳密に型指定されたリソースクラス」の型、Name プロパティにキー名を設定します。

最後に、web.config で globalization 要素の uiCulture, culture 属性を auto に設定します。これを忘れると、ブラウザの言語指定によるリソースファイルの自動切り替えは行われないので注意してください。(詳しくはこの記事の下の方の「2016/6/17 追記」を見てください)

<system.web>
  <globalization uiCulture="auto" culture="auto" />
</system.web>

これにより、例えば IE の言語の優先順位の設定を、下の画像のように日本語を最優先にしておくと、日本語のリソースファイル Resources1.ja-JP.resx からメッセージを取得して表示します。

IE の言語の優先順位の設定

その結果が一番上の画像です。

-------- 2016/6/17 追記 --------

Culture, UICulture を "auto" に設定すると、ASP.NET は、ブラウザから送信されてくる要求ヘッダに含まれる Accept-Language の設定を調べて、その要求を処理するスレッドのカルチャを Accept-Language に設定されているカルチャに書き換えるようです。

そして、リソースマネージャが実行時に、Thread.CurrentUICulture などで得られる CultureInfo(現在の要求を処理しているスレッドのカルチャ情報)を参照してローカライズされたリソースを検索し、UI に表示されるテキストを取得するという仕組みになっています。

Culture, UICulture を "auto" に設定するのを忘れるとブラウザの言語設定は無視されます。デフォルトではシステムのロケールに該当するカルチャがスレッドに設定されますので、例えば日本語 OS で xxx.ja-JP.resx というリソースがあれば、常にそれから UI に表示されるテキストを取得します。

Web サイトが日本語専用でサーバーも日本にあれば忘れても問題ないかもしれませんが、ホスティングサービス(Azure も含む)でサーバーが外国にある場合は Culture, UICulture を "auto" に設定するのを忘れると問題が出ると思います。

ロケール、カルチャ、Culture と UICulture の違いなどについては、記事「カルチャの基本とカルチャ情報」が参考になりましたので、忘れないようにリンクを張っておきます。

Tags: , ,

Validation

About this blog

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

Calendar

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

View posts in large calendar