2014年7月17日 星期四

yii framework - 如何上傳照片 (多張)? 使用CMultiFileUpload+getInstances

跟 yii framework - 如何上傳照片 (單張)? 使用CUploadedFile 一樣
要實現多張照片上傳功能, 一樣要在model、conrtoller、view三管齊下才能完成,
我會逐一說明 (以M->V->C的順序).

本次實作的目標:
一次將多張照片上傳至指定資料夾, 並將照片的檔名存到資料庫 (用,分隔每個檔名), 以便之後取用.



Model
在你的models裡的rules()新增以下code:
array('dbFieldsOfTheImage', 'file', 'types'=>'jpeg, jpg, png', 'allowEmpty'=>true),

1. 請將這邊的dbFieldsOfTheImage改為你照片上傳的資料表欄位名稱
2. types指的是, 指定的上傳檔案類型 (如果你要讓人上傳的是excel, 那就打上xls, xlsx...etc)

PS:還有許多參數可做設定, 例如檔案大小限制、錯誤訊息, 可參考 (CFileValidator)


重要:
網路上很多範例都會少了allowEmpty這個參數, 就像這篇 Multiple files uploader with CMultiFileUpload.

結果就會非常慘, 就是會發生檔案上傳了, 但是填寫表格的資料一直存不進資料庫. 然後會出現錯誤:XXX cannot be blank, 然後你就會匪夷所思, 明明檔案都上傳了, 檔名也都抓到了, 為什麼就是存不進去.

原來因為我們在model裡設定它是file, 所以它上傳的是檔案, 而不是一個字串 (檔名), 所以我們必須要設allowEmpty這個參數, 告訴model我們允許它是空的, 這樣在save的時候才不會被model的規則給擋住!!!

這部分我卡了好幾天, 真的很重要阿~ 各位.


View
一樣是改_form.php這個檔案 (框架預設), 修改以下的code:
<?php $form=$this->beginWidget('CActiveForm', array(
'id'=>'bnb-form',
'enableClientValidation'=>true,
'htmlOptions' => array('enctype' => 'multipart/form-data'),
)); ?>

<?php
$this->widget('CMultiFileUpload', array(
'model' => $model,
'attribute' => 'bnbImg',
'accept' => 'jpeg|jpg|png',
'duplicate' => '你選過這個照片了喔!',
'denied' => '請選擇jpeg、jpg、png格式的影像!',
 'htmlOptions' => array('multiple' => 'multiple'),
'max' => 5,
));
?>

上傳檔案最重要的就是htmlOptions這個參數裡面的設定一定要是'enctype' => 'multipart/form-data'.

至於'enableClientValidation'=>true, 這個非常好用! 它會依你的model的規則設定來生成對應的JavaScript來做Client端的驗證, 超屌. 不過跟上傳檔案沒關係.

1. 
attribute會生成HTML input的id跟name. 例如: 我資料表叫Test, 欄位是fuck, 這時候我設定'attribute' => 'fuck', 就會產生以下code:

<input id="Test_fuck" type="file" value="" name="Test[fuck][]" />

2. 
accept - 允許的檔案格式
duplicate - 當選到重複的照片時跳出的警告視窗
denied - 當夾帶不是我們規定的檔案格式時, 跳出的警告視窗
'htmlOptions' => array('multiple' => 'multiple') - 可讓你一次選多張
max - 最多可上傳的檔案數量

別懷疑, 做了這些設定, yii就幫你把它生成JavaScript, 讓你爽的死去活來的!!

重要:
有些人會在CActiveForm裡做AJAX, 所以會加上這個 'enableAjaxValidation'=>false,
在表格有檔案上傳的欄位時千萬不要加那個! 因為它會讓你出錯, 以下擷取官方部分內容:
基於AJAX的驗證有一些限制:首先,它不能用於文件上傳字段;其次,它不能用於可能產生服務器端狀態改變的驗證;第三,它目前還不能用於表格式輸入的驗證。

