2012年3月25日

Yii Framework: ラジオボタン、チェックボックスの値を保存する方法

モデル → コントローラ → ビュー の流れで、ラジオボタン、チェックボックスを使ったフォームを作って、値をデータベースに保存する流れをテストしてみました。

デモとして、よくある profile テーブルを作りました。カラムは以下。sex カラムをラジオボタンで hobby カラムをチェックボックスで作ります。

id int(11) NOT NULL AUTO_INCREMENT
sex tinyint(1) NOT NULL
birth date NOT NULL
hobby varchar(255) NOT NULL
body text NOT NULL

まず、Gii で Profile モデルを作成後、中身を編集していきます。 
<?php
class Profile extends CActiveRecord
{
...
/**
* @see Cmodel::rules()
*/
public function rules()
{
return array(
// sex
array('sex', 'required', 'message'=>'{attribute} が未選択です。'),
array('sex', 'in', 'range'=>array(0, 1)),
// birth
array('birth', 'required'),
array('birth', 'date', 'format'=>'yyyy-MM-dd'),
// hobby
array('hobby', 'required', 'message'=>'{attribute} が未選択です。'),
array('hobby', 'checkHobby'),
// body
array('body', 'required'),
array('body', 'length', 'max'=>1000),
);
}
/**
* Hobby validation
*/
public function checkHobby($attribute, $params)
{
if (is_array($this->hobby)) {
$pattern = '/^(' . implode('|', $this->hobbyOptions) . ')$/u';
foreach ($this->hobby as $hobby) {
if (!preg_match($pattern, $hobby)) {
$this->addError($attribute, '趣味の値が不正です');
break;
}
}
}
}
/**
* @see CActiveRecord::attributeLabels()
*/
public function attributeLabels()
{
return array(
'id' => 'ID',
'sex' => '性別',
'birth' => '生年月日',
'hobby' => '趣味',
'body' => '自己紹介文',
);
}
/**
* Gets sex options.
* @return array sex options
*/
public function getSexOptions()
{
return array(
'男',
'女',
);
}
/**
* Gets hobby options.
* @return array hobby options
*/
public function getHobbyOptions()
{
return array(
'買い物' => '買い物',
'スポーツ' => 'スポーツ',
'旅行' => '旅行',
'インターネット' => 'インターネット',
'読書' => '読書',
'映画鑑賞' => '映画鑑賞',
);
}
/**
* @see CActiveRecord::beforeSave()
*/
protected function beforeSave()
{
$this->hobby = implode(', ', $this->hobby);
return parent::beforeSave();
}
}
view raw Profile.php hosted with ❤ by GitHub

rules()

とりえあず sex カラムは 0 か 1 なので CRangeValidator でいけたのですが、問題はhobby カラムです。チェックボックスなのでバリデーションを通すと配列が返ってくるので CRangeValidator は使えないと判断し、あと、各配列の値をちゃんとチェックしたいので、自作で checkHobby というバリデーションを作りました (バリデーションの自作は Create your own Validation Rule が参考になります) 。

checkHobby()

これが hobby カラムで使うカスタムバリデーションです。何をやっているかというと、まず getHobbyOptions() の値を 「|」 で区切って正規表現で使うパターンを作ります。上記のコードの場合は '/^(買い物|スポーツ|旅行|インターネット|読書|映画鑑賞)$/u' みたいになる。そのあと、フォームから入力された hobby カラムの値を一つ一つパターンにマッチするかして、マッチしなければエラーメッセージを表示する、という流れです。

getSexOptions(), getHobbyOptions()

これは、ビューで使うもの。

beforeSave()

バリデーションでは、hobby カラムを一時的に配列から文字列に変換しただけなので、hobby カラムを含めたすべてのバリデーションが通れば、hobby カラムの値を配列から、文字列に変換して保存します。例えば、フォームで買い物、旅行、映画鑑賞がチェックされていれば '買い物, 旅行, 映画鑑賞' という文字列になり保存されます。

