slickgrid 2.3.16.1 → 2.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/slickgrid.gemspec +1 -1
  3. data/vendor/assets/javascripts/slickgrid/controls/columnpicker.js +49 -34
  4. data/vendor/assets/javascripts/slickgrid/controls/gridmenu.js +32 -13
  5. data/vendor/assets/javascripts/slickgrid/core.js +174 -0
  6. data/vendor/assets/javascripts/slickgrid/dataview.js +47 -18
  7. data/vendor/assets/javascripts/slickgrid/editors.js +49 -27
  8. data/vendor/assets/javascripts/slickgrid/grid.js +5357 -3990
  9. data/vendor/assets/javascripts/slickgrid/groupitemmetadataprovider.js +4 -4
  10. data/vendor/assets/javascripts/slickgrid/plugins/autotooltips.js +2 -1
  11. data/vendor/assets/javascripts/slickgrid/plugins/cellcopymanager.js +2 -0
  12. data/vendor/assets/javascripts/slickgrid/plugins/cellexternalcopymanager.js +29 -9
  13. data/vendor/assets/javascripts/slickgrid/plugins/cellrangedecorator.js +2 -1
  14. data/vendor/assets/javascripts/slickgrid/plugins/cellrangeselector.js +70 -9
  15. data/vendor/assets/javascripts/slickgrid/plugins/cellselectionmodel.js +27 -5
  16. data/vendor/assets/javascripts/slickgrid/plugins/checkboxselectcolumn.js +157 -26
  17. data/vendor/assets/javascripts/slickgrid/plugins/draggablegrouping.js +145 -19
  18. data/vendor/assets/javascripts/slickgrid/plugins/headerbuttons.js +1 -0
  19. data/vendor/assets/javascripts/slickgrid/plugins/headermenu.js +8 -2
  20. data/vendor/assets/javascripts/slickgrid/plugins/rowdetailview.js +542 -231
  21. data/vendor/assets/javascripts/slickgrid/plugins/rowmovemanager.js +2 -1
  22. data/vendor/assets/javascripts/slickgrid/plugins/rowselectionmodel.js +1 -0
  23. data/vendor/assets/stylesheets/slickgrid/controls/draggablegrouping.scss +52 -0
  24. data/vendor/assets/stylesheets/slickgrid/controls/gridmenu.css +17 -3
  25. data/vendor/assets/stylesheets/slickgrid/grid.scss +54 -5
  26. data/vendor/assets/stylesheets/slickgrid/plugins/headermenu.scss +14 -0
  27. data/vendor/assets/stylesheets/slickgrid/plugins/rowdetailview.scss +9 -11
  28. metadata +3 -2
@@ -16,13 +16,34 @@
16
16
  }
17
17
  });
18
18
 
19
- /**
20
- * DraggableGrouping plugin to show/hide tooltips when columns are too narrow to fit content.
21
- * @constructor
22
- * @param {boolean} [options.enableForCells=true] - Enable tooltip for grid cells
23
- * @param {boolean} [options.enableForHeaderCells=false] - Enable tooltip for header cells
24
- * @param {number} [options.maxToolTipLength=null] - The maximum length for a tooltip
19
+ /***
20
+ * A plugin to add Draggable Grouping feature.
21
+ *
22
+ * USAGE:
23
+ *
24
+ * Add the plugin .js & .css files and register it with the grid.
25
+ *
26
+ *
27
+ * The plugin expose the following methods:
28
+ * destroy: used to destroy the plugin
29
+ * setDroppedGroups: provide option to set default grouping on loading
30
+ * clearDroppedGroups: provide option to clear grouping
31
+ * getSetupColumnReorder: its function to setup draggable feature agains Header Column, should be passed on grid option. Also possible to pass custom function
32
+ *
33
+ *
34
+ * The plugin expose the following event(s):
35
+ * onGroupChanged: pass the grouped columns to who subscribed.
36
+ *
37
+ * @param options {Object} Options:
38
+ * deleteIconCssClass: an extra CSS class to add to the delete button (default undefined), if deleteIconCssClass && deleteIconImage undefined then slick-groupby-remove-image class will be added
39
+ * deleteIconImage: a url to the delete button image (default undefined)
40
+ * groupIconCssClass: an extra CSS class to add to the grouping field hint (default undefined)
41
+ * groupIconImage: a url to the grouping field hint image (default undefined)
42
+ * dropPlaceHolderText: option to specify set own placeholder note text
43
+ *
44
+
25
45
  */
46
+
26
47
  function DraggableGrouping(options) {
27
48
  var _grid;
28
49
  var _gridUid;
@@ -30,10 +51,12 @@
30
51
  var _dataView;
31
52
  var dropbox;
32
53
  var dropboxPlaceholder;
54
+ var groupToggler;
33
55
  var _self = this;
34
56
  var _defaults = {
35
57
  };
36
-
58
+ var onGroupChanged = new Slick.Event();
59
+
37
60
  /**
38
61
  * Initialize plugin.
39
62
  */
@@ -45,16 +68,81 @@
45
68
  _dataView = _grid.getData();
46
69
 
47
70
  dropbox = $(_grid.getPreHeaderPanel());
48
- dropbox.html("<div class='slick-placeholder'>Drop a column header here to group by the column</div>");
71
+ var dropPlaceHolderText = options.dropPlaceHolderText || 'Drop a column header here to group by the column';
72
+ dropbox.html("<div class='slick-placeholder'>" + dropPlaceHolderText + "</div><div class='slick-group-toggle-all expanded' style='display:none'></div>");
49
73
 
50
- dropboxPlaceholder = dropbox.find(".slick-placeholder");
74
+ dropboxPlaceholder = dropbox.find(".slick-placeholder");
75
+ groupToggler = dropbox.find(".slick-group-toggle-all");
51
76
  setupColumnDropbox();
77
+
78
+
79
+ _grid.onHeaderCellRendered.subscribe(function (e, args) {
80
+ var column = args.column;
81
+ var node = args.node;
82
+ if (!$.isEmptyObject(column.grouping)) {
83
+ var groupableIcon = $("<span class='slick-column-groupable' />");
84
+ if(options.groupIconCssClass) groupableIcon.addClass(options.groupIconCssClass)
85
+ if(options.groupIconImage) groupableIcon.css("background", "url(" + options.groupIconImage + ") no-repeat center center");
86
+ $(node).css('cursor', 'pointer').append(groupableIcon);
87
+ }
88
+ })
89
+
90
+ for (var i = 0; i < _gridColumns.length; i++) {
91
+ var columnId = _gridColumns[i].field;
92
+ _grid.updateColumnHeader(columnId);
93
+ }
94
+
52
95
  }
53
96
 
