HTML5 localStorageを使ったjqGrid

2012.09.23

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

HTML5のlocalStorageがどんなもんかと思い、サンプルを作ってみました。動きは以下の通りです

  1. ページロード時はオンラインモードで、検索項目やデータ更新はサーバを介してデータをやり取りする
  2. ボタンクリックでオフラインモードに切り替えし、フォーム類はdisableに変更、データはサーバから全データを取得し、localStorageへ書き込む
  3. localStorageへ書き込んだデータを読み込みjqGridへ展開する
  4. 編集データの保存先はclientArrayにし、localStorageへ変更情報を保存する
  5. ボタンクリックでオンラインモードに切り替えた際、localStorageに保存している変更情報をサーバに送信し、同期させる
  6. 同期した後はページロード時と同じくオンラインモードとして、検索やデータ更新はサーバを介してデータをやり取りする
  7. サーバ側はcoldfusionを使っていますが、長くなるのでcfcomponentのサンプルは省略します。

画面は前回までの記事で使っていたものを流用しています。

一応スクリーンショットを載せておきます。

mySQLのテーブルデータは以下のように登録しています

オフライン切り替え時のスクリーンショット

オンライン切り替え時のデータ同期処理件数確認ダイアログボックス

サンプル(index_ls.html)

<!DOCTYPE html>
<html lang="ja">
<head>
      <meta charset="utf-8">
      <title>ColdFusion 10 + jqGrid サンプル</title>
      <link rel="stylesheet" type="text/css" media="screen" href="css/jquery-ui-1.8.19.custom.css" />
      <link rel="stylesheet" type="text/css" media="screen" href="css/ui.jqgrid.css" />
      <link href="css/bootstrap.css" rel="stylesheet">
      <link href="css/bootstrap-responsive.min.css" rel="stylesheet">

      <script type="text/javascript" src="js/jquery-1.7.2.min.js"></script>
      <script type="text/javascript" src="js/jquery-ui-1.8.19.custom.min.js" ></script>
      <script type="text/javascript" src="js/jquery.jqGrid.min.js" ></script>
      <script type="text/javascript" src="js/i18n/grid.locale-ja.js" ></script>
      <script type="text/javascript" src="js/json2.js" ></script>

      <!-- ページロード時にjqGridのパラメータを設定する -->
      <script type="text/javascript">
      $(document).ready(function(){

         //オンラインフラグをlocalStorageに書き込む
         localStorage.setItem("mode","0");
         
         var settings = {
            formatter : {
            integer : {thousandsSeparator: " ", defaultValue: '0'},
            number : {decimalSeparator:".", thousandsSeparator: " ", decimalPlaces: 2, defaultValue: '0.00'},
            currency : {decimalSeparator:".", thousandsSeparator: " ", decimalPlaces: 2, prefix: "", suffix:"$", defaultValue: '0.00'}
            },
            colModel :[
            {name:'ID',index:'ID'},
            {name:'Remark',index:'Remark'},
            {name:'RealName',index:'RealName', editable:true},
            {name:'ProjectName',index:'ProjectName', align:"left", editable:true},
            {name:'CustomerName',index:'CustomerName', align:"left", editable:true},
            {name:'Resale',index:'Resale', formatter:'number', formatoptions:{decimalSeparator:".", thousandsSeparator: ",", decimalPlaces: 2},align:"right", editable:true,editrules:{number:true}}
            ],
            pager: $("#pager"),
            rowNum:10,
            rowList:[10,20,30],
            sortorder: "asc",
            viewrecords: true,
            cellEdit: true,
            imgpath: 'themes/basic/images',
            height: 180
            };

            settings.url = "GetData_sample.cfc?method=getinfo";
            settings.datatype = "json";
            settings.caption = 'jqGrid sample';
            settings.cellsubmit =  "remote";
            settings.cellurl = "SetData_sample.cfc?method=setinfo";
            settings.afterSaveCell = save_after_reload;
            settings.jsonReader = {
               root: "ROWS",
               page: "PAGE",
               total: "TOTAL",
               records: "RECORDS",
               id: "ID",
               cell: "CELL"};

      //グリッドの表示
      $("#list").jqGrid(settings);

      //検索ボタンをクリックされた場合の処理
      $("#disp_grid").click(function(){
            var PN = encodeURI(inclimental_search.ProjectName.value);
            var CN = encodeURI(inclimental_search.CustomerName.value);
            //jqGridをリロードする
            $("#list").setGridParam({url:"GetData_sample.cfc?method=getinfo&PN=" + PN + "&CN=" + CN}).trigger("reloadGrid");
            });
      });

      //オンライン時にCELL編集された後に呼び出される
      function save_after_reload(rowid, cellname, value, iRow, iCol)  { 
            $("#list").trigger("reloadGrid");
      }

      //オフライン時にCELL編集された後に呼び出される
      function save_after_func(rowid, cellname, value, iRow, iCol)  { 
         var row = $("#list").getRowData(rowid);
         var modify_info = "ID=" + row.ID +  ",cellname=" + cellname + ",value=" + value;
         //localStorageに変更情報を保管しておく。
         //存在する場合は削除してから保管する
         if(localStorage.getItem("change-" + cellname + "-" + row.ID,modify_info) != null){
            localStorage.removeItem("change-" + cellname + "-" + row.ID,modify_info);
         }
            localStorage.setItem("change-" + cellname + "-" + row.ID,modify_info);   
         }
      </script>

      <script type="text/javascript">
         function store_data(){
               if (localStorage.getItem("mode") == 0){
                  localStorage.clear();
                  localStorage.setItem("mode","1");
                  document.getElementById("disp_grid").disabled = "disabled";
                  document.getElementById("ProjectName").disabled = "disabled";
                  document.getElementById("CustomerName").disabled = "disabled";
                  document.getElementById("stored").value = "オフラインで実行中";
                  document.getElementById("stored").className = "btn btn-danger";
                  
                  $(".ui-jqgrid-title").text("jqGrid offline mode");
                  localStorage.setItem("mode","1");
                  var CN = document.getElementById("CustomerName").value;

                  //ローカル作業を前提とするので、全件取得する。
                  var httpObj = jQuery.get("GetData_sample.cfc?method=getinfo_stor",null, function(){
                  var data = JSON.parse(httpObj.responseText);
                  var str_row = "";
                     for (var i=0; i<data.item.length; i++){
                        //ローカルに保存するデータの容量を把握する
                        var str_row = str_row + data.item[i].ID + data.item[i].Remark + data.item[i].RealName + data.item[i].ProjectName + data.item[i].CustomerName + data.item[i].Resale;
                        localStorage.setItem("Rowslist", data.item.length);
                        localStorage.setItem("ID-" + i, data.item[i].ID);
                        localStorage.setItem("Remark-" + i, data.item[i].Remark);
                        localStorage.setItem("RealName-" + i, data.item[i].RealName);
                        localStorage.setItem("ProjectName-" + i, data.item[i].ProjectName);
                        localStorage.setItem("CustomerName-" + i, data.item[i].CustomerName);
                        localStorage.setItem("Resale-" + i, data.item[i].Resale);
                     }
                        //保存したバイト数を書き込んでおく
                        var length = countLength(str_row);
                        localStorage.setItem("diskused",Number(length));
                        DD = new Date();
                        Year = DD.getYear() + 1900;
                        Month = DD.getMonth() + 1;
                        Day = DD.getDate();
                        Hour = DD.getHours();
                        Minutes = DD.getMinutes();
                        Seconds = DD.getSeconds();

                        document.getElementById("date_memo").innerHTML = " " + Year+"年"+Month+"月"+Day+"日"+Hour+"時"+Minutes+"分に保存済(保存済データは約"+length+"バイトです)";

                        var mydata = new Array();
                        for ( var i=0; i<localStorage.getItem("Rowslist"); i++) {
                           mydata.push({
                              ID: localStorage.getItem("ID-" + i),
                              Remark: localStorage.getItem("Remark-" + i),
                              RealName: localStorage.getItem("RealName-" + i),
                              ProjectName: localStorage.getItem("ProjectName-" + i),
                              CustomerName: localStorage.getItem("CustomerName-" + i),
                              Resale: localStorage.getItem("Resale-" + i)
                           });
                        }

                        $("#list").setGridParam({
                           data: mydata,
                           datatype: "local",
                           sortorder: "asc",
                           cellsubmit: 'clientArray',
                           afterSaveCell: save_after_func,
                           colNames : ["ID","Remark","RealName","ProjectName","CustomerName","Resale"]
                        }).trigger("reloadGrid");
                  })

               } else if (localStorage.getItem("mode") == 1) {
                  var modify_cnt = 0;
                  //オフライン実行の際にローカルで変更したデータがあればサーバ側へ反映させる
                  for (var i=0, len = localStorage.length; i < len; i++){
                     if (localStorage.getItem("change-CustomerName-" + i) != null){
                        modify_cnt = modify_cnt + 1;
                        var str = localStorage.getItem("change-CustomerName-" + i);
                        $.get("SetData_sample.cfc?method=setinfo_syncro&str=" + encodeURI(str));
                     } 
                     if (localStorage.getItem("change-ProjectName-" + i) != null) {
                        modify_cnt = modify_cnt + 1;
                        var str = localStorage.getItem("change-ProjectName-" + i);
                        $.get("SetData_sample.cfc?method=setinfo_syncro&str=" + encodeURI(str));
                     }
                     if (localStorage.getItem("change-RealName-" + i) != null) {
                        modify_cnt = modify_cnt + 1;
                        var str = localStorage.getItem("change-RealName-" + i);
                        $.get("SetData_sample.cfc?method=setinfo_syncro&str=" + encodeURI(str));
                     }
                     if (localStorage.getItem("change-Resale-" + i) != null) {
                        modify_cnt = modify_cnt + 1;
                        var str = localStorage.getItem("change-Resale-" + i);
                        $.get("SetData_sample.cfc?method=setinfo_syncro&str=" + str);
                     }
                  }
/*
                  function sleep(time) {
                     var d1 = new Date().getTime();
                     var d2 = new Date().getTime();
                     while (d2 < d1 + time) {
                        d2 = new Date().getTime();
                     }
                     return;
                  }
                  sleep(200);
*/
                  //下記のダイアログボックスを出さないようにした時に
                  //グリッド内容が更新したデータで表示されない場合は上記のsleepコメントを外すことで回避可能
                  if ( modify_cnt == 0){
                     alert("変更箇所はありませんでした。");   
                  } else {
                     alert(modify_cnt+"箇所の変更データをシンクロしました。");
                  }

                  localStorage.clear();
                  localStorage.setItem("mode","0");
                  $("#disp_grid").removeAttr("disabled");
                  $("#ProjectName").removeAttr("disabled");
                  $("#CustomerName").removeAttr("disabled");
                  document.getElementById("stored").value = "オンライン実行中";
                  document.getElementById("date_memo").innerHTML ="";
                  document.getElementById("stored").className = "btn btn-success";

                  $(".ui-jqgrid-title").text("jqGrid online mode");

                  $("#list").setGridParam({
                     url:  "GetData_sample.cfc?method=getinfo",
                     afterSaveCell: save_after_reload,
                     datatype: "json",
                     sortorder: "asc",
                     cellsubmit: 'remote',
                     cellurl: "SetData_sample.cfc?method=setinfo",
                     jsonReader: {
                     root: "ROWS",
                     page: "PAGE",
                     total: "TOTAL",
                     records: "RECORDS",
                     id: "ID",
                     cell: "CELL"}
                  }).trigger("reloadGrid");
              }
         }

         function countLength(str) { 
         var r = 0; 
            for (var i = 0; i < str.length; i++) { 
               var c = str.charCodeAt(i); 
               if ( (c >= 0x0 && c < 0x81) || (c == 0xf8f0) || (c >= 0xff61 && c < 0xffa0) || (c >= 0xf8f1 && c < 0xf8f4)) { 
                  r += 1; 
               } else { 
                  r += 2; 
               } 
            } 
         return r; 
         }
      </script>
