jQgrid で multiselect を使ってみた際の覚書。

 

 

jqGrid と multiSelect

Javascript のグリッドライブラリ、jqGridmultiSelect というのはどういうことかというと、グリッドの行頭に選択用のチェックボックスを設けて、レコードを選択するようにするための機能を指します。

こんな感じをイメージしてもらえればいいと思います。

http://www.guriddo.net/demo/ より

サンプル

HTML

ヘッダ部には次のような Javascript ライブラリと CSS を読み込んでいます。jqGrid を使うには jQuery が必須になります。

  • jQuery 1.12.4
  • jQuery UI 1.12.1
  • jqGrid 4.6.0
<!DOCTYPE html>
<html lang="ja">
<head>
    <!-- jQyery 1.12.4 -->
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
    <!-- jqGrid 4.6.0 -->
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jqgrid/4.6.0/js/jquery.jqGrid.min.js"></script>
    <!-- jqGrid locale ライブラリ -->
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jqgrid/4.6.0/js/i18n/grid.locale-ja.js"></script>
    <!-- jQuery UI 1.12.1 -->
    <link rel="stylesheet" type="text/css" media="screen" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/smoothness/jquery-ui.css" />
    <!-- jqGrid Css -->
    <link rel="stylesheet" type="text/css" media="screen" href=https://cdnjs.cloudflare.com/ajax/libs/jqgrid/4.6.0/css/ui.jqgrid.css />
    <meta content="text/html; charset=UTF-8" http-equiv="content-type">
    <title>jqGrid multiselect sample</title>
</head>

 

HTML の ボディ部は次のようになっています。

jqGrid でグリッドを表示するのに必要なのは、<table id="任意のID"></table> の部分だけです。

あと、ページネーションを使用する場合には別途ページャ用の DIV (<div id="任意のID名"></div>) が必要になりますが、今回の例ではページャは使用しませんので省いています。

<button id="load" type="button">
  LOAD
</button>
<button id="save" type="button">
  SAVE
</button>
<table id="grid"></table>
<div id="status"></div>

 

Javascript

色々な書き方があるかと思うのですが、私はいつも以下のように書いています。

document.ready() 時にすべて読み込むようにするのが一般的だと思われますが、jqGrid オブジェクトの定義は別で分けてあります。

