最近、すっかり更新頻度が下がっているので、
たまには気合入れてみます。
Zend_FormのviewヘルパーとTwitterBootstrapのフォームのマークアップがけっこう違うので、
取り込もうとするとあれこれする必要があります。
過去作ったやつのソースなので、そのままでは動かないと思いますが、自分用も含めお役に立てれば幸いです。
ちなみにZendFrameworkは1系です。
今更がっつり書く内容でもないのかもしれませんが・・まだ現役なので(汗)
今回はこのTwitterBootstrapのマークアップの出力になるようにZend_Form周りを作ります。
http://twitter.github.io/bootstrap/base-css.html#forms
簡単なトピックス投稿フォームです。
ページャー付きの一覧リストから、新規投稿、編集、削除があって
トピックスはタイトルと本文、送信ボタン
そして確認画面と保存。
こんな流れを想定します。
フォームのグループ化を使い、フォームタイトルを出しています。
グループタイトルなので見出しを分けたフォームも可能な設計をします。
コントローラー部分
Zendコントローラーはだいたいこんな感じ。
継承先のCommon_Controller_Action
は、アプリのいろいろ定義してあるファイルと想定します。
_dbInfo = new DbTable_info();
parent::_init();
}
public function preDispatch()
{
parent::_preDispatch();
}
public function postDispatch()
{
parent::_postDispatch();
}
/**
* 登録済みリスト
*/
public function indexAction()
{
$select = $this->_dbInfo->select()
->from($this->_dbInfo, array(
'info_id', 'info_title', 'info_comment', 'info_udate'
))
->order('info_udate DESC')
;
$adapter = new Zend_Paginator_Adapter_DbTableSelect($select);
$paginator = new Zend_Paginator($adapter);
$paginator->setCurrentPageNumber($this->_getParam('p', 1))
->setItemCountPerPage(10)
->setPageRange(7);
$this->view->paginator = $paginator;
}
/**
* 新規登録
*/
public function addAction()
{
$row = $this->_dbInfo->createRow();
$this->_editform($row);
}
/**
* 編集
*/
public function editAction()
{
$select = $this->_dbInfo->select()
->from($this->_dbInfo, array(
'info_id', 'info_title', 'info_comment', 'info_udate'
))
->where('info_id=?', $this->_getParam('id'))
;
$row = $this->_dbInfo->fetchRow($select);
if (!$row) {
return $this->_forward('notfound', 'error');
}
$this->_editform($row);
}
/**
* 編集フォーム
*
* @param type $row
*/
private function _editform($row)
{
$form = new Default_Form_Info($row);
$this->_helper->viewRenderer->setNoController();
//確認画面
if ($this->getRequest()->getPost('btnConfirm') && $form->isValid($this->getRequest()->getPost())) {
$this->view->form = $form->confirmDecorator();
//確認画面
$this->_helper->viewRenderer
->setScriptAction('scripts/confirm');
} elseif ($this->getRequest()->getPost('save')) {
$input = $form->getSession();
$row->setFromArray($input);
$row->info_udate = new Zend_Db_Expr('NOW()');
$row->save();
//セッションの削除
$form->delSession();
$this->_flm->addMessage('保存しました');
$this->_redirector->gotoRoute(array(
'action' => 'index',
'id' => null,
));
} else {
if ($this->getRequest()->getPost('back')) {
$form->setDefaults($form->getSession());
} elseif ($form->isErrors()) {
$form->setDefaults($form->getValues());
} else {
$default = $row->toArray();
$form->setDefaults($default);
}
$this->_helper->viewRenderer->setScriptAction('scripts/form');
}
$this->view->form = $form;
}
/**
* 記事の削除
*/
public function delAction()
{
$select = $this->_dbInfo->select()
->from($this->_dbInfo, array('info_id'))
->where('info_id=?', $this->_getParam('id'))
;
$row = $this->_dbInfo->fetchRow($select);
if (!$row) {
throw new Exception('パラメータが違います。削除できませんでした。');
}
//商品データを削除
$row->delete();
$this->_flm->addMessage('削除しました。');
return $this->_redirector->gotoRoute(array(
'action' => 'index',
'id' => null,
));
}
}
public function indexAction()
ここは記事の一覧を出力。
ページャーのアダプターにZend_Paginator_Adapter_DbTableSelect
を使います。
このアダプタの種類はArray
、DbSelect
、DbTableSelect
、Iterator
、Null
と複数あるので間違えないように。
特にDbSelect
とDbTableSelect
はうっかり間違えやすい。
public function addAction()
public function editAction()
僕の場合は、新規も編集も同じfunctionへ飛ばします。
引数にZend_Db_Rowを持っているかどうかだけの判別です。
public function delAction()
削除部分に関しては手抜きですが、
$this->_dbInfo->select()
を使わずともfind()
でも充分です。
元ソースは関連データなどの削除もしたかったので、select()を使いました
フォーム部分
続いてフォーム部分。
こちらも継承先のCommon_Form_Registration
に
フォーム全体のもろもろを設定してある想定です。
_row = $row;
parent::__construct($options);
}
public function init()
{
$this->addElement('text', 'info_title', array(
'label' => 'タイトル',
'required' => true,
'filters' => array('StringTrim'),
'Decorators' => $this->ElementDecorators()
));
$this->addElement('textarea', 'info_comment', array(
'label' => 'コメント',
'rows' => 6,
'cols' => 80,
'required' => true,
'filters' => array('StringTrim'),
'Decorators' => $this->ElementDecorators()
));
foreach ($this->getElements() as $elem) {
$names[] = $elem->getName();
}
$this->addDisplayGroup($names, 'main', array(
'Legend'=>'お知らせ投稿','Decorators' => $this->GroupDecorators()
));
}
/**
* 入力チェック
* ViewHelperの変わりに値を出力
*
* @return Common_Form_Registration
*/
public function confirmDecorator()
{
return parent::_confirmDefaultDecorator();
}
}
グループ化を手抜きでループしちゃっていますが、
見出しを分けたいときは、別々にしましょう。
foreach ($this->getElements() as $elem) {
$names[] = $elem->getName();
}
$this->addDisplayGroup($names, 'main', array(
'Legend'=>'お知らせ投稿','Decorators' => $this->GroupDecorators()
));
こっからが今回のキモです。
さきほどのもろもろを入れた部分。
フォームはだいたい複数設置するパターンが多いのでコアを1つにまとめています。
確認画面用のデコレーターの設定等
入力内容はセッション保存してます。
class Common_Form_Registration extends Zend_Form
{
protected $_session;
/**
* フロントコントローラーインスタンス
*
* @var object Zend_Controller_Front::getInstance()
*/
protected $_ctrl;
public function __construct($options = null)
{
$this->_ctrl = Zend_Controller_Front::getInstance();
$this->_session = new Zend_Session_Namespace(
$this->_ctrl->getRequest()->getControllerName()
);
$this->setMethod('post')
->setDescription(
' 必須 '
. 'の項目は必ず入力してください。'
);
parent::__construct($options);
}
public function loadDefaultDecorators()
{
$this->setDecorators(array(
'FormElements',
array('decorator' => 'Description', 'options' => array('tag' => 'p', 'class' => 'description', 'escape' => false)),
array('decorator' => 'HtmlTag', 'tag' => 'div', 'options' => array('class' => 'form-horizontal')),
'Form',
));
}
public function GroupDecorators()
{
return array(
'FormElements',
'Fieldset',
array('decorator' => 'Description', 'options' =>
array('tag' => 'div', 'class' => 'help-block', 'escape' => false)
)
);
}
public function ElementDecorators()
{
return array(
'ViewHelper',
array('Errors', 'options' => array('class' => 'alert alert-error')),
array('Description', array('tag' => 'span', 'class' => 'help-block', 'escape' => false)),
array('HtmlTag', array('tag' => 'div', 'class' => 'controls')),
array('Label', 'options' => array('class' => 'control-label', 'requiredSuffix' => ' 必須', 'escape' => false)),
array('decorator' => array('OuterHtmlTag' => 'HtmlTag'),
'options' => array('tag' => 'div', 'class' => 'control-group'))
);
}
public function isValid($data)
{
$res = parent::isValid($data);
foreach (parent::getValues() as $key => $value) {
$this->_session->$key = $value;
}
return $res;
}
/**
* submit ボタンを各サブフォームに追加する
*
* @param Zend_Form $form
*/
public function inputForm()
{
$this->setAction(Zend_View_Helper_Url::url());
$this->addElement('submit', 'btnConfirm', array(
'label' => ' 確認 ',
'type' => 'submit',
'class' => 'btn btn-primary',
'Decorators' => array('viewHelper', 'Errors')
));
$this->addElement('button', 'btnReset', array(
'ignore' => true,
'label' => 'リセット',
'type' => 'reset',
'class' => 'btn',
'Decorators' => array('viewHelper', 'Errors')
));
$this->addDisplayGroup(array(
'btnConfirm',
'btnReset'
), 'subform');
$this->subform->clearDecorators()
->addDecorator('FormElements')
->addDecorator('HtmlTag', array('tag' => 'div', 'class' => 'form-actions'));
$this->setElementFilters(array('StringTrim'));
return $this;
}
/**
*
* @param $element
* @return
*/
private function _checkElement($element)
{
//選択なら項目を表示
switch ($element->getType()) {
//ファイル
case 'Zend_Form_Element_File':
if ($this->getValue($elem_id)) {
$value = basename($element->getFileName());
}
break;
//選択項目系
case 'Zend_Form_Element_Select':
if ($element->getValue() != "") {
$select = $element->getMultiOptions();
if (!$value = $select[$element->getValue()]) {
foreach ($select as $k => $v) {
if (is_array($v)) {
if ($value = $v[$element->getValue()]) {
break;
}
}
}
}
}
break;
case 'Zend_Form_Element_Radio':
$select = $element->getMultiOptions();
$value = $select[$element->getValue()];
break;
//複数選択項目系
case 'Zend_Form_Element_MultiCheckbox':
case 'Zend_Form_Element_MultiSelect':
$select = $element->getMultiOptions();
if (is_array($element->getValue())) {
foreach ($element->getValue() as $k => $v) {
$tmp[] = $select[$v];
}
$value = implode('
', $tmp);
} else {
$value = '-';
}
break;
default:
$value = $element->getValue();
}
return $value;
}
/**
* 入力チェック
* ViewHelperの変わりに値を出力
*
* @return Common_Form_Registration
*/
protected function _confirmDefaultDecorator()
{
foreach ($this->getDisplayGroup('main') as $element) {
$decorators = $element->getDecorators();
foreach ($decorators as $key => $deco) {
if ($key == 'Zend_Form_Decorator_ViewHelper') {
$decorators[$key] = 'ConfirmDefault';
} elseif ($key == 'Zend_Form_Decorator_File') {
$decorators[$key] = 'ConfirmDefaultFile';
} elseif ($key == 'Default_Form_Decorator_Upload') {
$decorators[$key] = array('ConfirmUpload', $deco->getOptions());
}
}
$element->setDecorators($decorators);
}
return $this;
}
}
protected function _confirmDefaultDecorator()
グループをmainと指定しちゃっていますが、
複数グループを作ったときは、それぞれ取得できるように二重ループにしましょう。
エラー用デコレーター
2013-09-25追記
入力エラーが起きたときに要素を赤くします。
getElement();
$view = $element->getView();
if (null === $view) {
return $content;
}
$errors = $element->getMessages();
if (empty($errors)) {
return $content;
}
$decorators = $element->getDecorators();
foreach ($decorators as $key => $deco) {
if ($key == 'OuterHtmlTag') {
//var_dump($decorators[$key]);
$decorators[$key]->setOption('class', 'control-group error');
}
}
$separator = $this->getSeparator();
$placement = $this->getPlacement();
$errors = $view->formErrors($errors, $this->getOptions());
switch ($placement) {
case self::APPEND:
return $content . $separator . $errors;
case self::PREPEND:
return $errors . $separator . $content;
}
}
}
?>
テンプレート
一覧リスト
index.tpl
お知らせ投稿 新着情報を発信しよう
{include 'scripts/pagination.tpl' pager=$paginator->getPages() assign="pagination"}
{$pagination}
-
{foreach $paginator->getCurrentItems() as $row}
-
{$row->info_title}
{/foreach}
削除ボタンとかはTwitterBootstrapのconfirmモーダルウィンドウを使って確認アラートを表示させています。
ページャー
スタイルシートのvisible-phone
を使って、PC用と前後ページ送りしかないスマホ用の2パターン作っています。
scripts/pagination.tpl
{strip}
{* 最初のページへのリンク *}
{if $pager->previous}
- «
{else}
- «
{/if}
{* 前のページへのリンク *}
{if $pager->previous}
- <
{else}
- <
{/if}
{* ページ番号へのリンク *}
{foreach $pager->pagesInRange as $page}
{if $page != $pager->current}
- {$page}
{else}
- {$page}
{/if}
{/foreach}
{* 次のページへのリンク *}
{if $pager->next}
- >
{else}
- >
{/if}
{* 最後のページへのリンク *}
{if $pager->next}
- »
{else}
- »
{/if}
{* 前のページへのリンク *}
{if $pager->previous}
- <
{else}
- <
{/if}
- {$pager->current}
{* 次のページへのリンク *}
{if $pager->next}
- >
{else}
- >
{/if}
{/strip}
scripts/form.tpl
{if $form->isErrors()}
入力エラー
{/if}
{$form->inputForm()->render()}
入力エラーサンプル
TwitterBootstrapのエラー
scripts/confirm.tpl
以下の内容でよろしいですか?
{$form->render()}
えーっと・・これで
一通りの説明ができたのかな。。
話が長すぎて、1回の記事では無理があったかも(汗)
でも頑張った俺。
質問などあれば追記していきまふ。
コメント