Conrtoller
public function actionCreate()
{
$model = new Bnb;

if(isset($_POST['Bnb']))
{
$model->attributes=$_POST['Bnb'];
/*
$uploadPath Set info:
Linux: /images/bnb/ , Win: \images\bnb\
*/
$uploadPath = DIRECTORY_SEPARATOR . 'images' . DIRECTORY_SEPARATOR . 'bnb' . DIRECTORY_SEPARATOR;

if($allFilesName=$this->uploadMultiFile($model, 'bnbImg', $uploadPath))
$model->bnbImg = implode(",", $allFilesName);

if($model->save())
$this->redirect(array('view', 'id'=>$model->bnbId));
}

$this->render('create',array(
'model'=>$model,
));

}

private function uploadMultiFile($model, $attr, $path)
{
if($fileInfo = CUploadedFile::getInstances($model, $attr))
{
foreach ($fileInfo as $i=>$file)
{
$formatName = $file->name;
$file->saveAs(Yii::getPathOfAlias('webroot') . DIRECTORY_SEPARATOR . $path . $formatName); //webroot: you'r project/protected
$allFilesName[$i] = $formatName;
}
return ($allFilesName);
}
 }

1. 用 DIRECTORY_SEPARATOR 是因為路徑在Linux跟windows不一樣的, 所以為了都能正常運做, 就用了比較長的寫法. (參考 DIRECTORY_SEPARATOR的作用)
2. 將上傳的code單獨寫成uploadMultiFile是為了讓整個可讀性更高, 避免程式絮亂, 以方便維護.


Finish~ thx GOD


參考資料:CMultiFileUpload

2014年7月5日 星期六

yii framework - 如何用AJAX來做表單驗證 (client端)

yii真的太猛了!

上次 如何用AJAX來做表單驗證 用的方式是在server端做驗證, 所以用的是

'enableAjaxValidation'=>true,

不過事實上我們很多時候, 是(只)需要用到client端驗證, 那怎麼辦呢?
很簡單! 加上下面的code就可以了!

'enableClientValidation'=>true,
'clientOptions'=>array(
'validateOnSubmit'=>true,
),

這樣yii就會用jQuery來作client端的驗證了~! 並且沒全部通過還不讓使用者送出喔^^

真的超屌的!!

所以如果有些地方需要很嚴謹的檢查的話, 就可以兩個都加上去, 這樣就可以在server及client端做驗證

yii framework - 如何用AJAX來做表單驗證 (server端)

在我們使用gii來建立我們的CURD後, 我們可以在Controller最下面看到這個function

protected function performAjaxValidation($model)
{
if(isset($_POST['ajax']) && $_POST['ajax']==='house-form')
{
echo CActiveForm::validate($model);
Yii::app()->end();
}
}

原來~ yii已經幫我們建立好AJAX的驗證code了!
那我們來實作吧!


STEP 1
在Contorller裡需要用到AJAX的action加上這句:
$this->performAjaxValidation($model);

結果就會像這樣:

public function actionCreate()
{
$model=new Bnb;
$this->performAjaxValidation($model);

if(isset($_POST['Bnb']))
{
$model->attributes=$_POST['House'];
if($model->save())
$this->redirect(array('view', 'id'=>$model->id));
}
$this->render('create',array(
'model'=>$model,
));

}


STEP 2
view裡的_form.php打開AJAX服務

<?php $form=$this->beginWidget('CActiveForm', array(
'id'=>'house-form',
'enableAjaxValidation'=>true,
)); ?>



NOTE:
驗證的相關規則是在models裡喔! 