$(function() {
    // データの定義
    var mydata = [ {
        id : "10",
        invdate : "2007-10-01",
        name : "test1",
        note : "note1",
        amount : "200.00",
        tax : "10.00",
        closed : true,
        ship_via : "TN",
        total : "210.00"
    }, {
        id : "20",
        invdate : "2007-10-02",
        name : "test2",
        note : "note2",
        amount : "300.00",
        tax : "20.00",
        closed : false,
        ship_via : "FE",
        total : "320.00"
    }, {
        id : "30",
        invdate : "2007-09-01",
        name : "test3",
        note : "note3",
        amount : "400.00",
        tax : "30.00",
        closed : false,
        ship_via : "FE",
        total : "430.00"
    }, {
        id : "40",
        invdate : "2007-10-04",
        name : "test4",
        note : "note4",
        amount : "200.00",
        tax : "10.00",
        closed : true,
        ship_via : "TN",
        total : "210.00"
    }, {
        id : "50",
        invdate : "2007-10-31",
        name : "test5",
        note : "note5",
        amount : "300.00",
        tax : "20.00",
        closed : false,
        ship_via : "FE",
        total : "320.00"
    }, {
        id : "60",
        invdate : "2007-09-06",
        name : "test6",
        note : "note6",
        amount : "400.00",
        tax : "30.00",
        closed : false,
        ship_via : "FE",
        total : "430.00"
    }, {
        id : "70",
        invdate : "2007-10-04",
        name : "test7",
        note : "note7",
        amount : "200.00",
        tax : "10.00",
        closed : true,
        ship_via : "TN",
        total : "210.00"
    }, {
        id : "80",
        invdate : "2007-10-03",
        name : "test8",
        note : "note8",
        amount : "300.00",
        tax : "20.00",
        closed : false,
        ship_via : "FE",
        total : "320.00"
    }, {
        id : "90",
        invdate : "2007-09-01",
        name : "test9",
        note : "note9",
        amount : "400.00",
        tax : "30.00",
        closed : false,
        ship_via : "TN",
        total : "430.00"
    }, {
        id : "100",
        invdate : "2007-09-08",
        name : "test10",
        note : "note10",
        amount : "500.00",
        tax : "30.00",
        closed : true,
        ship_via : "TN",
        total : "530.00"
    }, {
        id : "110",
        invdate : "2007-09-08",
        name : "test11",
        note : "note11",
        amount : "500.00",
        tax : "30.00",
        closed : false,
        ship_via : "FE",
        total : "530.00"
    }, {
        id : "120",
        invdate : "2007-09-10",
        name : "test12",
        note : "note12",
        amount : "500.00",
        tax : "30.00",
        closed : false,
        ship_via : "FE",
        total : "530.00"
    } ];

    // 選択されたIDを保持する配列
    var storedrows = [];

    // ID を追加する
    function setStored(rowid) {
        var i = storedrows.indexOf(rowid);
        if (i < 0) {
            storedrows.push(rowid);
        }
    }

    // ID を削除する
    function unSetStored(rowid) {
        var i = storedrows.indexOf(rowid);
        if (i > -1) {
            storedrows.splice(i, 1);
        }
    }

    // LOAD ボタンクリック時の処理
    $("#load").on("click", function() {
        // storedrows を初期化
        storedrows = [];
        // グリッドデータの消去
        $("#grid").jqGrid("clearGridData");
        // グリッドデータの定義とグリッドイベント登録
        $("#grid").jqGrid("setGridParam", {
            data : mydata,
            // 行選択時イベント
            onSelectRow : function(rowid, status, e) {
                // rowid は、クリックされた行のID
                // status は、チェックボックスの状態
                // チェックが入っている場合は、setStored、そうでない場合は unSetStored
                if (status) {
                    setStored(rowid);
                } else {
                    unSetStored(rowid);
                }
                // 選択されている行IDを配列で取得
                var s_row = $("#grid").jqGrid("getGridParam", "selarrrow");
                // 全行の行IDを配列で取得
                var a_row = $("#grid").jqGrid("getDataIDs");
                $("#status").html(a_row.length + " 件中 " + s_row.length + "件選択");
                // s_row と a_row を並べ替えて比較
                if (s_row.sort(function(a, b) { return a - b }).join() ==
                    a_row.sort(function(a, b) { return a - b }).join()) {
                    // 両方の配列が全く同じであれば、全選択チェックボックスにチェックを入れる
                    $("#cb_" + $.jgrid.jqID(this.p.id), this.grid.hDiv)[this.p.useProp ? "prop" : "attr"]("checked", true);
                }
            },
            // 全選択チェックボックスをクリックした際のイベント
            onSelectAll : function(ids, status) {
                // ids は status が true であろうが false であろうが、全レコードの ID を返します
                // status は、全選択チェックボックスの状態
                var a_row = $("#grid").jqGrid("getDataIDs");
                if (status) {
                    for ( var i in a_row) {
                        setStored(a_row[i]);
                    }
                    $("#status").html(a_row.length + " 件中 " + ids.length + "件選択");
                } else {
                    for ( var i in a_row) {
                        unSetStored(a_row[i]);
                    }
                    $("#status").html(a_row.length + " 件中 0 件選択");
                }
            },
            loadComplete : function() {
                // グリッド描画後 (主に並べ替え処理後)に storedrows の id にしたがって再度選択する
                for (var rowId of storedrows) {
                    $("#grid").jqGrid("setSelection", rowId, false);
                }
            }
        });
        // グリッドのリロード
        $("#grid").trigger("reloadGrid");
    });

    $("#save").on("click", function() {
        // グリッドのレコードがなにもない場合
        var count = $("#grid").jqGrid("getGridParam", "reccount");
        if (count < 1) {
            alert("レコードがありません。");
            return;
        }
        // レコードが選択されているかのチェック
        var ids = $("#grid").jqGrid("getGridParam", "selarrrow");
        if (ids.length < 1) {
            alert("レコードが選択されていません。");
            return;
        }
        var details = [];
        ids.sort(function(a, b) {
            return a - b;
        });

        // 選択されている行のデータを配列に追加
        for ( var i in ids) {
            details.push($("#grid").jqGrid("getRowData", ids[i]));
        }
        // 配列を JSON に変換してログに出力
        console.log(JSON.stringify(details));
    });

    // グリッドの表示
    myGrid.grid();
});

