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.
- checksums.yaml +4 -4
- data/slickgrid.gemspec +1 -1
- data/vendor/assets/javascripts/slickgrid/controls/columnpicker.js +49 -34
- data/vendor/assets/javascripts/slickgrid/controls/gridmenu.js +32 -13
- data/vendor/assets/javascripts/slickgrid/core.js +174 -0
- data/vendor/assets/javascripts/slickgrid/dataview.js +47 -18
- data/vendor/assets/javascripts/slickgrid/editors.js +49 -27
- data/vendor/assets/javascripts/slickgrid/grid.js +5357 -3990
- data/vendor/assets/javascripts/slickgrid/groupitemmetadataprovider.js +4 -4
- data/vendor/assets/javascripts/slickgrid/plugins/autotooltips.js +2 -1
- data/vendor/assets/javascripts/slickgrid/plugins/cellcopymanager.js +2 -0
- data/vendor/assets/javascripts/slickgrid/plugins/cellexternalcopymanager.js +29 -9
- data/vendor/assets/javascripts/slickgrid/plugins/cellrangedecorator.js +2 -1
- data/vendor/assets/javascripts/slickgrid/plugins/cellrangeselector.js +70 -9
- data/vendor/assets/javascripts/slickgrid/plugins/cellselectionmodel.js +27 -5
- data/vendor/assets/javascripts/slickgrid/plugins/checkboxselectcolumn.js +157 -26
- data/vendor/assets/javascripts/slickgrid/plugins/draggablegrouping.js +145 -19
- data/vendor/assets/javascripts/slickgrid/plugins/headerbuttons.js +1 -0
- data/vendor/assets/javascripts/slickgrid/plugins/headermenu.js +8 -2
- data/vendor/assets/javascripts/slickgrid/plugins/rowdetailview.js +542 -231
- data/vendor/assets/javascripts/slickgrid/plugins/rowmovemanager.js +2 -1
- data/vendor/assets/javascripts/slickgrid/plugins/rowselectionmodel.js +1 -0
- data/vendor/assets/stylesheets/slickgrid/controls/draggablegrouping.scss +52 -0
- data/vendor/assets/stylesheets/slickgrid/controls/gridmenu.css +17 -3
- data/vendor/assets/stylesheets/slickgrid/grid.scss +54 -5
- data/vendor/assets/stylesheets/slickgrid/plugins/headermenu.scss +14 -0
- data/vendor/assets/stylesheets/slickgrid/plugins/rowdetailview.scss +9 -11
- metadata +3 -2
@@ -16,13 +16,34 @@
|
|
16
16
|
}
|
17
17
|
});
|
18
18
|
|
19
|
-
|
20
|
-
*
|
21
|
-
*
|
22
|
-
*
|
23
|
-
*
|
24
|
-
*
|
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
|
-
|
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
|
131
|
-
|
230
|
+
var groupText = $("<div style='display: inline-flex'>" + column.text() + "</div>")
|
231
|
+
groupText.appendTo(entry);
|
232
|
+
var groupRemoveIcon = $("<div class='slick-groupby-remove'> </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
|
-
"
|
325
|
+
"pluginName": "DraggableGrouping",
|
326
|
+
|
327
|
+
"onGroupChanged": onGroupChanged,
|
328
|
+
"setDroppedGroups": setDroppedGroups,
|
329
|
+
"clearDroppedGroups": clearDroppedGroups,
|
330
|
+
"getSetupColumnReorder": setupColumnReorder,
|
205
331
|
});
|
206
332
|
}
|
207
|
-
})(jQuery);
|
333
|
+
})(jQuery);
|
@@ -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:
|
14
|
-
*
|
15
|
-
*
|
16
|
-
*
|
17
|
-
*
|
18
|
-
*
|
19
|
-
*
|
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
|
-
*
|
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
|
-
*
|
32
|
-
*
|
33
|
-
*
|
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
|
-
*
|
37
|
-
*
|
38
|
-
*
|
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
|
-
*
|
42
|
-
*
|
43
|
-
*
|
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
|
-
*
|
47
|
-
*
|
48
|
-
*
|
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:
|
69
|
-
cssClass:
|
70
|
-
|
71
|
-
|
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.
|
86
|
-
|
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().
|
89
|
-
|
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
|
-
|
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(
|
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
|
-
|
126
|
-
|
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
|
-
|
134
|
-
|
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
|
-
|
143
|
-
function
|
144
|
-
|
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
|
-
|
148
|
-
function
|
149
|
-
|
150
|
-
var
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
//
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
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
|
-
|
175
|
-
|
279
|
+
_expandedRows.forEach(function (row) {
|
280
|
+
var rowIndex = _dataView.getRowById(row.id);
|
176
281
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
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(
|
187
|
-
|
188
|
-
|
189
|
-
|
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
|
-
|
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
|
-
|
422
|
+
collapseDetailView(_expandedRows[i], true);
|
196
423
|
}
|
424
|
+
_dataView.endUpdate();
|
197
425
|
}
|
198
|
-
|
199
|
-
|
200
|
-
function
|
201
|
-
|
202
|
-
|
203
|
-
|
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
|
-
|
434
|
+
saveDetailView(item);
|
219
435
|
}
|
220
|
-
|
221
|
-
item
|
222
|
-
for (var idx = 1; idx <= item
|
223
|
-
_dataView.deleteItem(item.id +
|
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
|
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
|
-
|
235
|
-
function
|
236
|
-
item
|
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
|
241
|
-
|
460
|
+
if (!item[_keyPrefix + 'detailContent']) item[_keyPrefix + 'detailViewLoaded'] = false;
|
461
|
+
|
242
462
|
// display pre-loading template
|
243
|
-
if (!item
|
244
|
-
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
|
-
|
248
|
-
|
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.
|
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
|
-
|
510
|
+
itemDetail[_keyPrefix + 'detailContent'] = args.detailView;
|
276
511
|
} else {
|
277
|
-
|
512
|
+
itemDetail[_keyPrefix + 'detailContent'] = _options.postTemplate(itemDetail);
|
278
513
|
}
|
279
514
|
|
280
|
-
|
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
|
-
|
288
|
-
|
520
|
+
'grid': _grid,
|
521
|
+
'item': itemDetail,
|
522
|
+
'itemDetail': itemDetail
|
289
523
|
}, e, _self);
|
290
524
|
});
|
291
525
|
}
|
292
526
|
|
293
|
-
|
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
|
296
|
-
|
530
|
+
if (!item[_keyPrefix + 'collapsed']) {
|
531
|
+
collapseDetailView(item);
|
297
532
|
} else {
|
298
|
-
|
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 +
|
548
|
+
item.id = parent.id + '.' + offset;
|
312
549
|
|
313
|
-
//additional hidden padding metadata fields
|
314
|
-
item
|
315
|
-
item
|
316
|
-
item
|
317
|
-
item
|
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
|
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
|
332
|
-
item
|
333
|
-
item
|
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
|
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:
|
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
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
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
|
-
|
368
|
-
|
369
|
-
|
370
|
-
return
|
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
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
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)
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
var
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
}
|
412
|
-
|
413
|
-
var
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
var
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
if (
|
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
|
437
|
-
|
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
|
-
|
762
|
+
"onRowOutOfViewportRange": new Slick.Event(),
|
763
|
+
"onRowBackToViewportRange": new Slick.Event()
|
453
764
|
});
|
454
765
|
}
|
455
766
|
})(jQuery);
|