YUI2.7.0のEditable Table(編集可能な表)で編集したデータをサーバーに送信する(その2)
前回のログでは、YUI2.7.0のEditable Table(編集可能な表)について、更新・削除時に(DataTableオブジェクト内に)一時保管されるデータを、配列に蓄積するスクリプトを書いた。
今回は、それを発展させて、その内容をサーバーに送るコードを書いてみる。この際、
- 配列に蓄積されたデータは、Ajax(XHR)によって非同期、かつ、連続的におくる
ことにする。
これらの一連の仕様では、「データの送信に問題があった場合」の扱いが問題になる。連続的にAjaxでデータを送ると、回線状況やサーバーの状況によっては、送れたデータと送れなかったデータが混在してしまう可能性がある。DataTable内に蓄積されるデータは、一時的なものであるので、ブラウザーをリフレッシュしてしまうと、編集したデータをロストしてしまう。
したがって、回線やネットワーク機器が不安定な場合や帯域が圧迫されている場合、Httpサーバーの負荷が高い場合などでは、方針を変更して、「更新や削除が発生するたびにサーバーに送る」といった仕様の方がいいだろう。
今回は、上記のような制約は仮定せず、「編集データを蓄積して、ボタンで確定(=永続化)させる」という方針であるが、以下の方針をとる。
- データの送信に失敗した場合、そのデータを廃棄せず、再度の送信をエラーメッセージによって促す。
実際の利用局面では、この仕様では不十分かもしれない。ケースバイケース(利用環境やデータの性質など)で
- 自動的に再送を行う(…これはよくないだろうなぁ)
- エラーになったデータを保管する仕組みを用意する
といったことを考慮すればいいと思う。
前置きがながくなったが、以下が初期画面のスナップショット。
これを、以下に示す「POST/GETされたデータを、表形式にしてクライアントに送る」PHPプログラムに私、表の下に表示することとした。このPHPプログラムは、以前のログで使用したものと同じである。
<?php /* クライアントからのajax送信を受け取るサンプル author ; t.odaka date ; 2009/4/22 */ require("../Myznala/debugLog.php"); $m_log->debug('test_setPostForm start'); // Debugログ 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"); $m_log->debug($key.'; '.$value); // Debugログ $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); // 入力データのサニタイズを行います // function sanitize($var,$encoding){ $ret = htmlentities($var,ENT_QUOTES,$encoding); return $ret; } ?>
また、Ajaxのエラー発生時には、(これも)表下に表示することとした。以下は
- 1つのセル(areacode="01")を変更
- Submitボタンを押し、サーバーに送信
- もう1つのセル(areacode="03")を変更
- HTTPサーバーの停止(Ajaxを失敗させるため)
- Submitボタンを押し、サーバーに送信(エラーとなる)
- サーバーを起動
- 再度、Submitボタンを押し、サーバーに送信
とした場合の結果である。エラー後の再送信がうまくいっている。
以下が、HTMLの全文である。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <meta http-equiv="content-type" content="text/html; charset=utf-8"> <title>DataTable Validate</title> <style type="text/css"> </style> <link rel="stylesheet" type="text/css" href="../scripts/lib/yui/build/fonts/fonts-min.css" /> <link rel="stylesheet" type="text/css" href="../scripts/lib/yui/build/paginator/assets/skins/sam/paginator.css" /> <link rel="stylesheet" type="text/css" href="../scripts/lib/yui/build/datatable/assets/skins/sam/datatable.css" /> <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/animation/animation.js"></script> <script type="text/javascript" src="../scripts/lib/yui/build/element/element.js"></script> <script type="text/javascript" src="../scripts/lib/yui/build/paginator/paginator-min.js"></script> <script type="text/javascript" src="../scripts/lib/yui/build/datasource/datasource-min.js"></script> <script type="text/javascript" src="../scripts/lib/yui/build/datatable/datatable-min.js"></script> <script type="text/javascript" src="../scripts/lib/yui/build/connection/connection-min.js" ></script> <!--// MyValidatorの読み込み --> <script type="text/javascript" src="../scripts/myznala.js"> </script> <style type="text/css" id="defaultstyle"> #main { margin: 2px; padding: 3px; } .ez_error { /* red(エラー用) */ color:#ff0000; } .actdel img { /* 都道府県に付ける「×(行削除)」イメージのスタイル */ border:0; margin-bottom: -3px; } </style> <script type="text/javascript" src="data.js"></script> <script type="text/javascript"> EzTable = function() { var myDataSource; var myDataTable; var Dom = YAHOO.util.Dom; var Event = YAHOO.util.Event; var Connect = YAHOO.util.Connect; // MyValidatorオブジェクト var valObj; // 選択された行を保管 var wData; // 削除された行と行数を保管 var delRow; var delNum; // 更新データ保管用 var columnName; var uValue; // データ転送確認用 var eValue; /* * 都道府県に「×(行削除)」のマークを付ける * (注)カラム定義より前のこと */ var ezInitFormatter = function(elCell, oRecord, oColumn, oData) { elCell.innerHTML = oData + ' ' + ' ' + '<a class="actdel" href="#">' + '<img class="actdel" src="../images/action_delete.png">' + '</a>'; }; /* * メモ2のバリデーション */ var validateNotes2 = function(oData){ // エラーメッセージのクリア clearErrorMsg(); // MyValidatorをつかって数値チェックをする。 var _ret = valObj.validate('ja','isNum',oData); if(_ret['isNum']!=null &&_ret['isNum'].length > 0){ Dom.get('ez_error_res').innerHTML = _ret['isNum']; return undefined; } /* * 以下、相関チェック */ // 数値化 var _numData = 1 * oData; if("notes1" in wData){ var _savData = 1 * wData["notes1"]; }else{ var _savData = 0; } // 相関チェック if(_savData >= _numData){ // エラーの場合、undefindを返せばいい。 Dom.get('ez_error_res').innerHTML = 'メモ2>メモ1でなければなりません。'; return undefined; } return oData; }; /* * クリックイベントで行データを退避する * 「×」マークがクリックされたら行を削除する。 */ var onRowClick = function(oArgs){ // 選ばれた行のデータ var _rowData = this._oAnchorRecord._oData; // 行データを退避 wData = Array(); for(var i in _rowData){ wData[i]=_rowData[i]; } // 選ばれた行の取得 var _rowIdx = this._oAnchorRecord._nCount; // 行を削除する関数 // (注) 削除するとmyDataTable.deleteRow()のインデックスがずれるので補正する。 var _corr = 0; for(var i=0; i<delRow.length; i++){ if(delRow[i]<_rowIdx) _corr++; } var _wk = _rowIdx-_corr; if(onClickDelete(oArgs, _wk)){ // 削除した行の保管 delRow[delNum++] = _rowIdx; // 保管 uValue[wData['areacode']] = ['d',wData]; //debug用 var wk = uValue; } return; }; /* * クリック時に、選択された行を削除する * 削除したらtrue * 削除しなかったらfalse * を返却します。 */ var onClickDelete = function(oArgs, _rowIdx){ // イベントの発生が画面のクリックか判定する。 var _target = Event.getTarget(oArgs.event); var _targetClassName = getClassName(_target); var _idx = _targetClassName.indexOf('actdel',0) if(_idx != -1){ Event.preventDefault(oArgs.event); // oArgsから行を取り出して削除する。 myDataTable.deleteRow(_rowIdx); return true; } return false; }; /* * ダブルクリックイベントでエラーメッセージを消す */ var clearErrorMsg = function(){ Dom.get('ez_error_res').innerHTML = ''; return; }; /* * 編集&(表への一時的)セーブ時にuValueへの保管を行う * (注)値が設定されていない列のデータはoArgsに入らない。 */ var onSave = function(oArgs){ var _data = 'レコード '; // 初期化 var _u = new Array(); for(var i in columnName){ _u[columnName[i]] = ''; } for(var key in oArgs.editor._oRecord._oData){ _u[key] = String(oArgs.editor._oRecord._oData[key]); } var _id = oArgs.editor._oRecord._oData['areacode']; uValue[_id] = ['u',_u]; //debug用 var wk = uValue; return; } /** * ボタンがクリックされたときのハンドラー */ var onButtonClickHdlr = function(_evt,_obj){ /* * サーバーに送る */ // 転送終了確認用フィールドにuValueを退避 uValue.sort(); eValue = uValue; // パラメータの設定 var _url = "test_SetPostForm.php"; for(var i in uValue){ var _parm = 'o='+uValue[i][0]; for(var j in uValue[i][1]){ _parm += '&' + j + '=' + uValue[i][1][j]; } // ajaxの引数 var _arg ={ 'resId': 'result', 'key' : i }; ajaxCallback.argument = _arg; // サーバーにajaxで送信する YAHOO.util.Connect.asyncRequest('POST',_url, ajaxCallback, _parm); } } /* * Ajaxハンドラー * */ var ajaxHandlers = { // 受信成功時の処理 responseSuccess: function(_oj){ var _resId = _oj.argument.resId; // 結果の表示 if(Dom.get(_resId)==null){ Dom.get(_resId).innerHTML = _oj.responseText; }else{ Dom.get(_resId).innerHTML += '<br>' + _oj.responseText; } // 成功したデータを削除 for(var i=0; i < eValue.length; i++){ if(i == _oj.argument.key){ eValue.splice(i,1); } } // 転送が全て終わったらuValueをリフレッシュする。 if(eValue.length == 0){ uValue = new Array(); eValue = new Array(); } }, // 受信失敗時の処理 responseFailure: function(_oj){ var _resId = _oj.argument.resId; var _ret = '<br><span style="color:red;">エラー</span> ステータス: ' + _oj.status + '、ステータステキスト: ' + _oj.statusText + '<br> areacode: ' + _oj.argument.key + 'の送信に失敗しました。<br>' + '<span style="color:red;">システム管理者に連絡してください。' + '<strong>画面をリフレッシュすると編集データが失われます</stong></span><br>'; // alert(_ret); // 結果の表示 if(Dom.get(_resId)==null){ Dom.get(_resId).innerHTML = _ret; }else{ Dom.get(_resId).innerHTML += '<br>' + _ret; } } }; /* * コールバック成功/失敗時の振り分け * */ var ajaxCallback = { success: ajaxHandlers.responseSuccess, failure: ajaxHandlers.responseFailure, cache: false, scope: ajaxHandlers, argument: null }; /***************************************************** * 汎用関数 *****************************************************/ /** * クラス名の取得 */ 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; }; return { /** * 初期処理 */ init: function() { // DataTable用:列定義 var myColumnDefs = [ { key:"areacode", label:"コード", width:50, resizeable:true, sortable:true}, { key:"state", label:"都道府県", width:150, formatter:ezInitFormatter, // 上で定義したフォーマッターを適用する resizeable:true, sortable:true}, { key:"notes1", label:"メモ1 (編集可:数値)", editor:new YAHOO.widget.TextboxCellEditor( { validator:YAHOO.widget.DataTable.validateNumber, defaultValue:0 } ), resizeable:true, sortable:true}, { key:"notes2", label:"メモ 2(編集可:数値)", editor:new YAHOO.widget.TextboxCellEditor( { validator:validateNotes2, defaultValue:0 } ), resizeable:true, sortable:true} ]; // DataTable用:コンフィグ属性 var myConfigs = { sortedBy:{key:"areacode",dir:"asc"}, paginator: new YAHOO.widget.Paginator({ rowsPerPage: 10, template: YAHOO.widget.Paginator.TEMPLATE_ROWS_PER_PAGE, rowsPerPageOptions: [10,25,50,100], pageLinks: 5 }), caption:"都道府県" }; // DataSourceのインスタンス化 myDataSource = new YAHOO.util.DataSource(Data.areacodes); myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSARRAY; myDataSource.responseSchema = { fields: ["areacode","state"] }; // DataTableのインスタンス化 myDataTable = new YAHOO.widget.DataTable("table1", myColumnDefs, myDataSource, myConfigs); /* * 行の選択補助。 */ // クリックでハイライトするようにハンドラを設定 myDataTable.subscribe("rowClickEvent", myDataTable.onEventSelectRow); myDataTable.subscribe("rowMouseoverEvent", myDataTable.onEventHighlightRow); myDataTable.subscribe("rowMouseoutEvent", myDataTable.onEventUnhighlightRow); // クリック時にデータを退避する。 myDataTable.subscribe("rowClickEvent", onRowClick); /* * セルの処理 */ // ダブルクリックでエラーメッセージを消す。 myDataTable.subscribe("cellDblclickEvent", clearErrorMsg); // ダブルクリックでセルのエディターを呼ぶ。 myDataTable.subscribe("cellDblclickEvent", myDataTable.onEventShowCellEditor); // 編集&(表への一時的)セーブ時にuValueへの保管を行う myDataTable.subscribe("editorSaveEvent", onSave); /* * MyValidatorの初期化 */ valObj = new MyValidator(); /* * 変数の初期化 */ delRow = new Array(); delNum = 0; // 列のキーを保管する uValue = new Array(); columnName = new Array(); for(var i=0; i< myColumnDefs.length; i++){ columnName[i] = myColumnDefs[i].key; } // ボタンにハンドラーを仕掛ける。 Event.addListener('button1', 'click', onButtonClickHdlr, Dom.get('button1')); }, // initの終わり oDS: myDataSource, oDT: myDataTable }; }(); </script> </head> <body class=" yui-skin-sam" onload="EzTable.init()"> test_datatable_validate7 <br> <div id="main"> <p> メモ1とメモ2はダブルクリックで変更できます。「メモ1<メモ2」という相関チェックを行います。<br> 都道府県名の右の<img src="../images/action_delete.png" style="margin-bottom:-3px;">をクリックすると、行を削除します。<br> Submitボタンで、表の更新・削除の履歴をサーバーに送信し、結果を表下に表示します。 </p> <div id="ez_error_res" class="ez_error"></div> <div id="table1"></div> <form action="#"> <input id="button1" type="button" value="submit"> </form> <div id="result"></div> </div> </body> </html>