slickgrid 2.3.16.1 → 2.4.5

Sign up to get free protection for your applications and to get access to all the features.
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);