Yii 1.1.x で jQuery UI Datepicker を使う

日付の入力に便利な **jQuery UI Datepicker** が Yii 1.1.x でも **CJuiDatePicker** としてサポートされています。その使い方を具体的な例を示して解説します。

## 基本

フォームに日付を入力するテキスト・フィールドがあるとします。

```php:_form.php
<div class="row">
<?php echo $form->labelEx($model,'date_from'); ?>
<?php
echo $form->textField($model, 'date_from', array(
	'size' => 10,
	'maxlength' => 10,
));
?>
<?php echo $form->error($model,'date_from'); ?>
</div>
```

CActiveForm を使っているという想定です。このテキスト・フィールドを CJuiDatePicker に置き換えます。

```php:_form.php
<div class="row">
<?php echo $form->labelEx($model,'date_from'); ?>
<?php
$this->widget('zii.widgets.jui.CJuiDatePicker', array(
	'model' => $model,
	'attribute' => 'date_from',
	'htmlOptions' => array(
		'size' => '10',			// textField の size
		'maxlength' => '10',	// textField の maxlength
	),
));
?>
<?php echo $form->error($model,'date_from'); ?>
</div>
```

こうやると、CJuiDatePikcer ウィジェットが、必要な javascript ファイルをロードし、テキスト・フィールドを HTML で書き、それを Datepicker に変換するスクリプトを登録してくれます。つまり、

1. Datepicker に必要な javascript ファイル (jQuery, jQuery UI) や CSS ファイルを自分でロードする必要は無い。
2. Datepicker 用のテキスト・フィールドを自分で書く必要は無い。
3. テキスト・フィールドを Datepicker に変換する javascript コードを自分で書く必要は無い。

ということです。CJuiDatePicker ウィジェットが全部やってくれます（うーん、そう簡単に行かない場合があるのですが、まあ、それは後回しにしましょう）。

上記の例では、CJuiDatePicker を CActiveForm::textField と同じ感覚で使っています。つまり、 **'model'** 属性に $model をセットし、 **'attribute'** 属性に $model の 'from_date' 属性をセットしています。このようにやると、CActiveForm::textField と同じロジックで入力フィールドの名前を付けて、その値をモデルの属性と結び付けてくれます。

CActiveForm を使わない場合は、 **'name'** と **'value'** の属性を指定して、自分で入力フィールドの名前を付け、値を設定することが出来ます。CHtml::textField に対応する使い方ですね。

```php:_form.php
<?php
$this->widget('zii.widgets.jui.CJuiDatePicker', array(
	'name' => 'date_from',
	'value' => $fromDateValue,
	'htmlOptions' => array(
		'size' => '10',			// textField の size
		'maxlength' => '10',	// textField の maxlength
	),
));
?>
```

注意すべきは、'model' と 'attribute' のペアと 'name' と 'value' のペアは排他的である、ということです。混ぜないこと。

## 日本語化

日本語にします。

```php:_form.php
<?php
$this->widget('zii.widgets.jui.CJuiDatePicker', array(
	'model' => $model,
	'attribute' => 'date_from',
	'language' => 'ja',
	// 'i18nScriptFile' => 'jquery.ui.datepicker-ja.js',
	'htmlOptions' => array(
		'size' => '10',
		'maxlength' => '10',
	),
)); 
?>
```

最初に書いた時は、 **'language'** だけじゃ駄目みたいなので、 **'i18nScriptFile'** も指定していました。
今では、 **'language'** の指定だけで、日本語になります。

## テーマを適用

自作の jQuery UI テーマを適用します。

```php:_form.php
<?php
$this->widget('zii.widgets.jui.CJuiDatePicker', array(
	'model' => $model,
	'attribute' => 'date_from',
	'language' => 'ja',
	'themeUrl' => Yii::app()->baseUrl . '/css/jui',
	'theme' => 'softark',
	'cssFile' => 'jquery-ui-1.9.2.custom.css',
	'htmlOptions' => array(
		'size' => '10',
		'maxlength' => '10',
	),
)); 
?>
```