</head>
<body>

      <div class="row-fluid" style="background-color:mintcream;">
            <div class="span12" style="padding-top : 5px;">
                  <div>
                        <form name="inclimental_search" class="form-inline">
                              <input type="text" class="input-small" placeholder="ProjectName" name="ProjectName" id="ProjectName" />
                              <input type="text" class="input-large" placeholder="CustomerName" name="CustomerName" id="CustomerName" />
                              <input type="button" value="検索" id="disp_grid" class="btn btn-info button_1">
                              <!-- ボタンクリックでlocalStorageに保存する -->
                              <input type="button" value="オンライン/オフライン切り替え" id="stored" class="btn btn-success" onclick="store_data()"><span id="date_memo"></span>
                        </form>
                  </div>
            </div>

            <div class="span12" style="padding-top:5px;">
                  <!-- jqGrid表示 -->
                  <div class="row-fluid" style="background-color:mintcream;">
                        <div id="d_grid1" style="padding-top:5px;">
                              <table id="list" class="jgrid"></table>
                              <div id="pager" class="jgpager">
                              </div>
                        </div>
                  </div>
            </div>
      </div>
</body>
</html>

navigatorオブジェクトのonLine属性は実際にネットに接続しているかではなく、ブラウザが [オフラインモード] に切り替えられているか否かを判断します。と記載があるので、そちらを利用してオンライン/オフラインを判断してもいいかもしれません。

例えば社内で営業情報を入力中に外出時間が来てしまったがまだ入力を終えていないので、出かける前にローカルモードにして状態をローカル保存し、電車等で移動中に残りを入力し、社内に戻った際やオンライン環境になったら同期させるとかそういう使い方ができそうですね。保存容量はスマートフォン等を視野に入れるのであれば2MB程度で抑える必要がありますが、その容量制限さえ考慮すればとてもいい機能だと思います。