jqGrid使用メモ
jqGridを使った際のメモを残しておきます。
ページャーのAddボタンの動作
2011-08-22:jqGrid 4.1.1で確認
$("#datagrid").jqGrid.("navGrid", "#pager", opt)でページャーを追加するときのoptで以下のようなオプションを追加できる模様。
名前 | 説明 | 参照 | 初期値 |
addfunc | Addボタンを押されたときの動作。通常のフォームエディットの代わりに、実行したいFunctionを設定可能。引数なし | grid.formedit.js:1654 | null |
Firefox4で$().jqGrid is not a functionエラー
2011-06-09:jqGrid 3.8.2および4.0.0+jQuery 1.5.1+Firefox 4.0.1で確認
2011-07-05追記 -- jqGrid 4.1.1で解決されてました (プラグインが一つのファイルにまとめられているので)
いろいろ調べてみたのですが、解決策を書いたページが見つからなかったのでメモ。
Firefox 3.6までは問題なく使えていたのに、Firefox 4にしたところ、以下のようなエラーが出ました。
$("#datagrid").jqGrid is not a function
これはjQueryの$()関数中でjqGridプラグインにアクセスした際に発生することがあるようです。
なかなか核心を突くサイトが見つからなかったのですが、Firefox 4のHTMLパーサに関して以下のような資料がありました。
- HTML5 Script Execution Changes in Firefox 4 -- http://hsivonen.iki.fi/script-execution/
実はSolutions部分ぐらいしか読んでないのですが 、Firefox4では<script>タグは並列して解析される、と(たぶん)。そのため、HTMLファイルのインラインJavaScript中、$()部分(jQueryのonloadですね)がjqGridの外部ファイルを読み込む前に実行されてしまっていると推測されます(どうやらHTML5の仕様らしいです)。
jqGridのjquery.jqGrid.jsの動作
jqGridがくせ者なのはjquery.jqGrid.jsの存在であり、これを<script>タグで読み込んだ場合、解釈され、必要なファイルが読み込まれますが、Firefox4ではこれが問題になるようです。つまり、丁寧に<script>タグでjs/grid.*.jsを読み込んでいる場合は問題が起こらない・・・はず(試してません)。たぶん以下の感じで解析されていくのかな?
- Firefox 3.x
- jquery.jqGrid.jsが読み込まれる
- js/grid.*.jsが読み込まれる(同期処理)
- jquery.jqGrid.jsのloadedイベント発生
- インラインの<script>の$()が実行される
- jquery.jqGrid.jsが読み込まれる
- Firefox 4
- jquery.jqGrid.jsが読み込まれる
- jquery.jqGrid.jsのloadedイベント発生
- インラインの<script>の$()が実行される
- js/grid.*.jsが読み込まれる(非同期処理のためタイミング不定)
Firefox4では、js/grid.*.jsを読み込む処理がブロックされないため、実際に読み込みが完了していないのにloadedイベントが発生してしまう状態になると推測されます。そのため、環境によってはエラーが再現しないという状況になるようです。
解決方法
これを解消するためには上のサイトのSolutionsの1つめの方法、document.write()を使います。document.write()を使うと以下のタイミングになるようです。HTML5 Script Execution Changes in Firefox 4によると以下のように解説されています。
If you want to add an external script from a script during the parse and you want the script to block subsequent scripts appearing in the network stream…
- Use document.write("\u003Cscript src='foo.js'>\u003C/script>");. This solution is cross-browser-compatible without sniffing. If you write multiple scripts at once, Firefox 4 downloads the scripts in parallel, so concern about parallel downloads is no longer a reason to avoid document.write in Firefox.
- Alternatively, you could use plain <script> tags in the HTML source transferred over HTTP. This way Firefox (and other browsers) that implement speculative script loading will start fetching the scripts even earlier and will load the scripts in parallel.
ここに示されているdocument.write()による方法を使うと以下のように処理されると考えられます。なお、ここでは<の代わりに\u003Cを使っていますが、私が確認したところ通常の<でも問題ないようでした。
- jquery.jqGrid.jsが読み込まれる
- document.write()でjs/grid.*.jsのタグ書き込み、解釈(同期処理になる)
- jquery.jqGrid.jsのloadedイベント発生
- インラインの$()が実行される
具体的にはjquery.jqGrid.js中の以下の部分を修正します(これは3.8.2ですが4.0.0でも同様の箇所があるはず)。
34 if (jQuery.browser.msie) {
35 document.write('<script charset="utf-8" type="text/javascript" src="'+filename+'"></script>');
36 } else {
37 IncludeJavaScript(filename);
38 }
この部分によるとFirefoxではfunction IncludeJavaScript()でDOMを使って外部ファイルを読み込むようにしていますが、IEではdocument.write()を使っています。そこで、Firefox4でもdocument.write()を使うように修正します。
34行目のif文を以下のように修正します。
34 if (jQuery.browser.msie || $.browser.mozilla && $.browser.version >= "2") {
Firefox 4.0.1では$.browserは{mozilla=true,version="2.0.1"}でした。そのため、この条件によりFirefox4でもdocument.write()が使われ、問題が解消しました。
わかりにくい説明ですが、試してみてください
jqGrid-3.8.2メモ
jqGridのAJAXコール
grid.base.jsの該当部分
1584 case "json": 1585 case "jsonp": 1586 case "xml": 1587 case "script": 1588 $.ajax($.extend({ 1589 url:ts.p.url, 1590 type:ts.p.mtype, 1591 dataType: dt , 1592 data: $.isFunction(ts.p.serializeGridData)? ts.p.serializeGridData.call(ts,ts.p.postData) : ts.p.postData, 1593 success:function(data,st) { 1594 if(dt === "xml") { addXmlData(data,ts.grid.bDiv,rcnt,npage>1,adjust); } 1595 else { addJSONData(data,ts.grid.bDiv,rcnt,npage>1,adjust); } 1596 if(lc) { lc.call(ts,data); } 1597 if (pvis) { ts.grid.populateVisible(); } 1598 if( ts.p.loadonce || ts.p.treeGrid) {ts.p.datatype = "local";} 1599 data=null; 1600 endReq(); 1601 }, 1602 error:function(xhr,st,err){ 1603 if($.isFunction(ts.p.loadError)) { ts.p.loadError.call(ts,xhr,st,err); } 1604 endReq(); 1605 xhr=null; 1606 }, 1607 beforeSend: function(xhr){ 1608 beginReq(); 1609 if($.isFunction(ts.p.loadBeforeSend)) { ts.p.loadBeforeSend.call(ts,xhr); } 1610 } 1611 },$.jgrid.ajaxOptions, ts.p.ajaxGridOptions)); 1612 break;
これを見るとjqGridのオプションとしてはurl, mtype, datatype, postData, serializeGridData, loadErrorが使われる。また$.extendでajaxOptions, ajaxGridOptionsが結合されるため、ajaxGridOptionで設定すれば任意のオプションを上書きすることができる(おそらく高度な使い方だろうが)。
処理
- リクエストが送信される前にbeforeSendハンドラがコールされ、jqGrid#loadBeforeSendが設定されていればbeforeSendハンドラ内でjqGrid#loadBeforeSend(xhr)がコールされる。
- 送られるデータはjqGrid#serializeGridDataが定義されていればjqGrid#serializeGridData(postData)の戻り値が送信される。定義されていなければjqGrid#postDataの内容が送信される。
1592 data: $.isFunction(ts.p.serializeGridData)? ts.p.serializeGridData.call(ts,ts.p.postData) : ts.p.postData,
- 成功時は通常のjqGridのsuccessハンドラ(1593行目)、エラー時はloadErrorが定義されていればloadError(xhr,textStatus,errorThrown)が、されていなければerrorハンドラ(1602行目)がそれぞれコールされる。
jqGridのデータ取得でJSON-RPC
ここでは、jqGridを使う際にJSON-RPCを用いる方法を模索していきます。JSON-RPC 2.0提案(proposal)に則ったメッセージパッシングを試してみます。
jqGridのデータ取得リクエスト
データ取得リクエストは最後にjqGrid#serializeGridData()がコールされるので、ここで与えられるデータをJSON-RPCリクエストのJSON文字列に変換します。
serializeGridData: function(postData) { var obj = GLOBAL.json.wrapInJSONRPCRequest("objects.select", ["User", {gid:1}]); return GLOBAL.json.objToJson(obj); },
ここでのGLOBAL.json.wrapInJSONRPCRequestは
wrapInJSONRPCRequest(string Method name, mixed Arguments)
となっていて、
{ jsonrpc: "2.0", method : "Method name", params : Arguments, id : #ID }
というオブジェクトを返します。#IDはGLOBAL.jsonで保持しているランダムなID番号。
返されたオブジェクトをJSON文字列に変換すればそれがjqGridにより送信されます。
{"jsonrpc":"2.0","method":"Method name","params":JSON of Arguments,id:#ID}
がURLエンコードされた文字列で送られます
応答時のエラー処理
フックする部分が見つからず、送信よりも応答時の処理の方が難しく感じました。ここで言うエラーとはInternal Server Error 500などのHTTPエラーではなく、あくまでJSON-RPCのエラーです。従って、HTTP通信は正常に完了しているというところに留意してください。そのため、AJAX処理内ではsuccessハンドラがコールされます。
JSON-RPCエラーオブジェクトの処理はjqGridのsuccessハンドラ内(1593-1601行目)で行うことがスマートだと思いますが、successハンドラ内ではフックされるメソッドがありません。そのため、jqGrid#ajaxGridOptionsでいっそのことsuccessハンドラを上書きするかjqGrid#jsonReaderを使った少々トリッキーな方法をとることにします。
jsonReaderはデータ構造を定義するパラメータで、関数としてコールされるのでその中に処理を書くことでJSON-RPCエラー処理を行うことができます。jsonReader.root()がrecordsの配列データを返さなければ(空配列やundefined、null)、データは追加されないことを利用します。例えば以下のようにします。
jsonReader: { repeatitems: false, id: "id", root: function(obj){ // obj["result"]がなければundefinedでデータは追加されない if(obj.result === undefined && obj.error){ alert(obj.error.message + "\n" + obj.error.data); return []; } return obj["result"]; }, page: function(obj){ return 1; }, total: function(obj){ return 1; }, records: function(obj) { if(obj.result === undefined && obj.error){ return 0; } return obj["result"].length; } }
rootの前にpage, total, recordsが処理されるのでこの段階でobject#resultを使うとundefinedエラーになるのでその辺りは適宜処理します。この例ではrecords()で0を返し、root()で空配列[]を返すようにしています。エラーの表示等が必要ならrecords, rootどちらで行っても問題ないようです。
インライン編集でのJSON-RPC
インライン編集の場合もグリッドデータ取得とほぼ同じような手順で行います。
インライン編集を行う際はjqGrid APIのeditRowを使用します。
editRow(rowId, keys, onEditFunc, successFunc, url, extraParam, afterSaveFunc, errorFunc, afterRestoreFunc)
レコードを選択したときに編集開始
$("#datagrid").jqGrid({ ... onSelectRow: function(id) { if(id && id !== GLOBAL.lastSel){ $("#datagrid").restoreRow(GLOBAL.lastSel); GLOBAL.lastSel = od; } $("#datagrid").jqGrid("editRow", id, true, false, rpcOnSuccess, "JSONRPCService"); }, ... });
ここのポイントはeditRowの第4引数のrpcOnSuccessです。JSON-RPCメッセージで応答が返ってくるので、後述するようにレスポンスを処理しなければなりません。そのためにrpcOnSuccessメソッドで処理します。また、RPCリクエストは後述のjqGrid#serializeRowDataプロパティで処理を行います。リクエスト送信先は第5引数urlで指定しますが、省略はできないようです。
なお、ここでは第2引数のkeysがtrueになっているのでEnterを押すことでリクエストが発行されます。
リクエスト
リクエスト時の処理は、jqGrid#serializeRowDataをセットします。例えば以下のようにコードすると送信前にrow_dataをシリアライズすることが可能になります。serializeRowDataは送信文字列を戻すようにします。
serializeRowData: function(row_data) { var obj = JSONRPCRequest("objects.setuser", [row_data.id, row_data]); return obj.toJson(); },
レスポンスの処理
応答時の処理で問題となるのはグリッドデータと同様JSON-RPCのエラーオブジェクトの扱いです。HTTP成功後の処理をフックするときはeditRowの第4引数successFuncに関数で行うことができます。この関数で真を返せば編集処理成功、偽なら失敗と判定することが可能です。
たとえば以下のようにします。
function rpcOnSuccess(xhr) { obj = jsonToObj(xhr.responseText); if(obj.error){ alert("Error!!"); return false; } return true; } $("#datagrid").jqGrid("editRow", id, true, false, rpcOnSuccess, "JSONRPCService");
これでリクエストが成功すればonSuccess(XMLHttpRequest)がコールされます。真を返せば編集がデータグリッドに反映され、偽なら編集処理が失敗とみなされ、データグリッド内の対象データが元のデータに戻ります。ここで反映されるというのはデータグリッドに表示されているデータが書き換えられるということを示します。
ダイアログ編集でのJSON-RPC
フォームエディットでのJSON-RPCの方法を解説します。フォームエディットではjqGrid#editGridRow()で行います。
editGridRow(rowId, properties)
ここのpropertiesで値をセットすることで挙動を制御します。
レコードをダブルクリックした時に編集開始
$("#datagrid").jqGrid({ ... ondblClickRow: function(id, iRow, iCol, e) { if(id && id!== GLOBAL.lastSel){ $("#datagrid").restoreRow(GLOBAL.lastSel); GLOBAL.lastSel = id; }; $("#datagrid").jqGrid("editGridRow", id, { url: "JSONRPCService", serializeEditData: function(data): { ... }, afterSubmit: function(xhr, postData): { ... }, closeAfterEdit: true }; }, ... });
インライン編集とは書式が異なっているので注意。編集するレコードのIDと対するプロパティを指定することで編集します。後述のようにリクエストを発行する際にはserializeEditDataプロパティによる処理、レスポンスはafterSubmitプロパティで処理します。
JSON-RPCで処理する場合は上記のurl, serializeEditData, afterSubmitは必須です。
closeAfterEditプロパティを真にすると編集終了後にダイアログを閉じます。
フォームからのリクエスト
propertiesのserializeEditDataに送信前のデータを処理する関数を定義します。戻り値はJSON文字列になるようにします。例えば以下の通り。
serializeEditData: function(data) { delete data.id; var obj = GLOBAL.json.wrapInJSONRPCRequest("usermgr.add_user", [data]); return GLOBAL.json.objToJson(obj); },
戻り値の処理
レスポンスもインライン編集と同じように処理しますが、ここではpropertiesのafterSubmitに受信後の処理をセットします。引数はXMLHttpRequestとpostDataです。
afterSubmit: function(xhr, postdata) { var res = GLOBAL.json.jsonToObj(xhr.responseText); var msg = "Unexpected exception."; if(res.error && res.error.code){ // JSONRPCErrorの場合 e = res.error; msg = "<b>RPC error: " + e.message + "(" + e.code + ")</b><br/>" + e.data; return [false, msg, null]; } return [true, "OK", res.result.loid]; }
afterSubmit()はmixed配列を返し、[ステータス、メッセージ、新規レコードのID]となります。ステータスは真偽値で真なら成功です。ステータスに偽を返した場合、メッセージが表示され、操作が取り消されます。成功した場合は新規レコードのIDをセットすることでレコードに新たなIDが与えられます。
jqGrid JSON-RPCのまとめ
これらをまとめると以下の表のようになります。
処理 | リクエスト | レスポンス |
グリッドデータ取得 | serializeGridData | jsonReader |
インライン編集 | serializeRowData | editRowの第4引数 |
フォーム編集 | serializeEditData | afterSubmit |
レスポンスの扱いがややこしいですね。別の方法としてはレスポンス時にHTTPエラーステータスを返すのも一つの手ですが、それはそれで弊害がありそうな・・・
jqGridのTreeGridでJSON-RPC
TreeGridでツリーが追加される際にJSON-RPCにしたい場合、reloadGridイベントをあらかじめセットしておく。
バブリングにより、jqGrid本体のreloadGridが実行される前にここでセットしたreloadGridハンドラが実行される。
$("#treegrid").bind("reloadGrid", function() { ajaxアクセスが起こる前の処理... }); $("#treegrid").jqGrid({パラメータ...});
なお、bindは自分を返すのでカスケード可能。
$("#treegrid").bind("reloadGrid",...).gqGrid({...});
こんな感じ。
TreeGridのtreeReader.expand_fieldが動作しない
使わなければよい・・・
修正するなら以下のようにする。
--- grid.treegrid.js.orig 2010-12-23 09:10:43.000000000 +0900 +++ grid.treegrid.js 2010-12-23 09:11:22.000000000 +0900 @@ -412,8 +412,9 @@ collapseNode : function(rc) { return this.each(function(){ if(!this.grid || !this.p.treeGrid) { return; } - if(rc.expanded) { - rc.expanded = false; + var expanded = this.p.treeReader.expanded_field; + if(rc[expanded]) { + rc[expanded] = false; var id = $.jgrid.getAccessor(rc,this.p.localReader.id); var rc1 = $("#"+id,this.grid.bDiv)[0];
コメント
- d -- 2017-12-11 (月) 10:34:24