[ZF] Zend_Form 要素に対するデコレータの設定についての色々

最近Zend Frameworkを使い始めたんですがね、
Form要素を作る時、日本人的にはテーブルの方がしっくりくるじゃないですか?
バリデーションに続き海外製では良くあることですが、Zend_Formはデフォルトが定義リストなんです。
出力はラベルとフォーム要素のペアで、ラベルを指定しなかった場合はラベルのテキストが空白文字になります。
dtが空っぽだなんて気になるじゃないですか。私だけですか?
まあ兎に角自分好みにマークアップすべく、Zend_Formを調教してみることにしたわけです。

Default

これはクイックスタートで作るGuestBookのsignフォームです。
テキストボックスの角丸とか必須入力の赤文字とかはCSSの効果です。

zend_form_normal zend_form_normal-source

ソースはコメントが無いだけでドキュメントにあるものと同じ。

applications/forms/GuestBook.php

$this->setMethod('post');

$this->addElement('text', 'title', array(
	'label'      => 'Title',
	'required'   => true,
	'filters'    => array('StringTrim')
));
$this->addElement('text', 'name', array(
	'label'      => 'Name',
	'required'   => true,
	'filters'    => array('StringTrim')
));

$this->addElement('text', 'email', array(
	'label'      => 'Email',
	'required'   => true,
	'filters'    => array('StringTrim'),
	'validators' => array(
		'EmailAddress',
	)
));

$this->addElement('textarea', 'comment', array(
	'label'      => 'Comment',
	'required'   => true,
	'cols' => 50,
	'rows' => 5,
	'validators' => array(
		array('validator' => 'StringLength', 'options' => array(3, 1000))
		)
));

$this->addElement('captcha', 'captcha', array(
	'label'      => 'Please enter the 5 letters displayed below:',
	'required'   => true,
	'captcha'    => array(
		'captcha' => 'Figlet',
		'wordLen' => 5,
		'timeout' => 300
	)
));

$this->addElement('submit', 'submit', array(
	'ignore'   => true,
	'label'    => 'Post Comment',
	'class'=>'button blue'
));

$this->addElement('hash', 'csrf', array(
	'ignore' => true,
));

setElementDecorators

上のソースにsetElementDecoratorsを追加してフォームをデコレーションするタグを指定します。

applications/forms/GuestBook.php

$this->setDecorators(array(
	'FormElements', 
	array('decorator' => 'HtmlTag', 'options' => array('tag' => 'table', 'class' => 'zend_form')),
	'Form'
));

$this->setElementDecorators(
	array(
		'ViewHelper', 
		'Errors',
		array('decorator' => 'Description', 'options' => array('tag' => 'p', 'class' => 'description')),
		array('decorator' => 'HtmlTag', 'options' => array('tag' => 'td')),
		array('decorator' => 'Label', 'options' => array('tag' => 'th')),
		array('decorator' => array('OuterHtmlTag' => 'HtmlTag'), 'options' => array('tag' => 'tr')),
	)
);
$this->setElementDecorators(
	array(
		'ViewHelper', 
		'Errors',
		array('decorator' => 'Description', 'options' => array('tag' => 'p', 'class' => 'description')),
		array('decorator' => 'HtmlTag', 'options' => array('tag' => 'td')),
		array('decorator' => 'Label', 'options' => array('tag' => 'th', 'class'=>'hide')),
		array('decorator' => array('OuterHtmlTag' => 'HtmlTag'), 'options' => array('tag' => 'tr')),
	),
	array('submit')
);

$this->setElementDecorators(
	array(
		'ViewHelper', 
		'Errors',
		array('decorator' => 'Description', 'options' => array('tag' => 'p', 'class' => 'description')),
		array('decorator' => 'HtmlTag', 'options' => array('tag' => 'td', 'colspan'=>2)),
		array('decorator' => array('OuterHtmlTag' => 'HtmlTag'), 'options' => array('tag' => 'tr','class'=>'hide')),
	),
	array('csrf')
);

こっちでもいい。

applications/forms/GuestBook.php

$this->setElementDecorators(array(
			'viewHelper',
			'Errors',
			array('Description', array('tag' => 'p', 'class' => 'description')),
			array(array('data'=>'HtmlTag'),array('tag'=>'td')),
			array('Label',array('tag'=>'td')),
			array(array('row'=>'HtmlTag'),array('tag'=>'tr'))
));

$this->setElementDecorators(
	array(
		'ViewHelper', 
		'Errors',
		array('Description',array('tag' => 'p', 'class' => 'description')),
		array(array('data'=>'HtmlTag'),array('tag'=>'td')),
		array('Label',array('tag'=>'td','class'=>'hide')),
		array(array('row' => 'HtmlTag'), array('tag' => 'tr'))
	),
	array('submit')
);

$this->setElementDecorators(
	array(
		'ViewHelper', 
		'Errors',
		array(array('data'=>'HtmlTag'),array('tag'=>'td', 'colspan'=>2)),
		array(array('row' => 'HtmlTag'), array('tag' => 'tr','class'=>'hide')),
	),
	array('csrf')
);

