jqGrid 實用技巧 (十一) inline edit 及 confirm dialog before save

[anti-both]

所 有 強 橫 的 grid,都 必 須 要 支 援 inline editing 或 者 cell editing 的 ,jqgrid 當 然 也 不 例 外 。但 相 比 起 form editing,jqgrid 的 documentation 和 demo 網 站 都 比 較 簡 單 ,有 點 不 足 。

今 次 筆 者 就 為 大 家 介 紹 一 個 inline editing 的 例 子 吧 。

inline editing 如 何 控 制 那 一 個 column 能 修 改 呢 ?大 家 都 應 該 知 道 了 ,就 是 colModel 裡 面 的 editable。這 次 的 範 例 就 簡 單 一 點 ,只 有 一 個 欄 位 可 以 修 改 。

1
2
3
4
5
6
7
8
9
...
$("#list1").jqGrid({
....
colModel :[
....
{name:'prizeid',index:'prizeid',editable:true,edittype:'select',editoptions:{value:'0:---;1:Prize A;2:Prize B'}, formatter:'select',  },
....
],
...

inline editing 跟 cell editing 不 同 ,jqgrid 裡 面 是 沒 有 一 個 inlineEidt: true 的 選 項 的 。inline editing 一 切 的 控 制 都 在 幾 個 method:editRow、saveRow、restoreRow、addRow。

很 多 朋 友 誤 以 為 inline editing 欠 缺 form editing 的 眾 多 event 或 者 callback function,其 實 只 是 他 們 沒 有 在 設 定 好 以 上 method 的 parameters 而 已 。

一 般 來 說 ,inline editing 最 多 是 在 點 選 一 個 row 時 發 生 ,所 以 我 們 會 在 jqGrid 的 onSelectRow 事 件 加 入 。但 當 然 也 可 以 在 任 何 其 他 事 件 上 觸 發 。如 果 是 使 用 onSelectRow 事 件 ,我 們 多 數 不 希 望 使 用 者 一 次 修 改 多 於 一 筆 資 料 ,所 以 在 jqGrid 入 面 的 範 例 就 會 加 入 一 個 變 數 lastsel2 來 代 表 上 一 次 點 選 的 記 錄 ,這 個 要 預 先 宣 告 好 。

1
2
3
4
5
6
7
8
9
...
onSelectRow: function(id){
 if(id && id!==lastsel2){
  jQuery('#list1').jqGrid('restoreRow',lastsel2);
  jQuery('#list1').jqGrid('editRow',id);
  lastsel2=id;
 }
},
...

上 面 是 就 是 jqGrid demo 網 站 的 範 例 ,意 思 就 當 使 用 點 選 新 的 一 筆 記 錄 ,要 先 restore 之 前 點 選 的 記 錄 ,再 行 開 始 edit 現 在 新 點 選 的 記 錄 。但 裡 面 是 缺 少 了 一 些 很 有 用 的 parameters 的 。如 果 補 回 參 數 就 會 像 是 下 面 的 樣 子 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
...
var lastsel2;
$("#list1").jqGrid({
....
colModel :[
....
{name:'prizeid',index:'prizeid',editable:true,edittype:'select',editoptions:{value:'0:---;1:Prize A;2:Priz B'}, formatter:'select',  },
....
],
....
onSelectRow: function(id){
 if(id && id!==lastsel2){
  jQuery('#list1').jqGrid('restoreRow',lastsel2);
  var editparameters = {
   "keys" : true,
   "oneditfunc" : null,
   "successfunc" : null,
   "url" : "save_data.php",
   "extraparam" : {},
   "aftersavefunc" : reloadGrid,
   "errorfunc": null,
   "afterrestorefunc" : null,
   "restoreAfterError" : true,
   "mtype" : "POST"
  };
  jQuery('#list1').jqGrid('editRow',id,editparameters);
  lastsel2=id;
 }
},
...

在 上 例 中 ,大 部 分 的 參 數 都 是 預 設 值 ,其 實 如 果 你 使 用 預 設 值 的 話 是 不 用 加 進 去 的 。筆 者 修 改 了 是 keys、url 和 aftersavefunc。keys 預 設 是 false。如 果 改 成 true 的 話 ,你 就 可 以 在 修 改 的 時 候 按 Esc 來 restore,按 Enter 來 save,這 是 很 方 便 和 直 觀 的 。

url 就 是 post data 的 目 的 地 ,如 果 沒 有 設 定 ,jqgrid 就 會 使 用 editurl。

aftersavefunc 就 是 form editing 的 afterSubmit 事 件 。筆 者 在 這 裡 加 入 了 一 條 自 定 義 的 function reloadGrid,其 實 裡 面 也 做 沒 什 麼 ,就 只 是 reload 一 次 grid 而 已 。但 你 寫 的 時 候 ,就 可 以 按 需 要 而 做 任 何 事 情 了 。

那 如 果 不 是 每 一 筆 資 料 都 容 許 使 用 者 修 改 呢 ?inline editing 也 可 以 按 照 需 要 而 觸 發 的 。例 如 筆 者 舉 的 例 子 ,prizeid=0 就 是 未 選 擇 任 何 禮 品 ,prizeid=1 就 選 擇 了 prize A,如 此 類 推 。但 如 果 筆 者 想 每 筆 資 料 只 可 以 選 擇 一 次 禮 品 呢 ?就 是 說 ,如 果 選 好 了 禮 品 ,就 不 能 再 選 擇 了 ,這 如 何 做 呢 ?只 要 在 觸 發 editRow 之 前 檢 查 一 次 prizeid 就 可 以 了 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
...
var lastsel2;
$("#list1").jqGrid({
....
colModel :[
....
{name:'prizeid',index:'prizeid',editable:true,edittype:'select',editoptions:{value:'0:---;1:Prize A;2:Priz B'}, formatter:'select',  },
....
],
....
onSelectRow: function(id){
 var prizeID = $('#list1').jqGrid('getCell',id,'prizeid');
 if ( prizeID == 0 ) {
  if(id && id!==lastsel2){
   jQuery('#list1').jqGrid('restoreRow',lastsel2);
   var editparameters = {
    "keys" : true,
    "url" : "save_data.php",
    "aftersavefunc" : reloadGrid,
   };
   jQuery('#list1').jqGrid('editRow',id,editparameters);
   lastsel2=id;
  }
 }
},
...

當 使 用 點 選 一 行 的 時 候 ,我 們 檢 查 prizeid 的 值 ,如 果 是 零 的 話 ,我 們 才 觸 發 editRow 方 法 。這 樣 的 話 ,使 用 者 就 不 能 再 修 改 已 經 選 好 了 禮 品 的 資 料 行 了 。

inline editing 不 比 form editing,使 用 者 有 時 隨 便 點 選 一 行 就 開 始 edit 了 ,再 胡 亂 按 了 一 下 Enter,資 料 就 儲 存 入 資 料 庫 了 ,好 像 很 不 安 全 的 樣 子 。那 可 不 可 以 在 saveRow 之 前 給 一 個 confirm dialog 呢 ?很 多 朋 友 都 在 這 裡 觸 礁 了 ,他 們 會 想 找 一 個 beforeSave / beforeSubmit 的 事 件 ,可 惜 在 editRow 裡 面 是 沒 有 的 。相 反 的 ,其 實 我 們 應 該 主 動 拿 回 keypress 的 控 制 權 ,讓 我 們 自 己 選 擇 使 用 者 按 Enter 又 或 者 Esc 的 時 候 程 式 要 做 什 麼 。這 時 候 必 須 要 記 得 把 keys 設 定 為 false (false 是 預 設 值 )。

我 們 要 在 grid 裡 面 每 一 個 input 都 加 入 keypress event 控 制 ,最 方 便 的 莫 過 於 使 用 jquery 的 delegate 方 法 了 。delegate 不 只 可 以 綁 定 已 有 的 控 制 項 ,還 可 以 綁 定 將 來 新 增 的 控 制 項 (因 為 在 grid 裡 面 是 可 以 addRow 的 )。

筆 者 的 例 子 中 只 有 一 個 select 作 為 使 用 者 輸 入 的 控 制 項 ,所 以 delegate 就 會 如 下 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
...
var lastsel2;
...
$("#list1").jqGrid({
....
colModel :[
....
{name:'prizeid',index:'prizeid',editable:true,edittype:'select',editoptions:{value:'0:---;1:Prize A;2:Priz B'}, formatter:'select',  },
....
],
....
onSelectRow: function(id){
 var prizeID = $('#list1').jqGrid('getCell',id,'prizeid');
 if ( prizeID == 0 ) {
  if(id && id!==lastsel2){
   jQuery('#list1').jqGrid('restoreRow',lastsel2);
   jQuery('#list1').jqGrid('editRow',id);
   lastsel2=id;
  }
 }
},
.....
});
...
...
$('#list1').delegate('select.editable','keypress', function(e) {

 var code = (e.keyCode ? e.keyCode : e.which);

 if(code == 13) { //Enter keycode
  var currentID = $(this).attr("id");
  currentID = currentID.replace('_prizeid','');
  $("#confirm_dialog").dialog({
    modal: true,
    buttons: {
     'Ok': function() {
      var saveparameters = {
       "url" : "save_data.php",
       "aftersavefunc" : reloadGrid,
      };
      $('#list1').jqGrid('saveRow',currentID,saveparameters);
      $( this ).dialog( "close" );
     },
     'Cancel': function() {
      $('#list1').restoreRow(currentID);
      lastsel2=-1;
      $( this ).dialog( "close" );
     }
    }
   });
 }

 if(code == 27) { //Esc keycode
  var currentID = $(this).attr("id");
  currentID = currentID.replace('_prizeid','');
  $('#list1').restoreRow(currentID);
  lastsel2=-1;
 }
)};
...

那 個 confirm_dialog 筆 者 就 不 寫 出 來 了 ,不 就 是 一 句 Are you Sure 之 類 的 。上 例 中 比 較 不 好 理 解 的 是 如 何 取 得 saveRow 裡 面 需 要 的 row id。其 實 這 個 row id 就 已 經 存 在 於 每 一 個 由 jqgrid 自 動 生 成 的 input control 裡 面 。例 如 row id 是 50,那 麼 這 個 控 制 項 自 己 的 id 就 會 是 50_name,這 個 name 就 是 colModel 裡 面 的 name 參 數 。由 於 我 的 colModel 裡 面 的 name 是 prizeid,所 以 控 制 項 的 id 就 是 50_prizeid 了 。

要 由 這 個 控 制 項 的 id 取 得 row id,只 要 移 除 後 面 的 _prizeid 就 可 以 了 。row id 的 字 數 長 度 是 不 固 定 的 ,所 以 還 是 用 replace 比 較 好 ,而 不 能 用 substring。

當 你 取 得 row id (上 例 的 變 數 就 是 currentID),其 餘 一 切 也 好 辦 ,想 要 save 就 用 saveRow method,想 要 取 消 就 用 restoreRow method。在 上 例 中 ,如 果 使 用 按 了 Enter,就 彈 出 來 confirm_dialog,如 果 使 用 選 OK 就 save,如 果 選 Cancel 就 restore。另 外 ,如 果 在 編 輯 中 途 按 了 Esc,也 同 樣 是 restore。

當 restoreRow 方 法 之 後 ,記 得 也 要 把 那 個 lastsel2 變 數 回 復 一 下 ,不 然 的 話 ,你 restoreRow 之 後 ,想 要 再 點 選 回 同 一 行 來 修 改 就 會 無 反 應 的 了 。在 上 例 我 把 lastsel2 設 定 為 一 個 不 存 在 的 -1 。

然 後 在 onSelectRow 當 中 ,因 為 儲 存 的 動 作 改 為 由 SaveRow 去 處 理 ,所 以 editRow 的 參 數 就 全 部 使 用 預 設 值 就 可 以 了 ,但 記 得 keys 必 須 要 是 false (false 是 預 設 值 ),這 別 搞 錯 了 。然 後 ,本 來 的 url 和 aftersavefunc,就 都 搬 到 saveRow 作 為 參 數 。

就 這 樣 ,一 個 confirm dialog before save 就 完 成 了 。呵 呵 。

ctleung張先生,男性,肖龍。
職業:I.T. Consultant
簡介:不好好讀書;七尺差五寸,手長過膝,雙耳垂肩;性寬和,寡言語,喜怒不形於色。據說少時曾斬白蛇於鳳凰山下……

This entry was posted in jqGrid and tagged , , , , , , , . Bookmark the permalink.

9 Responses to jqGrid 實用技巧 (十一) inline edit 及 confirm dialog before save

  1. tony says:

    請問是否可以告知
    1. edit後, 存檔後, 編輯視窗還在, 要如何關閉, 且GRID的內容會跳到第一筆, 若是編輯下方的ROW, 就會看不到了..
    2. 若是master detail 的GRID, 要如何才能一起存檔, 若按取消, 則資料回到原來的資料..
    謝謝!!!!
    PS看了大大的文, 穫益良多, 再次感謝..

    • C.T. Leung says:

      1. inline edit 可以有什麼視窗要關閉的呀??? 如果你係講緊 form edit 不如你去返 form edit 果篇度問啦,我答完之後別人都知道講緊乜野嘛。如果你講緊個 dialog,就應該用 $(this).dialog(“close”),但我相信你不是在說這個,因為我上面的例子都寫得很清楚的了,哈哈。

      2. 我本身還沒有遇到過兩個 grid (不管是不是 master detail) 需要同時 edit 同時 save 的這種可能性呢…… 如果不是同時 edit / save,就根本不會有一起回復的問題了。我的意思是,當我 edit master grid 改了一些東西再 save 的時候,當然很有可能背後要 update detail table 的相關 records,但這只會在背後用程式做啊,不會在 detail grid 裡面手動做的吧 ? 在背後做好 update 之後再 reload 一次兩個 gird 就應該可以顯示正確的資料了。如果你確實有這種兩個 gird 必須要同時 edit 同時 save 的可能性的例子,可以在這裡給大家分享一下麼,我相信會十分有趣,也許我可以開一個新 post 做一個 sample 給大家參考呢。

  2. tony says:

    不好意思, 我指的是form edit沒錯, 我會於該篇文提問..
    第2點例如進貨單, master是進貨主檔,記錄廠商.日期……等資料
    detail是記錄各產品編號.名稱.數量.金額…等資料,
    所以實務上, 要建立一張進貨單, 必須等各項資料輸入完後, 再一併存檔
    所以才會提出該問題, 再次麻煩一下…
    謝謝!!!

    • C.T. Leung says:

      如果是新增進貨單這一類情況,我個人會獨立寫一個輸入介面的 (裡面會有一個 temporary 的 detail grid),到使用者按 save 才分別存入兩個 table。我個人覺得這樣對使用者比較直觀。[ edit 也是一樣,就是一個頁面顯示所有進貨單,點選 edit 才進入子表單的輸入介面 ] [ 另外,修改子表單用 temporary grid 也可以減少使用者修改子表單時不停寫入資料庫,造成 cancel 時要 rollback 多筆子表單的資料 ]

      如果你確定要用一個頁面兩個 grid 的設計,要考慮的特殊情況也會多一些,例如使用者修改子表單到一半又去點選另一張進貨單時又要如何處理,諸如此類的麻煩事。

      其實要 rollback 多於一行的資料,只靠 jqgrid 本身肯定不能。jqgrid 本身 inline editing 也不支援一次過修改多行資料,然後一起存入資料庫的。例如說你一張進貨單有三件貨,使用者在 grid 裡面修改了第一件,你究竟要不要寫入資料庫?如果寫入了資料庫,你也不可能再用 jqgrid 的 restoreRow 方法吧?所以必須要用 temporary grid。

      這個所謂 temporary grid 的方法,其實就是先把子表單的資料寫入一個 data array,再用那個 data array 做一個 local grid。完成修改後再一次過把那個 local grid 的 data 一行一行的寫入資料庫 [這情況下,cancel 就什麼也不用做了,哈哈]。

      即使你要用兩個 grid 的方法,也必須要 detail grid 是 local 的才行。所以很抱歉,這實在不是 master detail grid 一起存檔的例子呢,呵呵。

  3. Leo Yung says:

    參考大大的關於JqGrid的Post,解決了不少在JqGrid上的難題,在此表示感謝!

    看完這篇文章後,有個疑問不知可否代為解答!

    參考以上做法,當我一按下Enter就會彈出confirm box,然後進行儲存的階段。
    但可能是習慣上的問題,有不少人向我反映每次按下Enter就confirm要唔要save,顯得有點兒煩, 因此參考了另一篇文章,更改了Enter鍵的處理方法,但依然有點不便,如果想當用戶移離更改中的row才confirm要唔要save,可以如果更改?

    • C.T. Leung says:

      哈哈,我們老人在做資料輸入的時候老是想著 keyboard only 的,所以才會有這種按 enter 就代表 save 的習慣 (還有 Tab 就是移動到下一個欄位之類的)。不過這當然是非必要的 (更也許不合時宜)。

      你想要的是一種 row 的 blur 事件吧,可惜 jqGrid 本身沒有提供。如果不想去改 jqGrid 的話,就只能用回它本身的 onSelectRow 事件了。

      這其實跟文中換行的例子沒什麼兩樣,只不過,我們要把 confirm dialog 放到 onSelectRow 裡面。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      
      ...
      onSelectRow: function(id){
       if(id && id!==lastsel2){
        $("#confirm_dialog").dialog({
         modal: true,
         buttons: {
         'Ok': function() {
          var saveparameters = {
           "url" : "save_data.php",
           "aftersavefunc" : reloadGrid,
          };
          $('#list1').jqGrid('saveRow',lastsel2,saveparameters);
          $( this ).dialog( "close" );
         },
         'Cancel': function() {
          $('#list1').restoreRow(lastsel2);
          lastsel2=-1;
          $( this ).dialog( "close" );
         }
        });
       jQuery('#list1').jqGrid('editRow',id);
       lastsel2=id;
       }
      },
      ...

      不過,上面的例子是筆者隨便 copy & paste 合併出來的,是不會 work 的。實際上這裡面需要考慮的東西更多,例如

      1. 使用者第一次點選一行時如何處理 (可能就是 lastsel2=0 時不做野);
      2. 要檢查 lastsel2 是不是在 edit 中, 大概就是 if($(“tr#” + lastsel2).attr(“editable”) == 1) ;
      3. 又或者使用者不想點選另一個 row,但又想要 save (不點選另一行就不會引發 onSelectRow event 了);
      4. 又或者你的 Grid 裡面根本只有一行 data (例如使用者翻到最後一頁,而該頁只有一行 data );

      諸如此類的麻煩事….

      如果不想用 Enter 來 Save,其實也可以用回 Save Button 的吧?

      • Leo Yung says:

        多謝大大的建議!

        唔採用Enter來做save,主要是因為我設計的jqGrid要通用在手機或平版電腦等觸控平台,要根據用戶的習慣令用法盡可能人性化。

        而且在手機上版面有限,加入按鈕會頁面內容加闊,影響觀感。

        大大提出要考慮的地方,我會多加留意!

Leave a Reply

Your email address will not be published. Required fields are marked *