フォームビルダを使う

HTMLフォームを作成するときに、しばしばビューについて大量のコードを繰り返し書くはめに陥ります。 そしてそれは他のプロジェクトでは再利用が困難な場合が多いです。 例えば、全ての入力フィールドについて、テキストラベルと可能性のある入力検証エラーの表示を関連づける必要があります。 これらのコードの再利用性を高めるためにフォームビルダ機能を使うことが出来ます。

1. 基本概念

YiiフォームビルダはHTMLフォームを記述するのに必要な仕様を表現したCFormオブジェクトを使っています。 それはフォームに関係するデータモデルを含み、フォームにどのような種類の入力フィールドがあるのか、 どのようにフォームを表示するのかを示すものです。開発者は主にCFormオブジェクトを生成し、構成し、 最終的にフォームを表示するためにオブジェクトの表示メソッドをコールすることが必要です。

フォームの入力仕様はフォーム要素階層に関して系統づけられています。 階層の最下位はCFormオブジェクトです。 最下位フォームオブジェクトは2種類の子オブジェクトを持ちます。それはCForm::buttonsCForm::elementsです。前者はボタン要素、例えばサブミットボタンやリセットボタンを含み、後者は 入力要素、静的テキストやサブフォームを含みます。サブフォームは他のフォームのCForm::elementsに含まれるCFormオブジェクトです。それは自分自身のデータモデルとCForm::buttonsCForm::elementsを持ちます。

ユーザがフォームをサブミットするとき、全ての階層的なフォームの入力フィールドに入れられたデータはサブミットされ、サブフォーム階層に属する入力フィールドも含まれます。 CFormは自動的に入力データを対応するモデルアトリビュートに割り当てられ、データ検証をされる便利な方法を提供します。

2. 単純なフォーム生成

以下に、ログインフォームを作成するためにフォームビルダーを利用する方法を示します。

最初に、ログインアクションコードを書きます。

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オブジェクトはモデルを作成するで示されるように、 LoginFormモデルと関係しています。

コードに示すように、もしフォームがサブミットされて全ての入力がエラーなく検証されれば、 ユーザのブラウザはsite/indexページにリダイレクトされます。さもなければ、 loginフォームが再度表示されます。

パスエイリアスapplication.views.site.loginFormは実際にはprotected/views/site/loginForm.php というPHPファイルを参照します。このファイルは次に示すように、CFormで必要とされる構成を示す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::elementsCForm::buttonsです。 それぞれはフォームの要素のリストを指定する配列をとります。次のサブセクションにおいて、フォーム要素をどのように構成するかを詳しく説明します。

最後に、loginビュースクリプトを記述しますが、以下のようにたいへん簡単になります。

<h1>Login</h1>
 
<div class="form">
<?php echo $form; ?>
</div>

ヒント: 上記のecho $form;コードはecho $form->render();と等価です。 これはCForm__toStringマジックメソッドを実装しているからであり、それは render()を呼び出して、フォームオブジェクトのストリング表現をその結果として返すためです。

3. フォーム要素の指定

フォームビルダを使うことにより、ほとんどの作業はビュースクリプトコードを書くことからフォーム要素を指定することに移ります。 このサブセクションではCForm::elementsをどのように指定するかを示します。 CForm::buttonsCForm::elementsとほとんど同じであるため、説明しません。

CForm::elementsプロパティは配列をその値として受け付けます。それぞれの配列要素は単一のフォーム要素となり、それは、入力要素であっても良いし、 テキストストリングやサブフォームであっても構いません。

入力要素の指定

入力要素は主にラベルと入力フィールドと入力ヒントテキストとエラー表示から構成されます。 これはモデルアトリビュートに関連づけられる必要があります。入力要素の仕様はCFormInputElementインスタンスとして表現されます。 CForm::elements配列中の以下のコードは単一の入力要素を指定します。

'username'=>array(
    'type'=>'text',
    'maxlength'=>32,
),

モデルアトリビュートはusernameであり、入力フィールド型はtextであり、その最長長さを示すmaxlengthアトリビュートは32であることを示します。

CFormInputElement の設定可能なプロパティはすべて上記のようにして構成することが出来ます。 例えば、ヒントテキストを表示するためにhintオプションを指定できます。 また、入力フィールドがリストボックスやドロップダウンリストやチェックボックスやラジオボタンである場合はitemsを指定できます。 オプションの名前がCFormInputElementのプロパティでない場合は、対応するHTML入力要素の属性として扱われます。例えば、上記のmaxlengthCFormInputElementのプロパティではありませんので、HTMLのテキスト入力フィールドのmaxlength属性として扱われます。

typeオプションは注意が必要です。これは入力フィールドのタイプを指定します。 例えば、textは通常のテキスト入力フィールドが表示されることを意味します。一方、passwordタイプはパスワード入力フィールドが表示されることを意味します。CFormInputElementは以下の内蔵型のタイプを認識します。

  • text
  • hidden
  • password
  • textarea
  • file
  • radio
  • checkbox
  • listbox
  • dropdownlist
  • checkboxlist
  • radiolist

上記の内蔵型のタイプ中で、"リスト"タイプのものの使い方について、もう少し詳しく説明したいと思います。 リストタイプには、dropdownlistcheckboxlist、そして、radiolistが含まれます。 これらのタイプでは、対応する入力要素のitemsプロパティを設定することが要求されます。 これは次のようにして設定することが出来ます。

'gender'=>array(
    'type'=>'dropdownlist',
    'items'=>User::model()->getGenderOptions(),
    'prompt'=>'選択して下さい:',
),
 
...
 
class User extends CActiveRecord
{
    public function getGenderOptions()
    {
        return array(
            0 => '男性',
            1 => '女性',
        );
    }
}