var myGrid = ((function() {
    return {
        grid : getGrid,
        count : getCount,
        selcount : getSelCount
    }

    // 選択されている行数を返す
    function getSelCount() {
        var arry = $("#grid").jqGrid("getGridParam", "selarrrow");
        return arry.length;
    }

    // 全レコード数を返す
    function getCount() {
        var count = $("#grid").jqGrid("getGridParam", "reccount");
        return count;
    }

    // グリッドの定義
    function getGrid(data) {
        $("#grid").jqGrid({
            data : data,
            datatype : "local",
            colModel : [ {
                // この id の値が onSelectRow や onSelectAll で返される。
                // id列 がない場合は単純に行番号が返されるようになる。
                name : "id",
                index : "id",
                sorttype : "int",
                hidden : true
            }, {
                label : "Client",
                name : "name",
                align : "center",
                width : 65,
                editrules : {
                    required : true
                }
            }, {
                label : "Date",
                name : "invdate",
                width : 80,
                align : "center",
                sorttype : "date",
                formatter : "date",
                formatoptions : {
                    newformat : "Y-m-d"
                }
            }, {
                label : "Closed",
                name : "closed",
                width : 70,
                align : "center",
                formatter : "checkbox"
            }, {
                label : "Shiped Via",
                name : "ship_via",
                width : 105,
                align : "center",
                formatter : "select",
            }, {
                label : "Notes",
                name : "note",
                width : 60,
                sortable : false
            }, {
                label : "Tax",
                name : "tax",
                width : 52,
                hidden : true
            }, {
                label : "Amount",
                name : "amount",
                width : 75
            }, {
                label : "Total",
                name : "total",
                width : 60
            } ],
            shrinkToFit : true,
            height : 300,
            width : 800,
            regional : "ja",
            rowTotal : 10000,
            rowNum : 10000,
            multiselect : true,
            viewrecords : true,
            gridComplete : function() {
                $("#status").html(getCount() + "件");
            }
        });
    }
}))();

 

サンプルの説明をすると、

  • [LOAD] ボタンを押すとデータを読み込んでグリッドに表示します。
  • 適度にレコードを選択して、[SAVE] ボタンを押すと、選択したレコードの JSON を console.log() で吐きます。
  • 全選択チェックボックスクリックで、全レコードが選択状態になります。
  • 手動で一行ずつすべてのレコードを選択状態にしても、全選択ボタンにチェックが入ります。

 

注意点

ソース中のコメントにも書いていますが、onSelectRow や、 onSelectAll のイベント発火時に渡される rowId や、ids ですが、これはあらかじめ、渡されてくるデータ (この場合は myData) にあらかじめ、id というフィールドがあって、colModelid 列に対する定義を入れておけば、この id の値が返ってきますが、そうでない場合は、単純にグリッドの列番号が返ってきます。

これは、グリッドのレコードを並べ替えた際に問題になります。

あらかじめレコードの ID としてレコードと一意に紐付いた id であれば、並べ替えた後もその列を指し示すことが出来ますが、単なるグリッドの列番号がIDとして使われる場合は、並べ替えた場合にID と意図したレコードが紐付かなくなります。

jqGrid で multiselect を有効にしていた場合、列ヘッダーをクリックして並べ替えを実行すると、一旦選択された情報がなくなってしまいます。

なので、並べ替えが実行された時 (beforeSort イベント発火時) に、storedrows の配列の内容を一旦クリアすることにしておけば問題ありませんが、並び替え後も選択状態を維持したい場合は、並べ替え直後 (loadComplete イベント発火時) に storedrows の内容に従って setSelection メソッドで選択内容を復元する必要があります。

このとき、列番号としての id なのか、レコードに紐付いた id なのかで結果が異なってくるのは想像に難くありません。

よって、

  • jqGridに渡すデータは、レコードと 一意 に紐付いた id を設定しておくことが望ましい。

となります。また、この場合の id は、フィールド名は id でないといけません。recordId や、rid のようなフィールド名ではダメなのです。

こういった問題を回避するには、

  1. 各列の並べ替えを無効にする (sortable : false)。
  2. 並べ替えを実行された場合 (beforeSort イベント発火時) に、storedrows を初期化する。
  3. jqGrid 以外のグリッドライブラリを使用する。

などが挙げられます。

まとめ

jqGrid 4.7.1 以降、ライセンスが変更されて随分と時間が経ちましたね。

また新しいグリッドライブラリを探す旅をしないといけません。

・・といいつつ、実は HandsonTable を試しに使ってみて、なかなか使いやすそうだったので、また近いうちに紹介できればと思っています。


 
記事を共有する
カテゴリー: javascriptweb

zaturendo

中小企業社内SE。

0件のコメント

コメントを残す

メールアドレスが公開されることはありません。