ex:
public function rules()
{
return array(
array('adminId, adminPw, 'required'),
array('adminMail', 'email'),
);
}

yii內建的驗證功能如下:(CValidator)

required
驗證屬性值必需有值,不能為空
fil​​ter
用過濾器轉換屬性的值
match
驗證屬性值匹配一個正則表達式
email
驗證屬性值為有一個有效的Email地址
url
驗證屬性值是一個有效的URL
unique
驗證屬性值在表中的對應列中是唯一的
compare
驗證屬性值與另一個屬性的值相等
length
驗證屬性值的長度在一個範圍內
in
驗證屬性值在一個預定義列表中
numerical
驗證屬性值是數字
captcha
驗證屬性的值等於一個顯示的CAPTCHA(驗證碼)的值
file
驗證屬性值包含上傳的文件
type
驗證屬性值是一個指定的數據類型
default
驗證屬性值為分配的默認值
exist
驗證屬性值在表中的對應列中存在
boolean
驗證屬性值是布爾值(true或false)
safe
標記屬性值為安全
unsafe
標記屬性值為不安全
date
驗證屬性值是日期


2014年7月4日 星期五

yii framework - 如何上傳照片 (單張)? 使用CUploadedFile

要實現照片上傳功能, 需在model、conrtoller、view三個地方逐一修改才能完成,
我會逐一說明 (以M->V->C的順序).

本次實作的目標:
將照片上傳至指定資料夾, 並將照片的檔名存到資料庫, 以便之後取用.


Model
在你的models裡的rules()新增以下code:

array('dbFieldsOfTheImage', 'file', 'types'=>'jpg, gif, png'),

1. 請將這邊的dbFieldsOfTheImage改為你照片上傳的資料表欄位名稱
2. types指的是, 指定的上傳檔案類型 (如果你要讓人上傳的是excel, 那就打上xls, xlsx...etc)

PS:還有許多參數可做設定, 例如檔案大小限制、錯誤訊息, 可參考 (CFileValidator)


View
看看你的controller新增、修改對應的view, 會發現表單的實體是在_form.php這個檔案 (框架預設),
進到_form.php後修改以下code:

<?php $form=$this->beginWidget('CActiveForm', array(
'id'=>'bnb-form',
'enableAjaxValidation'=>false,
'htmlOptions' => array('enctype' => 'multipart/form-data'), <-新增這行
)); ?>

然後在新增這段, 才會有上傳的按鈕出現
<div class="row">
<?php echo $form->labelEx($model,'dbFieldsOfTheImage'); ?>
<?php echo $form->fileField($model,'dbFieldsOfTheImage'); ?>
<?php echo $form->error($model,'dbFieldsOfTheImage'); ?>
</div>

注意第二個的fileField, 在yii的框架預設下是textField (文字框), 要改成fileField.


Conrtoller
public function actionCreate()
{
$model=new House;

if(isset($_POST['House']))
{
$model->attributes=$_POST['House'];
$imageUploadFile = CUploadedFile::getInstance($model,'houseImg');
$fileName = $imageUploadFile->name;
$model->bnbImg = $fileName;
if($model->save())
$file_path = Yii::app()->basePath.'/../images/house/' . $fileName;
$imageUploadFile->saveAs($file_path);
$this->redirect(array('view', 'id'=>$model->bnbId));
}

$this->render('create',array(
'model'=>$model,
));
}

1. 使用CUploadedFile::getInstance為一個模型屬性生成一個文件輸入框
2. $imageUploadFile->name可以取得檔名 (連同副檔名)
3. saveAs是將上傳的照片從上傳的暫存區移到指定的目錄 (記得要目錄+檔名喔!)


PS:
1. Yii::app()->basePath是取得網站的protected資料夾位置, 如果你的網站建置時取名www, 那就會返回 - 網站在主機的路徑/www/protected. 所以如果你的照片位置在網站根目錄下的images, 那就要用".."返回到上一層, 並進到images資料夾. ex: Yii::app()->basePath.'/../images/

其實用 Yii::getPathOfAlias('webroot')就可以直接取得網站專案名/protected的路徑 - 2014.7.6 補充

2. 測試後發現上傳的資料夾要先建立好, 要不然會出錯 (如果有需要可以自己寫一個建資料夾的程式)