datavis-glide 4.0.0-PRE.0
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.
- package/LICENSE +45 -0
- package/README.md +129 -0
- package/datavis.js +101 -0
- package/dist/wcdatavis.css +1957 -0
- package/dist/wcdatavis.min.js +1 -0
- package/global-jquery.js +4 -0
- package/ie-fixes.js +13 -0
- package/index.js +70 -0
- package/meteor.js +1 -0
- package/package.json +102 -0
- package/src/flags.js +6 -0
- package/src/graph.js +1079 -0
- package/src/graph_renderer.js +85 -0
- package/src/grid.js +2777 -0
- package/src/grid_control.js +1957 -0
- package/src/grid_filter.js +1073 -0
- package/src/grid_renderer.js +276 -0
- package/src/group_fun_win.js +121 -0
- package/src/lang/en-US.js +188 -0
- package/src/lang/es-MX.js +188 -0
- package/src/lang/fr-FR.js +188 -0
- package/src/lang/id-ID.js +188 -0
- package/src/lang/nl-NL.js +188 -0
- package/src/lang/pt-BR.js +188 -0
- package/src/lang/ru-RU.js +188 -0
- package/src/lang/th-TH.js +188 -0
- package/src/lang/vi-VN.js +188 -0
- package/src/lang/zh-Hans-CN.js +188 -0
- package/src/operations_palette.js +176 -0
- package/src/prefs_modules.js +132 -0
- package/src/reg/graph_renderer.js +17 -0
- package/src/renderers/graph/chartjs.js +457 -0
- package/src/renderers/graph/google.js +584 -0
- package/src/renderers/graph/jit.js +61 -0
- package/src/renderers/graph/svelte-gantt.js +168 -0
- package/src/renderers/grid/dummy.js +79 -0
- package/src/renderers/grid/handlebars.js +217 -0
- package/src/renderers/grid/squirrelly.js +215 -0
- package/src/renderers/grid/table/group_detail.js +1404 -0
- package/src/renderers/grid/table/group_summary.js +380 -0
- package/src/renderers/grid/table/pivot.js +915 -0
- package/src/renderers/grid/table/plain.js +1592 -0
- package/src/renderers/grid/table.js +2510 -0
- package/src/trans.js +101 -0
- package/src/ui/collapsible.js +234 -0
- package/src/ui/filters/date.js +283 -0
- package/src/ui/grid_filter.js +398 -0
- package/src/ui/popup_menu.js +224 -0
- package/src/ui/popup_window.js +572 -0
- package/src/ui/slider.js +156 -0
- package/src/ui/tabs.js +202 -0
- package/src/ui/templates.js +131 -0
- package/src/ui/toolbar.js +63 -0
- package/src/ui/toolbars/grid.js +873 -0
- package/src/ui/windows/col_config.js +341 -0
- package/src/ui/windows/debug.js +164 -0
- package/src/ui/windows/grid_table_opts.js +139 -0
- package/src/util/handlebars.js +158 -0
- package/src/util/jquery.js +630 -0
- package/src/util/misc.js +1058 -0
- package/src/util/squirrelly.js +155 -0
- package/wcdatavis.css +1601 -0
|
@@ -0,0 +1,1592 @@
|
|
|
1
|
+
// Imports {{{1
|
|
2
|
+
|
|
3
|
+
import _ from 'underscore';
|
|
4
|
+
import sprintf from 'sprintf-js';
|
|
5
|
+
import jQuery from 'jquery';
|
|
6
|
+
|
|
7
|
+
import { trans } from '../../../trans.js';
|
|
8
|
+
import {
|
|
9
|
+
addFocusHandler,
|
|
10
|
+
removeFocusHandler,
|
|
11
|
+
determineColumns,
|
|
12
|
+
icon,
|
|
13
|
+
format,
|
|
14
|
+
gensym,
|
|
15
|
+
getProp,
|
|
16
|
+
getPropDef,
|
|
17
|
+
isElement,
|
|
18
|
+
isElementInViewport,
|
|
19
|
+
isVisible,
|
|
20
|
+
makeOperationButton,
|
|
21
|
+
makeSubclass,
|
|
22
|
+
mixinLogging,
|
|
23
|
+
onVisibilityChange,
|
|
24
|
+
setTableCell,
|
|
25
|
+
} from '../../../util/misc.js';
|
|
26
|
+
|
|
27
|
+
import {AggregateInfo, ComputedView, Source} from 'datavis-ace';
|
|
28
|
+
import {GridFilterSet} from '../../../grid_filter.js';
|
|
29
|
+
import {GridRenderer} from '../../../grid_renderer.js';
|
|
30
|
+
|
|
31
|
+
import GridTable from '../table.js';
|
|
32
|
+
import Slider from '../../../ui/slider.js';
|
|
33
|
+
|
|
34
|
+
// GridTablePlain {{{1
|
|
35
|
+
// Constructor {{{2
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The GridTablePlain is in charge of displaying the HTML table of data.
|
|
39
|
+
*
|
|
40
|
+
* @class
|
|
41
|
+
* @extends GridTable
|
|
42
|
+
*
|
|
43
|
+
* @property {Grid~Features} features
|
|
44
|
+
*
|
|
45
|
+
* @property {object} defn
|
|
46
|
+
*
|
|
47
|
+
* @property {ComputedView} view
|
|
48
|
+
*
|
|
49
|
+
* @property {Element} root
|
|
50
|
+
*
|
|
51
|
+
* @property {object} colConfig Map associating field name with the configuration of the
|
|
52
|
+
* corresponding column in this grid table.
|
|
53
|
+
*
|
|
54
|
+
* @property {Timing} timing
|
|
55
|
+
*
|
|
56
|
+
* @property {boolean} needsRedraw True if the grid needs to redraw itself when the view is done
|
|
57
|
+
* working.
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
var GridTablePlain = makeSubclass('GridTablePlain', GridTable, function (grid, defn, view, features, opts, timing, id) {
|
|
61
|
+
var self = this;
|
|
62
|
+
|
|
63
|
+
self.super['GridTable'].ctor.apply(self, arguments);
|
|
64
|
+
|
|
65
|
+
self.features.filter = false;
|
|
66
|
+
|
|
67
|
+
self._focusEventId = gensym('grid-plain-');
|
|
68
|
+
|
|
69
|
+
// Pagination state.
|
|
70
|
+
self._paginationPage = 0;
|
|
71
|
+
self._paginationRowsPerPage = getPropDef(40, self.defn, 'table', 'pagination', 'rowsPerPage');
|
|
72
|
+
|
|
73
|
+
self.logDebug(self.makeLogTag() + ' DataVis // %s // Constructing grid table; features = %O', self.toString(), features);
|
|
74
|
+
|
|
75
|
+
self.addFilterHandler();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
mixinLogging(GridTablePlain);
|
|
79
|
+
|
|
80
|
+
// #canRender {{{2
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Responds whether or not this grid table can render the type of data requested.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} what
|
|
86
|
+
* The kind of data the caller wants us to show. Must be one of: plain, group, or pivot.
|
|
87
|
+
*
|
|
88
|
+
* @return {boolean}
|
|
89
|
+
* True if this grid table can render that kind of data, false if it can't.
|
|
90
|
+
*/
|
|
91
|
+
|
|
92
|
+
GridTablePlain.prototype.canRender = function (what) {
|
|
93
|
+
return ['plain'].indexOf(what) >= 0;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
GridTablePlain.prototype.draw = function (root, opts, cont) {
|
|
97
|
+
var self = this;
|
|
98
|
+
|
|
99
|
+
GridTable.prototype.draw.call(self, root, opts, function () {
|
|
100
|
+
if (self.features.activeRow || self.features.omnifilter) {
|
|
101
|
+
self._hasFocus = false;
|
|
102
|
+
addFocusHandler(root, self._focusEventId, function (isFocused) {
|
|
103
|
+
self._hasFocus = isFocused;
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (self.features.omnifilter) {
|
|
108
|
+
jQuery(document).on('keydown.omnifilter-' + self._focusEventId, function (evt) {
|
|
109
|
+
var avoidElts = ['A', 'BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'];
|
|
110
|
+
|
|
111
|
+
if (avoidElts.indexOf(evt.target.tagName) >= 0) {
|
|
112
|
+
return; // These elements don't count for turning on the omnifilter.
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!self._hasFocus) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (evt.key === 'f') {
|
|
120
|
+
evt.preventDefault();
|
|
121
|
+
evt.stopPropagation();
|
|
122
|
+
if (!self.grid.ui.omnifilter.is(':visible')) {
|
|
123
|
+
self.grid.ui.omnifilterToggle.addClass('wcdv_omnifilter_active');
|
|
124
|
+
self.grid.ui.omnifilter.show();
|
|
125
|
+
}
|
|
126
|
+
self.grid.ui.omnifilterInput.focus();
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (self.features.activeRow) {
|
|
132
|
+
if (getProp(self.defn, 'table', 'activeRow', 'slider')) {
|
|
133
|
+
self.ui.slider = new Slider();
|
|
134
|
+
self.ui.slider.on('hide', function () {
|
|
135
|
+
self.clearActiveRow();
|
|
136
|
+
});
|
|
137
|
+
self.ui.slider.draw(root);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
self.ui.tbody.on('click', 'td', function (evt) {
|
|
141
|
+
var avoidElts = ['A', 'BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'];
|
|
142
|
+
|
|
143
|
+
if (avoidElts.indexOf(evt.target.tagName) >= 0) {
|
|
144
|
+
return; // These elements don't count for setting the active row.
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
self.setActiveRow(jQuery(this).closest('tr'));
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
jQuery(document).on('keydown.active-row-' + self._focusEventId, function (evt) {
|
|
151
|
+
var avoidElts = ['A', 'BUTTON', 'INPUT', 'SELECT', 'TEXTAREA'];
|
|
152
|
+
|
|
153
|
+
if (avoidElts.indexOf(evt.target.tagName) >= 0) {
|
|
154
|
+
return; // These elements don't count for setting the active row.
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!self._hasFocus) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
switch(evt.key.toLowerCase()) {
|
|
162
|
+
case 'j':
|
|
163
|
+
if (self.activeRow) {
|
|
164
|
+
evt.preventDefault();
|
|
165
|
+
self.activeRowNext();
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
case 'k':
|
|
169
|
+
if (self.activeRow) {
|
|
170
|
+
evt.preventDefault();
|
|
171
|
+
self.activeRowPrev();
|
|
172
|
+
}
|
|
173
|
+
break;
|
|
174
|
+
case 'escape':
|
|
175
|
+
self.clearActiveRow();
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return typeof cont === 'function' ? cont() : null;
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// #setActiveRow {{{2
|
|
186
|
+
|
|
187
|
+
GridTablePlain.prototype.setActiveRow = function (which) {
|
|
188
|
+
var self = this
|
|
189
|
+
, rowId
|
|
190
|
+
, tr;
|
|
191
|
+
|
|
192
|
+
if (!self.features.activeRow) {
|
|
193
|
+
console.warn('[DataVis // %s // Set Active Row] Active row feature is disabled', self.toString());
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (typeof which === 'number') {
|
|
198
|
+
rowId = which;
|
|
199
|
+
tr = self.ui.tbody.find('tr[data-row-num=' + which + ']');
|
|
200
|
+
}
|
|
201
|
+
else if (which instanceof jQuery) {
|
|
202
|
+
tr = which;
|
|
203
|
+
rowId = +tr.attr('data-row-num');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
self.activeRow = {
|
|
207
|
+
rowId: rowId,
|
|
208
|
+
tr: tr
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
self.ui.tbody.find('tr.wcdv-active-row').removeClass('wcdv-active-row');
|
|
212
|
+
tr.addClass('wcdv-active-row');
|
|
213
|
+
if (!isElementInViewport(self.opts.fixedHeight ? self.root : window, tr)) {
|
|
214
|
+
tr.get(0).scrollIntoView({
|
|
215
|
+
block: 'nearest'
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
var rowData = self.view.data.dataByRowId[rowId];
|
|
220
|
+
var cbObj = {
|
|
221
|
+
rowId: rowId,
|
|
222
|
+
rowData: rowData,
|
|
223
|
+
colConfig: self.colConfig,
|
|
224
|
+
tableRow: tr,
|
|
225
|
+
tableRenderer: self
|
|
226
|
+
};
|
|
227
|
+
if (getProp(self.defn, 'table', 'activeRow', 'slider')) {
|
|
228
|
+
if (getProp(self.defn, 'table', 'activeRow', 'callback')) {
|
|
229
|
+
cbObj.slider = self.ui.slider;
|
|
230
|
+
self.defn.table.activeRow.callback(cbObj);
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
var dataHtml = jQuery('<dl>');
|
|
234
|
+
self.colConfig.each(function (v, k) {
|
|
235
|
+
jQuery('<dt>').text(v.displayText || v.field).appendTo(dataHtml);
|
|
236
|
+
var dd = jQuery('<dd>').appendTo(dataHtml);
|
|
237
|
+
var cr = rowData[k].cachedRender || rowData[k].value || rowData[k].orig;
|
|
238
|
+
if (cr instanceof Element || cr instanceof jQuery) {
|
|
239
|
+
dd.append(cr);
|
|
240
|
+
}
|
|
241
|
+
else if (cr === '') {
|
|
242
|
+
dd.html(' ');
|
|
243
|
+
}
|
|
244
|
+
else if (v.allowHtml) {
|
|
245
|
+
dd.html(cr);
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
dd.text(cr);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
self.ui.slider.setHeader('Row Info');
|
|
252
|
+
self.ui.slider.setBody(dataHtml);
|
|
253
|
+
}
|
|
254
|
+
self.ui.slider.show();
|
|
255
|
+
}
|
|
256
|
+
else if (getProp(self.defn, 'table', 'activeRow', 'callback')) {
|
|
257
|
+
self.defn.table.activeRow.callback(cbObj);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// #clearActiveRow {{{2
|
|
262
|
+
|
|
263
|
+
GridTablePlain.prototype.clearActiveRow = function () {
|
|
264
|
+
var self = this;
|
|
265
|
+
|
|
266
|
+
if (!self.features.activeRow) {
|
|
267
|
+
console.warn('[DataVis // %s // Clear Active Row] Active row feature is disabled', self.toString());
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (getProp(self.defn, 'table', 'activeRow', 'slider')) {
|
|
272
|
+
self.ui.slider.hide();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
self.ui.tbody.find('tr.wcdv-active-row').removeClass('wcdv-active-row');
|
|
276
|
+
|
|
277
|
+
self.activeRow = null;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// #activeRowPrev {{{2
|
|
281
|
+
|
|
282
|
+
GridTablePlain.prototype.activeRowPrev = function () {
|
|
283
|
+
var self = this;
|
|
284
|
+
|
|
285
|
+
var activeRowId = self.activeRow.rowId - 1;
|
|
286
|
+
if (activeRowId < 0) {
|
|
287
|
+
activeRowId = self.view.data.dataByRowId.length - 1;
|
|
288
|
+
}
|
|
289
|
+
self.setActiveRow(activeRowId);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// #activeRowNext {{{2
|
|
293
|
+
|
|
294
|
+
GridTablePlain.prototype.activeRowNext = function () {
|
|
295
|
+
var self = this;
|
|
296
|
+
|
|
297
|
+
var activeRowId = self.activeRow.rowId + 1;
|
|
298
|
+
if (activeRowId >= self.view.data.dataByRowId.length) {
|
|
299
|
+
activeRowId = 0;
|
|
300
|
+
}
|
|
301
|
+
self.setActiveRow(activeRowId);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// #drawHeader {{{2
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Render the header columns of a GridTablePlain.
|
|
308
|
+
*
|
|
309
|
+
* @param {Array.<string>} columns A list of the fields that are to be included as columns within
|
|
310
|
+
* the GridTablePlain.
|
|
311
|
+
*
|
|
312
|
+
* @param {ComputedView~Data} data
|
|
313
|
+
*
|
|
314
|
+
* @param {Source~TypeInfo} typeInfo
|
|
315
|
+
*
|
|
316
|
+
* @param {object} opts
|
|
317
|
+
*/
|
|
318
|
+
|
|
319
|
+
GridTablePlain.prototype.drawHeader = function (columns, data, typeInfo, opts) {
|
|
320
|
+
var self = this;
|
|
321
|
+
|
|
322
|
+
var headingTr, headingSpan, headingTh, filterTr;
|
|
323
|
+
|
|
324
|
+
var headingThCss = {
|
|
325
|
+
'white-space': 'nowrap'
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
var filterThCss = {
|
|
329
|
+
'white-space': 'nowrap',
|
|
330
|
+
'padding-top': 4,
|
|
331
|
+
'vertical-align': 'top'
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
headingTr = jQuery('<tr>');
|
|
335
|
+
filterTr = jQuery('<tr>', {
|
|
336
|
+
'class': 'wcdv_grid_filterrow'
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
/*
|
|
340
|
+
* Create the checkbox that allows the user to select all rows.
|
|
341
|
+
*/
|
|
342
|
+
|
|
343
|
+
if (self.features.rowSelect) {
|
|
344
|
+
self.ui.checkAll_thead = jQuery('<input>', { 'name': 'checkAll', 'type': 'checkbox' })
|
|
345
|
+
.on('change', function (evt) {
|
|
346
|
+
self.checkAll(evt);
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
headingTh = jQuery('<th>', { scope: 'col' })
|
|
350
|
+
.addClass('wcdv_group_col_spacer')
|
|
351
|
+
.append(self.ui.checkAll_thead)
|
|
352
|
+
.appendTo(headingTr);
|
|
353
|
+
if (self.opts.drawInternalBorders) {
|
|
354
|
+
headingTh.addClass('wcdv_pivot_colval_boundary');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (self.features.filter) {
|
|
358
|
+
filterTr.append(jQuery('<th>').css(filterThCss));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Create the column for row-based operations.
|
|
363
|
+
|
|
364
|
+
if (self.hasOperations('row')) {
|
|
365
|
+
headingTh = jQuery('<th>', {
|
|
366
|
+
'class': 'wcdv_group_col_spacer'
|
|
367
|
+
});
|
|
368
|
+
headingTr.append(headingTh);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
var progress = self.makeProgress('Filter');
|
|
372
|
+
|
|
373
|
+
/*
|
|
374
|
+
* Set up the GridFilterSet instance that manages the (potentially multiple) filters on each
|
|
375
|
+
* column of the ComputedView that belongs to this GridTablePlain.
|
|
376
|
+
*/
|
|
377
|
+
|
|
378
|
+
if (self.features.filter) {
|
|
379
|
+
self.defn.gridFilterSet = new GridFilterSet(self.view, null, self, progress);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/*
|
|
383
|
+
* Configure every column which comes from the data (i.e. not the "select all" checkbox, and not
|
|
384
|
+
* the editing "options" column).
|
|
385
|
+
*/
|
|
386
|
+
|
|
387
|
+
_.each(columns, function (field, colIndex) {
|
|
388
|
+
var fcc = self.colConfig.get(field) || {};
|
|
389
|
+
|
|
390
|
+
if (self.features.rowSelect) {
|
|
391
|
+
colIndex += 1; // Add a column for the row selection checkbox.
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (self.hasOperations('row')) {
|
|
395
|
+
colIndex += 1; // Add a column for row-based operations.
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
var headingText = fcc.displayText || field;
|
|
399
|
+
|
|
400
|
+
// headingTh <TH>
|
|
401
|
+
// headingThContainer <DIV>
|
|
402
|
+
// headingThSpan <SPAN>
|
|
403
|
+
// headingThControls <DIV>
|
|
404
|
+
|
|
405
|
+
var headingSpan = jQuery('<span>', {
|
|
406
|
+
'class': 'wcdv_heading_title',
|
|
407
|
+
'data-wcdv-field': field,
|
|
408
|
+
'data-wcdv-draggable-origin': 'GRID_TABLE_HEADER',
|
|
409
|
+
})
|
|
410
|
+
.text(headingText)
|
|
411
|
+
._makeDraggableField();
|
|
412
|
+
|
|
413
|
+
var headingThControls = jQuery('<div>');
|
|
414
|
+
|
|
415
|
+
var headingThContainer = jQuery('<div>')
|
|
416
|
+
.addClass('wcdv_heading_container')
|
|
417
|
+
.append(headingSpan, headingThControls);
|
|
418
|
+
|
|
419
|
+
var headingTh = jQuery('<th>', { id: gensym(), scope: 'col' })
|
|
420
|
+
.css(headingThCss)
|
|
421
|
+
.append(headingThContainer);
|
|
422
|
+
|
|
423
|
+
var fti = typeInfo.get(field);
|
|
424
|
+
|
|
425
|
+
if (fti != null && fti.type != null) {
|
|
426
|
+
headingTh.attr('data-wcdv-field-type', fti.type);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// In the plain grid table output, the only way to sort is vertically by field.
|
|
430
|
+
|
|
431
|
+
self._addSortingToHeader(data, 'vertical', {field: field}, headingThControls.get(0));
|
|
432
|
+
|
|
433
|
+
self._addFilterToHeader(headingThControls, field, headingText);
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
if (self.opts.drawInternalBorders) {
|
|
437
|
+
headingTh.addClass('wcdv_pivot_colval_boundary');
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/*
|
|
441
|
+
* Configure filtering for this column. This mainly involves creating a button, which when
|
|
442
|
+
* clicked adds (for this column) a filter to the GridFilterSet instance.
|
|
443
|
+
*/
|
|
444
|
+
|
|
445
|
+
if (self.features.filter) {
|
|
446
|
+
|
|
447
|
+
// Add a TH to the TR that will contain the filters. Every filter will actually be a DIV
|
|
448
|
+
// inside this TH.
|
|
449
|
+
//
|
|
450
|
+
// The ID attribute here is used to provide a selector to NProgress, so the progress bar
|
|
451
|
+
// will be drawn in the header cell for the column we're filtering by. You can't pass an
|
|
452
|
+
// element to NProgress for this, it needs to be a selector string. Passing ('#' + id) was
|
|
453
|
+
// the easiest way to do it.
|
|
454
|
+
//
|
|
455
|
+
// Unfortunately, the ID attribute is copied when using TableTool so this might mess us up.
|
|
456
|
+
|
|
457
|
+
var filterThId = gensym();
|
|
458
|
+
var filterTh = jQuery('<th>', { id: filterThId }).addClass('wcdv_grid_filtercol filter_col_' + colIndex).css(filterThCss);
|
|
459
|
+
self.setCss(filterTh, field);
|
|
460
|
+
filterTr.append(filterTh);
|
|
461
|
+
|
|
462
|
+
// Create the "button" (really a SPAN) that will add the filter to the grid, and stick it
|
|
463
|
+
// onto the end of the column heading TH.
|
|
464
|
+
|
|
465
|
+
jQuery(icon('filter', null, 'Click to add a filter on this column'))
|
|
466
|
+
.css({'cursor': 'pointer', 'margin-left': '0.5ex'})
|
|
467
|
+
.on('click', function () {
|
|
468
|
+
// When using TableTool, we need to put the filter UI into the floating (clone) header,
|
|
469
|
+
// instead of the original (variable `filterTh` holds the original). This jQuery will
|
|
470
|
+
// always do the right thing.
|
|
471
|
+
|
|
472
|
+
var thead = jQuery(this).closest('thead');
|
|
473
|
+
var tr = thead.children('tr:eq(1)');
|
|
474
|
+
var th = tr.children('th.filter_col_' + colIndex);
|
|
475
|
+
|
|
476
|
+
var adjustTableToolHeight = function () {
|
|
477
|
+
if (self.features.floatingHeader) {
|
|
478
|
+
// Update the height of the original, non-floating header to be the same as that of
|
|
479
|
+
// the floating header. This is needed because otherwise the floating header will
|
|
480
|
+
// cover up the first rows of the table body as we add filters. TableTool does not
|
|
481
|
+
// keep the heights of the original and clone in sync on its own (using the `update`
|
|
482
|
+
// function only synchronizes the widths).
|
|
483
|
+
|
|
484
|
+
var trHeight = tr.innerHeight();
|
|
485
|
+
|
|
486
|
+
self.logDebug(self.makeLogTag() + ' Adjusting original table header height to ' + trHeight + 'px to match floating header height', self.toString());
|
|
487
|
+
filterTr.innerHeight(trHeight);
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
var onRemove = adjustTableToolHeight;
|
|
492
|
+
|
|
493
|
+
self.defn.gridFilterSet.add(field, th, {
|
|
494
|
+
filterType: fcc.filter,
|
|
495
|
+
filterButton: jQuery(this),
|
|
496
|
+
makeRemoveButton: true,
|
|
497
|
+
onRemove: onRemove,
|
|
498
|
+
autoUpdateInputWidth: true,
|
|
499
|
+
sizingElement: filterTh
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
adjustTableToolHeight();
|
|
503
|
+
})
|
|
504
|
+
.appendTo(headingTh);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
self.setCss(headingTh, field);
|
|
508
|
+
self.setAlignment(headingTh, fcc, typeInfo.get(field));
|
|
509
|
+
|
|
510
|
+
// Add column resize handle
|
|
511
|
+
if (self.features.columnResize !== false) {
|
|
512
|
+
self._addColumnResizeHandle(headingTh, field, colIndex);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Add column reorder handler
|
|
516
|
+
if (self.features.columnReorder !== false) {
|
|
517
|
+
self._addColumnReorderHandler(headingTh, field, colIndex, columns);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
self.ui.thMap[field] = headingTh;
|
|
521
|
+
headingTr.append(headingTh);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
if (self.opts.addCols) {
|
|
525
|
+
self.drawHeader_addCols(headingTr, typeInfo, opts);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/*
|
|
529
|
+
* Create a column with buttons that allows the user to reorder the rows.
|
|
530
|
+
*/
|
|
531
|
+
|
|
532
|
+
if (self.features.rowReorder) {
|
|
533
|
+
headingTh = jQuery('<th>', { scope: 'col' })
|
|
534
|
+
.text('Options')
|
|
535
|
+
.appendTo(headingTr);
|
|
536
|
+
if (self.opts.drawInternalBorders) {
|
|
537
|
+
headingTh.addClass('wcdv_pivot_colval_boundary');
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (self.features.filter) {
|
|
541
|
+
headingTh = jQuery('<th>').css(filterThCss).appendTo(filterTr);
|
|
542
|
+
if (self.opts.drawInternalBorders) {
|
|
543
|
+
headingTh.addClass('wcdv_pivot_colval_boundary');
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
self.ui.thead.append(headingTr);
|
|
549
|
+
|
|
550
|
+
if (self.features.filter) {
|
|
551
|
+
self.ui.thead.append(filterTr);
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
// #drawBody {{{2
|
|
556
|
+
|
|
557
|
+
GridTablePlain.prototype.drawBody = function (data, typeInfo, columns, cont, opts) {
|
|
558
|
+
var self = this;
|
|
559
|
+
|
|
560
|
+
// When pagination is enabled, disable the limit feature so that all rows are rendered into the
|
|
561
|
+
// DOM. Pagination controls visibility by showing/hiding TR elements per page.
|
|
562
|
+
var useLimit = self.features.pagination ? false : self.features.limit;
|
|
563
|
+
var limitConfig = getPropDef({}, self.defn, 'table', 'limit');
|
|
564
|
+
var usingTableTool = self.features.floatingHeader && getProp(self.defn, 'table', 'floatingHeader', 'method') === 'tabletool';
|
|
565
|
+
|
|
566
|
+
if (self.features.limit && !self.features.pagination && limitConfig && data.data.length > limitConfig.threshold) {
|
|
567
|
+
self.logDebug(self.makeLogTag() + ' Limiting output to first ' + limitConfig.threshold + ' rows', self.toString());
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
if (self.opts.generateCsv) {
|
|
571
|
+
self.addDataToCsv(data);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// When pagination is enabled, wrap the continuation so that page visibility and pagination
|
|
575
|
+
// controls are applied after all rows have been rendered. The originalCont must run first
|
|
576
|
+
// because it appends the tbody to the table and runs the full draw chain (including
|
|
577
|
+
// omnifilter, which sets all rows visible). Pagination is then applied last.
|
|
578
|
+
|
|
579
|
+
if (self.features.pagination) {
|
|
580
|
+
var originalCont = cont;
|
|
581
|
+
cont = function () {
|
|
582
|
+
if (typeof originalCont === 'function') {
|
|
583
|
+
originalCont();
|
|
584
|
+
}
|
|
585
|
+
self._paginationApply();
|
|
586
|
+
self._paginationDrawControls();
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Clear out the body of the table. We do this in case somebody invokes this function multiple
|
|
591
|
+
// times. This function draws the entirety of the data, we certainly don't want to just tack rows
|
|
592
|
+
// on to the end.
|
|
593
|
+
|
|
594
|
+
self.ui.tbody.children().remove();
|
|
595
|
+
|
|
596
|
+
// Reset pagination to page 0 when redrawing all rows.
|
|
597
|
+
if (self.features.pagination) {
|
|
598
|
+
self._paginationPage = 0;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
self._setupFullValueWin(data);
|
|
602
|
+
|
|
603
|
+
var renderDataRow = function (row, idx) {
|
|
604
|
+
var tr, td;
|
|
605
|
+
|
|
606
|
+
tr = document.createElement('tr');
|
|
607
|
+
tr.setAttribute('id', self.defn.table.id + '_' + row.rowNum);
|
|
608
|
+
tr.setAttribute('data-row-num', row.rowNum);
|
|
609
|
+
tr.classList.add(idx % 2 === 0 ? 'even' : 'odd');
|
|
610
|
+
|
|
611
|
+
// Create the check box which selects the row.
|
|
612
|
+
|
|
613
|
+
if (self.features.rowSelect) {
|
|
614
|
+
var checkbox = jQuery('<input>', {
|
|
615
|
+
'type': 'checkbox',
|
|
616
|
+
'data-row-num': row.rowNum,
|
|
617
|
+
});
|
|
618
|
+
td = jQuery('<td>').addClass('wcdv_group_col_spacer').append(checkbox).appendTo(tr);
|
|
619
|
+
if (self.opts.drawInternalBorders) {
|
|
620
|
+
td.addClass('wcdv_pivot_colval_boundary');
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Create the cell that contains row-based operations.
|
|
625
|
+
|
|
626
|
+
if (self.hasOperations('row')) {
|
|
627
|
+
td = document.createElement('td');
|
|
628
|
+
td.classList.add('wcdv_group_col_spacer');
|
|
629
|
+
td.classList.add('wcdv_pivot_colval_boundary');
|
|
630
|
+
td.classList.add('wcdv_nowrap');
|
|
631
|
+
td.classList.add('wcdv_row_operations');
|
|
632
|
+
|
|
633
|
+
_.each(self.defn.operations.row, function (op, index) {
|
|
634
|
+
var opBtn = makeOperationButton('row', op, index);
|
|
635
|
+
if (op.disableWhen && op.disableWhen(row)) {
|
|
636
|
+
opBtn.disabled = true;
|
|
637
|
+
}
|
|
638
|
+
if (op.hideWhen && op.hideWhen(row)) {
|
|
639
|
+
opBtn.style.display = 'none';
|
|
640
|
+
}
|
|
641
|
+
td.appendChild(opBtn);
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
tr.appendChild(td);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Create the data cells.
|
|
648
|
+
|
|
649
|
+
_.each(columns, function (field, colIndex) {
|
|
650
|
+
var fcc = self.colConfig.get(field) || {};
|
|
651
|
+
var cell = row.rowData[field];
|
|
652
|
+
|
|
653
|
+
var td = document.createElement('td');
|
|
654
|
+
var value = format(fcc, typeInfo.get(field), cell);
|
|
655
|
+
|
|
656
|
+
setTableCell(td, value, {
|
|
657
|
+
field: field,
|
|
658
|
+
colConfig: self.colConfig,
|
|
659
|
+
typeInfo: typeInfo,
|
|
660
|
+
operations: getProp(self.defn, 'operations', 'cell', field)
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
// Buttons within cells share a common 'onClick' handler, e.g. all "show full value" buttons
|
|
664
|
+
// have the same callback. In that handler, we need to be able to figure out what field we
|
|
665
|
+
// were called for. So if we're going to render buttons within the data cell, we need to
|
|
666
|
+
// attach the field name so it can be used by the handler.
|
|
667
|
+
//
|
|
668
|
+
// There are two such situations right now:
|
|
669
|
+
//
|
|
670
|
+
// 1. When `maxHeight` is set on the field (the "show full value" button).
|
|
671
|
+
// 2. When there are operations on the field.
|
|
672
|
+
|
|
673
|
+
if (fcc.maxHeight != null || self.hasOperations('cell', field)) {
|
|
674
|
+
td.setAttribute('data-wcdv-field', field);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
self.setCss(jQuery(td), field);
|
|
678
|
+
self.setAlignment(td, fcc, typeInfo.get(field));
|
|
679
|
+
|
|
680
|
+
if (self.opts.drawInternalBorders) {
|
|
681
|
+
td.classList.add('wcdv_pivot_colval_boundary');
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
tr.appendChild(td);
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
if (self.opts.addCols) {
|
|
688
|
+
_.each(self.opts.addCols, function (addColSpec) {
|
|
689
|
+
var value = addColSpec.value(row.rowData, row.rowNum);
|
|
690
|
+
var td = document.createElement('td');
|
|
691
|
+
|
|
692
|
+
if (!(value instanceof jQuery || value instanceof Element)) {
|
|
693
|
+
value = format(null, null, value);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
setTableCell(td, value);
|
|
697
|
+
|
|
698
|
+
if (self.opts.drawInternalBorders) {
|
|
699
|
+
td.classList.add('wcdv_pivot_colval_boundary');
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
tr.appendChild(td);
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Create button used as the "handle" for dragging/dropping rows.
|
|
707
|
+
|
|
708
|
+
if (self.features.rowReorder) {
|
|
709
|
+
jQuery('<td>').append(self.makeRowReorderBtn()).appendTo(tr);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
self.ui.tr[row.rowNum] = jQuery(tr);
|
|
713
|
+
self.ui.tbody.append(tr);
|
|
714
|
+
|
|
715
|
+
// When using TableTool with a pinned column, the pinned column is a clone on the left hand
|
|
716
|
+
// side. TableTool does not monitor the original tbody to see if new elements are added, so we
|
|
717
|
+
// need to add new data to the pinned column clone as well.
|
|
718
|
+
|
|
719
|
+
if (usingTableTool) {
|
|
720
|
+
self.ui.tbody.parents('div.ttsticky').find('table > tbody').append(tr);
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
var renderShowMore = function (rowNum) {
|
|
725
|
+
var tr;
|
|
726
|
+
var showMoreId = gensym();
|
|
727
|
+
|
|
728
|
+
tr = document.createElement('tr');
|
|
729
|
+
tr.classList.add('wcdvgrid_more');
|
|
730
|
+
tr.setAttribute('data-show-more-id', showMoreId);
|
|
731
|
+
|
|
732
|
+
var colSpan = columns.length
|
|
733
|
+
+ (self.features.rowSelect ? 1 : 0)
|
|
734
|
+
+ (self.hasOperations('row') ? 1 : 0)
|
|
735
|
+
+ (getPropDef(0, self.opts, 'addCols', 'length'))
|
|
736
|
+
+ (self.features.rowReorder ? 1 : 0);
|
|
737
|
+
|
|
738
|
+
var showMore = function () {
|
|
739
|
+
// When using pinned columns, TableTool will make a clone of the "show more rows" <TR> which
|
|
740
|
+
// we otherwise have no knowledge of. So we must track it using a data attribute instead, so
|
|
741
|
+
// we can remove both the original and the clone.
|
|
742
|
+
|
|
743
|
+
jQuery('tr[data-show-more-id="' + showMoreId + '"]').remove();
|
|
744
|
+
render(rowNum + 1, limitConfig.chunkSize, nextChunk);
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
var td = jQuery('<td>', {
|
|
748
|
+
colspan: colSpan
|
|
749
|
+
})
|
|
750
|
+
.on('click', showMore)
|
|
751
|
+
.append(icon('circle-chevron-down'))
|
|
752
|
+
.append(jQuery('<span>Showing rows '
|
|
753
|
+
+ '1–'
|
|
754
|
+
+ (rowNum + 1)
|
|
755
|
+
+ ' of '
|
|
756
|
+
+ data.data.length
|
|
757
|
+
+ '.</span>')
|
|
758
|
+
.css({
|
|
759
|
+
'padding-left': '0.5em',
|
|
760
|
+
}))
|
|
761
|
+
.append(jQuery('<span>Click to load ' + limitConfig.chunkSize + ' more rows.</span>')
|
|
762
|
+
.css({
|
|
763
|
+
'padding-left': '0.5em',
|
|
764
|
+
'padding-right': '0.5em'
|
|
765
|
+
}))
|
|
766
|
+
.append(icon('circle-chevron-down'));
|
|
767
|
+
|
|
768
|
+
self.moreVisibleHandler = onVisibilityChange(self.scrollEventElement, td, function(isVisible) {
|
|
769
|
+
if (isVisible && getProp(self.defn, 'table', 'limit', 'autoShowMore')) {
|
|
770
|
+
self.logDebug(self.makeLogTag() + ' "Show More Rows" button scrolled into view', self.toString());
|
|
771
|
+
showMore();
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
tr.appendChild(td.get(0));
|
|
776
|
+
self.ui.tbody.append(tr);
|
|
777
|
+
|
|
778
|
+
// When using TableTool with a pinned column, the pinned column is a clone on the left hand
|
|
779
|
+
// side. TableTool does not monitor the original tbody to see if new elements are added, so we
|
|
780
|
+
// need to add new data to the pinned column clone as well.
|
|
781
|
+
|
|
782
|
+
if (usingTableTool) {
|
|
783
|
+
self.ui.tbody.parents('div.ttsticky').find('table > tbody').append(tr);
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
var render = function (startIndex, howMany, nextChunk) {
|
|
788
|
+
var atLimit = false;
|
|
789
|
+
|
|
790
|
+
if (startIndex == null) {
|
|
791
|
+
startIndex = 0;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
if (howMany == null) {
|
|
795
|
+
howMany = data.data.length;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
self.logDebug(self.makeLogTag() + ' Rendering rows '
|
|
799
|
+
+ startIndex
|
|
800
|
+
+ ' - '
|
|
801
|
+
+ Math.min(useLimit && startIndex === 0 ? limitConfig.threshold - 1 : Number.POSITIVE_INFINITY
|
|
802
|
+
, startIndex + howMany - 1
|
|
803
|
+
, data.data.length - 1)
|
|
804
|
+
+ ' '
|
|
805
|
+
+ (data.data.length - 1 <= startIndex + howMany - 1
|
|
806
|
+
? '[END]'
|
|
807
|
+
: ('/ ' + data.data.length - 1)), self.toString());
|
|
808
|
+
|
|
809
|
+
for (var rowNum = startIndex; rowNum < data.data.length && rowNum < startIndex + howMany && !atLimit; rowNum += 1) {
|
|
810
|
+
renderDataRow(data.data[rowNum], rowNum);
|
|
811
|
+
|
|
812
|
+
if (!self.features.incremental
|
|
813
|
+
&& useLimit
|
|
814
|
+
&& limitConfig.method === 'more'
|
|
815
|
+
&& rowNum !== data.data.length - 1 // [0]
|
|
816
|
+
&& ((startIndex === 0 && rowNum === limitConfig.threshold - 1) // [1]
|
|
817
|
+
|| (startIndex > 0 && rowNum === startIndex + limitConfig.chunkSize - 1))) { // [2]
|
|
818
|
+
|
|
819
|
+
// Condition [0]: We haven't reached the end of the data.
|
|
820
|
+
// Condition [1]: We've reached the initial threshold for showing the more button.
|
|
821
|
+
// Condition [2]: We're showing additional rows because they clicked the more button.
|
|
822
|
+
|
|
823
|
+
renderShowMore(rowNum);
|
|
824
|
+
atLimit = true;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
if (atLimit) {
|
|
829
|
+
self.fire('limited');
|
|
830
|
+
}
|
|
831
|
+
else {
|
|
832
|
+
self.fire('unlimited');
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
self._updateSelectionGui();
|
|
836
|
+
|
|
837
|
+
if (self.features.floatingHeader) {
|
|
838
|
+
switch (getProp(self.defn, 'table', 'floatingHeader', 'method')) {
|
|
839
|
+
case 'tabletool':
|
|
840
|
+
window.TableTool.update();
|
|
841
|
+
break;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
if (rowNum === data.data.length) {
|
|
846
|
+
// All rows have been produced, so we're done!
|
|
847
|
+
|
|
848
|
+
delete self.moreVisibleHandler;
|
|
849
|
+
|
|
850
|
+
//self.ui.tbl.css({'table-layout': 'auto'}); // XXX - Does nothing?!
|
|
851
|
+
|
|
852
|
+
if (typeof cont === 'function') {
|
|
853
|
+
return cont();
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Nothing to do next, but we're done here.
|
|
857
|
+
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
else if (typeof nextChunk === 'function') {
|
|
861
|
+
return nextChunk(startIndex, howMany);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if (typeof cont === 'function') {
|
|
865
|
+
return cont();
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// Nothing to do next, but we're done here.
|
|
869
|
+
|
|
870
|
+
return;
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
var nextChunk;
|
|
874
|
+
|
|
875
|
+
if (self.features.incremental) {
|
|
876
|
+
var incrementalConfig = self.defn.table.incremental;
|
|
877
|
+
if (incrementalConfig.method === 'setTimeout') {
|
|
878
|
+
nextChunk = function (startIndex, howMany) {
|
|
879
|
+
window.setTimeout(function () {
|
|
880
|
+
render(startIndex + howMany, howMany, nextChunk);
|
|
881
|
+
}, incrementalConfig.delay);
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
// Kick off the initial render starting at index 0.
|
|
885
|
+
|
|
886
|
+
window.setTimeout(function () {
|
|
887
|
+
render(0, incrementalConfig.chunkSize, nextChunk);
|
|
888
|
+
}, incrementalConfig.delay);
|
|
889
|
+
}
|
|
890
|
+
else if (incrementalConfig.method === 'requestAnimationFrame') {
|
|
891
|
+
nextChunk = function (startIndex, howMany) {
|
|
892
|
+
window.requestAnimationFrame(function () {
|
|
893
|
+
render(startIndex + howMany, howMany, nextChunk);
|
|
894
|
+
});
|
|
895
|
+
};
|
|
896
|
+
|
|
897
|
+
// Kick off the initial render starting at index 0.
|
|
898
|
+
|
|
899
|
+
window.requestAnimationFrame(function () {
|
|
900
|
+
render(0, incrementalConfig.chunkSize, nextChunk);
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
else {
|
|
904
|
+
throw new Error('Invalid value for `table.incremental.method` (' + incrementalConfig.method + ') - must be either "setTimeout" or "requestAnimationFrame"');
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
else {
|
|
908
|
+
render();
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
//self.ui.tbl.css({'table-layout': 'fixed'}); // XXX - Does nothing?!
|
|
912
|
+
};
|
|
913
|
+
|
|
914
|
+
// #_paginationGetTotalPages {{{2
|
|
915
|
+
|
|
916
|
+
/**
|
|
917
|
+
* Return the total number of pages given the current data and rows-per-page setting.
|
|
918
|
+
*
|
|
919
|
+
* @return {number}
|
|
920
|
+
*/
|
|
921
|
+
|
|
922
|
+
GridTablePlain.prototype._paginationGetTotalPages = function () {
|
|
923
|
+
var self = this;
|
|
924
|
+
var rows = self.ui.tbody.children('tr[data-row-num]');
|
|
925
|
+
return Math.max(1, Math.ceil(rows.length / self._paginationRowsPerPage));
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
// #_paginationApply {{{2
|
|
929
|
+
|
|
930
|
+
/**
|
|
931
|
+
* Show rows belonging to the current page and hide all others. This is the core of the
|
|
932
|
+
* pagination feature: because every row is already in the DOM, switching pages is just toggling
|
|
933
|
+
* display on TR elements.
|
|
934
|
+
*/
|
|
935
|
+
|
|
936
|
+
GridTablePlain.prototype._paginationApply = function () {
|
|
937
|
+
var self = this;
|
|
938
|
+
var perPage = self._paginationRowsPerPage;
|
|
939
|
+
var page = self._paginationPage;
|
|
940
|
+
var startIdx = page * perPage;
|
|
941
|
+
var endIdx = startIdx + perPage;
|
|
942
|
+
var usingTableTool = self.features.floatingHeader && getProp(self.defn, 'table', 'floatingHeader', 'method') === 'tabletool' && window.TableTool != null;
|
|
943
|
+
|
|
944
|
+
self.ui.tbody.children('tr[data-row-num]').each(function (idx) {
|
|
945
|
+
if (idx >= startIdx && idx < endIdx) {
|
|
946
|
+
this.style.display = '';
|
|
947
|
+
}
|
|
948
|
+
else {
|
|
949
|
+
this.style.display = 'none';
|
|
950
|
+
}
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
if (usingTableTool) {
|
|
954
|
+
window.TableTool.update();
|
|
955
|
+
}
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
// #_paginationGoToPage {{{2
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* Navigate to a specific page and update the pagination controls.
|
|
962
|
+
*
|
|
963
|
+
* @param {number} page Zero-based page index.
|
|
964
|
+
*/
|
|
965
|
+
|
|
966
|
+
GridTablePlain.prototype._paginationGoToPage = function (page) {
|
|
967
|
+
var self = this;
|
|
968
|
+
var totalPages = self._paginationGetTotalPages();
|
|
969
|
+
|
|
970
|
+
if (page < 0) {
|
|
971
|
+
page = 0;
|
|
972
|
+
}
|
|
973
|
+
else if (page >= totalPages) {
|
|
974
|
+
page = totalPages - 1;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
self._paginationPage = page;
|
|
978
|
+
self._paginationApply();
|
|
979
|
+
self._paginationDrawControls();
|
|
980
|
+
};
|
|
981
|
+
|
|
982
|
+
// #_paginationDrawControls {{{2
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* Draw (or redraw) the pagination navigation bar below the table.
|
|
986
|
+
*
|
|
987
|
+
* Layout: [first] ... [cur-2] [cur-1] [cur] [cur+1] [cur+2] ... [last]
|
|
988
|
+
*/
|
|
989
|
+
|
|
990
|
+
GridTablePlain.prototype._paginationDrawControls = function () {
|
|
991
|
+
var self = this;
|
|
992
|
+
var totalPages = self._paginationGetTotalPages();
|
|
993
|
+
var current = self._paginationPage;
|
|
994
|
+
|
|
995
|
+
// Remove the existing controls if present.
|
|
996
|
+
if (self.ui.paginationControls) {
|
|
997
|
+
self.ui.paginationControls.remove();
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
if (totalPages <= 1) {
|
|
1001
|
+
// Only one page — no need for controls.
|
|
1002
|
+
self.ui.paginationControls = null;
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
var nav = jQuery('<nav>', {
|
|
1007
|
+
'class': 'wcdv_pagination',
|
|
1008
|
+
'aria-label': trans('GRID.PAGINATION.ARIA_LABEL')
|
|
1009
|
+
});
|
|
1010
|
+
|
|
1011
|
+
var makeBtn = function (label, pageIdx, isCurrent) {
|
|
1012
|
+
var btn = jQuery('<button>', {
|
|
1013
|
+
'type': 'button',
|
|
1014
|
+
'class': 'wcdv_pagination_btn' + (isCurrent ? ' wcdv_pagination_current' : ''),
|
|
1015
|
+
'aria-label': trans('GRID.PAGINATION.GO_TO_PAGE', pageIdx + 1),
|
|
1016
|
+
'aria-current': isCurrent ? 'page' : undefined
|
|
1017
|
+
}).text(label);
|
|
1018
|
+
|
|
1019
|
+
if (!isCurrent) {
|
|
1020
|
+
btn.on('click', function () {
|
|
1021
|
+
self._paginationGoToPage(pageIdx);
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
else {
|
|
1025
|
+
btn.attr('disabled', true);
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
return btn;
|
|
1029
|
+
};
|
|
1030
|
+
|
|
1031
|
+
var makeEllipsis = function () {
|
|
1032
|
+
return jQuery('<span>', { 'class': 'wcdv_pagination_ellipsis', 'aria-hidden': 'true' }).text('\u2026');
|
|
1033
|
+
};
|
|
1034
|
+
|
|
1035
|
+
// Determine the range of page buttons to show around the current page.
|
|
1036
|
+
var rangeStart = Math.max(0, current - 2);
|
|
1037
|
+
var rangeEnd = Math.min(totalPages - 1, current + 2);
|
|
1038
|
+
|
|
1039
|
+
// [first]
|
|
1040
|
+
if (rangeStart > 0) {
|
|
1041
|
+
nav.append(makeBtn('1', 0, current === 0));
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// ... before range
|
|
1045
|
+
if (rangeStart > 1) {
|
|
1046
|
+
nav.append(makeEllipsis());
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// Page buttons in range
|
|
1050
|
+
for (var i = rangeStart; i <= rangeEnd; i += 1) {
|
|
1051
|
+
nav.append(makeBtn(String(i + 1), i, i === current));
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// ... after range
|
|
1055
|
+
if (rangeEnd < totalPages - 2) {
|
|
1056
|
+
nav.append(makeEllipsis());
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// [last]
|
|
1060
|
+
if (rangeEnd < totalPages - 1) {
|
|
1061
|
+
nav.append(makeBtn(String(totalPages), totalPages - 1, current === totalPages - 1));
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
self.ui.paginationControls = nav;
|
|
1065
|
+
|
|
1066
|
+
// Insert after the grid table container so pagination stays outside the scrollable area.
|
|
1067
|
+
self.grid.ui.grid.after(nav);
|
|
1068
|
+
};
|
|
1069
|
+
|
|
1070
|
+
// #drawFooter {{{2
|
|
1071
|
+
|
|
1072
|
+
GridTablePlain.prototype.drawFooter = function (columns, data, typeInfo) {
|
|
1073
|
+
var self = this;
|
|
1074
|
+
|
|
1075
|
+
var makeSelectAll = function (tr) {
|
|
1076
|
+
self.ui.checkAll_tfoot = jQuery('<input>', { 'name': 'checkAll', 'type': 'checkbox' })
|
|
1077
|
+
.on('change', function (evt) {
|
|
1078
|
+
self.checkAll(evt);
|
|
1079
|
+
});
|
|
1080
|
+
jQuery('<td>', {'class': 'wcdv_group_col_spacer'}).append(self.ui.checkAll_tfoot).appendTo(tr);
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
var makeAggregateRow = function () {
|
|
1084
|
+
// Circumventing the correct logic here because TableTool requires an empty footer in order to
|
|
1085
|
+
// implement horizontal scrolling; if you omit the footer (with a TR and all appropriate TD's in
|
|
1086
|
+
// it) then you can't scroll horizontally.
|
|
1087
|
+
if (false && getProp(self.defn, 'table', 'footer') == null) {
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
var tr = jQuery('<tr>');
|
|
1092
|
+
|
|
1093
|
+
// Add the "select all" checkbox when row selection is enabled.
|
|
1094
|
+
|
|
1095
|
+
if (self.features.rowSelect) {
|
|
1096
|
+
makeSelectAll(tr);
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// If there are row operations, make a column in the footer to take up that space.
|
|
1100
|
+
//
|
|
1101
|
+
// | [ ] | op op op | col1 | col2 | ... |
|
|
1102
|
+
// +-----+----------+------+------+-----+
|
|
1103
|
+
// | | <here> | |
|
|
1104
|
+
|
|
1105
|
+
if (self.hasOperations('row')) {
|
|
1106
|
+
tr.append(jQuery('<td>'));
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// Create the columns for the data fields, which contain aggregate function results over those
|
|
1110
|
+
// fields.
|
|
1111
|
+
|
|
1112
|
+
var didFooterCell = false;
|
|
1113
|
+
|
|
1114
|
+
tr.append(_.map(columns, function (field, colIndex) {
|
|
1115
|
+
var fcc = self.colConfig.get(field) || {};
|
|
1116
|
+
var colTypeInfo = typeInfo.get(field);
|
|
1117
|
+
var td = jQuery('<td>');
|
|
1118
|
+
var footerConfig = getProp(self.defn, 'table', 'footer', field);
|
|
1119
|
+
var agg;
|
|
1120
|
+
var aggFun;
|
|
1121
|
+
var aggResult;
|
|
1122
|
+
var footerVal;
|
|
1123
|
+
|
|
1124
|
+
self.setCss(td, field);
|
|
1125
|
+
self.setAlignment(td, fcc, typeInfo.get(field));
|
|
1126
|
+
|
|
1127
|
+
if (footerConfig == null) {
|
|
1128
|
+
if (didFooterCell) {
|
|
1129
|
+
td.addClass('wcdv_divider');
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
didFooterCell = false;
|
|
1133
|
+
}
|
|
1134
|
+
else {
|
|
1135
|
+
if (colIndex > 0) {
|
|
1136
|
+
td.addClass('wcdv_divider');
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
didFooterCell = true;
|
|
1140
|
+
|
|
1141
|
+
// Although the footer config is an aggregate spec, there is one place we allow more
|
|
1142
|
+
// flexibility. If the fields aren't set, use the field for the column in which we're
|
|
1143
|
+
// displaying this footer. This is merely a convenience for the most common case.
|
|
1144
|
+
|
|
1145
|
+
if (footerConfig.fields == null) {
|
|
1146
|
+
footerConfig.fields = [field];
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
self.logDebug(self.makeLogTag() + ' Creating footer using config: %O', self.toString(), field, footerConfig);
|
|
1150
|
+
|
|
1151
|
+
var aggInfo = new AggregateInfo('all', footerConfig, 0, self.colConfig, typeInfo, function (tag, fti) {
|
|
1152
|
+
if (fti.needsDecoding) {
|
|
1153
|
+
self.logDebug(self.makeLogTag() + ' Converting data: { field = "%s", type = "%s" }',
|
|
1154
|
+
self.toString(), field, tag, fti.field, fti.type);
|
|
1155
|
+
|
|
1156
|
+
Source.decodeAll(data.dataByRowId, fti.field, typeInfo);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
fti.deferDecoding = false;
|
|
1160
|
+
fti.needsDecoding = false;
|
|
1161
|
+
});
|
|
1162
|
+
aggResult = aggInfo.instance.calculate(data.data);
|
|
1163
|
+
var aggResult_formatted;
|
|
1164
|
+
|
|
1165
|
+
if (isElement(aggResult)) {
|
|
1166
|
+
footerVal = aggResult;
|
|
1167
|
+
}
|
|
1168
|
+
else {
|
|
1169
|
+
if (aggInfo.instance.inheritFormatting) {
|
|
1170
|
+
aggResult_formatted = format(aggInfo.colConfig[0], aggInfo.typeInfo[0], aggResult, {
|
|
1171
|
+
overrideType: aggInfo.instance.getType()
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
else {
|
|
1175
|
+
aggResult_formatted = format(null, null, aggResult, {
|
|
1176
|
+
overrideType: aggInfo.instance.getType(),
|
|
1177
|
+
decode: false
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
if (aggInfo.debug) {
|
|
1182
|
+
self.logDebug(self.makeLogTag() + ' Aggregate result: %s',
|
|
1183
|
+
self.toString(), field, JSON.stringify(aggResult));
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
switch (typeof footerConfig.format) {
|
|
1187
|
+
case 'function':
|
|
1188
|
+
footerVal = footerConfig.format(aggResult_formatted);
|
|
1189
|
+
break;
|
|
1190
|
+
case 'string':
|
|
1191
|
+
footerVal = sprintf.sprintf(footerConfig.format, aggResult_formatted);
|
|
1192
|
+
break;
|
|
1193
|
+
default:
|
|
1194
|
+
throw new Error('Footer config for field "' + field + '": `format` must be a function or a string');
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
if (isElement(footerVal)) {
|
|
1199
|
+
td.append(footerVal);
|
|
1200
|
+
}
|
|
1201
|
+
else {
|
|
1202
|
+
td.text(footerVal);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
return td;
|
|
1207
|
+
}));
|
|
1208
|
+
|
|
1209
|
+
// ...
|
|
1210
|
+
|
|
1211
|
+
if (self.features.rowReorder) {
|
|
1212
|
+
tr.append(jQuery('<td>').text('Options'));
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// Finish the row that contains the aggregate functions.
|
|
1216
|
+
|
|
1217
|
+
self.ui.tfoot.append(tr);
|
|
1218
|
+
};
|
|
1219
|
+
|
|
1220
|
+
/*
|
|
1221
|
+
* Create a row in the footer for an external footer that we've absorbed into the grid.
|
|
1222
|
+
*/
|
|
1223
|
+
|
|
1224
|
+
var makeExternalFooterRow = function () {
|
|
1225
|
+
if (self.opts.footer == null || !self.opts.stealGridFooter) {
|
|
1226
|
+
return;
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
var tr = jQuery('<tr>');
|
|
1230
|
+
|
|
1231
|
+
if (!isVisible(self.opts.footer)) {
|
|
1232
|
+
tr.hide();
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
if (self.features.rowSelect) {
|
|
1236
|
+
// Circumventing the correct logic here because TableTool requires an empty footer in order to
|
|
1237
|
+
// implement horizontal scrolling; if you omit the footer (with a TR and all appropriate TD's
|
|
1238
|
+
// in it) then you can't scroll horizontally.
|
|
1239
|
+
if (true || getProp(self.defn, 'table', 'footer')) {
|
|
1240
|
+
// There is an aggregate row, so it contains the "select all" checkbox.
|
|
1241
|
+
jQuery('<td>', {'class': 'wcdv_group_col_spacer'}).appendTo(tr);
|
|
1242
|
+
}
|
|
1243
|
+
else {
|
|
1244
|
+
// There is no aggregate row, so make the "select all" checkbox here.
|
|
1245
|
+
makeSelectAll(tr);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
// If there are row operations, make a column in the footer to take up that space.
|
|
1250
|
+
//
|
|
1251
|
+
// | [ ] | op op op | col1 | col2 | ... |
|
|
1252
|
+
// +-----+----------+------+------+-----+
|
|
1253
|
+
// | | <here> | |
|
|
1254
|
+
|
|
1255
|
+
if (self.hasOperations('row')) {
|
|
1256
|
+
tr.append(jQuery('<td>'));
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// If there are row operations, make a column in the footer to take up that space.
|
|
1260
|
+
//
|
|
1261
|
+
// | [ ] | op op op | col1 | col2 | ... |
|
|
1262
|
+
// +-----+----------+------+------+------+
|
|
1263
|
+
// | | | <here> ----------> |
|
|
1264
|
+
|
|
1265
|
+
tr.append(jQuery('<td>', {'colspan': columns.length}).append(self.opts.footer));
|
|
1266
|
+
|
|
1267
|
+
if (self.features.rowReorder) {
|
|
1268
|
+
tr.append(jQuery('<td>'));
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
self.ui.tfoot.append(tr);
|
|
1272
|
+
};
|
|
1273
|
+
|
|
1274
|
+
makeAggregateRow();
|
|
1275
|
+
makeExternalFooterRow();
|
|
1276
|
+
};
|
|
1277
|
+
|
|
1278
|
+
// #makeRowReorderBtn {{{2
|
|
1279
|
+
|
|
1280
|
+
GridTablePlain.prototype.makeRowReorderBtn = function () {
|
|
1281
|
+
var self = this;
|
|
1282
|
+
|
|
1283
|
+
return jQuery('<button type="button" class="drag-handle fa">')
|
|
1284
|
+
.html(icon('move-vertical',null,'Drag or press up/down arrows to move'));
|
|
1285
|
+
};
|
|
1286
|
+
|
|
1287
|
+
// #updateFeatures {{{2
|
|
1288
|
+
|
|
1289
|
+
/**
|
|
1290
|
+
* Change the features of this grid table, then redraw the grid table.
|
|
1291
|
+
*
|
|
1292
|
+
* @param {Object} f
|
|
1293
|
+
* The new features to apply. Any features not indicated will maintain their current settings.
|
|
1294
|
+
*
|
|
1295
|
+
* @method
|
|
1296
|
+
*/
|
|
1297
|
+
|
|
1298
|
+
GridTablePlain.prototype.updateFeatures = function (f) {
|
|
1299
|
+
var self = this;
|
|
1300
|
+
|
|
1301
|
+
_.each(f, function (v, k) {
|
|
1302
|
+
self.features[k] = v;
|
|
1303
|
+
});
|
|
1304
|
+
|
|
1305
|
+
self.draw(self.root);
|
|
1306
|
+
};
|
|
1307
|
+
|
|
1308
|
+
// #addWorkHandler {{{2
|
|
1309
|
+
|
|
1310
|
+
GridTablePlain.prototype.addWorkHandler = function () {
|
|
1311
|
+
var self = this;
|
|
1312
|
+
|
|
1313
|
+
self.view.on(ComputedView.events.workEnd, function (info, ops) {
|
|
1314
|
+
if (self._destroyed) { return; }
|
|
1315
|
+
self.logDebug(self.makeLogTag() + ' ComputedView has finished doing work',
|
|
1316
|
+
self.toString());
|
|
1317
|
+
|
|
1318
|
+
if (ops.group || ops.pivot) {
|
|
1319
|
+
self.logDebug(self.makeLogTag() + ' Unable to render this data: %O',
|
|
1320
|
+
self.toString(), ops);
|
|
1321
|
+
self.fire('unableToRender', null, ops);
|
|
1322
|
+
return;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
self.logDebug(self.makeLogTag() + ' Redrawing because the view has done work',
|
|
1326
|
+
self.toString());
|
|
1327
|
+
self.draw(self.root);
|
|
1328
|
+
}, { who: self });
|
|
1329
|
+
};
|
|
1330
|
+
|
|
1331
|
+
//GridTablePlain.prototype.addWorkHandler = function () {
|
|
1332
|
+
// var self = this;
|
|
1333
|
+
//
|
|
1334
|
+
// // Sets up callbacks responsible for correctly redrawing the grid when the view has done work
|
|
1335
|
+
// // (e.g. sorting or filtering) that will change what is displayed. This is only needed when
|
|
1336
|
+
// // limiting output because otherwise, sort and filter callbacks don't need to redraw the whole
|
|
1337
|
+
// // grid, and they are taken care of by the 'sort' and 'filter' events on a row-by-row basis.
|
|
1338
|
+
//
|
|
1339
|
+
// self.view.on(ComputedView.events.workEnd, function (info, ops) {
|
|
1340
|
+
// self.logDebug(self.makeLogTag('handler(workEnd)') + ' ComputedView has finished doing work');
|
|
1341
|
+
//
|
|
1342
|
+
// if (ops.group || ops.pivot) {
|
|
1343
|
+
//
|
|
1344
|
+
// // If the data is grouped or pivotted, we can't render it. Emit the "unable to render" event
|
|
1345
|
+
// // so that our Grid instance can replace us with a GridTableGroup or GridTablePivot instance
|
|
1346
|
+
// // which can render the data.
|
|
1347
|
+
//
|
|
1348
|
+
// self.fire(GridTable.events.unableToRender);
|
|
1349
|
+
// return;
|
|
1350
|
+
// }
|
|
1351
|
+
//
|
|
1352
|
+
// if (self.needsRedraw) {
|
|
1353
|
+
// self.logDebug(self.makeLogTag('handler(workEnd)') + ' Redrawing because the view has done work');
|
|
1354
|
+
//
|
|
1355
|
+
// self.needsRedraw = false;
|
|
1356
|
+
//
|
|
1357
|
+
// return self.view.getData(function (data) {
|
|
1358
|
+
// return self.view.getTypeInfo(function (typeInfo) {
|
|
1359
|
+
// self.timing.start(['Grid Table', 'Redraw triggered by view']);
|
|
1360
|
+
//
|
|
1361
|
+
// // Determine what columns will be in the table. This comes from the user, or from the
|
|
1362
|
+
// // data itself. We may then add columns for extra features (like row selection or
|
|
1363
|
+
// // reordering).
|
|
1364
|
+
//
|
|
1365
|
+
// var columns = determineColumns(self.defn, data, typeInfo);
|
|
1366
|
+
//
|
|
1367
|
+
// // Draw the body.
|
|
1368
|
+
//
|
|
1369
|
+
// self.drawBody(data, typeInfo, columns, function () {
|
|
1370
|
+
// self.timing.stop(['Grid Table', 'Redraw triggered by view']);
|
|
1371
|
+
//
|
|
1372
|
+
// // Potentially the columns resized as a result of sorting, filtering, or adding new data.
|
|
1373
|
+
// self.fire(GridTable.events.columnResize);
|
|
1374
|
+
// });
|
|
1375
|
+
// });
|
|
1376
|
+
// });
|
|
1377
|
+
// }
|
|
1378
|
+
// else {
|
|
1379
|
+
// // Potentially the columns resized as a result of sorting, filtering, or adding new data.
|
|
1380
|
+
// self.fire(GridTable.events.columnResize);
|
|
1381
|
+
// }
|
|
1382
|
+
// }, { who: self });
|
|
1383
|
+
//};
|
|
1384
|
+
|
|
1385
|
+
// #addDataToCsv {{{2
|
|
1386
|
+
|
|
1387
|
+
/**
|
|
1388
|
+
* Add all data to the CSV file. Because plain tables frequently don't show all the data, it's not
|
|
1389
|
+
* enough to perform the CSV generation inside the `render()` method like we do with other GridTable
|
|
1390
|
+
* implementations.
|
|
1391
|
+
*
|
|
1392
|
+
* @param {object} data
|
|
1393
|
+
*/
|
|
1394
|
+
|
|
1395
|
+
GridTablePlain.prototype.addDataToCsv = function (data) {
|
|
1396
|
+
var self = this;
|
|
1397
|
+
var columns = determineColumns(self.colConfig, data, self.typeInfo);
|
|
1398
|
+
|
|
1399
|
+
self.logDebug(self.makeLogTag() + ' Started generating CSV file', self.toString());
|
|
1400
|
+
self.fire('generateCsvProgress', null, 0);
|
|
1401
|
+
|
|
1402
|
+
self.csv.start();
|
|
1403
|
+
self.csv.addRow();
|
|
1404
|
+
_.each(columns, function (field, colIndex) {
|
|
1405
|
+
var fcc = self.colConfig.get(field) || {};
|
|
1406
|
+
self.csv.addCol(fcc.displayText || field);
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
var howMany = data.data.length / 10;
|
|
1410
|
+
|
|
1411
|
+
var f = function (startIndex) {
|
|
1412
|
+
var endIndex = Math.min(data.data.length, startIndex + howMany);
|
|
1413
|
+
for (var i = startIndex; i < endIndex; i += 1) {
|
|
1414
|
+
var row = data.data[i];
|
|
1415
|
+
|
|
1416
|
+
self.csv.addRow();
|
|
1417
|
+
_.each(columns, function (field, colIndex) {
|
|
1418
|
+
var fcc = self.colConfig.get(field) || {};
|
|
1419
|
+
var cell = row.rowData[field];
|
|
1420
|
+
var value = format(fcc, self.typeInfo.get(field), cell);
|
|
1421
|
+
|
|
1422
|
+
if (value instanceof Element) {
|
|
1423
|
+
self.csv.addCol(jQuery(value).text());
|
|
1424
|
+
}
|
|
1425
|
+
else if (value instanceof jQuery) {
|
|
1426
|
+
self.csv.addCol(value.text());
|
|
1427
|
+
}
|
|
1428
|
+
else if (fcc.allowHtml && self.typeInfo.get(field).type === 'string' && value.charAt(0) === '<') {
|
|
1429
|
+
self.csv.addCol(jQuery(value).text());
|
|
1430
|
+
}
|
|
1431
|
+
else {
|
|
1432
|
+
self.csv.addCol(value);
|
|
1433
|
+
}
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
if (i === data.data.length) {
|
|
1438
|
+
self.csv.finish(function () {
|
|
1439
|
+
self.logDebug(self.makeLogTag() + ' Finished generating CSV file', self.toString());
|
|
1440
|
+
self.csvLock.unlock();
|
|
1441
|
+
self.fire('generateCsvProgress', null, 100);
|
|
1442
|
+
self.fire('csvReady');
|
|
1443
|
+
});
|
|
1444
|
+
}
|
|
1445
|
+
else {
|
|
1446
|
+
self.fire('generateCsvProgress', null, Math.floor((i / data.data.length) * 100));
|
|
1447
|
+
setTimeout(function () {
|
|
1448
|
+
return f(i);
|
|
1449
|
+
}, 100);
|
|
1450
|
+
}
|
|
1451
|
+
};
|
|
1452
|
+
|
|
1453
|
+
return f(0);
|
|
1454
|
+
};
|
|
1455
|
+
|
|
1456
|
+
// #_updateSelectionGui {{{2
|
|
1457
|
+
|
|
1458
|
+
/**
|
|
1459
|
+
* Update the checkboxes in the grid table to match what the current selection is.
|
|
1460
|
+
*/
|
|
1461
|
+
|
|
1462
|
+
GridTablePlain.prototype._updateSelectionGui = function () {
|
|
1463
|
+
var self = this;
|
|
1464
|
+
|
|
1465
|
+
// True if there are no rows to select.
|
|
1466
|
+
var isDisabled = self.data.data.length === 0;
|
|
1467
|
+
|
|
1468
|
+
// True if all rows are selected.
|
|
1469
|
+
var isAllChecked = !isDisabled && self.selection.length === self.data.data.length;
|
|
1470
|
+
|
|
1471
|
+
// True if some rows are selected, but not all of them.
|
|
1472
|
+
var isIndeterminate = !isDisabled && !isAllChecked && self.selection.length > 0;
|
|
1473
|
+
|
|
1474
|
+
var updateCheckboxState = function (elt) {
|
|
1475
|
+
elt.prop('disabled', isDisabled);
|
|
1476
|
+
elt.prop('checked', isAllChecked);
|
|
1477
|
+
elt.prop('indeterminate', isIndeterminate);
|
|
1478
|
+
};
|
|
1479
|
+
|
|
1480
|
+
// First, deselect all rows (remove "selected" class and uncheck the box).
|
|
1481
|
+
|
|
1482
|
+
self.root.find('tbody td.wcdv_selected_row').removeClass('wcdv_selected_row');
|
|
1483
|
+
self.root.find('tbody td:first-child input[type="checkbox"]').prop('checked', false);
|
|
1484
|
+
|
|
1485
|
+
// Next, find all the TR elements which correspond to selected rows.
|
|
1486
|
+
|
|
1487
|
+
var trs = self.root.find('tbody tr').filter(function (_idx, elt) {
|
|
1488
|
+
return self.selection.indexOf(+(jQuery(elt).attr('data-row-num'))) >= 0;
|
|
1489
|
+
});
|
|
1490
|
+
|
|
1491
|
+
// Set the "check all" input in the header.
|
|
1492
|
+
|
|
1493
|
+
if (self.ui.checkAll_thead) {
|
|
1494
|
+
updateCheckboxState(self.ui.checkAll_thead);
|
|
1495
|
+
updateCheckboxState(self.ui.checkAll_thead.parents('div.tabletool').find('input[name="checkAll"]'));
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
// Set the "check all" input in the footer.
|
|
1499
|
+
|
|
1500
|
+
if (self.ui.checkAll_tfoot) {
|
|
1501
|
+
updateCheckboxState(self.ui.checkAll_tfoot);
|
|
1502
|
+
updateCheckboxState(self.ui.checkAll_tfoot.parents('div.tabletool').find('input[name="checkAll"]'));
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
// Finally, select appropriate rows (add "selected" class and check the box).
|
|
1506
|
+
|
|
1507
|
+
trs.children('td').addClass('wcdv_selected_row');
|
|
1508
|
+
trs.find('td:first-child input[type="checkbox"]').prop('checked', true);
|
|
1509
|
+
};
|
|
1510
|
+
|
|
1511
|
+
// #checkAll {{{2
|
|
1512
|
+
|
|
1513
|
+
/**
|
|
1514
|
+
* Event handler for using the "check all" checkbox.
|
|
1515
|
+
*
|
|
1516
|
+
* @param {Event} evt
|
|
1517
|
+
* The event generated by the browser when the checkbox is changed.
|
|
1518
|
+
*/
|
|
1519
|
+
|
|
1520
|
+
GridTablePlain.prototype.checkAll = function (evt) {
|
|
1521
|
+
var self = this;
|
|
1522
|
+
|
|
1523
|
+
// Synchronize with floating header clone.
|
|
1524
|
+
jQuery(evt.target).parents('div.tabletool').find('input[name="checkAll"]').prop('checked', evt.target.checked);
|
|
1525
|
+
|
|
1526
|
+
// Either select or unselect all rows.
|
|
1527
|
+
if (evt.target.checked) {
|
|
1528
|
+
self.select();
|
|
1529
|
+
}
|
|
1530
|
+
else {
|
|
1531
|
+
self.unselect();
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
|
|
1535
|
+
// #_addRowReorderHandler {{{2
|
|
1536
|
+
|
|
1537
|
+
GridTablePlain.prototype._addRowReorderHandler = function () {
|
|
1538
|
+
var self = this;
|
|
1539
|
+
|
|
1540
|
+
// configureRowReordering(self.ui.tbody, _.bind(self.view.source.swapRows, self.view.source));
|
|
1541
|
+
};
|
|
1542
|
+
|
|
1543
|
+
// #_addRowSelectHandler {{{2
|
|
1544
|
+
|
|
1545
|
+
/**
|
|
1546
|
+
* Add an event handler for the row select checkboxes. The event is bound on `self.ui.tbody` and
|
|
1547
|
+
* looks for checkbox inputs inside TD elements with class `wcdv_group_col_spacer` to actually handle
|
|
1548
|
+
* the events. The handler calls `self.select(ROW_NUM)` or `self.unselect(ROW_NUM)` when the
|
|
1549
|
+
* checkbox is changed.
|
|
1550
|
+
*/
|
|
1551
|
+
|
|
1552
|
+
GridTablePlain.prototype._addRowSelectHandler = function () {
|
|
1553
|
+
var self = this;
|
|
1554
|
+
|
|
1555
|
+
self.ui.tbody.on('change', '.wcdv_group_col_spacer > input[type="checkbox"]', function () {
|
|
1556
|
+
if (this.checked) {
|
|
1557
|
+
self.select(+(jQuery(this).attr('data-row-num')));
|
|
1558
|
+
}
|
|
1559
|
+
else {
|
|
1560
|
+
self.unselect(+(jQuery(this).attr('data-row-num')));
|
|
1561
|
+
}
|
|
1562
|
+
});
|
|
1563
|
+
};
|
|
1564
|
+
|
|
1565
|
+
GridTablePlain.prototype.clear = function () {
|
|
1566
|
+
var self = this;
|
|
1567
|
+
|
|
1568
|
+
if (self.ui != null && self.ui.slider != null) {
|
|
1569
|
+
self.ui.slider.destroy();
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
if (self.ui != null && self.ui.paginationControls != null) {
|
|
1573
|
+
self.ui.paginationControls.remove();
|
|
1574
|
+
self.ui.paginationControls = null;
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
jQuery(document).off('keydown.active-row-' + self._focusEventId);
|
|
1578
|
+
jQuery(document).off('keydown.omnifilter-' + self._focusEventId);
|
|
1579
|
+
removeFocusHandler(self._focusEventId);
|
|
1580
|
+
|
|
1581
|
+
self.super['GridTable'].clear();
|
|
1582
|
+
};
|
|
1583
|
+
|
|
1584
|
+
// Registry {{{1
|
|
1585
|
+
|
|
1586
|
+
GridRenderer.registry.set('table_plain', GridTablePlain);
|
|
1587
|
+
|
|
1588
|
+
// Exports {{{1
|
|
1589
|
+
|
|
1590
|
+
export {
|
|
1591
|
+
GridTablePlain
|
|
1592
|
+
};
|