當創建 HTML 表單時,經常我們發現我們在寫很多重複而且在不同專案中很難重用的視圖程式碼。 例如,對於每個輸入框, 我們需要以一個文字標籤和顯示可能的驗證錯誤來關聯它。 為了改善這些程式碼的重複使用性,我們可以使用自版本 1.1.0 可用的表單產生器特性。
Yii 表單產生器使用 CForm 物件來代表描述一個HTML表單所需的內容,包含哪些資料模型關聯到此表單, 表單中有哪些輸入框,以及如何渲染整個表單。開發者主要需要建立和配置這個 CForm 對象,然後呼叫它的渲染方法來顯示表單。
表單的輸入框參數被組織為根據表單元素的分層結構。 在結構的頂層,是 CForm 對象。此物件的成員分為兩大類:CForm::buttons 和 CForm::elements。前者包含 按鈕元素(例如提交按鈕,重設按鈕),後者包含輸入元素,靜態文字和子表單。子表單也是 CForm 對象,只是它存在於 另一個表單的 CForm::elements 。子表單可以有它自己的資料模型, CForm::buttons 和 CForm::elements 集合。
當使用者提交一個表單時,整個表單結構中填寫的資料被提交, 也包含子表單中填寫的資料。 CForm 提供了便利方法,可以自動賦值輸入的資料到對應的資料屬性並執行資料驗證。
下面,我們展示如何使用表單產生器來建立一個登入表單。
首先,我們編寫登入action 程式碼:
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 site.loginForm 指定的了 CForm 對象。 CForm 物件和
LoginForm 模型(在Creating Model中已介紹)關聯。
site/index 頁面。否則, 我們以此表單渲染
login 視圖。
application.views.site.loginForm 實際指的是 PHP 檔案
protected/views/site/loginForm.php。此檔案應傳回一個PHP 陣列,這個陣列代表了 CForm 所需的配置, 如下圖所示:
return array( 'title'=>'Please provide your login credential', '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::elements 和 CForm::buttons。 它們的每一個是一個指定了表單元素清單的陣列。在後續部分我們將給出更多細節關於如何配置表單元素。 最後,我們編寫
login 視圖,可以簡潔地如下所示,
<h1>Login</h1> <p class="form"> <?php echo $form; ?> </p>
3. 指定表單元素使用表單產生器,我們大部分的工作由編寫視圖腳本程式碼轉為指定表單元素。在這一小節中,我們講述如何指定CForm::elements 屬性。 我們不準備講述 CForm::buttons 因為它的配置和 CForm::elements 的配置幾乎相同。 CForm::elements 屬性接受一個陣列作為它的值。每個陣列元素指定了一個單獨的表單元素,這個表單元素可以是一個輸入框,一個靜態文字字串或一個子表單。提示: 上面的代碼 echo $form;
相當於
echo $form-> render();。 這是因為 CForm 執行了
__toString魔術方法,它呼叫
render()並傳回它的結果為代表此表單物件的字串。
指定輸入元素
一個輸入元素主要由標籤,輸入框,提示文字和錯誤顯示組成。 它必須和一個模型屬性關聯。一個輸入元素的規格被代表為一個 CFormInputElement 實例。 CForm::elements 數組中的以下程式碼指定了一個單獨的輸入元素:'username'=>array( 'type'=>'text', 'maxlength'=>32, ),它說明模型屬性被命名為 maxlength
屬性為32。 <p>任何 CFormInputElement 可写的属性都可以如上配置。例如,我们可以指定 hint 选项来显示提示信息,或者我们可以指定 items 选项若输入框是一个 list box,一个下拉列表,一个多选列表或一个单选按钮列表。 若选项的名字不是一个CFormInputElement 属性,它将被认为是对应 HTML 输入元素的属性, 例如,因为上面的 <code>maxlength
不是一个CFormInputElement 属性,它被渲染作为 HTML 文本输入框的 maxlength
属性。
type 选项需要特别注意。它指定了输入框的类型。 例如,text
类型意味着将渲染一个普通的文本输入框;password
类型意味着将渲染一个密码输入框。 CFormInputElement 识别如下内置的类型:
text
hidden
password
textarea
file
radio
checkbox
listbox
dropdownlist
checkboxlist
radiolist
在上面的内置类型中,我们想要对这些 "list" 类型的用法多说一些, 包括 dropdownlist
, checkboxlist
和radiolist
。这些类型需要设置对应输入元素的 items 属性。可以这样做:
'gender'=>array( 'type'=>'dropdownlist', 'items'=>User::model()->getGenderOptions(), 'prompt'=>'Please select:', ), ... class User extends CActiveRecord { public function getGenderOptions() { return array( 0 => 'Male', 1 => 'Female', ); } }
上面的代码将生成一个下拉列表选择器,提示文字是 “please select:”。选项包括 “Male” 和 “Female”,它们是由 User
模型类中的 getGenderOptions
方法返回的。
除了这些内置的类型, type 选项也可以是一个 widget 类名字或 widget 类的路径别名。 widget 类必须扩展自CInputWidget 或 CJuiInputWidget。当渲染输入元素时, 一个指定 widget 类的实例将被创建并渲染。The widget will be configured using the specification as given for the input element.
指定静态文本
很多情况下,一个表单包含一些装饰性的 HTML 代码。 例如,一个水平线被用来分隔表单中不同的部分;一个图像出现在特定的位置来增强表单的视觉外观。 我们可以在 CForm::elements 集合中指定这些 HTML 代码作为静态文本。要这样做,我们只要指定一个静态文本字符串作为一个数组元素,在 CForm::elements 恰当的位置。例如,
return array( 'elements'=>array( ...... 'password'=>array( 'type'=>'password', 'maxlength'=>32, ), '<hr />', 'rememberMe'=>array( 'type'=>'checkbox', ) ), ...... );
在上面,我们在 password
输入框和 rememberMe
之间插入一个水平线。
静态文本最好用于文本内容和它们的位置不规则时。 若表单中的每个输入元素需要被相似的装饰,我们应当定制表单渲染方法,此章节将简短介绍。
指定子表单
子表单被用来分离一个长的表单为几个逻辑部分。 例如,我们可以分离用户注册表单为两部分:登录信息和档案信息。 每个子表单和一个数据模型有无关联均可。例如在用户注册表单,若我们存储用户登录信息和档案信息到两个分离的数据表中(表示为两个数据模型), 然后每个子表单需要和一个对应的数据模型关联。若我们存储所有信息到一个数据表中,任意一个子表单都没有数据模型,因为它们和根表单分享相同的模型。
一个子表单也表示为一个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
对象。
访问表单元素和访问数组元素一样简单。CForm::elements 属性返回一个 CFormElementCollection 对象, 它扩展自 CMap 并允许以类似于一个普通数组的方式来访问它的元素。例如,要访问登录表单中的元素 username
,我们可以使用下面的代码:
$username = $form->elements['username'];
要访问用户注册表单中的 email
元素,使用
$email = $form->elements['user']->elements['email'];
因为 CForm 为它的 CForm::elements 属性执行数组访问,上面的代码可以简化为:
$username = $form['username']; $email = $form['user']['email'];
我们已经描述了子表单。我们称一个有子表单的表单为一个嵌套表单。在这一章节, 我们使用用户注册表单作为例子来展示如何创建一个关联多个数据模型的嵌套表单。我们假设用户的认证信息存储为一个 User
模型,而用户的档案信息被存储为一个 Profile
模型。
我们首先创建 register
action 如下:
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
指定的配置创建了表单。 在表单被提交且成功验证之后,我们尝试保存 user 和 profile 模型。 我们通过访问相应子表单对象的 model
属性来检索 user 和 profile 模型。 因为输入验证已经完成,我们调用 $user->save(false)
来跳过验证。为 profile 模型也这样做。
接下来,我们编写表单配置文件 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 属性。 默认的表单渲染逻辑将封装每个子表单到一个 field-set 中,使用此属性作为它的标题。
最后,我们编写 register
视图脚本:
<h1>Register</h1> <p class="form"> <?php echo $form; ?> </p>
使用表单生成器最主要的好处是逻辑 (表单配置被存储在一个单独的文件中) 和表现 (CForm::render方法) 的分离。 这样,我们可以实现定制表单显示,通过重写 CForm::render 或提供一个局部视图来渲染表单。两种方法都可以保持表单配置的完整性,并且可以容易地重用。
当重写 CForm::render 时, 你主要需要遍历 CForm::elements 和 CForm::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();
要使用此视图脚本,我们需要调用:
<p class="form"> $this->renderPartial('_form', array('form'=>$form)); </p>
若一个通用的表单渲染不适用于一个特殊的表单(例如,表单为特定的元素需要不规则的装饰),在视图脚本中我们可以这样做:
some complex UI elements here <?php echo $form['username']; ?> some complex UI elements here <?php echo $form['password']; ?> some complex UI elements here
在最后的方法中,表单生成器看起来并没有带来好处,因为我们仍然需要写很多表单代码。然而,它仍然是有好处的,表单被使用一个分离的配置文件指定,这样可以帮助开发者更专注于逻辑部分。
以上就是Yii框架官方指南系列21——使用表单:使用表单生成器(CForm)的内容,更多相关内容请关注PHP中文网(www.php.cn)!