97
+ function setupColumnReorder(grid, $headers, headerColumnWidthDiff, setColumns, setupColumnResize, columns, getColumnIndex, uid, trigger) {
98
+ $headers.filter(":ui-sortable").sortable("destroy");
99
+ var $headerDraggableGroupBy = $(grid.getPreHeaderPanel());
100
+ $headers.sortable({
101
+ distance: 3,
102
+ cursor: "default",
103
+ tolerance: "intersection",
104
+ helper: "clone",
105
+ placeholder: "slick-sortable-placeholder ui-state-default slick-header-column",
106
+ forcePlaceholderSize: true,
107
+ appendTo: "body",
108
+ start: function(e, ui) {
109
+ $(ui.helper).addClass("slick-header-column-active");
110
+ $headerDraggableGroupBy.find(".slick-placeholder").show();
111
+ $headerDraggableGroupBy.find(".slick-dropped-grouping").hide();
112
+ },
113
+ beforeStop: function(e, ui) {
114
+ $(ui.helper).removeClass("slick-header-column-active");
115
+ var hasDroppedColumn = $headerDraggableGroupBy.find(".slick-dropped-grouping").length;
116
+ if(hasDroppedColumn > 0){
117
+ $headerDraggableGroupBy.find(".slick-placeholder").hide();
118
+ $headerDraggableGroupBy.find(".slick-dropped-grouping").show();
119
+ }
120
+ },
121
+ stop: function(e) {
122
+ if (!grid.getEditorLock().commitCurrentEdit()) {
123
+ $(this).sortable("cancel");
124
+ return;
125
+ }
126
+ var reorderedIds = $headers.sortable("toArray");
127
+ var reorderedColumns = [];
128
+ for (var i = 0; i < reorderedIds.length; i++) {
129
+ reorderedColumns.push(columns[getColumnIndex(reorderedIds[i].replace(uid, ""))]);
130
+ }
131
+ setColumns(reorderedColumns);
132
+ trigger(grid.onColumnsReordered, {
133
+ grid: grid
134
+ });
135
+ e.stopPropagation();
136
+ setupColumnResize();
137
+ }
138
+ });
139
+ }
140
+
54
141
  /**
55
142
  * Destroy plugin.
56
143
  */
57
144
  function destroy() {
145
+ onGroupChanged.unsubscribe();
58
146
  }
59
147
 
60
148
 
@@ -105,10 +193,22 @@
105
193
  }
106
194
  }
107
195
  columnsGroupBy = newGroupingOrder;
108
- updateGroupBy();
196
+ updateGroupBy("sort-group");
109
197
  }
110
198
  });
111
199
  emptyDropbox = dropbox.html();
200
+
201
+ groupToggler.on('click', function(e) {
202
+ if (this.classList.contains('collapsed')) {
203
+ this.classList.remove('collapsed');
204
+ this.classList.add('expanded');
205
+ _dataView.expandAllGroups();
206
+ } else {
207
+ this.classList.add('collapsed');
208
+ this.classList.remove('expanded');
209
+ _dataView.collapseAllGroups();
210
+ }
211
+ });
112
212
  }
113
213
 
114
214
  var columnsGroupBy = [];
@@ -125,10 +225,16 @@
125
225
  if (columnAllowed) {
126
226
  _gridColumns.forEach(function(e, i, a) {
127
227
  if (e.id == columnid) {
128
- if (e.grouping != null) {
228
+ if (e.grouping != null && !$.isEmptyObject(e.grouping)) {
129
229
  var entry = $("<div id='" + _gridUid + e.id + "_entry' data-id='" + e.id + "' class='slick-dropped-grouping'>");
130
- var span = $("<span class='slick-groupby-remove'></span>").text(column.text() + " ")
131
- span.appendTo(entry);
230
+ var groupText = $("<div style='display: inline-flex'>" + column.text() + "</div>")
231
+ groupText.appendTo(entry);
232
+ var groupRemoveIcon = $("<div class='slick-groupby-remove'>&nbsp;</div>")
233
+ if(options.deleteIconCssClass) groupRemoveIcon.addClass(options.deleteIconCssClass);
234
+ if(options.deleteIconImage) groupRemoveIcon.css("background", "url(" + options.deleteIconImage + ") no-repeat center right");
235
+ if(!options.deleteIconCssClass && !options.deleteIconImage) groupRemoveIcon.addClass('slick-groupby-remove-image');
236
+ groupRemoveIcon.appendTo(entry);
237
+
132
238
  $("</div>").appendTo(entry);
133
239
  entry.appendTo(container);
134
240
  addColumnGroupBy(e, column, container, entry);
@@ -136,24 +242,37 @@
136
242
  }
137
243
  }
138
244
  });
245
+ groupToggler.css('display', 'block');
139
246
  }
140
247
  }
141
248
 
142
249
  function addColumnGroupBy(column) {
143
250
  columnsGroupBy.push(column);
144
- updateGroupBy();
251
+ updateGroupBy("add-group");
145
252
  }
146
253
 
147
254
  function addGroupByRemoveClickHandler(id, container, column, entry) {
148
255
  var text = entry;
149
- $("#" + _gridUid + id + "_entry").on('click', function() {
256
+ $("#" + _gridUid + id + "_entry >.slick-groupby-remove").on('click', function() {
150
257
  $(this).off('click');
151
258
  removeGroupBy(id, column, text);
152
259
  });
153
260
  }
154
261
 
262
+ function setDroppedGroups(groupingInfo) {
263
+ groupingInfos = (groupingInfo instanceof Array) ? groupingInfo : [groupingInfo];
264
+ dropboxPlaceholder.hide()
265
+ for (var i = 0; i < groupingInfos.length; i++) {
266
+ var column = $(_grid.getHeaderColumn(groupingInfos[i]));
267
+ handleGroupByDrop(dropbox, column);
268
+ }
269
+ }
155
270
  function clearDroppedGroups() {
156
271
  columnsGroupBy = [];
272
+ updateGroupBy("clear-all");
273
+ dropbox.find(".slick-dropped-grouping").remove();
274
+ groupToggler.css("display", "none");
275
+ dropboxPlaceholder.show()
157
276
  }
158
277
 
159
278
  function removeFromArray(arr) {
@@ -179,12 +298,13 @@
179
298
  if(columnsGroupBy.length == 0){
180
299
  dropboxPlaceholder.show();
181
300
  }
182
- updateGroupBy();
301
+ updateGroupBy("remove-group");
183
302
  }
184
303
 
185
- function updateGroupBy() {
304
+ function updateGroupBy(originator) {
186
305
  if (columnsGroupBy.length == 0) {
187
306
  _dataView.setGrouping([]);
307
+ onGroupChanged.notify({ caller: originator, groupColumns: [] });
188
308
  return;
189
309
  }
190
310
  var groupingArray = [];
@@ -195,13 +315,19 @@
195
315
  /*
196
316
  collapseAllGroups();
197
317
  */
318
+ onGroupChanged.notify({ caller: originator, groupColumns: groupingArray})
198
319
  }
199
320
 
200
321
  // Public API
201
322
  $.extend(this, {
202
323
  "init": init,
203
324
  "destroy": destroy,
204
- "clearDroppedGroups": clearDroppedGroups
325
+ "pluginName": "DraggableGrouping",
326
+
327
+ "onGroupChanged": onGroupChanged,
328
+ "setDroppedGroups": setDroppedGroups,
329
+ "clearDroppedGroups": clearDroppedGroups,
330
+ "getSetupColumnReorder": setupColumnReorder,
205
331
  });
206
332
  }
207
- })(jQuery);
333
+ })(jQuery);
@@ -170,6 +170,7 @@
170
170
  $.extend(this, {
171
171
  "init": init,
172
172
  "destroy": destroy,
173
+ "pluginName": "HeaderButtons",
173
174
 
174
175
  "onCommand": new Slick.Event()
175
176
  });
@@ -8,7 +8,6 @@
8
8
  }
9
9
  });
10
10
 
