フォームビルダを使う
==================
HTML フォームを作成するときに、しばしば、他のプロジェクトで再利用が困難な大量のビューコードを繰り返し書いている事に気付きます。
例えば、全ての入力フィールドについて、テキストラベルを関連付けたり、可能性のある入力検証エラーを表示する必要があります。
これらのコードの再利用性を高めるためにフォームビルダ機能を使うことが出来ます。
基本概念
--------------
Yii フォームビルダは HTML フォームを記述するのに必要な仕様を表現した [CForm] オブジェクトを使います。
これには、フォームにどのようなデータモデルが関係するのか、フォームにどのような種類の入力フィールドがあるのか、フォーム全体がどのように表示されるのか、という事が含まれます。
開発者は主な仕事は、この [CForm] オブジェクトを生成して構成することであり、オブジェクトの表示メソッドをコールすることで最終的なフォームの表示を行ないます。
フォームの入力仕様は、フォームの要素の階層形式で組織化されます。
階層の最上位には [CForm] オブジェクトが位置します。
この最上位のフォームオブジェクトは、2種類の子オブジェクトを持ちます。
[CForm::buttons] と [CForm::elements] です。
前者はボタン要素、例えば送信ボタンやリセットボタンを含み、後者は入力要素、静的テキストやサブフォームを含みます。
サブフォームは、他のフォームの [CForm::elements] に含まれる [CForm] オブジェクトです。
サブフォームは自分自身のデータモデルと [CForm::buttons] と [CForm::elements] を持つことが出来ます。
ユーザがフォームを送信すると、サブフォームに属する入力フィールドも含めて、全ての階層の入力フィールドに入れられたデータが送信されます。
[CForm] は、自動的に入力データを対応するモデル属性に割り当ててデータ検証をすることが可能な便利なメソッドを提供します。
単純なフォームを作成する
----------------------
以下に、ログインフォームを作成するためにフォームビルダーを利用する方法を示します。
最初に、ログインアクションコードを書きます。
~~~
[php]
public function actionLogin()
{
$model = new LoginForm;
$form = new CForm('application.views.site.loginForm', $model);
if($form->submitted('login') && $form->validate())
$this->redirect(array('site/index'));
else
$this->render('login', array('form'=>$form));
}
~~~
上記のコードでは、`application.views.site.loginForm` (後で説明されます) というパスエイリアスで示される仕様を用いて [CForm] オブジェクトを作成しています。
この [CForm] オブジェクトは [モデルの作成] (/doc/guide/form.model)で示されるように、`LoginForm` モデルと関連付けられています。
コードが示しているように、もしフォームが送信されて全ての入力がエラーなく検証されれば、ユーザのブラウザは `site/index` ページにリダイレクトされます。
さもなければ、`login` フォームが再度表示されます。
パスエイリアス `application.views.site.loginForm` は、実際には `protected/views/site/loginForm.php` という PHP ファイルを参照します。
このファイルは、次に示すように、[CForm] で必要とされる構成を示す PHP 配列を返す必要があります。
~~~
[php]
return array(
'title'=>'ログインの証明となる情報を入力してください',
'elements'=>array(
'username'=>array(
'type'=>'text',
'maxlength'=>32,
),
'password'=>array(
'type'=>'password',
'maxlength'=>32,
),
'rememberMe'=>array(
'type'=>'checkbox',
)
),
'buttons'=>array(
'login'=>array(
'type'=>'submit',
'label'=>'Login',
),
),
);
~~~
構成は、[CForm] の対応するプロパティを初期化するために用いられる "名前-値" のペアから構成される連想配列です。
構成するために最も重要なプロパティは、上に示したように、[CForm::elements] と [CForm::buttons] です。
それぞれはフォームの要素のリストを指定する配列をとります。
次の節において、フォーム要素をどのように構成するかを詳しく説明します。
最後に、`login` ビュースクリプトを記述しますが、以下のようにたいへん簡単になります。
~~~
[php]
Login
~~~
> Tip|ヒント: 上記の `echo $form;` というコードは `echo $form->render();` と等価です。
> これは [CForm] が `__toString` マジックメソッドを実装しているからであり、それが `render()` を呼び出して、フォームオブジェクトの文字列表現をその結果として返すためです。
フォーム要素の指定
------------------------
フォームビルダを使うことにより、ほとんどの作業はビュースクリプトコードを書くことからフォーム要素を指定することに移ります。
この節では [CForm::elements] をどのように指定するかを示します。
[CForm::buttons] は [CForm::elements] とほとんど同じであるため、説明しません。
[CForm::elements] プロパティは配列をその値として受け付けます。
それぞれの配列要素は単一のフォーム要素となり、それは、入力要素であっても良いし、文字列やサブフォームであっても構いません。
### 入力要素の指定
入力要素は主にラベルと入力フィールドと入力ヒントテキストとエラー表示から構成されます。
これはモデル属性に関連づけられる必要があります。
入力要素の仕様は [CFormInputElement] インスタンスとして表現されます。
[CForm::elements] 配列中の以下のコードは単一の入力要素を指定します。
~~~
[php]
'username'=>array(
'type'=>'text',
'maxlength'=>32,
),
~~~
上記のコードは、モデル属性の名前が `username` であり、入力フィールド型は `text` であり、その最大長を示す `maxlength` アトリビュートは 32 であることを示しています。
[CFormInputElement] の設定可能なプロパティはすべて上記のようにして構成することが出来ます。
例えば、ヒントテキストを表示するために [hint|CFormInputElement::hint] オプションを指定できます。
また、入力フィールドがリストボックス、ドロップダウンリスト、チェックボックス、ラジオボタンである場合は [items|CFormInputElement::items] を指定できます。
オプションの名前が [CFormInputElement] のプロパティでない場合は、対応する HTML 入力要素の属性として扱われます。例えば、上記の `maxlength` は [CFormInputElement] のプロパティではありませんので、HTML のテキスト入力フィールドの `maxlength` 属性として扱われます。
[type|CFormInputElement::type] オプションは注意が必要です。これは入力フィールドのタイプを指定します。
例えば、`text` は通常のテキスト入力フィールドが表示されることを意味します。また、`password` タイプはパスワード入力フィールドが表示されることを意味します。[CFormInputElement] は以下の内蔵型のタイプを認識します。
- text
- hidden
- password
- textarea
- file
- radio
- checkbox
- listbox
- dropdownlist
- checkboxlist
- radiolist
上記の内蔵型のタイプ中で、"リスト" タイプのものの使い方について、もう少し詳しく説明したいと思います。
リストタイプには、`dropdownlist`, `checkboxlist`, そして、`radiolist` が含まれます。
これらのタイプでは、対応する入力要素の [items|CFormInputElement::items] プロパティを設定することが要求されます。
これは次のようにして設定することが出来ます。
~~~
[php]
'gender'=>array(
'type'=>'dropdownlist',
'items'=>User::model()->getGenderOptions(),
'prompt'=>'選択して下さい:',
),
...
class User extends CActiveRecord
{
public function getGenderOptions()
{
return array(
0 => '男性',
1 => '女性',
);
}
}
~~~
上記のコードによって、"選択して下さい:" というプロンプトテキストを持ったドロップダウンリストセレクタが生成されます。
セレクタのオプションは "男性" と "女性" を含みますが、これらは `User` モデルクラスの `getGenderOptions` メソッドから返されたものです。
これら内蔵型のタイプの他に、[type|CFormInputElement::type] には、ウィジェットクラス名かそれのパスエイリアスを指定する事が出来ます。
ウィジェットクラスは [CInputWidget] または [CJuiInputWidget] を継承する必要があります。
入力フィールドを表示する場合、指定されたウィジェットクラスインスタンスが生成され、表示されます。ウィジェットは入力要素のために指定した仕様によって構成されます。
### スタティックテキストの指定
多くの場合フォームは、入力フィールド以外になんらかの飾りの HTML コードを含みます。
例えば、フォームの異なる部分を分離するために水平線が描かれたり、フォームの見栄えを良くするためにある部分にイメージが配置されたりします。
これらの HTML コードはスタティックテキスト (変化しないテキスト) として [CForm::elements] の中に指定することが出来ます。
これを実現するためは、スタティックテキストを [CForm::elements] の適当な場所に指定するだけです。例えば、
~~~
[php]
return array(
'elements'=>array(
......
'password'=>array(
'type'=>'password',
'maxlength'=>32,
),
'
',
'rememberMe'=>array(
'type'=>'checkbox',
)
),
......
);
~~~
上記では、`password` 入力と `rememberMe` 入力の間に水平線が挿入されています。
スタティックテキストはテキストの内容と配置が通常でない場合に用いるのが適切です。
すべての入力要素を同じ様に飾る必要がある場合には、フォーム表示をカスタマイズするアプローチをとるべきで、それはこの章の少し後で説明します。
### サブフォームの指定
サブフォームは長いフォームを論理的に関係する部分に分割するために使用されます。
例えば、ユーザ登録フォームは、ログイン情報とプロファイル情報の二つの部分に分割されます。
それぞれのサブフォームは同一のデータモデルに関係してもしなくても構いません。
この例のユーザ登録フォームにおいて、ログイン情報とプロファイル情報を二つの別のデータベーステーブルに (従って二つのデータモデルに) 格納する場合は、それぞれのサブフォームは対応するデータモデルに対応づけられるでしょう。
そうでなく、もし全ての情報を単一のテーブルに格納する場合は、サブフォームはデータモデルを持ちません。
なぜならサブフォームは親フォームに関係づけられたデータモデルを共有するからです。
サブフォームも [CForm] オブジェクトとして表現されます。
サブフォームを指定するためには、[CForm::elements] プロパティに、タイプが `form` である要素を加えます。
~~~
[php]
return array(
'elements'=>array(
......
'user'=>array(
'type'=>'form',
'title'=>'Login Credential',
'elements'=>array(
'username'=>array(
'type'=>'text',
),
'password'=>array(
'type'=>'password',
),
'email'=>array(
'type'=>'text',
),
),
),
'profile'=>array(
'type'=>'form',
......
),
......
),
......
);
~~~
親フォームを構成するように、サブフォームに対しても主として [CForm::elements] を指定する必要があります。
もしサブフォームがデータモデルに関連づけられるなら、[CForm::model] も構成します。
ときとして、フォームをデフォルトの [CForm] 以外のクラスを使って表現したい場合があります。
例えば、この章のすぐ後で示すように、フォーム表示ロジックをカスタマイズするために [CForm] を継承することが出来ます。
入力要素のタイプを `form` と指定すると、サブフォームは自動的に親フォームと同じクラスのオブジェクトとして表現されます。
もし入力要素のタイプを `XyzForm` (`Form`という語で終ること) のような形式で指定した場合は、サブフォームは `XyzForm` オブジェクトとして表現されます。
フォーム要素へのアクセス
-----------------------
フォーム要素へのアクセスは配列要素へのアクセスと同様に単純です。
[CForm::elements] プロパティは、[CMap] から継承され、通常の配列と同様に要素にアクセス可能な [CFormElementCollection] オブジェクトを返します。
例えば、ログインフォームの例において `username` 要素へアクセスするには、以下のコードを用います。
~~~
[php]
$username = $form->elements['username'];
~~~
そしてユーザ登録フォームの例において、`email` 要素にアクセスするためには、下記のようにします。
~~~
[php]
$email = $form->elements['user']->elements['email'];
~~~
[CForm] は、その [CForm::elements] プロパティへの配列アクセスを実装しているため、上記コードはさらに以下のように単純化されます。
~~~
[php]
$username = $form['username'];
$email = $form['user']['email'];
~~~
入れ子フォームの作成
----------------------
既にサブフォームを説明しました。
サブフォームを持つフォームのことを入れ子フォームと呼ぶことにしましょう。
この章では、ユーザ登録フォームを例として、複数のデータモデルに関連付けられた入れ子フォームを作成する方法を説明します。
ユーザ認証情報は `User` モデルに格納されており、ユーザプロファイル情報は `Profile` モデルに格納されているものとします。
最初に `register` アクションを次のように作成します。
~~~
[php]
public function actionRegister()
{
$form = new CForm('application.views.user.registerForm');
$form['user']->model = new User;
$form['profile']->model = new Profile;
if($form->submitted('register') && $form->validate())
{
$user = $form['user']->model;
$profile = $form['profile']->model;
if($user->save(false))
{
$profile->userID = $user->id;
$profile->save(false);
$this->redirect(array('site/index'));
}
}
$this->render('register', array('form'=>$form));
}
~~~
上記においては、`application.views.user.registerForm` によりフォームの構成を指定しています。
フォームが送信され正常に検証された後に、ユーザモデルとプロファイルモデルの保存を試みます。
対応するサブフォームオブジェクトの `model` プロパティにアクセスすることで、ユーザモデルとプロファイルモデルを取得します。
入力値の検証は既に済んでいるため、検証をスキップするために `$user->save(false)` をコールします。これはプロファイルモデルも同様です。
次にフォーム構成ファイルである `protected/views/user/registerForm.php` を記述します。
~~~
[php]
return array(
'elements'=>array(
'user'=>array(
'type'=>'form',
'title'=>'Login information',
'elements'=>array(
'username'=>array(
'type'=>'text',
),
'password'=>array(
'type'=>'password',
),
'email'=>array(
'type'=>'text',
)
),
),
'profile'=>array(
'type'=>'form',
'title'=>'Profile information',
'elements'=>array(
'firstName'=>array(
'type'=>'text',
),
'lastName'=>array(
'type'=>'text',
),
),
),
),
'buttons'=>array(
'register'=>array(
'type'=>'submit',
'label'=>'Register',
),
),
);
~~~
上記において、それぞれのサブフォームを指定する場合に、それぞれの [CForm::title] プロパティも指定しています。
デフォルトのフォーム表示ロジックでは、それぞれのサブフォームは、このプロパティをタイトルとして用いたフィールドセットで囲まれます。
最後に、単純な `register` ビュースクリプトを記述します。
~~~
[php]
ユーザ登録
~~~
フォーム表示のカスタマイズ
------------------------
フォームビルダを使用する主なメリットは、ロジック (別のファイルに格納されたフォーム構成) と表現 ([CForm::render] メソッド) の分離です。
この結果、[CForm::render]を上書きしたり、部分ビューを提供したりすることで、フォーム表示のカスタマイズが可能となります。
両方のアプローチ共、フォーム構成を触ることが無いため、容易に再利用が可能です。
[CForm::render] を上書きする場合には、[CForm::elements] や [CForm::buttons] をたどり、それぞれのフォームエレメントに対して [CFormElement::render] メソッドをコールする必要があります。例えば、
~~~
[php]
class MyForm extends CForm
{
public function render()
{
$output = $this->renderBegin();
foreach($this->getElements() as $element)
$output .= $element->render();
$output .= $this->renderEnd();
return $output;
}
}
~~~
フォームを表示するためのビュースクリプト `_form` を以下のように記述します。
~~~
[php]
renderBegin();
foreach($form->getElements() as $element)
echo $element->render();
echo $form->renderEnd();
~~~
このビュースクリプトを使用するには、単に以下のようにコールするだけです。
~~~
[php]
renderPartial('_form', array('form'=>$form)); ?>
~~~
もし汎用のフォーム表示では特殊なフォームについて対応できない場合 (例えば、特定の要素に対して不規則な飾りを必要とする場合) には、ビュースクリプト中で以下のようなやり方を行うことができます。
~~~
[php]
複雑な UI 要素
複雑な UI 要素
複雑な UI 要素
~~~
最後のアプローチにおいては、同様の量のフォームコードを書く必要があるため、フォームビルダはあまり利点があるとは言えません。
しかしながら、フォームが独立した構成ファイルを使って指定されるため、開発者がロジックに集中しやすくなるという利点があります。
$Id$