前提知識
- C#、Visual Studio .NETに精通し、ASP.NETアプリケーションの作成に習熟していること
- .NETのイベントについての基礎知識があること
- JavaScriptの基礎知識があること
- HTMLの基礎知識があること
はじめに
少し前、私はWebフォームに取り組んでいて、ユーザーがTextBox
コントロールに簡単に日付を入力できるような方法を実現したいと考えていました。クラシックASPの時代には、小さなASPページをポップアップウィンドウで開き、そこでカレンダを表示するという方法をよく使用していました。これで、ユーザーは前月/翌月に移動することができ、日付をクリックすると、選択された日付が基本的なJavaScriptによって入力フィールドに入力されました。
しかし、今は.NETの時代です。私は、ASP.NETに組み込まれているリッチコントロールを利用して、これを簡単に行う方法を考え出したいと思いました。そこでGoogleで軽く検索をかけてみた結果、解決策が見つかりました。それは、ASP.NET Calendar
コントロールのSelectionChanged
イベントに新しいEventHander
を追加するという方法です。これで、TextBox
の値を設定するのに必要なJavaScriptを実行するわけです。
この方法を試したところ、すべてうまくいきました。サードパーティコントロールを使用したり、自分で一から作成することなく、望んでいた機能を実現するソリューションを手に入れたのです。しかし、それはテストの始まりでした。
すべて動作はしたものの、一方で、ユーザーが日付をクリックしてからTextBox
に値が入力されるまでに2、3秒の遅れが発生するという問題がありました。しかし、その原因がPostBackにあることに気付くまでに長くはかかりませんでした。
Calendar
コントロールはJavaScriptを使用してTextBox
の値を設定しているのですが、その処理をSelectionChanged
イベントの発生時に行っていました。このイベントはPostBack時に発生するため、ユーザーがクリックしてからTextBox
にデータが入力されるまでに遅延が生じていたのです。PostBackは、自分自身に対してWebフォームを発行します。これは、ユーザーに無駄な待ち時間を与え、Webサーバーに不要な負荷をかける原因になります。
改良案の模索
私は依然として、自分でカスタムのカレンダコントロールを作るつもりはありませんでしたが、このPostBack方式には不満がありました。そこで、ASP.NET Calendar
コントロールの機能を拡張する方法を探ることにしました。特に、Calendar
コントロールのDayRender
メソッドに興味を引かれたので、あれこれ調べてみました。
その結果、カレンダの個々の日付から標準の日付リンクを消去し、自分でリンクを作成できることを発見しました。そこで、TextBox
に値を入力してポップアップウィンドウを閉じるためのHTMLとJavaScriptをリンクに組み込むことにしました。さて、ここからは実際の作業です。
ソリューションの作成
では、今回のソリューションを構成する要素を見てみましょう。
TextBox
コントロールを備えたWebフォーム。TextBox
の値はカレンダによって入力される- 日付入力ポップアップを起動するJavaScriptを備えた、Webフォーム上のリンク
- 日付入力ポップアップのためのWebフォーム
- 特別な
DayRenderEventHandler
がアタッチされた、日付入力ポップアップページ上のCalendar
コントロール - 標準の日付リンクをカスタムのJavaScriptリンクで置き換えるための
DayRenderEventHandler
デリゲート
Webフォームは、次のように単純なものです。
以下は「pick」リンクのHTMLです。このリンクから、日付入力ポップアップが起動します。
<a href="javascript:;" onclick="calendarPicker('Form1.txtEventDate');" title="Pick Date from Calendar">pick</a>
<a>
タグにrunat="server"
属性が指定されていないので、onClick
イベントはサーバー上でなく、クライアント側(ブラウザ内)で発生します。
コードからわかるとおり、このリンクはcalendarPicker
というJavaScript関数を呼び出します。フォーム上に複数の日付フィールドを用意する場合を考慮して、私はインラインでコードを書くのではなく、リンクから関数を呼び出す方法を採用しました。パラメータを使用して、設定したいフィールドへの参照を指定する関数を作成し、その関数内にコードを書いておけば、単一ページ上のTextBox
コントロール用に、好きなだけコードを再利用することができます。JavaScript関数をデバッグすることもできますが、それについては後で説明します。
calendarPicker
関数には、「FormName.FieldName」形式で、編集したいフィールドの名前を引き渡すことにします。VS .NETの標準の「Form1
」の代わりに独自のフォームIDを使用したい場合があるかもしれませんから、この部分をハードコードするのはお勧めしません。
calendarPicker
関数は次のとおりです。
<script language="javascript" type="text/javascript"> /// <summary> /// Launches the DatePicker page in a popup window, /// passing a JavaScript reference to the field that we want to set. /// </summary> /// <param name="strField">String. /// The JavaScript reference to the field that we want to set, /// in the format: FormName.FieldName /// Please note that JavaScript is case-sensitive.</param> function calendarPicker(strField) { window.open('DatePicker.aspx?field=' + strField, 'calendarPopup', 'width=250,height=190,resizable=yes'); } </script>
calendarPicker
関数は、新しいウィンドウの中に「DatePicker.aspx」というページを開きます。この関数は、カレンダで設定するフィールドの名前をQueryString
内に設定します。私は、クライアントサイドスクリプトにはVisual Studioスタイルのコメントを使用するのが好きです。というのは、コードの文書化を習慣付けてくれるからです。
次に示すのは、DatePicker ASPXページです。
DatePickerページは、1つの標準ASP.NET Calendar
コントロールを含んでいます。
<%@ Page language="c#" Codebehind="DatePicker.aspx.cs" AutoEventWireup="false" Inherits="CalendarPopup.DatePicker" %> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > <html> <head> <title>DatePicker</title> <meta name="GENERATOR" content="Microsoft Visual Studio .NET 7.1"> <meta name="CODE_LANGUAGE" content="C#"> <meta name="vs_defaultClientScript" content="JavaScript"> <meta name="vs_targetSchema" content="http://schemas.microsoft.com/intellisense/ie5"> <style type="text/css"> BODY { PADDING-RIGHT: 0px; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 4px; PADDING-TOP: 0px } BODY { FONT-SIZE: 9pt; FONT-FAMILY: Verdana, Geneva, Sans-Serif } TABLE { FONT-SIZE: 9pt; FONT-FAMILY: Verdana, Geneva, Sans-Serif } TR { FONT-SIZE: 9pt; FONT-FAMILY: Verdana, Geneva, Sans-Serif } TD { FONT-SIZE: 9pt; FONT-FAMILY: Verdana, Geneva, Sans-Serif } </style> </head> <body onblur="this.window.focus();" ms_positioning="FlowLayout"> <form id="Form1" method="post" runat="server"> <div align="center"> <asp:calendar id="Calendar1" runat="server" showgridlines="True" bordercolor="Black"> <todaydaystyle forecolor="White" backcolor="#FFCC66"/> <selectorstyle backcolor="#FFCC66"/> <nextprevstyle font-size="9pt" forecolor="#FFFFCC"/> <dayheaderstyle height="1px" backcolor="#FFCC66"/> <selecteddaystyle font-bold="True" backcolor="#CCCCFF/> <titlestyle font-size="9pt" font-bold="True" forecolor="#FFFFCC" backcolor="#990000"/> <othermonthdaystyle forecolor="#CC9966"/> </asp:calendar> </div> </form> </body> </html>
BODY
タグは、onBlur
イベント(クライアント側)で起動し、ポップアップをモーダルダイアログボックスにする、簡単なスクリプトを含んでいます。ポップアップは小さくしたいので、またポップアップの唯一の目的はメインページ内のフィールド値を設定することなので、他のウィンドウに切り替える理由はありません。
今回のソリューションの鍵になるのは、Calendar
コントロールのDayRender
イベントにアタッチする次のメソッドです。
/// <summary> /// Replaces the standard post-back link for each calendar day /// with the javascript to set the opener window's TextBox text. /// Eliminates a needless round-trip to the server. /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Calendar1_DayRender(object sender, System.Web.UI.WebControls.DayRenderEventArgs e) { // Clear the link from this day e.Cell.Controls.Clear(); // Add the custom link System.Web.UI.HtmlControls.HtmlGenericControl Link = new System.Web.UI.HtmlControls.HtmlGenericControl(); Link.TagName = "a"; Link.InnerText = e.Day.DayNumberText; Link.Attributes.Add("href", String.Format("JavaScript:window.opener.document.{0}.value" + "= \'{1:d}\'; window.close();", Request.QueryString["field"], e.Day.Date)); // By default, this will highlight today's date. if(e.Day.IsSelected) { Link.Attributes.Add("style", this.Calendar1.SelectedDayStyle.ToString()); } // Now add our custom link to the page e.Cell.Controls.Add(Link); }
このメソッドは、Calendar
コントロールが日にちをレンダリングするたびに呼び出されます。このメソッドが最初に行うことは、レンダリングされている日にちに対応するテーブルのセルの内容を消去することです。次に、プログラムによってハイパーリンク(<A>
タグ)を作成します。Calendar
コントロールが通常行うのと同様に、Calendar
からの現在の日にち番号をリンクのInnerText
として使用します。opener
ウィンドウ内のTextBox
コントロールの値を設定するために必要なJavaScriptを含む文字列を作成することにより、リンクのHREF
プロパティを設定します。それから、DatePicker
ウィンドウを閉じます。
JavaScriptにおけるopener
は、window.open
コマンドによって現在のウィンドウを開いたウィンドウを参照するためのキーワードです。ASP.NETのTextBox
コントロールは、HTML内では<INPUT>
としてレンダリングされます。スクリプトは続いて、変更したい特定のフィールドの値を設定します。今回は、選択された日付を使用(ShortDatePattern
で書式化)してフィールドの値を設定するように指定しています。
Calendar1_DayRender
メソッドでは、次に、今いる日にちが今日の日付を表しているかどうかを調べます。Calendar
のSelectedDate
プロパティを指定していないため、既定で今日の日付が選択されます。ユーザーに基準点を示したいので、リンクにstyle
属性を追加し、Calendar
のSelectedDayStyle
を使用しています。
今いる日にちに対応するテーブルセル内に、置き換えたハイパーリンクを挿入すれば、すべて完了です。
ただし、まだカスタムメソッドを呼び出すようにCalendar
コントロールを配線し直していません。Calendar1_DayRender
をCalendar
のDayRender
イベントにアタッチするには、VisualStudio .NETのデザインモードで「DatePicker.aspx」ファイルを編集する方法と、分離コードファイル内でページのInitializeComponent
メソッドを直接修正する方法があります。
InitializeComponent
メソッド内が自動生成されたコードでごちゃごちゃするのを防ぐには、デザインモードでDatePickerページを表示することによって、EventHander
を追加します。Calendar
コントロールをクリックし、[Properties]ウィンドウ内のイベントアイコン(稲妻マーク)をクリックします。DayRender
アクションの隣のドロップダウンリストから、Calendar1_DayRender
メソッドを選択できるはずです。
VS .NETは、ASP.NET Calendar
コントロールのDayRender
イベントデリゲートシグニチャにマッチするメソッドの分離コードファイルを検索することで、利用可能な選択肢をドロップダウンリストに表示してくれます(DayRenderEventHandler
デリゲートは、1つのオブジェクトとDayRenderEventArgs
型という、2つのパラメータを取ります)。
カスタムデリゲートを自分でアタッチしたい場合(あるいは単にVS .NETのIDEによって変更された箇所を確認したい場合)は、分離コードファイル内の「Web Form Designer generated code」と書かれた場所でInitializeComponent
メソッドを見つけることができます。
/// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InitializeComponent() { this.Calendar1.DayRender += new System.Web.UI.WebControls.DayRenderEventHandler(this.Calendar1_DayRender); this.Load += new System.EventHandler(this.Page_Load); }
Calendar
コントロールの組み込みのEventHandler
でなく、独自のDayRenderEventHandler
を追加していることに注目してください。これは+=
構文で示されています。カレンダのHTMLテーブルの作成処理、および月内のすべての日にちの処理はASP.NETにさせましょう。私たちは、各日にちのテーブルセルを独自の内容に置き換えればよいだけです。
テスト
これで、求めている機能を達成するための作業はすべて終わりました。プロジェクトをビルドし、実行して、結果をテストしましょう。「pick」リンクをクリックすれば、DatePickerポップアップを起動できるはずです。Calendar
コントロールでは、標準のカレンダと同様に、(PostBackによって)前月または翌月に移動できます。また、特定の日にちをクリックすると、ほぼ瞬時にサンプルのフォーム内のTextBox
コントロールの値が設定され、ポップアップが閉じます。
起こり得る問題
動作が正しく行われているか確認しなければならない箇所がいくつかあります。最初に、分離コードファイル内のInitializeComponent
メソッドが独自のDayRenderEventHandler
デリゲートにアタッチされているか確認します。VS .NETには、ASPXページにおいてデザインモードとHTMLモードをあまり頻繁に切り替えると、InitializeComponent
の内容が消去されてしまうという奇妙な癖があります。
ページがまだ正常に動作しない場合は、JavaScriptエラーの可能性があります。JavaScriptは大文字と小文字を区別することに注意してください。正しい「FormName.FieldName」をcalendarPicker
関数に引き渡しているかどうか確認しましょう。
疑わしい場合はデバッグをすることです。VS .NETではJavaScriptコードのデバッグも可能です。JavaScriptをデバッグするには、Internet Explorerの[Tools]メニューから[Internet Options]ダイアログボックスを開きます。そして、[Advanced]タブで[Disable script debugging]のチェックを外します。
スクリプトのデバッグを有効にしたら、プロジェクトをデバッグモードで起動します。[Debug]メニューから[Windows]→[Running Documents]を選択します。
[Running Documents]ウィンドウ内のDefault.aspxをダブルクリックし、JavaScriptにブレークポイントを設定します。
ブラウザの呼び出しがブレークポイントに達すると、コントロールからVisual Studioに戻ります。そこでスクリプト内のJavaScriptパラメータの値を調べることができます。この機能は非常に役に立ちます。
さらなる改善の可能性
PostBackの回数をさらに減らすために、自分で「月入力」コントロールを作成して、ユーザーが何回もクリックすることなく特定の月に1ステップで到達できるようにすることもできるでしょう。この単純なサンプルは、さまざまに拡張および改善することができます。
結論
.NET以前に覚えたWeb開発について、すべてを忘れる必要はありません。Microsoftは、Webコントロールの機能拡張を簡単にしてくれています。この記事を読んで、ASP.NETが開発者が物事のしくみに好奇心を持ち、変更を加えることを奨励するように作られていることをわかっていただけたら、と思います。