20
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

SlickGridを使ってみる

Last updated at Posted at 2018-10-06

##SlickGridを使ってみます。

###1.なぜ、SlickGridなのか

・WEBアプリ
・インラインのCRUDができる(編集行は2000行程度可能であること。)
・インラインはフィルタができること
という要件を満たす必要があり、軽量なgirdライブラリである「SlickGrid」を利用することにしました。

###2.ダウンロード
github.com/6pac/6pac.github.ioからダウンロードします。

※ https://github.com/mleibman/SlickGrid
こちらは古いバージョンのようです。こちらは使いません。

###3.実装
ダウンロードしたファイルを解凍すると、「examples」というフォルダの中に使用例がたくさんあります。

####(1)ベースとなるhtml
「example4-model.html」が今回の要件に一番近いので、それを編集して作成しました。

####(2). selectBoxで編集可能にする。
インラインで編集する場合に、SelectBoxで編集するエディタが必要です。
SelectをEditorにする方法は、以下に投稿がありましたので、そちらを参考にしてください。
【SlickGrid】Value,Label付きのselectセルを作る

カスタムエディタ。selectBoxを使うためのソースは参考サイトのまんまです。

slick.editors.select.js
(function ($) {
    $.extend(true, window, {
        "Extends": {
            "Editors": {
                "Select": SelectEditor
            },
            "Formatters": {
                "Select": SelectFormatter
            }
        }
    });

    function SelectEditor(args) {
        var $select;
        var defaultValue;
        var scope = this;

        this.init = function () {
            var option_str = "";
            for( key in args.column.options ){
                option_str += "<OPTION value='" + key +"'>" + args.column.options[key] + "</OPTION>";
            }
            $select = $("<SELECT tabIndex='0' class='editor-select'>"+ option_str +"</SELECT>");
            $select.appendTo(args.container);
            $select.focus();
        };

        this.destroy = function () {
            $select.remove();
        };

        this.focus = function () {
            $select.focus();
        };

        this.loadValue = function (item) {
            defaultValue = item[args.column.field];
            $select.val(defaultValue);
        };

        this.serializeValue = function () {
            return $select.val();
        };

        this.applyValue = function (item, state) {
            item[args.column.field] = state;
        };

        this.isValueChanged = function () {
            return ($select.val() != defaultValue);
        };

        this.validate = function () {
            return {
                valid: true,
                msg: null
            };
        };

        this.init();
    }

    function SelectFormatter(row, cell, value, columnDef, dataContext) {
        //valueがnullのときにundefinedになるので
        if (columnDef.options[value] == undefined){
          return "";
        }
        return columnDef.options[value];
    }

})(jQuery);

####(3). 選択した行を記憶する
「example-checkbox-row-select.html」を参考にして、チェックボックスを実装します。

####(4).作成したソース

かなり長いソースになってしまいましたが、サンプルのまんまで、それほど難解ではありません。

slick.html
<!DOCTYPE HTML>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <link rel="shortcut icon" type="image/ico" href="favicon.ico" />
  <title>SlickGrid example 4: Model</title>
  <link rel="stylesheet" href="../slick.grid.css" type="text/css"/>
  <link rel="stylesheet" href="../controls/slick.pager.css" type="text/css"/>
  <link rel="stylesheet" href="../../../lib/jquery-ui-1.12.1.custom/jquery-ui.min.css" type="text/css"/>
  <link rel="stylesheet" href="examples.css" type="text/css"/>
  <link rel="stylesheet" href="../controls/slick.columnpicker.css" type="text/css"/>
  <style>
    .cell-title {
      font-weight: bold;
    }

    .cell-effort-driven {
      text-align: center;
    }

    .cell-selection {
      border-right-color: silver;
      border-right-style: solid;
      background: #f5f5f5;
      color: gray;
      text-align: right;
      font-size: 10px;
    }

    .slick-row.selected .cell-selection {
      background-color: transparent; /* show default selected row background */
    }
    
    .editor-select{
      width:100%;
    }
  </style>
</head>
<body>

<div style="position:relative">
  <div style="width:760px;">
    <label>絞り込み:</label>
    <input type=text id="txtSearch" style="width:100px;">
    <button id="btnSelectRows">Select first 10 rows</button>
    <div id="myGrid" style="width:100%;height:500px;"></div>
    <div id="pager" style="width:100%;height:20px;"></div>
  </div>
  <br/>
  <button type="button" id="show-selected">選択した行をログに出力する</button>
</div>