次はコントローラ。
<?php
class ProfileController extends Controller
{
...
/**
* Creates a new model.
*/
public function actionCreate()
{
$model = new Profile;
$model->sex = 0;
$this->save($model);
}
/**
* Updates a particular model.
* @param integer $id a particular model.
*/
public function actionUpdate($id)
{
$model = $this->loadModel($id);
$model->hobby = explode(', ', $model->hobby);
$this->save($model);
}
/**
* Saves a model.
*/
protected function save($model)
{
if (isset($_POST['Profile'])) {
$model->attributes = $_POST['Profile'];
if ($model->save()) {
$this->redirect(array('index'));
}
}
$this->render('_form', compact('model'));
}
...
}

actionCreate()

$model->sex はフォームの sex カラムのデフォルト値を設定しています。これをしないと、ラジオボタンが未選択になってしまいます。

actionUpdate()

$this->loadModel($id) は findByPk()でデータを1件取得しているものと思ってください
そのあとの $model->hobby = explode(', ', $model->hobby); は、hobby カラムは文字列として保存したので、それをフォームのチェックボックスにしっかり適用できるように配列に変換しています。

save()

これは典型的なデータを保存するコードの流れです。actionCreate(), actionUpdate() ともに変わらないので、まとめています。

最後にビュー。 
<div class="form">
<?php echo CHtml::form(); ?>
<?php echo CHtml::errorSummary($model); ?>
<div class="row">
<?php echo CHtml::activeLabel($model, 'sex'); ?>
<?php echo CHtml::activeRadioButtonList($model, 'sex', $model->sexOptions, array(
'separator' => '',
)); ?>
</div><!-- /.row -->
<div class="row">
<?php echo CHtml::activeLabel($model, 'birth'); ?>
<?php $this->widget('zii.widgets.jui.CJuiDatePicker', array(
'model' => $model,
'attribute' => 'birth',
)); ?>
</div><!-- /.row -->
<div class="row">
<?php echo CHtml::activeLabel($model, 'hobby'); ?>
<?php echo CHtml::activeCheckBoxList($model, 'hobby', $model->hobbyOptions); ?>
</div><!-- /.row -->
<div class="row">
<?php echo CHtml::activeLabel($model, 'body'); ?>
<?php echo CHtml::activeTextArea($model, 'body', array('rows' => 10, 'cols' => 70)); ?>
</div><!-- /.row -->
<div class="row buttons">
<?php echo CHtml::submitButton($model->isNewRecord ? 'Create' : 'Save'); ?>
</div><!-- /.row buttons-->
<?php echo CHtml::endForm(); ?>
</div><!-- /.form -->
view raw _form.php hosted with ❤ by GitHub
ビューのコードを説明する前にまず css の話。css を自作していなければYiiにもともと入っている main.css, forms.css を使うかと思いますが、これがやや曲者です。ラジオボタンや、チェックボックスを CHtml ヘルパーで作ってみればわかるかと思いますが、ラベル、フォームやらがすべて縦に並ぶんですね...。以下のような感じです (Yii バージョン 1.1.10現在) 。


いろいろ対策はあるみたいですが (例えば Form layout: wrong design activeCheckBoxList and activeRadioButtonList ) 、ビューのコードを汚すのは避けたいので、自分は form.css に直接コードを追加して、対応してみました。div.form input + label{ display:inline; position:relative; right:3px; top:-2px; *top:-1px; font-weight:normal; } position は今はどうでも良いんですが、div.form の input の次に label がくれば、そこは inline で表示、フォントは normal という形です。ちょっと強引ですが、これでなんとかなりました。


あとは上記のビューのコードを見るとわかりますが、array('separator' => '') を指定すれば、横並びに、しなければ br タグが自動で挿入されるため縦並びになります。最後にモデルであらかじめ用意してた getSexOptions() と getHobbyOptions() を追加すればOKです (birth カラムは ビューでウィジェットのコードをシンプルに書くいくつかの方法 でまとめていると思ってください) 。


課題

確認画面付きの場合はどう実装するか、hobby カラムを数字で保存する (「1,2,5」 のような形) にはどうすればいいか、数字で保存した場合、どう表示すればいいか (「スポーツ, 旅行, 映画鑑賞」のような形) 。

その他

あと、もっと簡単に実装できる方法を知っている方は是非教えてください... (特にチェックボックスのバリデーション方法) 。

0 件のコメント:

コメントを投稿