上記のコードによって、"選択して下さい:"というプロンプトテキストを持ったドロップダウンリストセレクタが生成されます。 セレクタのオプションは"男性"と"女性"を含みますが、これらはUserモデルクラスのgetGenderOptionsメソッドから返されたものです。

これら内蔵型のタイプの他に、typeには、ウィジェットクラス名かそれのパスエイリアスを指定する事が出来ます。 ウィジェットクラスはCInputWidgetまたはCJuiInputWidgetを継承する必要があります。 入力フィールドを表示する場合、指定されたウィジェットクラスインスタンスが生成され、表示されます。ウィジェットは入力要素のために指定した仕様によって構成されます。

スタティックテキストの指定

多くの場合フォームは、入力フィールド以外になんらかの飾りのHTMLコードを含みます。例えば、フォームの異なる部分を分離するために水平線が描かれたり、フォームの見栄えを良くするためにある部分にイメージが配置されたりします。 これらのHTMLコードはスタティックテキスト(変化しないテキスト)としてCForm::elementsの中に指定されます。 これを実現するため、スタティックテキストはCForm::elementsの適当な場所に指定します。例えば、

return array(
    'elements'=>array(
        ......
        'password'=>array(
            'type'=>'password',
            'maxlength'=>32,
        ),
 
        '<hr />',
 
        'rememberMe'=>array(
            'type'=>'checkbox',
        )
    ),
    ......
);

上記では、password入力とrememberMe入力の間に水平線が挿入されます。

スタティックテキストはテキストの内容と配置が通常でない場合に良く用いられます。もしそれぞれの入力要素が同様に飾られる必要がある場合には フォーム表示をカスタマイズするアプローチをとるべきで、それはこの章の後で説明します。

サブフォームの指定

サブフォームは長いフォームを、論理的に関係する部分に分割するために使用されます。例えば、 ユーザ登録フォームは、ログイン情報とプロファイル情報の2つの部分に分割されます。 それぞれのサブフォームはデータモデルに関係してもしなくても構いません。この例のユーザ登録フォームにおいて、 ログイン情報とプロファイル情報を2つの別のデータベーステーブルに(従って2つのデータモデルに)格納する場合は、 それぞれのサブフォームは対応するデータモデルに対応づけられるでしょう。そうでなく、もし全ての情報を単一のテーブルに格納する場合は、 サブフォームはデータモデルを持ちません。なぜならサブフォームは親フォームに関係づけられたデータモデルを共有するからです。

サブフォームもCFormオブジェクトの表現です。サブフォームを指定するために、CForm::elementsプロパティの型をformとして構成する必要があります。

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オブジェクトで表されます。

4. フォーム要素へのアクセス

フォーム要素へのアクセスは配列要素へのアクセスと同様に単純です。CForm::elementsプロパティは、CMapから継承され、通常の配列と同様に要素にアクセス可能なCFormElementCollectionオブジェクト を返します。例えば、ログインフォームの例においてusername要素へアクセスするには、以下のコードを用います。

$username = $form->elements['username'];

そしてユーザ登録フォームの例において、email要素にアクセスするためには、

$email = $form->elements['user']->elements['email'];

CFormは、そのCForm::elementsプロパティへの配列アクセスを実装しているため、上記コードはさらに以下のように単純化されます。

$username = $form['username'];
$email = $form['user']['email'];

5. 入れ子フォームの作成

既にサブフォームを見てきました。サブフォームを持つフォームのことを入れ子フォームと呼ぶことにしましょう。 この章では複数のデータモデルに関連された入れ子フォームを、ユーザ登録フォームの例としてとりあげます。 ユーザ認証情報はUserモデルに格納されており、ユーザプロファイル情報はProfileモデルに格納されているものとします。

最初にregisterアクションを次のように作成します。

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を記述します。

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ビュースクリプトを記述します。

<h1>Register</h1>
 
<div class="form">
<?php echo $form; ?>
</div>

6. フォーム表示のカスタマイズ

フォームビルダを使用する主なメリットはロジック(別のファイルに格納されたフォーム構成)と表現(CForm::renderメソッド)の分離です。 結果としてフォーム表示を、CForm::renderを上書きしたり、部分ビューを提供したりすることでカスタマイズが可能となります。 両方のアプローチ共、フォーム構成を触ることが無いため、容易に再利用が可能です。

CForm::renderを上書きする場合には、CForm::elementsCForm::buttonsをたどり、それぞれのフォームエレメントに対してCFormElement::renderメソッドをコールする必要があります。例えば、

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
echo $form->renderBegin();
 
foreach($form->getElements() as $element)
    echo $element->render();
 
echo $form->renderEnd();

このビュースクリプトを使用するには、単に以下のようにコールするだけです。

<div class="form">
$this->renderPartial('_form', array('form'=>$form));
</div>

もし標準的なフォーム表示では特殊なフォーム(例えばフォームがある要素に特別な飾りを必要とする場合)について対応できない場合には、 ビュースクリプト中で以下のようなやり方を行うことができます。

なんらかの複雑なUI要素
 
<?php echo $form['username']; ?>
 
なんらかの複雑なUI要素
 
<?php echo $form['password']; ?>
 
なんらかの複雑なUI要素

最後のアプローチにおいては同様の量のフォームコードを書く必要があるため、フォームビルダはあまり利点があるとは言えません。 しかしながら、フォームは別の構成ファイルにより指定されるため、開発者はロジックに集中できるという利点があります。

$Id: form.builder.txt 2890 2011-01-18 15:58:34Z qiang.xue $

Be the first person to leave a comment

Please to leave your comment.