<script src="../lib/firebugx.js"></script>
<script src="../lib/jquery-3.3.1.min.js"></script>
<script src="../lib/jquery-ui-1.12.1.min.js"></script>
<script src="../lib/jquery.event.drag-2.3.0.js"></script>
<script src="../slick.core.js"></script>
<script src="../plugins/slick.checkboxselectcolumn.js"></script>
<script src="../slick.formatters.js"></script>
<script src="../slick.editors.js"></script>
<!-- カスタムエディタ。selectBoxを使うためのソース -->
<script src="../slick.editors.select.js"></script>
<script src="../plugins/slick.rowselectionmodel.js"></script>
<script src="../slick.grid.js"></script>
<script src="../slick.dataview.js"></script>
<script src="../controls/slick.pager.js"></script>
<script src="../controls/slick.columnpicker.js"></script>

<script>
function isIEPreVer9() { var v = navigator.appVersion.match(/MSIE ([\d.]+)/i); return (v ? v[1] < 9 : false); }

<!-- SelectBoxのデータgrid上のselectボックスのプルダウンに表示されます -->
var my_options = {
  "1": "AAA",
  "2": "BBB",
  "3": "CCC"
};

var dataView;
var grid;
var data = [];
var checkboxSelector1;
var columns = [];
<!-- (6)CheckBoxの全選択ボタンの表示設定 -->
var isSelectAllCheckbox1Hidden = false;

var columns = [];

<!-- (7)CheckBox用の列オブジェクト -->
checkboxSelector1 = new Slick.CheckboxSelectColumn({
  cssClass: "slick-cell-checkboxsel"
});
columns.push(checkboxSelector1.getColumnDefinition());
<!-- チェックボックス以外はExsampleのままです -->
columns.push({id: "title", name: "Title", field: "title", width: 120, cssClass: "cell-title", editor: Slick.Editors.Text, validator: requiredFieldValidator, sortable: true});
columns.push({id: "desc", name: "Description", field: "description", width: 100, editor: Slick.Editors.LongText, sortable: true});
columns.push({id: "duration", name: "Duration", field: "duration", editor: Slick.Editors.Text, sortable: true});
columns.push({id: "%", name: "% Complete", field: "percentComplete", width: 80, resizable: false, formatter: Slick.Formatters.PercentCompleteBar, editor: Slick.Editors.PercentComplete, sortable: true});
columns.push({id: "start", name: "Start", field: "start", minWidth: 60, editor: Slick.Editors.Date, sortable: true});
columns.push({id: "finish", name: "Finish", field: "finish", minWidth: 60, editor: Slick.Editors.Date, sortable: true});
columns.push({id: "effort-driven", name: "Effort Driven", width: 80, minWidth: 20, maxWidth: 80, cssClass: "cell-effort-driven", field: "effortDriven", formatter: Slick.Formatters.Checkmark, editor: Slick.Editors.Checkbox, sortable: true});

<!-- 最終列にselectBoxの編集列を追加する -->
columns.push({id: "select",
  name: "Select",
  field: "select",
  formatter: Extends.Formatters.Select,
  editor: Extends.Editors.Select,
  options: my_options
});

var options = {
  columnPicker: {
    columnTitle: "Columns",
    hideForceFitButton: false,
    hideSyncResizeButton: false, 
    forceFitTitle: "Force fit columns",
    syncResizeTitle: "Synchronous resize",
  },
  editable: true,
  enableAddRow: true,
  enableCellNavigation: true,
  asyncEditorLoading: true,
  forceFitColumns: false,
  topPanelHeight: 25
};

var sortcol = "title";
var sortdir = 1;
var percentCompleteThreshold = 0;
var searchString = "";

function requiredFieldValidator(value) {
  if (value == null || value == undefined || !value.length) {
    return {valid: false, msg: "This is a required field"};
  }
  else {
    return {valid: true, msg: null};
  }
}

function myFilter(item, args) {
  if (item["percentComplete"] < args.percentCompleteThreshold) {
    return false;
  }

  if (args.searchString != "" && item["title"].indexOf(args.searchString) == -1) {
    return false;
  }

  return true;
}

function percentCompleteSort(a, b) {
  return a["percentComplete"] - b["percentComplete"];
}

function comparer(a, b) {
  var x = a[sortcol], y = b[sortcol];
  return (x == y ? 0 : (x > y ? 1 : -1));
}

function toggleFilterRow() {
  grid.setTopPanelVisibility(!grid.getOptions().showTopPanel);
}

$(".grid-header .ui-icon")
        .addClass("ui-state-default ui-corner-all")
        .mouseover(function (e) {
          $(e.target).addClass("ui-state-hover")
        })
        .mouseout(function (e) {
          $(e.target).removeClass("ui-state-hover")
        });