11
-
12
11
  /***
13
12
  * A plugin to add drop-down menus to column headers.
14
13
  *
@@ -46,6 +45,7 @@
46
45
  *
47
46
  * Available menu item options:
48
47
  * title: Menu item text.
48
+ * divider: Whether the current item is a divider, not an actual command.
49
49
  * disabled: Whether the item is disabled.
50
50
  * tooltip: Item tooltip.
51
51
  * command: A command identifier to be passed to the onCommand event handlers.
@@ -212,6 +212,11 @@
212
212
  $li.addClass("slick-header-menuitem-disabled");
213
213
  }
214
214
 
215
+ if (item.divider) {
216
+ $li.addClass("slick-header-menuitem-divider");
217
+ continue;
218
+ }
219
+
215
220
  if (item.tooltip) {
216
221
  $li.attr("title", item.tooltip);
217
222
  }
@@ -264,7 +269,7 @@
264
269
  var columnDef = $(this).data("column");
265
270
  var item = $(this).data("item");
266
271
 
267
- if (item.disabled) {
272
+ if (item.disabled || item.divider) {
268
273
  return;
269
274
  }
270
275
 
@@ -287,6 +292,7 @@
287
292
  $.extend(this, {
288
293
  "init": init,
289
294
  "destroy": destroy,
295
+ "pluginName": "HeaderMenu",
290
296
  "setOptions": setOptions,
291
297
 
292
298
  "onBeforeMenuShow": new Slick.Event(),
@@ -3,50 +3,79 @@
3
3
  * Original StackOverflow question & article making this possible (thanks to violet313)
4
4
  * https://stackoverflow.com/questions/10535164/can-slickgrids-row-height-be-dynamically-altered#29399927
5
5
  * http://violet313.org/slickgrids/#intro
6
- *
7
6
  *
8
7
  * USAGE:
9
- *
10
8
  * Add the slick.rowDetailView.(js|css) files and register the plugin with the grid.
11
9
  *
12
10
  * AVAILABLE ROW DETAIL OPTIONS:
13
- * cssClass: A CSS class to be added to the row detail
14
- * loadOnce: Booleang flag, when True will load the data once and then reuse it.
15
- * preTemplate: Template that will be used before the async process (typically used to show a spinner/loading)
16
- * postTemplate: Template that will be loaded once the async function finishes
17
- * process: Async server function call
18
- * panelRows: Row count to use for the template panel
19
- * useRowClick: Boolean flag, when True will open the row detail on a row click (from any column), default to False
20
- *
11
+ * cssClass: A CSS class to be added to the row detail
12
+ * expandedClass: Extra classes to be added to the expanded Toggle
13
+ * collapsedClass: Extra classes to be added to the collapse Toggle
14
+ * loadOnce: Defaults to false, when set to True it will load the data once and then reuse it.
15
+ * preTemplate: Template that will be used before the async process (typically used to show a spinner/loading)
16
+ * postTemplate: Template that will be loaded once the async function finishes
17
+ * process: Async server function call
18
+ * panelRows: Row count to use for the template panel
19
+ * useRowClick: Boolean flag, when True will open the row detail on a row click (from any column), default to False
20
+ * keyPrefix: Defaults to '_', prefix used for all the plugin metadata added to the item object (meta e.g.: padding, collapsed, parent)
21
+ * collapseAllOnSort: Defaults to true, which will collapse all row detail views when user calls a sort. Unless user implements a sort to deal with padding
22
+ * saveDetailViewOnScroll: Defaults to true, which will save the row detail view in a cache when it detects that it will become out of the viewport buffer
23
+ * useSimpleViewportCalc: Defaults to false, which will use simplified calculation of out or back of viewport visibility
24
+ *
21
25
  * AVAILABLE PUBLIC OPTIONS:
22
26
  * init: initiliaze the plugin
27
+ * expandableOverride: callback method that user can override the default behavior of making every row an expandable row (the logic to show or not the expandable icon).
23
28
  * destroy: destroy the plugin and it's events
24
29
  * collapseAll: collapse all opened row detail panel
25
- * getColumnDefinition: get the column definitions
30
+ * collapseDetailView: collapse a row by passing the item object (row detail)
31
+ * expandDetailView: expand a row by passing the item object (row detail)
32
+ * getColumnDefinition: get the column definitions
33
+ * getExpandedRows: get all the expanded rows
34
+ * getFilterItem: takes in the item we are filtering and if it is an expanded row returns it's parents row to filter on
26
35
  * getOptions: get current plugin options
36
+ * resizeDetailView: resize a row detail view, it will auto-calculate the number of rows it needs
37
+ * saveDetailView: save a row detail view content by passing the row object
27
38
  * setOptions: set or change some of the plugin options
28
- *
39
+ *
29
40
  * THE PLUGIN EXPOSES THE FOLLOWING SLICK EVENTS:
30
- * onAsyncResponse: This event must be used with the "notify" by the end user once the Asynchronous Server call returns the item detail
31
- * Event args:
32
- * itemDetail: Item detail returned from the async server call
33
- * detailView: An explicit view to use instead of template (Optional)
41
+ * onAsyncResponse: This event must be used with the "notify" by the end user once the Asynchronous Server call returns the item detail
42
+ * Event args:
43
+ * item: Item detail returned from the async server call
44
+ * detailView: An explicit view to use instead of template (Optional)
34
45
  *
35
46
  * onAsyncEndUpdate: Fired when the async response finished
36
- * Event args:
37
- * grid: Reference to the grid.
38
- * itemDetail: Column definition.
39
- *
47
+ * Event args:
48
+ * grid: Reference to the grid.
49
+ * item: Item data context
50
+ *
40
51
  * onBeforeRowDetailToggle: Fired before the row detail gets toggled
41
- * Event args:
42
- * grid: Reference to the grid.
43
- * item: Column definition.
44
- *
52
+ * Event args:
53
+ * grid: Reference to the grid.
54
+ * item: Item data context
55
+ *
45
56
  * onAfterRowDetailToggle: Fired after the row detail gets toggled
46
- * Event args:
47
- * grid: Reference to the grid.
48
- * item: Column definition.
57
+ * Event args:
58
+ * grid: Reference to the grid.
59
+ * item: Item data context
60
+ * expandedRows: Array of the Expanded Rows
61
+ *
62
+ * onRowOutOfViewportRange: Fired after a row becomes out of viewport range (user can't see the row anymore)
63
+ * Event args:
64
+ * grid: Reference to the grid.
65
+ * item: Item data context
66
+ * rowId: Id of the Row object (datacontext) in the Grid
67
+ * rowIndex: Index of the Row in the Grid
68
+ * expandedRows: Array of the Expanded Rows
69
+ * rowIdsOutOfViewport: Array of the Out of viewport Range Rows
49
70
  *
71
+ * onRowBackToViewportRange: Fired after the row detail gets toggled
72
+ * Event args:
73
+ * grid: Reference to the grid.
74
+ * item: Item data context
75
+ * rowId: Id of the Row object (datacontext) in the Grid
76
+ * rowIndex: Index of the Row in the Grid
77
+ * expandedRows: Array of the Expanded Rows
78
+ * rowIdsOutOfViewport: Array of the Out of viewport Range Rows
50
79
  */
51
80
  (function ($) {
52
81
  // register namespace
@@ -58,59 +87,134 @@
58
87
  }
59
88
  });
60
89
 
