超簡単にフォーム入力&サブミットしたい;その2
前回の戦略に沿って、コードを書いてみた。
サンプルの初期画面は以下。
戦略通りに、テキスト入力フィールドには、グレーで入力ガイドを書いている。
以下のスクリーンショットは、入力途中でバリデーションがかかったところ。
入力して、次の入力フィールドに移動した(フォーカスが外れた)時にバリデーションがかかる。
以下のスクリーンショットは、必須入力のチェックボックスを入れずにサブミットしたところ。
チェックボックスとラジオボタンの未入力検証は、サブミット時に行う。(同時に、テキストの未入力チェックも行う)
サブミットがうまくいったら、入力値を画面下方に表示してみた(サーバーサイドでクロスサイトスクリプティング対策をしてから表示する)。
フォーム入力&検証&サブミットをできるだけ簡単にしたい、のがコンセプト。
以下のような作りにしてみた。
○type=textとpasswordについて
- input要素のクラス属性に、ez_initguide(入力フィールドの初期設定)とmy_isXxxx(Xxxは検証のタイプを指定。たとえば、my_isReruiredなど)を指定する。XxxとMyValidator.class.phpのキーが対応する。
- 検証タイプは複数指定可能にする。
- input要素のid属性値に対して、「id属性値_res」を用意し、ここにエラーを吐き出す。
○type=checkboxとradioについて
- input要素をdiv要素で囲んで、検証用のグループを作る。
- 上のdiv要素のclass属性にmy_isRequiredを指定。
- div要素のid属性値に対して、「id属性値_res」を用意し、ここにエラーを吐き出す。
上のサンプルの場合、BODY部は以下のようになっている。
<BODY onload="MyValidate.init()"> <h3> バリデーション+サブミットの確認用 </h3> <!--// scriptの初期処理のエラーはここに書き出す。 --> <div id="ez_init_res" class="ez_error"></div> <form id="testForm" method="post" action='#'> <!--// 入力検証項目 --> 未入力のチェック; <!--// id属性値(xxx)と、エラーメッセージ用id(xxx_res)で自動的に対応を付ける チェックするクラス属性は、my_isXxxx(Xxxは検証のタイプを指定。たとえば、my_isReruiredなど)。 複数指定可。 同時に、ez_initguideを加えること。 --> <input id="v_required" class="ez_initguide my_isRequired" name="input1" type="text" size=50 value="入力してください." /> <div id="v_required_res" class="ez_error"></div> <br> アルファベットのチェック; <input id="v_alpha" class="ez_initguide my_isRequired my_isAlpha" name="input2" type="text" size=50 value="アルファベットを入力してください." /> <div id="v_alpha_res" class="ez_error"></div> <br> ひらがなのチェック; <input id="v_hiragana" class="ez_initguide my_isHiragana" name="input3" type="text" size=50 value="ひらがなを入力してください." /> <div id="v_hiragana_res" class="ez_error"></div> <br> <!--// chekboxとradioのグループを作り、未入力検証ができる。この際、inputをdivで括る。 class属性値(my_isRequired)を入力すること。 id属性値(xxx)と、エラーメッセージ用id(xxx_res)で自動的に対応を付ける --> <div id="checkbox1" class="my_isRequired"> 未入力のチェック(checkbox);<br> <input id="checkbox1_1" type="checkbox" name="check1_1" value="1">1_1 <br> <input id="checkbox1_2" type="checkbox" name="check1_2" value="2">1_2 </div> <div id="checkbox1_res" class="ez_error"></div> <br> 未入力のチェックしない(checkbox);<br> <input id="checkbox2_1" type="checkbox" name="check2_1" value="1">2_1 <br> <input id="checkbox2_2" type="checkbox" name="check2_2" value="2">2_2 <br> <br> <div id="radio1" class="my_isRequired"> 未入力のチェック(radio);<br> <input id="radio1_1" type="radio" name="radio1" value="1">1_1 <br> <input id="radio1_2" type="radio" name="radio1" value="2">1_2 </div> <div id="radio1_res" class="ez_error"></div> <br> 未入力のチェックしない(radio);<br> <input id="radio2_1" type="radio" name="radio2" value="1">2_1 <br> <input id="radio2_2" type="radio" name="radio2" value="2">2_2 <br> <br> <input id="button1" type="button" value="submit"> </form> <div id="result"></div> <br> </body>
以下にサンプルのHTML全文を示す。BODY内は上記と同じである。
MyValidator.class.phpは、(一応)国際化メッセージを想定しているので、初期処理にロケールの取得(phpソースはこちら)を行っている。
冗長な感が残っているが、まぁいいことにしよう。
<HTML> <HEAD> <META http-equiv="Content-Type" content="text/html; charset=UTF-8"> <META http-equiv="Content-Style-Type" content="text/css"> <TITLE>MyValidate</TITLE> <style type="text/css"> input.ez_initguide { /* gray(変えないで!! スクリプトが動かなくなるよ!!) */ color:#808080; } .ez_error { /* red(エラー用) */ color:#ff0000; } </style> <!-- 読み込むjs --> <script type="text/javascript" src="../scripts/lib/yui/build/yahoo/yahoo-min.js"> </script> <script type="text/javascript" src="../scripts/lib/yui/build/yahoo-dom-event/yahoo-dom-event.js"> </script> <script type="text/javascript" src="../scripts/lib/yui/build/connection/connection-min.js" > </script> <script type="text/javascript"> /****************************************************************** * 入力検証 * (モジュールパターンで実装) *****************************************************************/ MyValidate = function() { var locale; var Dom = YAHOO.util.Dom; var Event = YAHOO.util.Event; var Connect = YAHOO.util.Connect; /** * Input(type=text,password)にフォーカスが当たったときのハンドラー */ var onFocusHdlr = function(_evt,_obj) { // 初期値(入力ガイド)の場合だけ、入力値を消す。 // Dom.getStyleColor()は // rgb(128,128,128); mozilla, chrome, // #808080; IE7 // と異なった戻り値となる。 // _objは,HTMLInputElement; _e = YAHOO.util.Event.getTarget(_evt) var _id = _e.id; var _resId = _id + '_res'; var _fColor=Dom.getStyle(_id, 'color'); if( _fColor == 'rgb(128, 128, 128)' || _fColor == '#808080'){ // for IE Dom.get(_id).value=""; } // 入力フィールドの色を白にする。 Dom.setStyle(_id, 'background-color', 'white'); // 入力フィールドの文字の色をグレーから黒に変える Dom.setStyle(_id, 'color', 'black'); // メッセージフィールドを消す Dom.get(_resId).innerHTML=""; }; /** * Input(type=text,password)からフォーカスが外れたときのハンドラー */ var onBlurHdlr = function(_evt,_obj){ _e = YAHOO.util.Event.getTarget(_evt) var _id = _e.id; var _resId = _id + '_res'; // 検証キーをclass属性から取得. var _keys = getClassName(Dom.get(_id)); //内部関数を呼ぶ // 検証キーを取り出す var _keyArray = _keys.split(" "); // 検証対象を取得. var _str = Dom.get(_id).value; // サーバーで検証 var _url = "test_validate.php" for(var i = 0; i < _keyArray.length; i++){ // class名の判別(my_isXxxxのみを選別する) var _idx = _keyArray[i].toLowerCase().indexOf('my_is',0); if(_idx != -1){ // リクエストパラメータのセット var _opt = _keyArray[i].replace(/^(my_)/,''); // my_をトリムする。 // Validator.class.phpの仕様にしたがって、パラメータを設定する。 var _parm = 'opt='+_opt+'&'+'val='+_str; // 戻ってきたときのために、エレメントのidをセットする。 var _arg ={ 'id' : _id, 'resId' : _resId }; ajaxCallback.argument = _arg; // ajaxで検証 Connect.asyncRequest('POST',_url, ajaxCallback, _parm); } } }; /* * input(type=text,password)のonblur時のAjaxハンドラー * */ var ajaxHandlers = { // 受信成功時の処理 responseSuccess: function(_oj){ //alert("responseSuccess"); var _id = _oj.argument.id; var _resId = _oj.argument.resId; // データの取得 var _ret = _oj.responseText; if(_ret.length>0){ // エラーのハンドル(皆さん適当に) // 入力フィールドの色をピンクにする。 Dom.setStyle(_id, 'background-color', 'pink'); // エラーが複数のときは複数行にして表示する。 if(Dom.get(_resId).innerHTML.length>0){ Dom.get(_resId).innerHTML += '   ' +_ret; }else{ Dom.get(_resId).innerHTML = _ret; } } }, // 受信失敗時の処理 responseFailure: function(_oj){ alert("responseFailure"); var _id = _oj.argument.id; var _resId = _oj.argument.resId; var _ret = 'ステータス: ' + oj.status + 'ステータステキスト: ' + oj.statusText + '読み込みに失敗しました。'; // エラーのハンドル(皆さん適当に) // 入力フィールドの色をピンクにする。 Dom.setStyle(_id, 'background-color', 'pink'); Dom.get(_resId).innerHTML = _ret; } }; /* * input(type=text,password)のonblur時のコールバック成功/失敗時の振り分け * */ var ajaxCallback = { success: ajaxHandlers.responseSuccess, failure: ajaxHandlers.responseFailure, cache: false, scope: ajaxHandlers, argument: null }; /** * ボタンがクリックされたときのハンドラー * checkbox, radioの未入力チェックを行う。 * */ var onClickHdlr = function(_evt,_obj){ /* * テキストフィールドとパスワードの未入力確認 */ // HTMLInputElementの配列を再取得する。 var _inObj = document.getElementsByTagName('input'); for(var i = 0; i < _inObj.length; i++){ if(_inObj[i].getAttribute('type') == 'text' || _inObj[i].getAttribute('type') == 'password'){ // 検証対象のみ if(isValidate(_inObj[i])){ //if(Event.getListeners(_inObj[i],'blur')){ // IEは使えない。 var _id = _inObj[i].id; var _resId = _id + '_res'; var _fColor=Dom.getStyle(_id, 'color'); // 入力行為をおこなった形跡がなければチェックする。 if( _fColor == 'rgb(128, 128, 128)' || _fColor == '#808080'){ // for IE var _errStr = 'required'; if(locale=='ja') _errStr = '入力必須です.' Dom.setStyle(_id, 'background-color', 'pink'); Dom.get(_resId).innerHTML = _errStr; } } } } /* * チェックボックスとラジオボタンの処理(必須入力チェック) */ var _divObj = document.getElementsByTagName('div'); for(var i = 0; i < _divObj.length; i++){ // 検証するnameをclass属性から取得. var _keys = getClassName(_divObj[i]); //内部関数を呼ぶ // 関係ないdivはとばす。 if(_keys){ // class名の判別(my_isRequiredのみを選別する) var _idx = _keys.toLowerCase().indexOf('my_isrequired',0); if(_idx > -1){ var _id = _divObj[i].id; var _resId = _id + '_res'; // チェック var _ret = isCheckRequired(_divObj[i]); if(!_ret){ // エラー var _errStr = 'required'; if(locale=='ja') _errStr = '入力必須です.' Dom.get(_resId).innerHTML = _errStr; }else{ Dom.get(_resId).innerHTML = ""; } } } } // エラーが無かったらフォームをサブミットする。 if(!_errStr) MySubmit.submit(); return; }; /** * チェックボックスとラジオボタンの未入力チェック処理 * */ var isCheckRequired = function(_obj){ // HTMLInputElementの配列を取得する。 var _inObj = _obj.getElementsByTagName('input'); for(var i = 0; i < _inObj.length; i++){ if(_inObj[i].checked) return true; } return false; } /***************************************************** * 汎用関数 *****************************************************/ /** * クラス名の取得 */ var getClassName = function(_obj){ if(document.all){ //for IE var _keys = _obj.getAttribute('className'); }else{ // for FF, Chrome, Safari var _keys = _obj.getAttribute('class'); } return _keys; }; /** * 検証対象のエレメントかを判断する。 * クラス名にmy_isXxxが含まれていたらtrueを返す。 * パラメータ ; HTMLInputElement */ var isValidate = function(_obj){ var _keys = getClassName(_obj); var _idx = _keys.toLowerCase().indexOf('my_is',0); if(_idx == -1) return false; return true; } /** * ロケールの取得 * リモートサーバーにリクエストを送り、ブラウザーロケールを取得する。 * dependencies; YUI2.7.0 */ var getLocale = function(){ // 戻ってきたときのために、エレメントのidをセットする。 var _resId = 'ez_init_res'; var _arg ={ 'resId' : _resId }; localeCallback.argument = _arg; Connect.asyncRequest('POST','getLocale.php', localeCallback, null); } /* * ロケール取得時のAjaxハンドラー * dependencies; YUI2.7.0 * */ var localeHandlers = { // 受信成功時の処理 responseSuccess: function(_oj){ // ロケールの取得 locale = _oj.responseText; }, // 受信失敗時の処理 responseFailure: function(_oj){ var _resId = _oj.argument.resId; var _ret = 'ステータス: ' + _oj.status + 'ステータステキスト: ' + _oj.statusText + '読み込みに失敗しました。'; // エラーのハンドル(皆さん適当に) Dom.get(_resId).innerHTML = _ret; } }; /* * ロケール取得時のコールバック成功/失敗時の振り分け * dependencies; YUI2.7.0 * */ var localeCallback = { success: localeHandlers.responseSuccess, failure: localeHandlers.responseFailure, cache: false, scope: localeHandlers, argument: null }; return { /** * 初期処理 */ init: function() { // ロケールを取得する。 getLocale(); // HTMLInputElementの配列を取得する。 var _inObj = document.getElementsByTagName('input'); for(var i = 0; i < _inObj.length; i++){ // 検証対象となるinput(type=text,password)に、focusとblurにイベントを仕掛ける。 if(_inObj[i].getAttribute('type') == 'text' || _inObj[i].getAttribute('type') == 'password'){ //検証対象かの判断 if(isValidate(_inObj[i])){ Event.addFocusListener(_inObj[i].id, onFocusHdlr, _inObj[i]); Event.addBlurListener(_inObj[i].id, onBlurHdlr, _inObj[i]); } } } // ボタンにハンドラーを仕掛ける。 Event.addListener('button1', 'click', onClickHdlr, Dom.get('button1')); } // initの終わり }; }(); /****************************************************************** * サブミット * (モジュールパターンで実装) *****************************************************************/ MySubmit = function() { var Dom = YAHOO.util.Dom; var Event = YAHOO.util.Event; var Connect = YAHOO.util.Connect; var ajaxHandlers = { // 受信成功時の処理 responseSuccess:function(_oj){ var _resId = _oj.argument.okId; // 取得したデータの表示 Dom.get(_resId).innerHTML=_oj.responseText; }, // 受信失敗時の処理 responseFailure:function(_oj){ var _resId = _oj.argument.ngId; var _ret = 'ステータス: ' + _oj.status + 'ステータステキスト: ' + _oj.statusText + '読み込みに失敗しました。'; // エラーのハンドル(皆さん適当に) Dom.get(_resId).innerHTML = _ret; } }; // コールバック成功/失敗時の振り分け var ajaxCallback = { success:ajaxHandlers.responseSuccess, failure:ajaxHandlers.responseFailure, cache: false, scope: ajaxHandlers }; return { submit: function() { // 結果表示用のパラメータを作成する。 var _okId = 'result'; var _ngId = 'ez_init_res'; var _arg ={ 'okId' : _okId, 'ngId' : _ngId }; ajaxCallback.argument = _arg; // フォームの内容を保存 Connect.setForm("testForm"); // POSTする var _url='test_setPostForm.php'; Connect.asyncRequest('POST',_url, ajaxCallback, null); } }; }(); </script> </HEAD> <BODY onload="MyValidate.init()"> <h3> バリデーション+サブミットの確認用 </h3> <!--// scriptの初期処理のエラーはここに書き出す。 --> <div id="ez_init_res" class="ez_error"></div> <form id="testForm" method="post" action='#'> <!--// 入力検証項目 --> 未入力のチェック; <!--// id属性値(xxx)と、エラーメッセージ用id(xxx_res)で自動的に対応を付ける チェックするクラス属性は、複数指定可。 同時に、ez_initguideを加えること。 --> <input id="v_required" class="ez_initguide my_isRequired" name="input1" type="text" size=50 value="入力してください." /> <div id="v_required_res" class="ez_error"></div> <br> アルファベットのチェック; <input id="v_alpha" class="ez_initguide my_isRequired my_isAlpha" name="input2" type="text" size=50 value="アルファベットを入力してください." /> <div id="v_alpha_res" class="ez_error"></div> <br> ひらがなのチェック; <input id="v_hiragana" class="ez_initguide my_isHiragana" name="input3" type="text" size=50 value="ひらがなを入力してください." /> <div id="v_hiragana_res" class="ez_error"></div> <br> <!--// chekboxとradioのグループを作り、未入力検証ができる。この際、inputをdivで括る。 class属性値(my_isRequired)を入力すること。 id属性値(xxx)と、エラーメッセージ用id(xxx_res)で自動的に対応を付ける --> <div id="checkbox1" class="my_isRequired"> 未入力のチェック(checkbox);<br> <input id="checkbox1_1" type="checkbox" name="check1_1" value="1">1_1 <br> <input id="checkbox1_2" type="checkbox" name="check1_2" value="2">1_2 </div> <div id="checkbox1_res" class="ez_error"></div> <br> 未入力のチェックしない(checkbox);<br> <input id="checkbox2_1" type="checkbox" name="check2_1" value="1">2_1 <br> <input id="checkbox2_2" type="checkbox" name="check2_2" value="2">2_2 <br> <br> <div id="radio1" class="my_isRequired"> 未入力のチェック(radio);<br> <input id="radio1_1" class="isRequired" type="radio" name="radio1" value="1">1_1 <br> <input id="radio1_2" class="isRequired" type="radio" name="radio1" value="2">1_2 </div> <div id="radio1_res" class="ez_error"></div> <br> 未入力のチェックしない(radio);<br> <input id="radio2_1" type="radio" name="radio2" value="1">2_1 <br> <input id="radio2_2" type="radio" name="radio2" value="2">2_2 <br> <br> <input id="button1" type="button" value="submit"> </form> <div id="result"></div> <br> </body> </HTML>
以下は、上のスクリプトで(Ajaxで)呼び出しているtest_setPostForm.php。
<?php /* クライアントからのajax送信を受け取るサンプル author ; t.odaka date ; 2009/4/22 */ switch($_SERVER['REQUEST_METHOD']) { case 'GET' : $rMethod = &$_GET; break; case 'POST' : $rMethod = &$_POST; break; default: } // パラメータをサニタイズして配列に入れる。 $reqParm; foreach ($rMethod as $key => $value) { $key =sanitize($key,"UTF-8"); $value =sanitize($value,"UTF-8"); $reqParm[$key]=$value; } // 戻すデータを作成する。 $ret='<table border="1"><tr><th>key</th><th>value</th></tr>'; foreach ($rMethod as $key => $value) { $ret.='<tr><td>'.$key.'</td><td>'.$value.'</td></tr>'; } $ret.='</table>'; // 出力 header("Content-Type:text/html"); echo($ret); return; // 入力データのサニタイズを行います // function sanitize($var,$encoding){ $ret = htmlentities($var,ENT_QUOTES,$encoding); return $ret; } ?>