$(function () {

  // prepare the data
  for (var i = 0; i < 50000; i++) {
    data.push( {"id":"id_" + i, "num": i, "title": "Task " + i, "duration": "5 days", "percentComplete": Math.round(Math.random() * 100), "start": "01/01/2009", "finish": "01/05/2009", "effortDriven": (i % 5 == 0)});
  }

  dataView = new Slick.Data.DataView({ inlineFilters: true });
  grid = new Slick.Grid("#myGrid", dataView, columns, options);
  grid.setSelectionModel(new Slick.RowSelectionModel({selectActiveRow: false}));
  //チェックボックスをregist
  grid.registerPlugin(checkboxSelector1);

  var pager = new Slick.Controls.Pager(dataView, grid, $("#pager"));
  var columnpicker = new Slick.Controls.ColumnPicker(columns, grid, options);

  // move the filter panel defined in a hidden div into grid top panel
  $("#inlineFilterPanel")
      .appendTo(grid.getTopPanel())
      .show();

  grid.onCellChange.subscribe(function (e, args) {
    dataView.updateItem(args.item.id, args.item);
  });

  grid.onAddNewRow.subscribe(function (e, args) {
    var item = {"num": data.length, "id": "new_" + (Math.round(Math.random() * 10000)), "title": "New task", "duration": "1 day", "percentComplete": 0, "start": "01/01/2009", "finish": "01/01/2009", "effortDriven": false};
    $.extend(item, args.item);
    dataView.addItem(item);
  });

  grid.onKeyDown.subscribe(function (e) {
    // select all rows on ctrl-a
    if (e.which != 65 || !e.ctrlKey) {
      return false;
    }

    var rows = [];
    for (var i = 0; i < dataView.getLength(); i++) {
      rows.push(i);
    }

    grid.setSelectedRows(rows);
    e.preventDefault();
  });

  grid.onSort.subscribe(function (e, args) {
    sortdir = args.sortAsc ? 1 : -1;
    sortcol = args.sortCol.field;

    if (isIEPreVer9()) {
      // using temporary Object.prototype.toString override
      // more limited and does lexicographic sort only by default, but can be much faster

      var percentCompleteValueFn = function () {
        var val = this["percentComplete"];
        if (val < 10) {
          return "00" + val;
        } else if (val < 100) {
          return "0" + val;
        } else {
          return val;
        }
      };

      // use numeric sort of % and lexicographic for everything else
      dataView.fastSort((sortcol == "percentComplete") ? percentCompleteValueFn : sortcol, args.sortAsc);
    } else {
      // using native sort with comparer
      // preferred method but can be very slow in IE with huge datasets
      dataView.sort(comparer, args.sortAsc);
    }
  });

  // wire up model events to drive the grid
  // !! both dataView.onRowCountChanged and dataView.onRowsChanged MUST be wired to correctly update the grid
  // see Issue#91
  dataView.onRowCountChanged.subscribe(function (e, args) {
    grid.updateRowCount();
    grid.render();
  });

  dataView.onRowsChanged.subscribe(function (e, args) {
    grid.invalidateRows(args.rows);
    grid.render();
  });

  dataView.onPagingInfoChanged.subscribe(function (e, pagingInfo) {
    grid.updatePagingStatusFromView( pagingInfo );
  });

  // wire up the search textbox to apply the filter to the model
  $("#txtSearch").keyup(function (e) {
    Slick.GlobalEditorLock.cancelCurrentEdit();

    // clear on Esc
    if (e.which == 27) {
      this.value = "";
    }

    searchString = this.value;
    updateFilter();
  });

  function updateFilter() {
    dataView.setFilterArgs({
      percentCompleteThreshold: percentCompleteThreshold,
      searchString: searchString
    });
    dataView.refresh();
  }
  $("#btnSelectRows").click(function () {
    if (!Slick.GlobalEditorLock.commitCurrentEdit()) {
      return;
    }

    var rows = [];
    for (var i = 0; i < 10 && i < dataView.getLength(); i++) {
      rows.push(i);
    }

    grid.setSelectedRows(rows);
  });

  $("#show-selected").click(function () {
    var selectedrows = grid.getSelectedRows();

selectedrows.forEach(function( value ) { 
      alert(JSON.stringify(dataView.getItem(value)));
});
  });

  // initialize the model after all the events have been hooked up
  dataView.beginUpdate();
  dataView.setItems(data);
  dataView.setFilterArgs({
    percentCompleteThreshold: percentCompleteThreshold,
    searchString: searchString
  });
  dataView.setFilter(myFilter);
  dataView.endUpdate();

  // if you don't want the items that are not visible (due to being filtered out
  // or being on a different page) to stay selected, pass 'false' to the second arg
  dataView.syncGridSelection(grid, true);

  $("#gridContainer").resizable();
})
</script>
</body>
</html>

選択した行のオブジェクトは、以下のような感じで取得できます。

  $("#show-selected").click(function () {
    var selectedrows = grid.getSelectedRows();

selectedrows.forEach(function( value ) { 
      alert(JSON.stringify(dataView.getItem(value)));
});
  });

###3.動作確認
image.png

50000行の編集画面ですが、軽快にうごきます。

あとは、データをajaxで受信し、選択した行をサーバに送信して、サーバサイドで更新処理を実装してしまえば、サーバ上のある程度大量のデータ数をもつテーブルのインライン編集画面が実現できそうです。

20
33
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
33

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?