CActiveForm講解
CActiveForm提供了一些方法,這些方法能夠方便的去創建一個與數據模型相關聯的Form表單。CActiveForm繼承自CWidget,所以他需要實現CWidget的init() 和 run() 方法,同時它也實現了一些自己的封裝方法。CActiveForm很重要的一個特性就是它支持Ajax校驗。我們可以設置CActiveForm的enableAjaxValidation屬性為ture來啟動Ajax校驗。例如,當用戶在input框中輸入一些值后就會觸發Ajax校驗。CActiveForm會向服務器提交Ajax請求,用來校驗用戶當前輸入的值。服務器的校驗一般是調用模型類Model的validate()方法。如果校驗失敗,相對應的錯誤信息將會被返回並顯示給用戶。即使用戶在瀏覽器禁用javascript,他也會通過整個頁面的提交自動回滾到傳統的頁面驗證。
在客戶端,Yii認為input框可以存在四個狀態:初始化,校驗,錯誤和成功。為了區分這些狀態,CActiveForm自動指定了不同的CSS樣式給包含此input框的HTML
element。默認情況下,這些CSS樣式類的名字為:validating,error,success。當然我們可以使用CActiveForm的options屬性去自定義他們。
CActiveForm的提交和校驗是基於Ajax模式的。如果你的Form表單中有很大量的數據需要提交,那么這種Ajax模式的提交可能就不那么好了。這種情況下,你可以設計自己輕量級的Ajax校驗。使用Yii對JQuery的支持?
使用CActiveForm來做Ajax校驗,我們需要使用兩個JS庫:jquery.js和jquery.yiiactiveform.js。他們的位置在工程根目錄下:assets\5ce53e17\文件夾中。不用擔心,這些JS庫Yii會自動發布到你的工程中。雖然這些動作Yii會悄悄的做,當你必須知道。
首先我們來看我們的模型類,這里我們使用CFormModel:
1 class LoginForm extends CFormModel 2 { 3 public $username; 4 public $password; 5 public function rules() 6 { 7 return array( 8 array('username, password', 'required'), 9 array('username', 'authenticate'), 10 ); 11 } 12 public function authenticate($attribute,$params) 13 { 14 if($this->username=='admin') 15 $this->addError('username','can not login with admin!'); 16 } 17 }
我們的模型類非常簡單,只有username和passsword兩個屬性。username的校驗規則是不為空,且不能是“admin”,而password的校驗規則只是不為空。注意,當我們檢查到用戶輸入的username的值為“admin”時候,校驗失敗,我們添加錯誤信息為“can
not login with admin”,這段信息會在頁面中的Yii提供的error元素中輸出。
我們可以在rules方法中指定任何一條驗證規則的錯誤信息,我們需要使用message屬性。例如:array(‘username’, ‘required’,'message’=>’not
null’),當‘username’的內容為空的時候,我們會使用message指定的內容在頁面上顯示錯誤信息。當然我們可以使用中文,如果出現亂碼,可以這樣設置:array(‘username’,
‘required’,'message’=>iconv(“gb2312″,”utf-8″,”不能為空“))
你將main.php里的app配置加上language=>’zh_cn’,系統默認的提示就是中文的了!
其他驗證規則案例:
array(‘title, content,
status’, ‘required’),
array(‘title’, ‘length’,
‘max’=>128),
array(‘status’, ‘in’,
‘range’=>array(0, 1, 2)),
array(‘tags’, ‘match’,
‘pattern’=>’/^[\w\s,]+$/’,'message’=>’Tags can only contain word
characters.’),
我們的頁面代碼如下:
1 <div class="form"> 2 <?php $form=$this->beginWidget('CActiveForm', 3 array('id'=>'login-form','enableAjaxValidation'=>true,'action'=>array('site/login'))); ?> 4 <div class="row"> 5 <?php echo $form->labelEx($model,'username'); ?> 6 <?php echo $form->textField($model,'username'); ?> 7 <?php echo $form->error($model,'username'); ?> 8 </div> 9 <div class="row"> 10 <?php echo $form->labelEx($model,'password'); ?> 11 <?php echo $form->passwordField($model,'password'); ?> 12 <?php echo $form->error($model,'password'); ?> 13 </div> 14 <div class="row submit"> 15 <?php echo CHtml::submitButton('Login'); ?> 16 </div> 17 <?php $this->endWidget(); ?> 18 </div>
對於每一個模型屬性的頁面表示來說,我們都會有一個Label來說明其屬性名,還有一個input框用來輸入該屬性值,最后有個error元素來顯示校驗失敗時的錯誤信息。Yii對Form及其子元素封裝的太多,我們將以上php代碼翻譯成靜態頁面為:
1 <div class="form"> 2 <form id="login-form" action="/validator/index.php?r=site/login" method="post"> 3 <div class="row"> 4 <label for="LoginForm_username" class="required">Username 5 <span class="required">*</span></label> 6 <input name="LoginForm[username]" id="LoginForm_username" type="text" value="" /> 7 <div id="LoginForm_username_em_" class="errorMessage" style="display:none"></div> 8 </div> 9 <div class="row"> 10 <label for="LoginForm_password" class="required">Password 11 <span class="required">*</span></label> 12 <input name="LoginForm[password]" id="LoginForm_password" type="password" value="" /> 13 <div id="LoginForm_password_em_" class="errorMessage" style="display:none"></div> 14 </div> 15 <div class="row submit"> 16 <input type="submit" name="yt0" value="Login" /> 17 </div> 18 </form> 19 </div>
Yii的Ajax的校驗效果是通過CSS來實現的,上文提到一個input框可以存在四個狀態:初始化,校驗,錯誤和成功。每個狀態對應不同CSS樣式類。初始化和校驗這兩個狀態我們不需要關心,我們只注重用戶校驗失敗和成功時,該input框的CSS樣式。此外當校驗失敗的時候,我們還要定義錯誤信息的樣式,從翻譯過來的靜態文本來來,我們需要定義errorMessage類。以下是我的CSS類:
/*標簽是否換行*/
.form label {
font-size:12px; display:block; }
/*屬性是否必填項,客戶端根據model的rule方法而定*/
.form span.required {
color:red; }
/*校驗出錯時標簽CSS樣式*/
.form .error label {
color:#FFCC33; }
/*校驗出錯時輸入框的CSS樣式*/
.form .error input {
background:#FEE; border-color:#C00; }
/*校驗出錯時錯誤信息的CSS樣式,我們可以設置其display屬性讓其不換行。*/
.form .errorMessage {
display:inline; color:red; font-size:12px; }
/*校驗成功時標簽CSS樣式*/
.form .success label {
color:#000000; }
/*校驗成功時輸入框的CSS樣式*/
.form .success input {
background:#E6EFC2; border-color:#C6D880; }
以上的CSS注釋已經很詳細了,我們來看看校驗流程,首先我們需要先渲染我們的頁面:
$model = new LoginForm;
$this->render(‘index’,array(‘model’=>$model));
注意,這里我們必須使用render()方法對其進行渲染。
我們的actionLogin如下:
public function actionLogin() { $model = new LoginForm; //ajax validate if(isset($_POST['ajax']) && $_POST['ajax']==='login-form') { echo CActiveForm::validate($model); Yii::app()->end(); } //submit handle and validate if(isset($_POST['LoginForm'])) { $model->attributes = $_POST['LoginForm']; if($model->validate()){ //no business logic handle $this->renderPartial('success'); Yii::app()->end(); } } $this->render('index',array('model'=>$model)); }
當input框失去焦點的時候,頁面會向服務器提交Ajax請求,服務器端就會根據模型類LoginForm中定義的校驗規則就其就行驗證。如果校驗成功,則input框會按指定的success樣式類去顯示,如果校驗失敗,則input框會按指定的error樣式類去顯示,同時還會按照errorMessage指定的樣式類顯示錯誤信息。
需要注意的是,如果我們對用戶輸入的信息進行了校驗,而且有可能校驗失敗,但是用戶在校驗失敗的情況下,仍然可以提交Form表單。所以當用戶提交Form表單的處理代碼中,我們仍需要對用戶的輸入進行校驗。
CActiveForm其他組件的使用
1. textArea
這個組件沒有太多講的,主要注意行列的屬性配置,使用代碼:
<?php echo
$form->textArea($model,’textArea’,array(‘rows’=>10,’cols’=>50)); ?>
2. fileField
雖然Yii封裝了這個組件,但是它並沒有做更多的支持,它的上傳需要更多的配置,它也不支持Ajax校驗。使用代碼:
<?php echo $form->fileField($model,’fileField’);
?>
3. radioButtonList
這是一個radio集合組件。使用代碼:
<?php echo
$form->radioButtonList($model,’radioButtonList’,
array(’1′=>’Male’,’0′=>’Female’),
array(‘separator’=>’ ’,'labelOptions’=>array(‘class’=>’radiolabel’))
)?>
Yii框架封裝的元素集合組件大致為4個參數:
$model,$property,$data,$htmlOptios
前面兩個參數是我們關聯模型類和指定的屬性。第三個參數是一個數組,他是元素集合組件的數據來源,上面的代碼中配置了兩個數組元素,則會對應生成兩個radio,radio的值為1或者0,radio的標簽為Male或者Female。Yii默認將兩個radio之間使用<br/>間隔,即兩個radio不在同一行上,我們可以使用separator屬性更改其間隔方式,這里我們使用空格符來間隔兩個radio,這樣他們顯示在同一行上。另外在同一個radio中,其標簽和實體也是換行的,原因在於標簽<label>會換行,我們可以給label標簽添加CSS樣式,改變其display值為inline將其與radio實體排列在一行。
radioButtonList對應模型類中的屬性值不是數組,只是一個單一數值或者字符而已,雖然他是一個集合組件,但是他是單選的,所以最終只有一個單一值提交服務器端。
另外值得注意的是,Yii封裝的這些組件的初始值不能夠在標簽中設置,Yii自動會從模型類中讀取屬性值,然后在組件上顯示出來。所以,如果你想在頁面渲染前初始化一些組件的默認值,那么你可以直接初始化模型類就可以了。
4. checkBoxList
這是一個checkBox集合組件,使用代碼:
<?php echo $form->checkBoxList($model,’checkBoxList’, array(’1′=>’Football’,’2′=>’Music’,’3′=>’Game’,’4′=>’basketball’), array(‘separator’=>’ ’,'labelOptions’=>array(‘class’=>’checkboxlabel’)) )?>
這個組件同上,唯一不同的是這個組件是多選的,所以他對應的模型類的屬性應該是一個數組。這個組件將你選中的每個checkBox的值構造成一個數組提交服務器端。例如我們選中了Football和Game,那個該組件構造的數組將是array(‘1’,’3’),沒有選中的checkBox不會被構造進這個數組中。反之從服務器段讀取數組,然后顯示該組件也是同樣的道理。
5. listBox
本質上它是一個select,但是它會顯示所有的option。使用代碼如下:
<?php echo $form->listBox($model,’listBox’, array(’1′=>’Football’,’2′=>’Music’,’3′=>’Game’,’4′=>’basketball’), array(‘size’=>8,’multiple’=>false,’class’=>’listbox’) )?>
需要說明的屬性‘size’表示該select的大小,雖然我只定義了4個option,但是我仍然想讓它占據8個option的高度。屬性multiple為是否多選。屬性class為select的CSS樣式類,但是貌似不起作用。在需要說明的一點是,雖然它可以單選和多選,但是他對應的模型類的屬性始終是一個數組。
6. dropDownList
本質上它是一個真正意義上select,因為他不會顯示所有的option,使用代碼如下:
1 <?php 2 3 $models = 4 person::model()->findAll(array(‘order’=>’age’)); 5 6 $list = CHtml::listData($models,’id’,'username’); 7 8 echo $form->dropDownList($model,’dropDownList’,$list,array(‘empty’=>’Select 9 a user’) 10 11 )?>
這個組件的使用跟select差不多,對應模型層的屬性是一個單一數值或者字符,而不是數組。屬性empty指定了select的第一個option,相當於初始化值。
這里我們需要說明的是以上這些集合組件的數據來源。本質上其實我們就是查詢數據庫,將結果集封裝成一個數組,其實查詢數據結果集本來就是一個數組。這里我們使用AR類person按年齡查詢所有的記錄,然后使用CHtml的listData方法將查詢記錄中的id字段和username字段構造成一個簡單的數組$list,然后我們只需要在dropDownList的標簽配置中應用即可。
最后我們介紹一下服務器端獲取頁面數據,因為我們已經將模型類和我們的頁面標簽相關聯,所以使用標簽顯示模型類屬性或者從顯示標簽中獲取模型類屬性是十分簡單的。在服務器端我們獲取頁面數據的時候,可以使用如下代碼:
$model = new Form; $model->attributes = $_POST['Form'];
以上代碼會將用戶提交的數據自動填充到模型類的屬性中,這個方式稱為安全特性分配。首先我們的頁面中必須已經將標簽和模型類Form關聯。需要注意的是$_POST的參數就是模型類的名稱Form,而不是標簽名稱。最重要的問題在於我們模型類中的屬性必須是安全的,否則我們的安全特性分配將會失敗。指定模型類中的屬性為安全的是通過實現模型類中的
Rules方法,即校驗規則方法。我們可以直接將模型類的指定屬性指定為”safe”或者指定其他校驗規則,因為Yii因為如果一個屬性通過某個校驗后它就可以被認為是安全的了。
public function rules() { return array( array(‘property’,'safe’) ); }
CactiveDataProvider講解
CactiveDataProvider是基於ActiveRecord的一個數據提供者,同時它也繼承自CDataProvider。它提供了ActiveRecord對象的集合。我們可以使用“modelClass”屬性指定CactiveDataProvider所要提供的ActiveRecord對象的類型。CactiveDataProvider可以使用AR方法CActiveRecord::findAll從數據庫中檢索信息。它還可以使用criteria來指定查詢條件,排序以及分頁等等。
CActiveDataProvider的時候方法如下:
$dataProvider=new CActiveDataProvider('Post', array( 'criteria'=>array( 'condition'=>'status=1 AND tags LIKE :tags', 'params'=>array(':tags'=>$_GET['tags']), 'with'=>array('author'), ), 'pagination'=>array( 'pageSize'=>20, 'currentPage'=>0, ), ));
在Yii的blog案例中,很很多地方用到了CactiveDataProvider,尤其是在讀取很多數據且需要分頁的情況下,CactiveDataProvider是很出色的。CactiveDataProvider只是一個數據的集合,並不是一個顯示組件,所以在blog案例中,它是和CGridView一起使用的,CGridView確切的說是一個使用表格顯示數據的widget。其實包含了很多的封裝,在這里我們不是用CGridView,只使用CActiveDataProvider來進行分頁查詢。使用CActiveDataProvider進行分頁查詢最重要的是“currentPage”屬性,默認它是0。
在以上的CActiveDataProvider使用代碼中,我們配置了它的兩個屬性:criteria和pagination。
Criteria屬性值就是CDbCriteria類的實例,而pagination值並非一個CPagination類的實例,而是一個數組而已。實例化CPagination類需要表中記錄總數作為參數。
以下是我action的代碼:
public function actionPage() { $currentPage = 0; $pageSize = 5; if(isset($_GET['id'])){ $currentPage = $_GET['id']; } $criteria = new CDbCriteria(array('order'=>'age desc',)); $pagination = array('currentPage'=>$currentPage,'pageSize'=>$pageSize,); $dataProvider = new CActiveDataProvider('person',array('pagination'=>$pagination,'criteria'=>$criteria,)); $totalItemCount = $dataProvider->getTotalItemCount(); $pageCount = ceil($totalItemCount/$pageSize); $itemCount = $dataProvider->getItemCount(); $page = array('totalItemCount'=>$totalItemCount, 'pageCount'=>$pageCount,'itemCount'=>$itemCount,'currentPage'=>$currentPage,); $data = $dataProvider->getData(); $this->render('all',array('data'=>$data,'page'=>$page,)); }
以上代碼我們將數據信息data和分頁信息page分開存放,並傳遞到被渲染的頁面。上文提到了“currentPage”,其實是CPagination類的一個屬性,它默認是從0開始的,即0代表第一頁。在我的代碼中,我將pageSize設置成5,即每頁顯示5條信息。CDbCriteria類可以幫助我們進行復雜的數據庫查詢,而且代碼書寫很清晰。這里我只是查詢我的person表,然后按照年齡排序。pagination值並非一個CPagination類的實例,而是一個數組而已。所以我將pagination配置成一個數組,數組元素只有currentPage和pageSize兩個。最后我們便可以實例化CActiveDataProvider,並且按我們的配置查詢數據庫了。
雖然我們可以只向頁面傳遞CActiveDataProvider對象,但是這樣會使得我們的頁面很繁雜,所以我將數據信息data和頁面信息page分離開來,然后傳遞到被渲染的頁面。CActiveDataProvider類的getData()方法返回檢索數據結果集,類型為數組。我們可以是使用foreach循環讀取data里面的信息:
<?php foreach($data as $person){ echo "<tr height='30'>"; echo "<td>".$person['id']."</td>"; echo "<td>".$person['username']."</td>"; echo "</tr>"; } ?>
頁面信息page的構建其實很簡單,我們獲取到總的記錄數,然后每頁顯示5條,自然我們會得到總的頁碼數。我們也可以獲取該頁下的記錄數,因為最后一頁的記錄數不一定是5。我們將這些信息封裝成page數組,傳遞到被渲染的頁面上以供使用。當然我們還可以封裝更多的信息,當然這取決於你是怎么顯示分頁信息,以及顯示什么信息。
CHtml::link()的使用
方法說明:
public static string
link(string $text, mixed $url=’#', array $htmlOptions=array ( ))
例如:
<?php echo
CHtml::link(‘Link Text’,array(‘controller/action’)); ?>
HTML輸出為:
<a
href=”index.php?r=controller/action”>Link Text</a>
帶參數的:
<?php echo
CHtml::link(‘Link Text’,array(‘controller/action’,'param1′=>’value1′));
?>
HTML輸出為:
<a
href=”index.php?r=controller/action¶m1=value1″>Link
Text</a>
多參數的:
<?php echo
CHtml::link(‘Link Text’,array(‘controller/action’,
’param1′=>’value1′,’param2′=>’value2′));
?>
HTML輸出為:
<a
href=”index.php?r=controller/action¶m1=value1¶m2=value2″>Link
Text</a>
額外參數的:
<?php echo
CHtml::link(‘Link Text’,array(‘controller/action’,'param1′=>’value1′),
array(‘target’=>’_blank’); ?>
HTML輸出:
<a
target=”_blank”
href=”index.php?r=controller/action¶m1=value1″>Link
Text</a>
絕對路徑:
<?php echo
CHtml::link(‘Link Text’,array(‘/controller/action’)); ?>
指定模塊下的路徑:
<?php echo
CHtml::link(‘Link Text’,array(‘/module-id/controller/action’)); ?>
<?php echo
CHtml::linkButton(‘LinkName’,
array(‘submit’=>array(‘controller/action’,'param’=>’value’),’confirm’=>”Are
you sure?”,)); ?>
無效鏈接:
echo CHtml::link(‘LinkName’,”,array(‘href’=>’javascript:void(0)’));
HTNL輸出:
<a href=”javascript:void(0)”>LinkName</a>
URL生成:
$route =
“site/test”;
$params =
array(‘id’=>100);
$url =
$this->createUrl($route,$params);
Widget的使用
繼承 CWidget 以及重載它的init() 和 run() 方法,可以定義一個新的 widget:
class MyWidget extends
CWidget
{
public function init()
{
// this method is called by
CController::beginWidget()
}
public function run()
{
// this method is called by
CController::endWidget()
}
}
Widget可以像一個控制器一樣擁有它自己的視圖。默認的, widget 的視圖文件位於包含了widget文件的views 子目錄之下。這些視圖可以通過調用 CWidget::render() 渲染,這一點和控制器很相似。唯一不同的是,widget的視圖沒有布局文件支持。同時,view 文件中的 $this 指的是 widget 實例而不是 controller 實例。
現在我們創建一個 PageCode widget(分頁碼):
首先我們需要在Web應用下的components包下創建自定義的widget:
components/ PageCode.php:
<?php
class PageCode extends
CWidget
{
public $page = array();
public function run()
{
$this->render(‘pagecode’,array(‘page’=>$this->page));
}
}
其次我們還需要創建該widget所需要的視圖文件pagecode.php:
components/views/pagecode.php:
<?php
echo
CHtml::link(‘ first ’,array(‘site/page’,'id’=>0));
if($page['currentPage']<=0){
echo
CHtml::link(‘ previous ’,”,array(‘href’=>’javascript:void(0)’));
}else{
echo
CHtml::link(‘ previous ’,array(‘site/page’,'id’=>$page['currentPage']-1));
}
echo “|”;
if($page['currentPage']>=$page['pageCount']-1){
echo
CHtml::link(‘ next ’,”,array(‘href’=>’javascript:void(0)’));
}else{
echo
CHtml::link(‘ next ’,array(‘site/page’,'id’=>$page['currentPage']+1));
}
echo “|”;
echo
CHtml::link(‘ last ’,array(‘site/page’,'id’=>$page['pageCount']-1));
echo
“ ”;
echo
“ ”;
echo “current page
:”.($page['currentPage']+1);
echo
“ ”;
echo “total page
:”.$page['pageCount'];
echo
“ ”;
echo “total record
:”.$page['totalItemCount'];
echo
“ ”;
echo
“ ”;
現在我們就可以在我們的php頁面上使用該widget了:
<?php
$this->widget(‘application.components.PageCode’,array(‘page’=>$page));
?>
CCaptcha的使用
使用CCaptcha的話,你不得不使用CCaptchaAction,這兩個都是Yii提供的,CCaptcha本質上是一個widget,而CCaptchaAction則是一個action類。
CCaptcha的作用只是顯示校驗碼圖片和注冊Js腳本,而CCaptchaAction才是核心,它負責生成校驗碼和圖片,並且支持Ajax校驗。在CCaptcha生成的靜態頁面中,我們可以看到生成的<img/>標簽請求的地址正是該CCaptchaAction。
實際上,CCaptchaAction將生成的校驗碼是放在Session中的,這樣有利於校驗。我們使用CCaptcha,一般是將其作為Model的一個屬性的,並在該Model的rules方法中指定該屬性的校驗規則為“captcha”。暫時這樣理解。
使用CCaptcha注意:
1. 在Model中聲明一個屬性,其校驗規則為“captcha”。
2. 在需要渲染CCaptcha的Controller中重寫actions 方法,將CCaptchaAction導入進來,作為當前Controller的一個action,此action的ID為“captcha”。因為生成的<img>請求的地址是當前Controller的captcha。
3. 在需要渲染CCaptcha的頁面中,添加input以對應Model中聲明的屬性。
4. 在需要渲染CCaptcha的頁面中,使用<?php $this->widget(‘CCaptcha’); ?>
這樣,當我們在input輸入框中填完CCaptcha生成的校驗碼的時候,就會觸發Ajax校驗,本質上就是執行CCaptchaAction的validate方法,該方法會獲取用戶輸入的校驗碼,然后從Session中獲取正確的校驗碼,然后比對。
CCaptchaAction是怎么樣生成校驗碼的?
CCaptchaAction的generateVerifyCode()方法用來生成校驗碼。默認情況下,校驗碼的長度為6或者7,當然這個可以自定義。YII將26個英文字母分成兩組,一組21個,一組5個。然后按一定的隨機規則從其中一組中選出一個字母。
CCaptchaAction是怎么樣生成圖片的?
CCaptchaAction的renderImage()方法用來生成圖片。該方法中依次指定圖片的寬度,高度,背景色,背景色是否透明,前景色,字體(字體采用Duality.ttf,在CCaptchaAction同目錄下我們可以看到該字體文件)。圖片字體的不規則排列是由字體大小,angle,位置來決定的,這些參數會在一定范圍內隨機生成。
Yii上傳文件
CUploadedFile的使用:
$image =
CUploadedFile::getInstance($model,’image’);
$name =
$image->getName();
$size =
$image->getSize();
$type =
$image->getType();
$file =
“E:/temp.jpg”;
$image->saveAs($file,true);