'/css/jui' の下に 'softark' というテーマが置いてあるという想定です。jQuery UI のテーマは [ThemeRoller](http://jqueryui.com/themeroller/) で作ります。その説明は省略。気に入ったものを作るのは、なかなか難しい。

## オプションを指定

いろいろとオプションを指定します。

```php:_form.php
<?php
$this->widget('zii.widgets.jui.CJuiDatePicker', array(
	'model' => $model,
	'attribute' => 'date_from',
	'language' => 'ja',
	'themeUrl' => Yii::app()->baseUrl . '/css/jui',
	'theme' => 'softark',
	'cssFile' => 'jquery-ui-1.9.2.custom.css',
	'options' => array(
		'showOn' => 'both',				// ボタンでも開く
		'dateFormat' => 'yy-mm-dd',		// "2012-12-25" の形式
		'showOtherMonths' => true,		// 他の月の日付も表示
		'selectOtherMonths' => true,	// 他の月の日付も選択可能
		'changeYear' => true,			// 年を変更可能
		'changeMonth' => true,			// 月を変更可能
		'yearRange' => '2000:2099',		// 年の範囲
		'minDate' => '2000-01-01',		// 日付の最小値
		'maxDate' => '2099-12-31',		// 日付の最大値
		'showButtonPanel' => true,		// ボタン・パネルを表示
	),
	'htmlOptions' => array(
		'size' => '10',
		'maxlength' => '10',
	),
)); 
?>
```

ここらは、各自の好みです。オプションについての詳細は、DatePicker の [デモページ](http://jqueryui.com/datepicker/) や、[API](http://api.jqueryui.com/datepicker/) を参照して下さい。

開くときと閉じるときのアニメーションの指定 **'showAnim'** は、ぼくの経験では、使わない方が動作が安定します。特に、画面上で二つ以上の Datepicker を使いたい場合は、やめておきましょう。

## 表示の調整

が、しかし、表示が気にくわない。何よりも、年と月のドロップ・ダウンが大きすぎて、ヘッダが二段になってしまうのが致命的です。不細工なこと、この上ない。それに、もう少しフォントを小さくしたいですね。

で、ThemeRoller が作った CSS ファイルを手作業で修正します。

```CSS:jquery-ui-1.9.2.custom.css
/* Component containers
----------------------------------*/
/* --- 削除 : メインのフォント指定に従わせる ---
.ui-widget { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1.1em; }
.ui-widget .ui-widget { font-size: 1em; }
.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Trebuchet MS, Tahoma, Verdana, Arial, sans-serif; font-size: 1em; }
*/
...
/* 追加 : フォントを小さめにする */
#ui-datepicker-div { font-size: 80%;}
button.ui-datepicker-trigger { font-size: 80%; line-height: 1.4em; }
...

/* --- 年と月のレイアウト ... 気にくわないので大幅変更 ---
.ui-datepicker select.ui-datepicker-month,
.ui-datepicker select.ui-datepicker-year { width: 49%;}
*/
.ui-datepicker select.ui-datepicker-month { with: 35%; }
.ui-datepicker select.ui-datepicker-year { width: 40%; }
/* 馬鹿ブラウザにも気を使ってやる */
* html .ui-datepicker .ui-datepicker-title select.ui-datepicker-month { width: 30%; }
* html .ui-datepicker .ui-datepicker-title select.ui-datepicker-year { width: 35%;}
/* ハイライトに関する微修正 */
.ui-datepicker .ui-state-active, .ui-datepicker .ui-state-active.ui-state-highlight { border-color: #0b5110;}
.ui-datepicker .ui-state-highlight { border-color: #448844;}
```

こんな具合に、いろいろ、ごちょごちょと弄る訳でありますよ。このあたり、こうすれば良いという、はっきりした正解は無いと思います。

## GCalHolidays.js で土日祝日に対応

便利な [GCalHolidays.js](http://0-oo.net/sbox/javascript/google-calendar-holidays) を利用させてもらいます。

```php:_form.php
<?php
Yii::app()->cientScript->registerScriptFile(
	Yii::app()->baseUrl . '/js/GCalHolidays.js',
	CClientScript::POS_END
);
?>
```
これで、日曜日、土曜日、祝日（そう、日本の祝日ですよ!）が、カレンダー上で色分けして表示されるようになります。

デフォルトの色付けはケバイので（ごめん）、ちょっとだけ修正させてもらいます。

```javascript:GCalHolidays.js
/** jQuery UI Datepicker用のstyle（Themeに合わせてお好みで上書きして使う） */
datepickerStyles: {
	// sunday:   "background-image: none; background-color: #f99", //日曜日
	// saturday: "background-image: none; background-color: #6cf", //土曜日
	// holiday:  "background-image: none; background-color: #f9f"  //祝日
	sunday:   "color: #f00", //日曜日
	saturday: "color: #0af", //土曜日
	holiday:  "color: #f00"  //祝日
}
```

なお、GCalHolidays.js は、バージョン 0.6.0 において、Google Calendar API v3 への対応に伴い、サーバ側のプログラムが必要なものに変りました。今まで通り、クライアント側の javascript だけで済ませたい、と言う場合は、[GCalendar Holidays - jui Datepicker 専用版](http://qiita.com/softark/items/ea39ef0adf0dec1f7b47) をご覧下さい。

## AJAX で更新される領域内で使う場合

特定の領域を **AJAX** でごっそり入れ替えるという処理は、最近のウェブ・アプリでは、きわめて頻繁に発生します。その領域の中で Datepicker を使いたい場合（フォームの内容を AJAX で更新したい場合など）は、ちょっとだけ注意が必要です。

jQuery UI Datepicker は、既存の HTML コードに変更を加えることによって、テキスト・フィールドを Datepicker に変身させています。ところが、その HTML コードが AJAX によって更新されると、Datepicker であったテキスト・フィールドは、素のテキスト・フィールドに戻ってしまいます。

ページをロードした直後は Datepicker が動くのに何かをやった後に Datepicker が動かなくなる、というのは、たいてい、そういう理由です。

これに対しては、素のテキスト・フィールドをもう一度 Datepicker に作り直す、という処理をする以外に解決方法はありません。

二つの解法があります:

1. サーバが返す AJAX のレスポンスに Datepicker 用のスクリプトをもう一度含める。
2. AJAX による更新処理をした後に Datepicker 変換作業を（手作業で）する。

第一の方法は、やったことがないので、よく分りません。CController::renderPartial() の 4 番目のパラメータを true にするやつですね。ところが、これが、CClientScript::scriptMap を弄って jQuery を再送しないように細工する必要がある、とか何とか、いろいろと面妖な感じがして、どうも、やってみる気になれないのです。

僕が好むのは、第二の方法です。サーバからのレスポンスにはスクリプトを含めません。
例えばこんなコードを使います。

```javascript
$('#submit-button').click(function() {
	// フォームの内容を AJAX で POST
	$.ajax({
		'type' : 'POST',
		'url' : 'hoge-update',
		'dataType' : 'json',
		'data' : $('#hoge-form').serialize(),
		'success' : function(data){
			// フォームの内容を更新
			$('#hoge-form').html(data.contents);
			// Datepicker 再設定
			$('#hoge-form .d-picker').datepicker();
		}
	});
});
```

ちょっといい加減ですが、要は、AJAX で内容を更新した後に、jQuery UI の datepicker() 関数を呼んで、テキスト・フィールドを再度 Datepicker に作り直す訳です。

あ、つまり、もう Yii の事は忘れて、jQuery でゴリゴリという世界です。jQuery は全然わからんとか、jQuery UI のドキュメントを読むのが苦痛だとかでない限り、この方が何かと見通しが良くて、楽ちんだと思いますよ。

とは言うものの、初期ロードのためには、CJuiDatePicker を使ってます。その方が楽だから。

で、AJAX のことを考えた場合は、下記のような書き方をする方が便利です。

```php:_form.php
<?php
$this->widget('zii.widgets.jui.CJuiDatePicker', array(
	...
	'defaultOptions' => array(
		'showOn' => 'both',
		...
	),
	'htmlOptions' => array(
		...
		'class' => 'd-picker',
	),
)); 
?>
```

'htmlOptions' で **'class'** を指定します。これは **'id'** でも構いません。要は、後で jQuery でセレクタとして使おうという魂胆です。

もうひとつ、 **'options'** の代りに **'defaultOptions'** を使っています。こうしておくと、jQuery UI の datepicker 関数を呼ぶときに、オプションを省略できます。