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.
- 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);
|