Cakephp กับ Form Validation ด้วย Javascript
ผมได้ออกแบบฐานข้อมูลชื่อ db_cakephp และสร้างเทเบิล products กับ categories ดังรูป

มาดูโค๊ดกันเลยครับ
1.สร้างไฟล์ ValidationHelper.php ไว้ใน View/Helper ให้ใช้โค๊ดดังนี้
<?php
/*
* CakePHP jQuery Validation Plugin
* Copyright (c) 2009 Matt Curry
* www.PseudoCoder.com
* http://github.com/mcurry/cakephp_plugin_validation
* http://sandbox2.pseudocoder.com/demo/validation
*
* @author mattc <matt@pseudocoder.com>
* @license MIT
*
*/
//feel free to replace these or overwrite in your bootstrap.php
if (!defined('VALID_EMAIL_JS')) {
define('VALID_EMAIL_JS', '/^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/');
}
//I know the octals should be capped at 255
if (!defined('VALID_IP_JS')) {
define('VALID_IP_JS', '/^[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}$/');
}
class ValidationHelper extends Helper {
var $helpers = array('Javascript');
//For security reasons you may not want to include all possible validations.
//In your bootstrap you can define which are allowed
//Configure::write('javascriptValidationWhitelist', array('alphaNumeric', 'minLength'));
var $whitelist = false;
public function bind($modelNames, $options=array()) {
$defaultOptions = array('form' => 'form', 'inline' => true, 'root' => Router::url('/'), 'watch' => array(), 'catch' => true);
$options = am($defaultOptions, $options);
$pluginOptions = array_intersect_key($options, array('messageId' => true, 'root' => true, 'watch' => true));
//load the whitelist
$this->whitelist = Configure::read('javascriptValidationWhitelist');
$validation = array();
if (!is_array($modelNames)) {
$modelNames = array($modelNames);
}
//filter the rules to those that can be handled with JavaScript
foreach($modelNames as $modelName) {
$model = classRegistry::init($modelName);
$arr=explode('.',$modelName);
$realModelName=$arr[0];
foreach ($model->validate as $field => $validators) {
if (array_intersect(array('rule', 'allowEmpty', 'on', 'message', 'last'), array_keys($validators))) {
$validators = array($validators);
}
foreach($validators as $key => $validator) {
$rule = null;
if (!is_array($validator)) {
$validator = array('rule' => $validator);
}
//skip rules that are applied only on created or updates
if (!empty($validator['on'])) {
continue;
}
if (!isset($validator['message'])) {
$message = sprintf(__($key, true), __($field, true));
if($key != $message) {
$validator['message'] = $message;
} else {
$validator['message'] = sprintf('%s %s',
__('There was a problem with the field', true),
Inflector::humanize($field)
);
}
}
if (!empty($validator['rule'])) {
$rule = $this->__convertRule($validator['rule']);
}
if ($rule) {
$temp = array('rule' => $rule,
'message' => __($validator['message'], true)
);
if (isset($validator['last']) && $validator['last'] === true) {
$temp['last'] = true;
}
if (isset($validator['allowEmpty']) && $validator['allowEmpty'] === true) {
$temp['allowEmpty'] = true;
}
if (in_array($validator['rule'], array('blank'))) {
//Cake Validation::_check returning true is actually false for blank
//add a "!" so that JavaScript knows
$temp['negate'] = true;
}
$validation[$realModelName . Inflector::camelize($field)][] = $temp;
}
}
}
if(!empty($pluginOptions['watch'])) {
$pluginOptions['watch'] = $this->__fixWatch($modelName, $pluginOptions['watch']);
}
}
if ($options['form']) {
if($options['catch']) {
$js = sprintf('$(function() { $("%s").validate(%s, %s) });',
$options['form'],
$this->Javascript->object($validation),
$this->Javascript->object($pluginOptions));
} else {
$js = sprintf('$(function() { $("%s").data("validation", %s) });',
$options['form'],
$this->Javascript->object($validation));
}
} else {
return $this->Javascript->object($validation);
}
if ($options['inline']) {
return sprintf($this->Javascript->tags['javascriptblock'], $js);
} else {
$this->Javascript->codeBlock($js, array('inline' => false));
}
return;
}
public function __convertRule($rule) {
$regex = false;
if ($rule == '_extract') {
return false;
}
$params = array();
if (is_array($rule)) {
$params = array_slice($rule, 1);
$rule = $rule[0];
}
if (is_array($this->whitelist) && !in_array($rule, $this->whitelist)) {
return false;
}
if ($rule == 'comparison') {
$params[0] = str_replace(array(' ', "\t", "\n", "\r", "\0", "\x0B"), '', strtolower($params[0]));
switch ($params[0]) {
case 'isgreater':
$params[0] = '>';
break;
case 'isless':
$params[0] = '<';
break;
case 'greaterorequal':
$params[0] = '>=';
break;
case 'lessorequal':
$params[0] = '<=';
break;
case 'equalto':
$params[0] = '==';
break;
case 'notequal':
$params[0] = '!=';
break;
}
}
//Certain Cake built-in validations can be handled with regular expressions,
//but aren't on the Cake side.
switch ($rule) {
case 'alphaNumeric':
return '/^[0-9A-Za-z]+$/';
case 'between':
return sprintf('/^.{%d,%d}$/', $params[0], $params[1]);
case 'boolean':
return array('rule' => 'boolean');
case 'date':
//Some of Cake's date regexs aren't JavaScript compatible. Skip for now
if (!empty($params[0])) {
$params = $params[0];
} else {
$params = 'ymd';
}
return array('rule' => 'date', 'params' => $params);
case 'email':
return VALID_EMAIL_JS;
case 'equalTo':
return sprintf('/^%s$/', $params[0]);
case 'extension':
if (empty($params[0])) {
$params = array('gif', 'jpeg', 'png', 'jpg');
} else {
$params = $params[0];
}
return sprintf('/\.(%s)$/', implode('|', $params));
case 'inList':
return array('rule' => 'inList', 'params' => $params[0]);
case 'ip':
return VALID_IP_JS;
case 'minLength':
return sprintf('/^[\s\S]{%d,}$/', $params[0]);
case 'maxLength':
return sprintf('/^[\s\S]{0,%d}$/', $params[0]);
case 'money':
//The Cake regex for money was giving me issues, even within PHP. Skip for now
return array('rule' => 'money');
case 'multiple':
$defaults = array('in' => null, 'max' => null, 'min' => null);
$params = array_merge($defaults, $params[0]);
return array('rule' => 'multiple', 'params' => $params);
case 'notEmpty':
return array('rule' => 'notEmpty');
case 'numeric':
//Cake uses PHP's is_numeric function, which actually accepts a varied input
//(both +0123.45e6 and 0xFF are valid) then what is allowed in this regular expression.
//99% of people using this validation probably want to restrict to just numbers in standard
//decimal notation. Feel free to alter or delete.
return '/^[+-]?[0-9|.]+$/';
case 'range':
case 'comparison':
//Don't think there is a way to do this with a regular expressions,
//so we'll handle this with plain old javascript
return array('rule' => $rule, 'params' => array($params[0], $params[1]));
}
//try to lookup the regular expression from
//CakePHP's built-in validation rules
$Validation =& Validation::getInstance();
if (method_exists($Validation, $rule)) {
$Validation->regex = null;
call_user_func_array(array(&$Validation, $rule), array_merge(array(true), $params));
if ($Validation->regex) {
$regex = $Validation->regex;
}
}
if ($regex) {
//special handling
switch ($rule) {
case 'postal':
case 'ssn':
//I'm not a regex guru and I have no idea what "\\A\\b" and "\\b\\z" do.
//Is it just to match start and end of line? Why not use
//"^" and "$" then? Eitherway they don't work in JavaScript.
return str_replace(array('\\A\\b', '\\b\\z'), array('^', '$'), $regex);
}
return $regex;
}
// If not rule is selected handle with a regular expression
return($rule);
}
public function __fixWatch($modelName, $fields) {
foreach($fields as $i => $field) {
if (strpos($field, '.') !== false) {
list($model, $field) = explode('.', $field);
$fields[$i] = ucfirst($model) . ucfirst($field);
} else {
$fields[$i] = $modelName . ucfirst($field);
}
}
return $fields;
}
}
?>
2.สร้างไฟล์ validation.js ใน webroot/js ให้ใช้โค๊ดดังนี้
/*
* CakePHP jQuery Validation Plugin
* Copyright (c) 2009 Matt Curry
* www.PseudoCoder.com
* http://github.com/mcurry/cakephp_plugin_validation
* http://sandbox2.pseudocoder.com/demo/validation
*
* @author mattc <matt@pseudocoder.com>
* @license MIT
*
*/
(function($) {
$.fn.validate = function(rules, opts) {
options = $.extend({watch: []}, opts);
$.each(options.watch,
function(fieldId) {
$("#" + options.watch[fieldId]).change(function() {
$.fn.validate.ajaxField($(this));
});
});
return this.each(function() {
$this = $(this);
$this.submit(function() {
return $.fn.validate.check(rules);
});
});
};
$.fn.validate.check = function(rules) {
var errors = [];
var val = null;
$.fn.validate.beforeFilter();
$.each(rules,
function(field) {
$field = $("#" + field);
if ($field.attr("type") == "checkbox") {
if ($field.filter(":checked").length > 0) {
val = $field.filter(":checked").val();
} else {
val = "0";
}
} else {
val = $field.val();
}
var fieldName = $field.attr('name');
if (typeof val == "string") {
val = $.trim(val);
}
$.each(this,
function() {
//field doesn't exist...skip
if ($("#" + field).attr("id") == undefined) {
return true;
}
if (this['allowEmpty'] && typeof val == "string" && val == '') {
return true;
}
if (this['allowEmpty'] && typeof val == "object" && val == null) {
return true;
}
if (!$.fn.validate.validateRule(val, this['rule'], this['negate'], fieldName)) {
errors.push(this['message']);
$.fn.validate.setError(field, this['message']);
if (this['last'] === true) {
return false;
}
}
});
});
$.fn.validate.afterFilter(errors);
if (errors.length > 0) {
return false;
}
return true;
}
$.fn.validate.validateRule = function(val, rule, negate, fieldName) {
if (negate == undefined) {
negate = false;
}
//handle custom functions
if (typeof rule == 'object') {
if ($.fn.validate[rule.rule] != undefined) {
return $.fn.validate[rule.rule](val, rule.params, fieldName);
} else {
return true;
}
}
//handle regex rules
if (negate && val.match(eval(rule))) {
return false;
} else if (!negate && !val.match(eval(rule))) {
return false;
}
return true;
};
$.fn.validate.boolean = function(val) {
return $.fn.validate.inList(val, [0, 1, '0', '1', true, false]);
};
$.fn.validate.comparison = function(val, params) {
if (val == "") {
return false;
}
val = Number(val);
if (val == "NaN") {
return false;
}
if (eval(val + params[0] + params[1])) {
return true;
}
return false;
};
$.fn.validate.inList = function(val, params) {
if (params != null) {
if ($.inArray(val, params) == -1) {
return false;
}
}
return true;
};
$.fn.validate.multiple = function(val, params) {
if (typeof val != "object" || val == null) {
return false;
}
if (params.min != null && val.length < params.min) {
return false;
}
if (params.max != null && val.length > params.max) {
return false;
}
if (params["in"] != null) {
for (i = 0; i < params["in"].length; i++) {
if ($.inArray(params["in"][i], val) == -1) {
return false;
}
}
}
return true;
};
$.fn.validate.notEmpty = function(val, params) {
if (typeof val == "string" && val == "") {
return false;
}
if (typeof val == "object" && val.length == 0) {
return false;
}
return true;
}
$.fn.validate.range = function(val, params) {
if (val < parseInt(params[0])) {
return false;
}
if (val > parseInt(params[1])) {
return false;
}
return true;
};
$.fn.validate.ajaxField = function($field) {
$.fn.validate.clearError($field);
$.fn.validate.ajaxBeforeFilter($field);
var data = new Object;
data[$field.attr("name")] = $field.val();
$.post(options.root + "js_validate/field/" + $field.attr("id"), data,
function(validates) {
$.fn.validate.ajaxAfterFilter($("#" + validates.field));
if (!validates.result) {
$.fn.validate.setError(validates.field, validates.message);
}
},
"json");
}
$.fn.validate.ajaxBeforeFilter = function($field) {
$field.after("<img class=\"ajax-loader\" src=\"" + options.root + "js_validate/img/ajax-loader.gif\">");
}
$.fn.validate.ajaxAfterFilter = function($field) {
$field.siblings(".ajax-loader").remove();
}
$.fn.validate.clearError = function($field) {
if (typeof $field == "string") {
$field = $("#" + field);
}
$field.removeClass("form-error").parents("div:first").removeClass("error").children(".error-message").remove();
}
$.fn.validate.setError = function(field, message) {
$("#" + field).addClass("form-error").parents("div:first").addClass("error").append('<div class="error-message">' + message + '</div>');
};
$.fn.validate.beforeFilter = function() {
if (options.messageId != null) {
$("#" + options.messageId).html("").slideDown();
}
$(".error-message").remove();
$("input").removeClass("form-error");
$("div").removeClass("error")
};
$.fn.validate.afterFilter = function(errors) {
if (options.messageId != null) {
$("#" + options.messageId).html(errors.join("<br />")).slideDown();
}
};
var options = [];
})(jQuery);
แค่นี้เราก็ได้ Validation Javascript ไว้ใช้งานแล้วครับ ต่อมาก็เป็นวิธีใช้ครับ
3.สร้างไฟล์ ProductsController.php ในโฟลเดอร์ Controller ให้พิมพ์โค๊ดดังนี้
<?php
class ProductsController extends AppController {
public $name = 'Products';
public $helpers = array('Html', 'Form', 'Validation'); //เรียกใช้ Helpers ให้ทำงานในส่วนของ View
public $uses = array('Product', 'Category'); //เรียกใช้ Model Product,Category
public function add() {
$this->set('title_for_layout', 'เพิ่มรายการสินค้า');
if ($this->request->is('post')) {//ตรวจว่ามีการส่งค่าแบบ post เข้ามา
$data = $this->data['Product'];
if ($this->Product->save($data)) {//Validationและบันทึกข้อมูลและReturnค่ากลับมาในคำสั่งนี้
//บันทึกข้อมูลเรียบร้อยแล้ว
}
}
$categories = $this->Category->getCategory(); //ไปดึงข้อมูลหมวดสินค้าจากเมธอด getCategoy() ใน Class Category จากไฟล์ Category.php
$this->set('category', $categories);
}
}
?>
4.ในส่วนของ Model ให้สร้างไฟล์ Product.php ไว้ในโฟลเดอร์ Model ครับ และพิมพ์โค๊ดดังนี้
<?php
class Product extends AppModel {
public $name = 'Product';
var $validate = array(
'name' => array(
'notEmpty' => array(
'rule' => 'notEmpty',
'required' => true,
'message' => 'กรุณากรอกชื่อสินค้า'
),
'between' => array(
'rule' => array('minLength', 5),
'message' => 'กรุณาระบุชือสินค้าไม่ต่ำกว่า 5 ตัวอักษร'
)
),
'category_id' => array(
'rule' => 'notEmpty',
'message' => 'กรุณาเลือกประเภทสินค้า'
),
'quantity' => array(
'rule' => 'numeric',
'message' => 'กรุณาระบุจำนวนเป็นตัวเลข'
),
'price' => array(
'rule' => 'numeric',
'message' => 'กรุณาระบุราคาเป็นตัวเลข'
),
'image' => array(
'rule' => 'notEmpty',
'message' => 'กรุณาเลือกรูปสินค้า'
)
);
}
?>
5.Model อีกตัวนึงคือ Category เพื่อสำหรับไว้แสดงหมวดสินค้า ให้สร้างไฟล์ชื่อ Category.php ไว้ในโฟลเดอร์ Model และให้ใช้โค๊ดังนี้
<?php
class Category extends AppModel{
public $name='Category';
public $validate =array(
'name'=>array(
'notEmpty'=>array(
'rule'=>'notEmpty','message'=>'กรอกชือหมวดสินค้าด้วยครับ'
)
)
);
public function getCategory(){
return $this->find('list',array('fields'=>array('Category.id','Category.name'),'order'=>'Category.name ASC')
);
}
}
?>
6.สร้างไฟล์ add.php ใน View/Products/ ครับ และให้พิมพ์โค๊ดดังนี้
<?php
echo $this->Html->script('validation');//เรียกใช้ validation.js
echo $this->Html->script('ckeditor/ckeditor');//เรียกใช้ ckeditor/ckeditor.js
echo $this->Form->create('Product', array('action' => 'add'));//สร้างฟอร์ม
echo $this->Validation->bind('Product');//เรียกใช้ Validation จาก Model "Product"
?>
<table width="780" border="0" align="center" cellpadding="4" cellspacing="0">
<tr>
<td colspan="2" align="center" ><h2>เพิ่มรายการสินค้า</h2></td>
</tr>
<tr>
<td align="right"><strong>ชื่อสินค้า</strong></td>
<td><?php echo $this->Form->input('name', array('label' => false, 'style' => 'width:450px')); ?></td>
</tr>
<tr>
<td align="right"><strong>ประเภทสินค้า</strong></td>
<td><?php echo $this->Form->input('category_id', array('label' => false, 'type' => 'select', 'empty' => 'เลือกประเภท', 'options' => $category)); ?></td>
</tr>
<tr>
<td align="right"><strong>จำนวน</strong></td>
<td><?php echo $this->Form->input('quantity', array('label' => false, 'style' => 'width:80px')); ?></td>
</tr>
<tr>
<td align="right"><strong>ราคา</strong></td>
<td><?php echo $this->Form->input('price', array('label' => false, 'style' => 'width:100px')); ?></td>
</tr>
<tr>
<td align="right"><strong>เลือกรูปสินค้า</strong></td>
<td><?php echo $this->Form->input('image', array('label' => false, 'type' => 'file')); ?></td>
</tr>
<tr>
<td align="right"><strong>รายละเอียด</strong></td>
<td><?php
echo $this->Form->textarea('detail', array('label' => false, 'cols' => '70', 'rows' => '20'));
echo $this->Ckeditor->loadcustom('Product.detail'); //วิธีเลือกใช้ ชือโมเดล.ชื่อฟิลด์
?></td>
</tr>
<tr>
<td width="150"> </td>
<td><?php echo $this->Form->submit('บันทึกข้อมูล'); ?> </td>
</tr>
</table>
<?php echo $this->Form->end(); ?>
จากนั้นให้ลองรันทดสอบ โดยพิมพ์ว่า http://localhost/cakephp/products/add
ความคิดเห็น
แสดงความคิดเห็น