これでDLをTableに出来るのだけども…

zend_form_table1 zend_form_table1-source

何故かCatpchaのhidden要素がテキストエリアで複製される。

さらに、File要素を追加すると、『Warning: Exception caught by form: No file decorator found… unable to render file element 』というエラーが出る。

applications/forms/GuestBook.php

$this->addElement('file', 'file', array(
		'label'      => 'File:',
		'required'   => false,
		'destination'=> APPLICATION_UPLOADS_DIR,
		'validators' => array(
			array(
				'validator' => 'Count',
				'options' => 1
			),
			array(
				'validator'=>'Extension',
				'options'=>'jpg,gif,png'
			)
		)
	));

この記事によると

つまりZend_Form_Element_Fileを利用する時はZend_Form_Decorator_Formが必須となるという事。
となるとViewHelperは使わないという事を意味する。

ということだったので、多分Fileと同様にCapchaも同じ仕様なんでしょう。
setElementDecoratorsを別に作るか、

$this->setElementDecorators(array(
			'Errors',
			array('Description', array('tag' => 'p', 'class' => 'description')),
			array(array('data'=>'HtmlTag'),array('tag'=>'td')),
			array('Label',array('tag'=>'td')),
			array(array('row'=>'HtmlTag'),array('tag'=>'tr'))
),array('catpha', 'files'));

個別にsetDecoratorsを当ててViewHelperを外せば直る。

$file = $this->getElement('file');
$file->setDecorators(array( 
	array('File', array()),
	array('HtmlTag', 'options' => array('tag' => 'td')),
	array('Label', array('tag' => 'td')), 
	array(array('row' => 'HtmlTag'), array('tag' => 'tr')) 
));

$captcha = $this->getElement('captcha'); 
$captcha->setDecorators(array( 
	  array('HtmlTag', 'options' => array('tag' => 'td')),
	  array('Label', array('tag' => 'td')), 
	  array(array('row' => 'HtmlTag'), array('tag' => 'tr')) 
));

要素にはgetElementの代わりに$this->fileでもアクセスできる。

zend_form_table-fixed

なんでこんな二度手間な仕様なのかいまいち理解できないわ 😐

DisplayGroup

フォームってリセットボタンやキャンセルボタンもあるじゃないですか。
で、何も考えずにリセットボタンを入れると送信フォームの下に出ます。

zend_form_table2

リセットボタンを送信フォームの横に並べるならaddDisplayGroupです。
addDisplayGroupはFieldsetでグループ分けするものだけど、タグを指定すれば別の要素で分けることが出来るものです。

$this->setElementDecorators(
            array(
                'ViewHelper', 
                'Errors',
		array('decorator' => 'Description', 'options' => array('tag' => 'p', 'class' => 'description'))
            ),
            array('submit','reset')
        );
$this->addDisplayGroup(
               array('submit', 'reset'),
               'buttons',
                array('decorators' => array('FormElements',
                       array('HtmlTag',array('tag'=>'td', 'colspan'=>2, 'class'=>'buttons')),
                       array(array('row' => 'HtmlTag'), array('tag' => 'tr')),
                     )
                 )
            
             );

zend_form_table2-tdgroup

addDisplayGroupのラベルはキーがLegendです。fieldsetなだけに。

$this->addDisplayGroup(
               array('submit', 'reset'),
               'buttons',
                array('decorators' => array('FormElements',
                       array('HtmlTag',array('tag'=>'td', 'class'=>'buttons')),
                       array(array('regend' => 'HtmlTag'), array('tag' => 'td')),
                     )
                 )
            
             );

zend_form_table2-tdgroup2

ZeroMailでもそうなんだけど、私はボタンを入力項目とは別にコーディングするクセがあります。
そこでZend_Formでもボタンをtableから外してdiv要素に入れたい。と思ったとき、
decoratorsのHtmlTagをDivにすればいいんだけど、

applications/forms/GuestBook.php

$this->addDisplayGroup(
               array('submit', 'reset'),
               'buttons',
                array('decorators' => array('FormElements',
                       array('HtmlTag',array('tag'=>'div', 'class'=>'buttons')),
                     )
                 )
            
             );

※まとめて書くのをやめるとこうなります。

$buttons = $this->getDisplayGroup('buttons');
$buttons->setDecorators(array(
			'FormElements',
			array('HtmlTag',array('tag'=>'div','class'=>'buttons'))
));

レンダリングされると何故か他のフォーム要素を無視して一番上に来ている。

zend_form_table2-group

ソースの順番を入れ替えても全く変化ナシ。
聞きかじったところによると、どうもsetDecoratorsが原因らしい。

SubForm

結局のところZend_Form_SubFormを使ったら思惑通りの表示になりました。

zend_form_table3 zend_form_table3-source

applications/forms/GuestBook.php

$this->setMethod('post');

$main = new Zend_Form_SubForm();