61
-
90
+ /** Constructor of the Row Detail View Plugin */
62
91
  function RowDetailView(options) {
63
92
  var _grid;
93
+ var _gridOptions;
94
+ var _gridUid;
95
+ var _expandableOverride = null;
64
96
  var _self = this;
97
+ var _lastRange = null;
65
98
  var _expandedRows = [];
66
99
  var _handler = new Slick.EventHandler();
100
+ var _outsideRange = 5;
101
+ var _visibleRenderedCellCount = 0;
67
102
  var _defaults = {
68
- columnId: "_detail_selector",
69
- cssClass: null,
70
- toolTip: "",
71
- width: 30
103
+ columnId: '_detail_selector',
104
+ cssClass: 'detailView-toggle',
105
+ expandedClass: null,
106
+ collapsedClass: null,
107
+ keyPrefix: '_',
108
+ loadOnce: false,
109
+ collapseAllOnSort: true,
110
+ saveDetailViewOnScroll: true,
111
+ useSimpleViewportCalc: false,
112
+ alwaysRenderColumn: true,
113
+ toolTip: '',
114
+ width: 30,
115
+ maxRows: null
72
116
  };
73
-
117
+ var _keyPrefix = _defaults.keyPrefix;
118
+ var _gridRowBuffer = 0;
119
+ var _rowIdsOutOfViewport = [];
74
120
  var _options = $.extend(true, {}, _defaults, options);
75
121
 
122
+ /**
123
+ * Initialize the plugin, which requires user to pass the SlickGrid Grid object
124
+ * @param grid: SlickGrid Grid object
125
+ */
76
126
  function init(grid) {
127
+ if (!grid) {
128
+ throw new Error('RowDetailView Plugin requires the Grid instance to be passed as argument to the "init()" method');
129
+ }
77
130
  _grid = grid;
131
+ _gridUid = grid.getUID();
132
+ _gridOptions = grid.getOptions() || {};
78
133
  _dataView = _grid.getData();
134
+ _keyPrefix = _options && _options.keyPrefix || '_';
79
135
 
80
136
  // Update the minRowBuffer so that the view doesn't disappear when it's at top of screen + the original default 3
137
+ _gridRowBuffer = _grid.getOptions().minRowBuffer;
81
138
  _grid.getOptions().minRowBuffer = _options.panelRows + 3;
82
139
 
83
140
  _handler
84
141
  .subscribe(_grid.onClick, handleClick)
85
- .subscribe(_grid.onSort, handleSort)
86
- .subscribe(_grid.onScroll, handleScroll);
142
+ .subscribe(_grid.onScroll, handleScroll);
143
+
144
+ // Sort will, by default, Collapse all of the open items (unless user implements his own onSort which deals with open row and padding)
145
+ if (_options.collapseAllOnSort) {
146
+ _handler.subscribe(_grid.onSort, collapseAll);
147
+ _expandedRows = [];
148
+ _rowIdsOutOfViewport = [];
149
+ }
150
+
151
+ _handler.subscribe(_grid.getData().onRowCountChanged, function () {
152
+ _grid.updateRowCount();
153
+ _grid.render();
154
+ });
87
155
 
88
- _grid.getData().onRowCountChanged.subscribe(function () { _grid.updateRowCount(); _grid.render(); });
89
- _grid.getData().onRowsChanged.subscribe(function (e, a) { _grid.invalidateRows(a.rows); _grid.render(); });
156
+ _handler.subscribe(_grid.getData().onRowsChanged, function (e, a) {
157
+ _grid.invalidateRows(a.rows);
158
+ _grid.render();
159
+ });
90
160
 
91
161
  // subscribe to the onAsyncResponse so that the plugin knows when the user server side calls finished
92
162
  subscribeToOnAsyncResponse();
163
+
164
+ // if we use the alternative & simpler calculation of the out of viewport range
165
+ // we will need to know how many rows are rendered on the screen and we need to wait for grid to be rendered
166
+ // unfortunately there is no triggered event for knowing when grid is finished, so we use 250ms delay and it's typically more than enough
167
+ if (_options.useSimpleViewportCalc) {
168
+ _handler.subscribe(_grid.onRendered, function (e, args) {
169
+ if (args && args.endRow) {
170
+ _visibleRenderedCellCount = args.endRow - args.startRow;
171
+ }
172
+ });
173
+ }
93
174
  }
94
175
 
176
+ /** destroy the plugin and it's events */
95
177
  function destroy() {
96
178
  _handler.unsubscribeAll();
97
179
  _self.onAsyncResponse.unsubscribe();
98
180
  _self.onAsyncEndUpdate.unsubscribe();
99
181
  _self.onAfterRowDetailToggle.unsubscribe();
100
182
  _self.onBeforeRowDetailToggle.unsubscribe();
183
+ _self.onRowOutOfViewportRange.unsubscribe();
184
+ _self.onRowBackToViewportRange.unsubscribe();
101
185
  }
102
186
 
103
- function getOptions(options) {
187
+ /** Get current plugin options */
188
+ function getOptions() {
104
189
  return _options;
105
190
  }
106
191
 
192
+ /** set or change some of the plugin options */
107
193
  function setOptions(options) {
108
194
  _options = $.extend(true, {}, _options, options);
109
195
  }
110
-
196
+
197
+ /** Find a value in an array and return the index when (or -1 when not found) */
198
+ function arrayFindIndex(sourceArray, value) {
199
+ if (sourceArray) {
200
+ for (var i = 0; i < sourceArray.length; i++) {
201
+ if (sourceArray[i] === value) {
202
+ return i;
203
+ }
204
+ }
205
+ }
206
+ return -1;
207
+ }
208
+
209
+ /** Handle mouse click event */
111
210
  function handleClick(e, args) {
211
+ var dataContext = _grid.getDataItem(args.row);
212
+ if (!checkExpandableOverride(args.row, dataContext, _grid)) {
213
+ return;
214
+ }
215
+
112
216
  // clicking on a row select checkbox
113
- if (_options.useRowClick || _grid.getColumns()[args.cell].id === _options.columnId && $(e.target).hasClass("detailView-toggle")) {
217
+ if (_options.useRowClick || _grid.getColumns()[args.cell].id === _options.columnId && $(e.target).hasClass(_options.cssClass)) {
114
218
  // if editing, try to commit
115
219
  if (_grid.getEditorLock().isActive() && !_grid.getEditorLock().commitCurrentEdit()) {
116
220
  e.preventDefault();
@@ -122,16 +226,17 @@
122
226
 
123
227
  // trigger an event before toggling
124
228
  _self.onBeforeRowDetailToggle.notify({
125
- "grid": _grid,
126
- "item": item
229
+ 'grid': _grid,
230
+ 'item': item
127
231
  }, e, _self);
128
232
 
129
- toggleRowSelection(item);
233
+ toggleRowSelection(args.row, item);
130
234
 
131
235
  // trigger an event after toggling
132
236
  _self.onAfterRowDetailToggle.notify({
133
- "grid": _grid,
134
- "item": item
237
+ 'grid': _grid,
238
+ 'item': item,
239
+ 'expandedRows': _expandedRows,
135
240
  }, e, _self);
136
241
 
137
242
  e.stopPropagation();
@@ -139,113 +244,229 @@
139
244
  }
140
245
  }
141
246
 
142
- // Sort will just collapse all of the open items
143
- function handleSort(e, args) {
144
- collapseAll();
247
+ /** If we scroll save detail views that go out of cache range */
248
+ function handleScroll(e, args) {
249
+ if (_options.useSimpleViewportCalc) {
250
+ calculateOutOfRangeViewsSimplerVersion();
251
+ } else {
252
+ calculateOutOfRangeViews();
253
+ }
145
254
  }
146
255
 
147
- // If we scroll save detail views that go out of cache range
148
- function handleScroll(e, args) {
149
-
150
- var range = _grid.getRenderedRange();
151
-
152
- var start = (range.top > 0 ? range.top : 0);
153
- var end = (range.bottom > _dataView.getLength() ? range.bottom : _dataView.getLength());
154
-
155
- // Get the item at the top of the view
156
- var topMostItem = _dataView.getItemByIdx(start);
157
-
158
- // Check it is a parent item
159
- if (topMostItem._parent == undefined)
160
- {
161
- // This is a standard row as we have no parent.
162
- var nextItem = _dataView.getItemByIdx(start + 1);
163
- if(nextItem !== undefined && nextItem._parent !== undefined)
164
- {
165
- // This is likely the expanded Detail Row View
166
- // Check for safety
167
- if(nextItem._parent == topMostItem)
168
- {
169
- saveDetailView(topMostItem);
170
- }
256
+ /** Calculate when expanded rows become out of view range */
257
+ function calculateOutOfRangeViews() {
258
+ if (_grid) {
259
+ var renderedRange = _grid.getRenderedRange();
260
+ // Only check if we have expanded rows
261
+ if (_expandedRows.length > 0) {
262
+ // Assume scroll direction is down by default.
263
+ var scrollDir = 'DOWN';
264
+ if (_lastRange) {
265
+ // Some scrolling isn't anything as the range is the same
266
+ if (_lastRange.top === renderedRange.top && _lastRange.bottom === renderedRange.bottom) {
267
+ return;
268
+ }
269
+
270
+ // If our new top is smaller we are scrolling up
271
+ if (_lastRange.top > renderedRange.top ||
272
+ // Or we are at very top but our bottom is increasing
273
+ (_lastRange.top === 0 && renderedRange.top === 0) && _lastRange.bottom > renderedRange.bottom) {
274
+ scrollDir = 'UP';
171
275
  }
276
+ }
172
277
  }
173
278
 
174
- // Find the bottom most item that is likely to go off screen
175
- var bottomMostItem = _dataView.getItemByIdx(end - 1);
279
+ _expandedRows.forEach(function (row) {
280
+ var rowIndex = _dataView.getRowById(row.id);
176
281
 
177
- // If we are a detailView and we are about to go out of cache view
178
- if(bottomMostItem._parent !== undefined)
179
- {
180
- saveDetailView(bottomMostItem._parent);
181
-
282
+ var rowPadding = row[_keyPrefix + 'sizePadding'];
283
+ var rowOutOfRange = arrayFindIndex(_rowIdsOutOfViewport, row.id) >= 0;
284
+
285
+ if (scrollDir === 'UP') {
286
+ // save the view when asked
287
+ if (_options.saveDetailViewOnScroll) {
288
+ // If the bottom item within buffer range is an expanded row save it.
289
+ if (rowIndex >= renderedRange.bottom - _gridRowBuffer) {
290
+ saveDetailView(row);
291
+ }
292
+ }
293
+
294
+ // If the row expanded area is within the buffer notify that it is back in range
295
+ if (rowOutOfRange && rowIndex - _outsideRange < renderedRange.top && rowIndex >= renderedRange.top) {
296
+ notifyBackToViewportWhenDomExist(row, row.id);
297
+ }
298
+
299
+ // if our first expanded row is about to go off the bottom
300
+ else if (!rowOutOfRange && (rowIndex + rowPadding) > renderedRange.bottom) {
301
+ notifyOutOfViewport(row, row.id);
302
+ }
303
+ }
304
+ else if (scrollDir === 'DOWN') {
305
+ // save the view when asked
306
+ if (_options.saveDetailViewOnScroll) {
307
+ // If the top item within buffer range is an expanded row save it.
308
+ if (rowIndex <= renderedRange.top + _gridRowBuffer) {
309
+ saveDetailView(row);
310
+ }
311
+ }
312
+
313
+ // If row index is i higher than bottom with some added value (To ignore top rows off view) and is with view and was our of range
314
+ if (rowOutOfRange && (rowIndex + rowPadding + _outsideRange) > renderedRange.bottom && rowIndex < rowIndex + rowPadding) {
315
+ notifyBackToViewportWhenDomExist(row, row.id);
316
+ }
317
+
318
+ // if our row is outside top of and the buffering zone but not in the array of outOfVisable range notify it
319
+ else if (!rowOutOfRange && rowIndex < renderedRange.top) {
320
+ notifyOutOfViewport(row, row.id);
321
+ }
322
+ }
323
+ });
324
+ _lastRange = renderedRange;
325
+ }
326
+ }
327
+
328
+ /** This is an alternative & more simpler version of the Calculate when expanded rows become out of view range */
329
+ function calculateOutOfRangeViewsSimplerVersion() {
330
+ if (_grid) {
331
+ var renderedRange = _grid.getRenderedRange();
332
+
333
+ _expandedRows.forEach(function (row) {
334
+ var rowIndex = _dataView.getRowById(row.id);
335
+ var isOutOfVisibility = checkIsRowOutOfViewportRange(rowIndex, renderedRange);
336
+ if (!isOutOfVisibility && arrayFindIndex(_rowIdsOutOfViewport, row.id) >= 0) {
337
+ notifyBackToViewportWhenDomExist(row, row.id);
338
+ } else if (isOutOfVisibility) {
339
+ notifyOutOfViewport(row, row.id);
340
+ }
341
+ });
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Check if the row became out of visible range (when user can't see it anymore)
347
+ * @param rowIndex
348
+ * @param renderedRange from SlickGrid
349
+ */
350
+ function checkIsRowOutOfViewportRange(rowIndex, renderedRange) {
351
+ if (Math.abs(renderedRange.bottom - _gridRowBuffer - rowIndex) > _visibleRenderedCellCount * 2) {
352
+ return true;
353
+ }
354
+ return false;
355
+ }
356
+
357
+ /** Send a notification, through "onRowOutOfViewportRange", that is out of the viewport range */
358
+ function notifyOutOfViewport(item, rowId) {
359
+ var rowIndex = item.rowIndex || _dataView.getRowById(item.id);
360
+
361
+ _self.onRowOutOfViewportRange.notify({
362
+ 'grid': _grid,
363
+ 'item': item,
364
+ 'rowId': rowId,
365
+ 'rowIndex': rowIndex,
366
+ 'expandedRows': _expandedRows,
367
+ 'rowIdsOutOfViewport': syncOutOfViewportArray(rowId, true)
368
+ }, null, _self);
369
+ }
370
+
371
+ /** Send a notification, through "onRowBackToViewportRange", that a row came back to the viewport */
372
+ function notifyBackToViewportWhenDomExist(item, rowId) {
373
+ var rowIndex = item.rowIndex || _dataView.getRowById(item.id);
374
+
375
+ setTimeout(function () {
376
+ // make sure View Row DOM Element really exist before notifying that it's a row that is visible again
377
+ if ($('.cellDetailView_' + item.id).length) {
378
+ _self.onRowBackToViewportRange.notify({
379
+ 'grid': _grid,
380
+ 'item': item,
381
+ 'rowId': rowId,
382
+ 'rowIndex': rowIndex,
383
+ 'expandedRows': _expandedRows,
384
+ 'rowIdsOutOfViewport': syncOutOfViewportArray(rowId, false)
385
+ }, null, _self);
182
386
  }
387
+ }, 100);
388
+ }
389
+
390
+ /**
391
+ * This function will sync the out of viewport array whenever necessary.
392
+ * The sync can add a row (when necessary, no need to add again if it already exist) or delete a row from the array.
393
+ * @param rowId: number
394
+ * @param isAdding: are we adding or removing a row?
395
+ */
396
+ function syncOutOfViewportArray(rowId, isAdding) {
397
+ var arrayRowIndex = arrayFindIndex(_rowIdsOutOfViewport, rowId);
398
+
399
+ if (isAdding && arrayRowIndex < 0) {
400
+ _rowIdsOutOfViewport.push(rowId);
401
+ } else if (!isAdding && arrayRowIndex >= 0) {
402
+ _rowIdsOutOfViewport.splice(arrayRowIndex, 1);
403
+ }
404
+ return _rowIdsOutOfViewport;
183
405
  }
184
-
406
+
185
407
  // Toggle between showing and hiding a row
186
- function toggleRowSelection(row) {
187
- _grid.getData().beginUpdate();
188
- HandleAccordionShowHide(row);
189
- _grid.getData().endUpdate();
408
+ function toggleRowSelection(rowNumber, dataContext) {
409
+ if (!checkExpandableOverride(rowNumber, dataContext, _grid)) {
410
+ return;
411
+ }
412
+
413
+ _dataView.beginUpdate();
414
+ handleAccordionShowHide(dataContext);
415
+ _dataView.endUpdate();
190
416
  }
191
417
 
192
- // Collapse all of the open items
418
+ /** Collapse all of the open items */
193
419
  function collapseAll() {
420
+ _dataView.beginUpdate();
194
421
  for (var i = _expandedRows.length - 1; i >= 0; i--) {
195
- collapseItem(_expandedRows[i]);
422
+ collapseDetailView(_expandedRows[i], true);
196
423
  }
424
+ _dataView.endUpdate();
197
425
  }
198
-
199
- // Saves the current state of the detail view
200
- function saveDetailView(item)
201
- {
202
- var view = $("#innerDetailView_" + item.id);
203
- if (view)
204
- {
205
- var html = $("#innerDetailView_" + item.id).html();
206
- if(html !== undefined)
207
- {
208
- item._detailContent = html;
209
- }
210
- }
211
- }
212
-
213
- // Colapse an Item so it is notlonger seen
214
- function collapseItem(item) {
215
-
426
+
427
+ /** Colapse an Item so it is not longer seen */
428
+ function collapseDetailView(item, isMultipleCollapsing) {
429
+ if (!isMultipleCollapsing) {
430
+ _dataView.beginUpdate();
431
+ }
216
432
  // Save the details on the collapse assuming onetime loading
217
433
  if (_options.loadOnce) {
218
- saveDetailView(item);
434
+ saveDetailView(item);
219
435
  }
220
-
221
- item._collapsed = true;
222
- for (var idx = 1; idx <= item._sizePadding; idx++) {
223
- _dataView.deleteItem(item.id + "." + idx);
436
+
437
+ item[_keyPrefix + 'collapsed'] = true;
438
+ for (var idx = 1; idx <= item[_keyPrefix + 'sizePadding']; idx++) {
439
+ _dataView.deleteItem(item.id + '.' + idx);
224
440
  }
225
- item._sizePadding = 0;
441
+ item[_keyPrefix + 'sizePadding'] = 0;
226
442
  _dataView.updateItem(item.id, item);
227
443
 
228
444
  // Remove the item from the expandedRows
229
445
  _expandedRows = _expandedRows.filter(function (r) {
230
446
  return r.id !== item.id;
231
447
  });
448
+
449
+ if (!isMultipleCollapsing) {
450
+ _dataView.endUpdate();
451
+ }
232
452
  }
233
453
 
234
- // Expand a row given the dataview item that is to be expanded
235
- function expandItem(item) {
236
- item._collapsed = false;
454
+ /** Expand a row given the dataview item that is to be expanded */
455
+ function expandDetailView(item) {
456
+ item[_keyPrefix + 'collapsed'] = false;
237
457
  _expandedRows.push(item);
238
-
458
+
239
459
  // In the case something went wrong loading it the first time such a scroll of screen before loaded
240
- if (!item._detailContent) item._detailViewLoaded = false;
241
-
460
+ if (!item[_keyPrefix + 'detailContent']) item[_keyPrefix + 'detailViewLoaded'] = false;
461
+
242
462
  // display pre-loading template
243
- if (!item._detailViewLoaded || _options.loadOnce !== true) {
244
- item._detailContent = _options.preTemplate(item);
463
+ if (!item[_keyPrefix + 'detailViewLoaded'] || _options.loadOnce !== true) {
464
+ item[_keyPrefix + 'detailContent'] = _options.preTemplate(item);
245
465
  } else {
246
466
  _self.onAsyncResponse.notify({
247
- "itemDetail": item,
248
- "detailView": item._detailContent
467
+ 'item': item,
468
+ 'itemDetail': item,
469
+ 'detailView': item[_keyPrefix + 'detailContent']
249
470
  }, undefined, this);
250
471
  applyTemplateNewLineHeight(item);
251
472
  _dataView.updateItem(item.id, item);
@@ -260,196 +481,286 @@
260
481
  _options.process(item);
261
482
  }
262
483
 
484
+ /** Saves the current state of the detail view */
485
+ function saveDetailView(item) {
486
+ var view = $('.' + _gridUid + ' .innerDetailView_' + item.id);
487
+ if (view) {
488
+ var html = $('.' + _gridUid + ' .innerDetailView_' + item.id).html();
489
+ if (html !== undefined) {
490
+ item[_keyPrefix + 'detailContent'] = html;
491
+ }
492
+ }
493
+ }
494
+
263
495
  /**
264
496
  * subscribe to the onAsyncResponse so that the plugin knows when the user server side calls finished
265
- * the response has to be as "args.itemDetail" with it's data back
497
+ * the response has to be as "args.item" (or "args.itemDetail") with it's data back
266
498
  */
267
- function subscribeToOnAsyncResponse() {
268
- _self.onAsyncResponse.subscribe(function (e, args) {
269
- if (!args || !args.itemDetail) {
270
- throw 'Slick.RowDetailView plugin requires the onAsyncResponse() to supply "args.itemDetail" property.'
499
+ function subscribeToOnAsyncResponse() {
500
+ _self.onAsyncResponse.subscribe(function (e, args) {
501
+ if (!args || (!args.item && !args.itemDetail)) {
502
+ throw 'Slick.RowDetailView plugin requires the onAsyncResponse() to supply "args.item" property.'
271
503
  }
272
504
 
505
+ // we accept item/itemDetail, just get the one which has data
506
+ var itemDetail = args.item || args.itemDetail;
507
+
273
508
  // If we just want to load in a view directly we can use detailView property to do so
274
509
  if (args.detailView) {
275
- args.itemDetail._detailContent = args.detailView;
510
+ itemDetail[_keyPrefix + 'detailContent'] = args.detailView;
276
511
  } else {
277
- args.itemDetail._detailContent = _options.postTemplate(args.itemDetail);
512
+ itemDetail[_keyPrefix + 'detailContent'] = _options.postTemplate(itemDetail);
278
513
  }
279
514
 
280
- args.itemDetail._detailViewLoaded = true;
281
-
282
- var idxParent = _dataView.getIdxById(args.itemDetail.id);
283
- _dataView.updateItem(args.itemDetail.id, args.itemDetail);
515
+ itemDetail[_keyPrefix + 'detailViewLoaded'] = true;
516
+ _dataView.updateItem(itemDetail.id, itemDetail);
284
517
 
285
518
  // trigger an event once the post template is finished loading
286
519
  _self.onAsyncEndUpdate.notify({
287
- "grid": _grid,
288
- "itemDetail": args.itemDetail
520
+ 'grid': _grid,
521
+ 'item': itemDetail,
522
+ 'itemDetail': itemDetail
289
523
  }, e, _self);
290
524
  });
291
525
  }
292
526
 
293
- function HandleAccordionShowHide(item) {
527
+ /** When row is getting toggled, we will handle the action of collapsing/expanding */
528
+ function handleAccordionShowHide(item) {
294
529
  if (item) {
295
- if (!item._collapsed) {
296
- collapseItem(item);
530
+ if (!item[_keyPrefix + 'collapsed']) {
531
+ collapseDetailView(item);
297
532
  } else {
298
- expandItem(item);
533
+ expandDetailView(item);
299
534
  }
300
535
  }
301
536
  }
302
537
 
303
538
  //////////////////////////////////////////////////////////////
304
539
  //////////////////////////////////////////////////////////////
540
+
541
+ /** Get the Row Detail padding (which are the rows dedicated to the detail panel) */
305
542
  var getPaddingItem = function (parent, offset) {
306
543
  var item = {};
307
544
 
308
545
  for (var prop in _grid.getData()) {
309
546
  item[prop] = null;
310
547
  }
311
- item.id = parent.id + "." + offset;
548
+ item.id = parent.id + '.' + offset;
312
549
 
313
- //additional hidden padding metadata fields
314
- item._collapsed = true;
315
- item._isPadding = true;
316
- item._parent = parent;
317
- item._offset = offset;
550
+ // additional hidden padding metadata fields
551
+ item[_keyPrefix + 'collapsed'] = true;
552
+ item[_keyPrefix + 'isPadding'] = true;
553
+ item[_keyPrefix + 'parent'] = parent;
554
+ item[_keyPrefix + 'offset'] = offset;
318
555
 
319
556
  return item;
320
557
  }
321
558
 
322
559
  //////////////////////////////////////////////////////////////
323
- //create the detail ctr node. this belongs to the dev & can be custom-styled as per
560
+ // create the detail ctr node. this belongs to the dev & can be custom-styled as per
324
561
  //////////////////////////////////////////////////////////////
325
562
  function applyTemplateNewLineHeight(item) {
326
- // the height seems to be calculated by the template row count (how many line of items does the template have)
563
+ // the height is calculated by the template row count (how many line of items does the template view have)
327
564
  var rowCount = _options.panelRows;
328
565
 
329
- //calculate padding requirements based on detail-content..
330
- //ie. worst-case: create an invisible dom node now &find it's height.
331
- var lineHeight = 13; //we know cuz we wrote the custom css innit ;)
332
- item._sizePadding = Math.ceil(((rowCount * 2) * lineHeight) / _grid.getOptions().rowHeight);
333
- item._height = (item._sizePadding * _grid.getOptions().rowHeight);
334
-
566
+ // calculate padding requirements based on detail-content..
567
+ // ie. worst-case: create an invisible dom node now & find it's height.
568
+ var lineHeight = 13; // we know cuz we wrote the custom css init ;)
569
+ item[_keyPrefix + 'sizePadding'] = Math.ceil(((rowCount * 2) * lineHeight) / _gridOptions.rowHeight);
570
+ item[_keyPrefix + 'height'] = (item[_keyPrefix + 'sizePadding'] * _gridOptions.rowHeight);
335
571
  var idxParent = _dataView.getIdxById(item.id);
336
- for (var idx = 1; idx <= item._sizePadding; idx++) {
572
+ for (var idx = 1; idx <= item[_keyPrefix + 'sizePadding']; idx++) {
337
573
  _dataView.insertItem(idxParent + idx, getPaddingItem(item, idx));
338
574
  }
339
575
  }
340
576
 
341
-
577
+ /** Get the Column Definition of the first column dedicated to toggling the Row Detail View */
342
578
  function getColumnDefinition() {
343
579
  return {
344
580
  id: _options.columnId,
345
- name: "",
581
+ name: '',
346
582
  toolTip: _options.toolTip,
347
- field: "sel",
583
+ field: 'sel',
348
584
  width: _options.width,
349
585
  resizable: false,
350
586
  sortable: false,
587
+ alwaysRenderColumn: _options.alwaysRenderColumn,
351
588
  cssClass: _options.cssClass,
352
589
  formatter: detailSelectionFormatter
353
590
  };
354
591
  }
355
592
 
356
- function detailSelectionFormatter(row, cell, value, columnDef, dataContext) {
357
-
358
- if (dataContext._collapsed == undefined) {
359
- dataContext._collapsed = true,
360
- dataContext._sizePadding = 0, //the required number of pading rows
361
- dataContext._height = 0, //the actual height in pixels of the detail field
362
- dataContext._isPadding = false,
363
- dataContext._parent = undefined,
364
- dataContext._offset = 0
365
- }
593
+ /** return the currently expanded rows */
594
+ function getExpandedRows() {
595
+ return _expandedRows;
596
+ }
366
597
 
367
- if (dataContext._isPadding == true) {
368
- //render nothing
369
- } else if (dataContext._collapsed) {
370
- return "<div class='detailView-toggle expand'></div>";
598
+ /** The Formatter of the toggling icon of the Row Detail */
599
+ function detailSelectionFormatter(row, cell, value, columnDef, dataContext) {
600
+ if (!checkExpandableOverride(row, dataContext, grid)) {
601
+ return null;
371
602
  } else {
372
- var html = [];
373
- var rowHeight = _grid.getOptions().rowHeight;
374
- var bottomMargin = 5;
375
-
376
- //V313HAX:
377
- //putting in an extra closing div after the closing toggle div and ommiting a
378
- //final closing div for the detail ctr div causes the slickgrid renderer to
379
- //insert our detail div as a new column ;) ~since it wraps whatever we provide
380
- //in a generic div column container. so our detail becomes a child directly of
381
- //the row not the cell. nice =) ~no need to apply a css change to the parent
382
- //slick-cell to escape the cell overflow clipping.
383
-
384
- //sneaky extra </div> inserted here-----------------v
385
- html.push("<div class='detailView-toggle collapse'></div></div>");
386
-
387
- html.push("<div id='cellDetailView_", dataContext.id, "' class='dynamic-cell-detail' "); //apply custom css to detail
388
- html.push("style='height:", dataContext._height, "px;"); //set total height of padding
389
- html.push("top:", rowHeight, "px'>"); //shift detail below 1st row
390
- html.push("<div id='detailViewContainer_", dataContext.id, "' class='detail-container' style='max-height:" + (dataContext._height - rowHeight + bottomMargin) + "px'>"); //sub ctr for custom styling
391
- html.push("<div id='innerDetailView_" , dataContext.id , "'>" , dataContext._detailContent, "</div></div>");
392
- //&omit a final closing detail container </div> that would come next
393
-
394
- return html.join("");
603
+ if (dataContext[_keyPrefix + 'collapsed'] == undefined) {
604
+ dataContext[_keyPrefix + 'collapsed'] = true,
605
+ dataContext[_keyPrefix + 'sizePadding'] = 0, //the required number of pading rows
606
+ dataContext[_keyPrefix + 'height'] = 0, //the actual height in pixels of the detail field
607
+ dataContext[_keyPrefix + 'isPadding'] = false,
608
+ dataContext[_keyPrefix + 'parent'] = undefined,
609
+ dataContext[_keyPrefix + 'offset'] = 0
610
+ }
611
+
612
+ if (dataContext[_keyPrefix + 'isPadding'] == true) {
613
+ // render nothing
614
+ }
615
+ else if (dataContext[_keyPrefix + 'collapsed']) {
616
+ var collapsedClasses = _options.cssClass + ' expand ';
617
+ if (_options.collapsedClass) {
618
+ collapsedClasses += _options.collapsedClass;
619
+ }
620
+ return '<div class="' + collapsedClasses + '"></div>';
621
+ }
622
+ else {
623
+ var html = [];
624
+ var rowHeight = _gridOptions.rowHeight;
625
+
626
+ var outterHeight = dataContext[_keyPrefix + 'sizePadding'] * _gridOptions.rowHeight;
627
+ if (_options.maxRows !== null && dataContext[_keyPrefix + 'sizePadding'] > _options.maxRows) {
628
+ outterHeight = _options.maxRows * rowHeight;
629
+ dataContext[_keyPrefix + 'sizePadding'] = _options.maxRows;
630
+ }
631
+
632
+ //V313HAX:
633
+ //putting in an extra closing div after the closing toggle div and ommiting a
634
+ //final closing div for the detail ctr div causes the slickgrid renderer to
635
+ //insert our detail div as a new column ;) ~since it wraps whatever we provide
636
+ //in a generic div column container. so our detail becomes a child directly of
637
+ //the row not the cell. nice =) ~no need to apply a css change to the parent
638
+ //slick-cell to escape the cell overflow clipping.
639
+
640
+ //sneaky extra </div> inserted here-----------------v
641
+ var expandedClasses = _options.cssClass + ' collapse ';
642
+ if (_options.expandedClass) expandedClasses += _options.expandedClass;
643
+ html.push('<div class="' + expandedClasses + '"></div></div>');
644
+
645
+ html.push('<div class="dynamic-cell-detail cellDetailView_', dataContext.id, '" '); //apply custom css to detail
646
+ html.push('style="height:', outterHeight, 'px;'); //set total height of padding
647
+ html.push('top:', rowHeight, 'px">'); //shift detail below 1st row
648
+ html.push('<div class="detail-container detailViewContainer_', dataContext.id, '" style="min-height:' + dataContext[_keyPrefix + 'height'] + 'px">'); //sub ctr for custom styling
649
+ html.push('<div class="innerDetailView_', dataContext.id, '">', dataContext[_keyPrefix + 'detailContent'], '</div></div>');
650
+ // &omit a final closing detail container </div> that would come next
651
+
652
+ return html.join('');
653
+ }
395
654
  }
396
655
  return null;
397
656
  }
398
657
 
658
+ /** Resize the Row Detail View */
399
659
  function resizeDetailView(item) {
400
- if (!item) return;
401
-
402
- // Grad each of the dom items
403
- var mainContainer = document.getElementById('detailViewContainer_' + item.id);
404
- var cellItem = document.getElementById('cellDetailView_' + item.id);
405
- var inner = document.getElementById('innerDetailView_' + item.id);
406
-
407
- if (!mainContainer || !cellItem || !inner) return;
408
-
409
- for (var idx = 1; idx <= item._sizePadding; idx++) {
410
- _dataView.deleteItem(item.id + "." + idx);
411
- }
412
-
413
- var rowHeight = _grid.getOptions().rowHeight; // height of a row
414
- var lineHeight = 13; //we know cuz we wrote the custom css innit ;)
415
-
416
- // Get the inner Item height as this will be the actual size
417
- var itemHeight = inner.clientHeight;
418
-
419
- // Now work out how many rows
420
- var rowCount = Math.ceil(itemHeight / rowHeight) + 1;
421
-
422
- item._sizePadding = Math.ceil(((rowCount * 2) * lineHeight) / rowHeight);
423
- item._height = (item._sizePadding * rowHeight);
424
-
425
- // If the padding is now more than the original minRowBuff we need to increase it
426
- if (_grid.getOptions().minRowBuffer < item._sizePadding)
427
- {
428
- // Update the minRowBuffer so that the view doesn't disappear when it's at top of screen + the original default 3
429
- _grid.getOptions().minRowBuffer =item._sizePadding + 3;
430
- }
431
-
432
- mainContainer.setAttribute("style", "max-height: " + item._height + "px");
433
- if (cellItem) cellItem.setAttribute("style", "height: " + item._height + "px;top:" + rowHeight + "px");
434
-
660
+ if (!item) {
661
+ return;
662
+ }
663
+
664
+ // Grad each of the DOM elements
665
+ var mainContainer = document.querySelector('.' + _gridUid + ' .detailViewContainer_' + item.id);
666
+ var cellItem = document.querySelector('.' + _gridUid + ' .cellDetailView_' + item.id);
667
+ var inner = document.querySelector('.' + _gridUid + ' .innerDetailView_' + item.id);
668
+
669
+ if (!mainContainer || !cellItem || !inner) {
670
+ return;
671
+ }
672
+
673
+ for (var idx = 1; idx <= item[_keyPrefix + 'sizePadding']; idx++) {
674
+ _dataView.deleteItem(item.id + '.' + idx);
675
+ }
676
+
677
+ var rowHeight = _gridOptions.rowHeight; // height of a row
678
+ var lineHeight = 13; // we know cuz we wrote the custom css innit ;)
679
+
680
+ // remove the height so we can calculate the height
681
+ mainContainer.style.minHeight = null;
682
+
683
+ // Get the scroll height for the main container so we know the actual size of the view
684
+ var itemHeight = mainContainer.scrollHeight;
685
+
686
+ // Now work out how many rows
687
+ var rowCount = Math.ceil(itemHeight / rowHeight);
688
+
689
+ item[_keyPrefix + 'sizePadding'] = Math.ceil(((rowCount * 2) * lineHeight) / rowHeight);
690
+ item[_keyPrefix + 'height'] = itemHeight;
691
+
692
+ var outterHeight = (item[_keyPrefix + 'sizePadding'] * rowHeight);
693
+ if (_options.maxRows !== null && item[_keyPrefix + 'sizePadding'] > _options.maxRows) {
694
+ outterHeight = _options.maxRows * rowHeight;
695
+ item[_keyPrefix + 'sizePadding'] = _options.maxRows;
696
+ }
697
+
698
+ // If the padding is now more than the original minRowBuff we need to increase it
699
+ if (_grid.getOptions().minRowBuffer < item[_keyPrefix + 'sizePadding']) {
700
+ // Update the minRowBuffer so that the view doesn't disappear when it's at top of screen + the original default 3
701
+ _grid.getOptions().minRowBuffer = item[_keyPrefix + 'sizePadding'] + 3;
702
+ }
703
+
704
+ mainContainer.setAttribute('style', 'min-height: ' + item[_keyPrefix + 'height'] + 'px');
705
+ if (cellItem) cellItem.setAttribute('style', 'height: ' + outterHeight + 'px; top:' + rowHeight + 'px');
706
+
435
707
  var idxParent = _dataView.getIdxById(item.id);
436
- for (var idx = 1; idx <= item._sizePadding; idx++) {
437
- _dataView.insertItem(idxParent + idx, getPaddingItem(item, idx));
708
+ for (var idx = 1; idx <= item[_keyPrefix + 'sizePadding']; idx++) {
709
+ _dataView.insertItem(idxParent + idx, getPaddingItem(item, idx));
710
+ }
711
+
712
+ // Lastly save the updated state
713
+ saveDetailView(item);
714
+ }
715
+
716
+ /** Takes in the item we are filtering and if it is an expanded row returns it's parents row to filter on */
717
+ function getFilterItem(item) {
718
+ if (item[_keyPrefix + 'isPadding'] && item[_keyPrefix + 'parent']) {
719
+ item = item[_keyPrefix + 'parent'];
720
+ }
721
+ return item;
722
+ }
723
+
724
+ function checkExpandableOverride(row, dataContext, grid) {
725
+ if (typeof _expandableOverride === 'function') {
726
+ return _expandableOverride(row, dataContext, grid);
438
727
  }
728
+ return true;
729
+ }
730
+
731
+ /**
732
+ * Method that user can pass to override the default behavior or making every row an expandable row.
733
+ * In order word, user can choose which rows to be an available row detail (or not) by providing his own logic.
734
+ * @param overrideFn: override function callback
735
+ */
736
+ function expandableOverride(overrideFn) {
737
+ _expandableOverride = overrideFn;
439
738
  }
440
-
739
+
441
740
  $.extend(this, {
442
741
  "init": init,
443
742
  "destroy": destroy,
743
+ "pluginName": "RowDetailView",
744
+
444
745
  "collapseAll": collapseAll,
746
+ "collapseDetailView": collapseDetailView,
747
+ "expandDetailView": expandDetailView,
748
+ "expandableOverride": expandableOverride,
445
749
  "getColumnDefinition": getColumnDefinition,
750
+ "getExpandedRows": getExpandedRows,
751
+ "getFilterItem": getFilterItem,
446
752
  "getOptions": getOptions,
753
+ "resizeDetailView": resizeDetailView,
754
+ "saveDetailView": saveDetailView,
447
755
  "setOptions": setOptions,
756
+
757
+ // events
448
758
  "onAsyncResponse": new Slick.Event(),
449
759
  "onAsyncEndUpdate": new Slick.Event(),
450
760
  "onAfterRowDetailToggle": new Slick.Event(),
451
761
  "onBeforeRowDetailToggle": new Slick.Event(),
452
- "resizeDetailView": resizeDetailView
762
+ "onRowOutOfViewportRange": new Slick.Event(),
763
+ "onRowBackToViewportRange": new Slick.Event()
453
764
  });
454
765
  }
455
766
  })(jQuery);