超簡単にフォーム入力&サブミットしたい;その2

前回の戦略に沿って、コードを書いてみた。

サンプルの初期画面は以下。
戦略通りに、テキスト入力フィールドには、グレーで入力ガイドを書いている。


以下のスクリーンショットは、入力途中でバリデーションがかかったところ。
入力して、次の入力フィールドに移動した(フォーカスが外れた)時にバリデーションがかかる。


以下のスクリーンショットは、必須入力のチェックボックスを入れずにサブミットしたところ。
チェックボックスラジオボタンの未入力検証は、サブミット時に行う。(同時に、テキストの未入力チェックも行う)


サブミットがうまくいったら、入力値を画面下方に表示してみた(サーバーサイドでクロスサイトスクリプティング対策をしてから表示する)。


フォーム入力&検証&サブミットをできるだけ簡単にしたい、のがコンセプト。
以下のような作りにしてみた。

○type=textとpasswordについて

  1. input要素のクラス属性に、ez_initguide(入力フィールドの初期設定)とmy_isXxxx(Xxxは検証のタイプを指定。たとえば、my_isReruiredなど)を指定する。XxxとMyValidator.class.phpのキーが対応する。
  2. 検証タイプは複数指定可能にする。
  3. input要素のid属性値に対して、「id属性値_res」を用意し、ここにエラーを吐き出す。

○type=checkboxとradioについて

  1. input要素をdiv要素で囲んで、検証用のグループを作る。
  2. 上のdiv要素のclass属性にmy_isRequiredを指定。
  3. 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 += ' &nbsp ' +_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;
	}

?>