$main->addElement('text', 'email', array(
	'label'      => 'Your email address:',
	'required'   => true,
	'filters'    => array('StringTrim'),
	'validators' => array(
		'EmailAddress',
	)
));

$main->addElement('textarea', 'comment', array(
	'label'      => 'Please Comment:',
	'rows'       => 5,
	'required'   => true,
	'validators' => array(
		array('validator' => 'StringLength', 'options' => array(0, 20))
		)
));

$main->addElement('captcha', 'captcha', array(
	'label'      => 'Please enter the 5 letters displayed below:',
	'required'   => true,
	'captcha'    => array(
		'captcha' => 'Figlet',
		'wordLen' => 5,
		'timeout' => 300
	)
));

$this->addSubForm($main, 'main');

$this->getSubForm('main')
	 ->clearDecorators()
	 ->addDecorator('FormElements')
	 ->addDecorator('HtmlTag',array('tag'=>'table', 'class' => 'zend_form'));

$main->setElementDecorators(
	array(
		'ViewHelper', 
		'Errors',
		array('decorator' => 'Description', 'options' => array('tag' => 'p', 'class' => 'description')),
		array('decorator' => 'HtmlTag', 'options' => array('tag' => 'td')),
		array('decorator' => 'Label', 'options' => array('tag' => 'th')),
		array('decorator' => array('OuterHtmlTag' => 'HtmlTag'), 'options' => array('tag' => 'tr')),
	)
);

$main->captcha->setDecorators(array( 
	  array('HtmlTag', 'options' => array('tag' => 'td')),
	  array('Label', array('tag' => 'td')), 
	  array(array('row' => 'HtmlTag'), array('tag' => 'tr')) 
));


$sub = new Zend_Form_SubForm();

$sub->addElement('submit', 'submit', array(
	'ignore'   => true,
	'label'    => 'Sign Guestbook',
));

$sub->addElement('reset', 'reset', array(
	'ignore'   => true,
	'label'    => 'Reset',
));

$sub->addElement('hash', 'csrf', array(
	'ignore' => true,
));

$this->addSubForm($sub, 'subform');

$this->getSubForm('subform')
	 ->clearDecorators()
	 ->addDecorator('FormElements')
	 ->addDecorator('HtmlTag',array('tag'=>'div', 'class' => 'buttons'));

$sub->setElementDecorators(array('viewHelper','Errors'));

オプションでform要素を表示しないように設定しています。

Viewで直接ソースを書く

もうひとつの方法として、$this->form->elementName でフォーム要素を個別に取得出来るので、テンプレートにデコレータのソースを直接書いてしまうという手もありました。
ファイルを2枚触る必要があるものの見た目からして分かりやすいし、こっちのほうがフリーダムにレイアウトできそうです。

views/scripts/guestbook/sign.phtml

<form action="<?php echo $this->url() ?>" method="post" enctype="multipart/form-data">
    <table class="zend_form">
        <tbody>
            <tr>
                <td id="email-label"><label class="required" for="email">Your email address:</label></td>
                <td><?php echo $this->form->email; ?></td>
            </tr>
            <tr>
                <td id="comment-label"><label class="required" for="comment">Please Comment:</label></td>
                <td><?php echo $this->form->comment; ?></td>
            </tr>
            <tr>
                <td id="captcha-input-label"><label class="required" for="captcha-input">Please enter the 5 letters displayed below:</label></td>
                <td><?php echo $this->form->captcha; ?></td>
            </tr>
        </tbody>
    </table>
    <div class="buttons"><?php echo $this->form->submit; ?>
    <?php echo $this->form->reset; ?>
    <?php echo $this->form->csrf; ?></div>
</form>

applications/forms/GuestBook.php

$this->setMethod('post');
$this->setAttrib('enctype', 'multipart/form-data');

$this->addElement('text', 'email', array(
	'label'      => '',
	'required'   => true,
	'filters'    => array('StringTrim'),
	'validators' => array(
		'EmailAddress',
	)
));

$this->addElement('textarea', 'comment', array(
	'label'      => '',
	'rows'       => 5,
	'required'   => true,
	'validators' => array(
		array('validator' => 'StringLength', 'options' => array(0, 20))
		)
));

$this->addElement('captcha', 'captcha', array(
	'label'      => '',
	'required'   => true,
	'captcha'    => array(
		'captcha' => 'Figlet',
		'wordLen' => 5,
		'timeout' => 300
	)
));

$this->addElement('submit', 'submit', array(
	'ignore'   => true,
	'label'    => 'Sign Guestbook',
));

$this->addElement('reset', 'reset', array(
	'ignore'   => true,
	'label'    => 'Reset',
));

$this->addElement('hash', 'csrf', array(
	'ignore' => true,
));

$this->setElementDecorators(array(
	'viewHelper',
	'Errors',
	array('Description', array('tag' => 'p', 'class' => 'description')),
));

$captcha = $this->getElement('captcha'); 
$captcha->setDecorators(array());

1件のコメント

  1. Pingback: Outgoing Makeover

Leave a Comment.