CodeIgniterの学習 73 - 標準のValidation機能を拡張し、Javascriptとphpの両方でバリデーションチェックしてみるその2(Form_validation化対応コード、暫定版)
こんにちは!
今日は以前のエントリー
http://d.hatena.ne.jp/dix3/20081013/1223862786 -
( CodeIgniterの学習 21 - 標準のValidation機能を拡張し、Javascriptとphpの両方でバリデーションチェックしてみる(ClientServer Validation の改造) )
で俺俺改造した、ClientServer Validation の Form_validation 化版コードを貼っておく。
説明しだすと長いので、今日は画面ダンプと、ライブラリーのコードと、ポイントのみ。
使用例とかは次回のエントリーにでも。
以前のエントリー記述時のCodeIgniter(1.6系)では、Form_validationライブラリは存在せず、Validationライブラリのみが存在していた。
ValidationライブラリはCI 1.7.1でも存在するが、Validationライブラリは過去のものとなり、Form_validationの使用が推奨されていたはず。
以前の俺のエントリーも過去のものに化しているので、俺俺改造ソースもForm_validation上で何となく動くように更新。
画面
こんな感じ。POST前にjavascriptで必須チェック等を実行する。
簡易バリデーションチェックをクライアント側で、厳密なバリデーションチェックをサーバ側で行う二段構え。
コード
こんな感じ。一応動くけどテストが足りない。故に暫定版。
但し、<input name='hoge[]' /> みたいに配列で渡したときに動くかは未テスト。
テストが足りないコードを貼るなよというツッコミには、お好きにいじくり回して改造してください。・改良したらまた貼るよ。という事で逃げておく。
ちなみに元ネタは、http://codeigniter.com/wiki/ClientServer_Validation/ です。かなり変わっているけど。
ライブラリ:application/libraries/MY_Form_validation.php
<?php if ( !defined( 'BASEPATH' ) ) exit( 'No direct script access allowed' ); // 日本語 wikiから // ------------------------------------------------------------------------ /** * Validation Class extension * * @package CodeIgniter * @subpackage Libraries * @category Validation * @author AK * @link http://codeigniter.com/user_guide/libraries/validation.html */ /** * modified : http://d.hatena.ne.jp/dix3 at 2008/10/13 * link : http://d.hatena.ne.jp/dix3/20081013/1223862786 */ /** * modified : http://d.hatena.ne.jp/dix3 at 2009/09/12 * link : http://d.hatena.ne.jp/dix3/20090912 */ class MY_Form_validation extends CI_Form_validation { private $_tmp_field_data = array(); private $_defaultbgcolor = '#fff'; //input領域の背景色 private $_alertbgcolor = '#ffbbcc'; //アラートの背景色 public function __construct() { parent :: __construct(); } public function set_rules( $field, $label = '', $rules = '' ) { // If an array was passed via the first parameter instead of indidual string // values we cycle through it and recursively call this function. if ( is_array( $field ) ) { foreach ( $field as $row ) { // Houston, we have a problem... if ( ! isset( $row['field'] ) OR ! isset( $row['rules'] ) ) { continue; } // If the field label wasn't passed we use the field name $label = ( ! isset( $row['label'] ) ) ? $row['field'] : $row['label']; // Here we go! $this -> set_rules( $row['field'], $label, $row['rules'] ); } return; } // No fields? Nothing to do... if ( ! is_string( $field ) OR ! is_string( $rules ) OR $field == '' ) { return; } // If the field label wasn't passed we use the field name $label = ( $label == '' ) ? $field : $label; // Is the field name an array? We test for the existence of a bracket "[" in // the field name to determine this. If it is an array, we break it apart // into its components so that we can fetch the corresponding POST data later if ( strpos( $field, '[' ) !== FALSE AND preg_match_all( '/\[(.*?)\]/', $field, $matches ) ) { // Note: Due to a bug in current() that affects some versions // of PHP we can not pass function call directly into it $x = explode( '[', $field ); $indexes[] = current( $x ); for ( $i = 0; $i < count( $matches['0'] ); $i++ ) { if ( $matches['1'][$i] != '' ) { $indexes[] = $matches['1'][$i]; } } $is_array = TRUE; }else { $indexes = array(); $is_array = FALSE; } // Build our master array $this -> _tmp_field_data[$field] = array( 'field' => $field, 'label' => $label, 'rules' => $rules, 'is_array' => $is_array, 'keys' => $indexes, 'postdata' => NULL, 'error' => '' ); parent :: set_rules( $field, $label, $rules); } /** * JavaScript * * This function provides default implementation of the built-in CI validation rules in javascript. * The function generates a client-side js script, complete with <script>...</script> html tags, * suitable for inclusion in the document header. Additionally, custom rules can be added by * defining global js functions, or extending Validation js object. * * @access public * @param string $ - custom error message (optional) * @param string $ - name of a js error callback function (optional) * @return string - js */ public function javascript( $alert_msg = '', $alert_func = 'null' ) { if ( !$this -> _tmp_field_data ) { return $this -> _get_js_null_validation_run(); } // client-side javascript implementation of CI built-in validation rules. // default alert message if ( $alert_msg == '' ) { $alert_msg = addslashes( 'Please fix the following errors:' ); } // Load the language file containing error messages $this -> CI -> lang -> load( 'form_validation' ); $params = null; foreach ( $this -> _tmp_field_data as $field => $v ) { // Explode out the rules! $ex = explode( '|', $v['rules'] ); $messages = array(); foreach ( $ex as $rule ) { $param = false; if ( preg_match( "/(.*?)\[(.*?)\]/", $rule, $match ) ) { $rule = $match[1]; $param = $match[2]; } if ( ! isset( $this -> _error_messages[$rule] ) ) { if ( false === ( $line = $this -> CI -> lang -> line( $rule ) ) ) { // エラー文言が見つからないものはとりあえずメッセージは空白にしておく // $line = 'Unable to access an error message corresponding to your field name.'; //$line = ''; // エラー文言が見つからないものは処理素通り continue; } }else { $line = $this -> _error_messages[$rule]; } // Build the error message $mparam = isset( $v['field'] ) ? $v['field'] : $field ; $mfield = isset( $v['label'] ) ? $v['label'] : $v['label']; $messages[] = sprintf( $line, $mfield, $mparam ); } if(!$messages){ continue; } $params[] = '{input:"' . $field . '",rule:"' . $v['rules'] . '",name:"' . ( isset( $v['field'] ) ? addslashes( $v['field'] ) : $field ) . '",msg:"' . join( ' ', $messages ) . '"}'; } $join_params = join( ",\n\t\t", $params ) ; return $this -> _get_js_validation_run( $this -> _defaultbgcolor, $this -> _alertbgcolor, $alert_msg, $join_params, $alert_func ); } // ------------------------------------------ private function _get_js_null_validation_run() { $script = <<<___END___ <script type="text/javascript"> // <![CDATA[ var validation_run = function(f) {} // ]]> </script> ___END___; return $script; } // ------------------------------------------ private function _get_js_validation_run( $defaultbgcolor, $alertbgcolor, $alert_msg, $join_params, $alert_func ) { $script = <<<___END___ <script type="text/javascript"> // <![CDATA[ String.prototype.trim = function() { return this.replace(/^\s\s*/, "").replace(/\s\s*$/, ""); } var Validator = function(f) { this.form = f; } Validator.prototype.required = function(str) { return str.search(/\S/) > -1; } Validator.prototype.matches = function(str, field) { return (str == this.form.elements[field].value); } // FIX! change "field" from input name to input ref? Validator.prototype.max_length = function(str, val) { return (str.length <= val); } Validator.prototype.min_length = function(str, val) { return (str.length >= val); } Validator.prototype.exact_length = function(str, val) { return (str.length == val); } Validator.prototype.valid_email = function(str) { return str.search(/^([\w\+\-]+)(\.[\w\+\-]+)*@([a-z\d\-]+\.)+[a-z]{2,6}$/i) > -1; } Validator.prototype.valid_ip = function(ip) { var segments = ip.split("."); for (var i in segs) if(segs[i].length>3 || segs[i]>255 || segs[i].search(/\D/)>-1) return false; return true; } Validator.prototype.alpha = function(str) { return str.search(/[^a-z]/i) == -1; } Validator.prototype.alpha_numeric = function(str) { return str.search(/[^a-z0-9]/i) == -1; } Validator.prototype.alpha_dash = function(str) { return str.search(/[^\w-]/i) == -1; } Validator.prototype.numeric = function(str) { return ! isNaN(str); } Validator.prototype.integer = function(str) { return ! (isNaN(str) || str.indexOf(".") > -1); } Validator.prototype.valid_base64 = function(str) { return str.search(/[^a-zA-Z0-9\/\+=]/) == -1; } Validator.prototype.validate = function (rules, callback) { try { if (!rules.length) return true; var res, errors=[]; var disperr = false;//個別エラー表示用フラグ追加 var focus_flg = false; for (var i in rules) { var item = rules[i]; var field = this.form.elements[item.input]; var rule_list = item.rule.split("|"); var required = false;//必須かどうかのフラグ for (var r in rule_list) { required = (item.rule.search(/required/) == -1) ? required : true; if(rule_list[r].search(/trim/)){ //trimの時は画面の値をまずtrimして判定する field.value = field.value.replace(/^\s\s*/, "").replace(/\s\s*$/, ""); } //任意項目かどうかのフラグ追加 var re = /(callback_|validate_)?(\w+)(?:\[(.+)\])?/i.exec(rule_list[r]); var func = re[2]; if (!this[func]) { //try { func = eval(func); } catch (e2) { } //javascript側で未知のファンクション(xss_clean 等があるときは、クライアントサイド側では無視する。 //サーバーサイド側でもう一度走るのであまり問題ないはず。 try{ func = eval(func); res = (typeof(func) == "function") ? func(field.value, re[3]) : false; }catch(e2){} } else { res = this[func](field.value, re[3]); } //任意項目で且つ空白の時はエラー文言は表示しない if( (false == required) && ( "" ==field.value) && ( res == false ) ){ res = true; } } var obj_input = document.getElementById( item.input ); var obj = document.getElementById( item.input + "_error" ); if (!res && item.msg) { if(obj){//もし id="input項目_error" が存在しているならば、そこに個別にエラー表示する obj.innerHTML = item.msg ; if(obj_input){//もし入力項目に同名のidが振っているならば、背景色をピンク色にする obj_input.style.background="{$alertbgcolor}"; if(!focus_flg){ obj_input.focus(); focus_flg = true; } } disperr = true; errors.push([item.msg, item.input]); }else{ //元ソースではこっち errors.push([item.msg, item.input]); } }else{ if(obj){ obj.innerHTML = "" ; } if(obj_input){ obj_input.style.background="{$defaultbgcolor}"; } } } } catch (e) { alert(e); return false; } if (errors.length) { // show errors if(disperr){//個別エラー表示時 //display_alert(errors);//画面上部にも同時にエラーを出したい時 return callback ? callback(errors) : false ; }else{ return callback ? callback(errors) : display_alert(errors); } } return true; } var display_alert = function(errors) { var str = ''; var obj = document.getElementById('error_string'); //もしid="error_string" が存在しているときはこのinnerHTML上に出す、それ以外はアラートで出す if(obj){ for (var i in errors){ str += '<br>'+ errors[i][0]; obj.style.display = 'block'; obj.innerHTML = '{$alert_msg}' + str ; } }else{ for (var i in errors){ str += "\\n- " + errors[i][0]; window.alert('{$alert_msg}' + str); } } return false; } var validation_run = function(f) { var rules = [{$join_params}]; return new Validator(f).validate(rules,{$alert_func}); } // ]]> </script> ___END___; return $script; # 元々はこっち // default implementation of the validation action // Original display_alert /** * $script .= ' * function display_alert(errors) { * var str = ""; * for (var i in errors) * str += "\n- " + errors[i][0]; * alert("' . addslashes( $alert_msg ) . '" + str); * return false; * } * '; */ } } //endofclass /** * End of file MY_Form_validation.php */ /** * Location: ./application/libraries/MY_Form_validation.php */ ?>
使い方
1)適当なコントローラとかでform_validationライブラリをloadした後、
2)コントローラ内等で
<?php //上略 // 編集画面のバリデーション $this -> form_validation -> set_rules( 'tmp_name' , 'テンプレート名', 'trim|required|xss_clean' ); $this -> form_validation -> set_rules( 'tmp_body' , 'テンプレート', 'trim|required' ); $this -> form_validation -> set_rules( 'memo' , 'メモ', 'trim|xss_clean' ); //下略 ?>
みたいにルールの設定をする。(ココまではマニュアル通り。)
このフォームバリデーションの設定に従って、javascript側の簡易バリデーションチェック用ソースを作ってくれるわけです。
3)上のルールを設定した後にビュー側に、
<?php //上略 $data['validation_js'] = $this -> form_validation -> javascript() ; //下略 ?>
とかで、今回のライブラリ拡張で作られるjavascriptを生成して渡してあげる。
4)ビューのhead部分に、
<?php if(isset($validation_js)){echo $validation_js ;} ?>
とかで3)で生成したjavascriptを貼りつける。
5)ビュー側のsubmitボタンを、
<input type="submit" onclick="javascript:try{if(!validation_run(this.form)){return false;}}catch(e){window.alert(e);return false;}" name="submit" value="保存" />
みたいに、onclick時に、validation_run(this.form)
を呼んで、falseならPOSTしないようにする。
6)ビュー側の各inputタグ周りを、
<label>テンプレート名<em>(必須)</em></label> <span id="tmp_name_error"><?=form_error('tmp_name')?></span> <input type="text" name="tmp_name" id="tmp_name" value="<?=set_value('tmp_name',$tmp_name) ?>" size="60" />
みたいにする。
エラー表示文字列部分を、
とidを付けて囲い、
id="hogehoge_error"
inputタグ部分を、
とname,idを付けるところがキモ。
type="text" name="hogehoge" id="hogehoge"
あとは、Form_validationのマニュアル通り、
を使う。
今日はここまで。
いろいろやらなくてはならないことが沢山あって忙しいです。
OpenCartをCodeIgniterに移植して、日本の商慣習に合わせたいとか、やりたいことも色々。