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,1404 @@
|
|
|
1
|
+
// Imports {{{1
|
|
2
|
+
|
|
3
|
+
import _ from 'underscore';
|
|
4
|
+
import sprintf from 'sprintf-js';
|
|
5
|
+
import jQuery from 'jquery';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
determineColumns,
|
|
9
|
+
icon,
|
|
10
|
+
format,
|
|
11
|
+
getProp,
|
|
12
|
+
isElement,
|
|
13
|
+
isVisible,
|
|
14
|
+
makeSubclass,
|
|
15
|
+
mergeSort2,
|
|
16
|
+
mixinLogging,
|
|
17
|
+
} from '../../../util/misc.js';
|
|
18
|
+
|
|
19
|
+
import {GridRenderer} from '../../../grid_renderer.js';
|
|
20
|
+
import {AggregateInfo, ComputedView, GROUP_FUNCTION_REGISTRY} from 'datavis-ace';
|
|
21
|
+
|
|
22
|
+
import GridTable from '../table.js';
|
|
23
|
+
|
|
24
|
+
// GridTableGroupDetail {{{1
|
|
25
|
+
// Constructor {{{2
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @class
|
|
29
|
+
* @extends GridTable
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
var GridTableGroupDetail = makeSubclass('GridTableGroupDetail', GridTable, function (grid, defn, view, features, opts, timing, id) {
|
|
33
|
+
var self = this;
|
|
34
|
+
|
|
35
|
+
self.super['GridTable'].ctor.apply(self, arguments);
|
|
36
|
+
|
|
37
|
+
self.features.sort = false;
|
|
38
|
+
self.features.columnResize = false;
|
|
39
|
+
self.features.columnReorder = false;
|
|
40
|
+
|
|
41
|
+
self.logDebug(self.makeLogTag() + ' DataVis // %s // Constructing grid table; features = %O', self.toString(), features);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
mixinLogging(GridTableGroupDetail);
|
|
45
|
+
|
|
46
|
+
// #canRender {{{2
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Responds whether or not this grid table can render the type of data requested.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} what
|
|
52
|
+
* The kind of data the caller wants us to show. Must be one of: plain, group, or pivot.
|
|
53
|
+
*
|
|
54
|
+
* @return {boolean}
|
|
55
|
+
* True if this grid table can render that kind of data, false if it can't.
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
GridTableGroupDetail.prototype.canRender = function (what) {
|
|
59
|
+
return ['group'].indexOf(what) >= 0;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// #drawHeader {{{2
|
|
63
|
+
|
|
64
|
+
GridTableGroupDetail.prototype.drawHeader = function (columns, data, typeInfo, opts) {
|
|
65
|
+
var self = this,
|
|
66
|
+
headingTr,
|
|
67
|
+
headingSpan,
|
|
68
|
+
headingTh,
|
|
69
|
+
headingThContainer,
|
|
70
|
+
headingThControls,
|
|
71
|
+
headingThCss = {
|
|
72
|
+
'white-space': 'nowrap'
|
|
73
|
+
},
|
|
74
|
+
filterThCss = {
|
|
75
|
+
'white-space': 'nowrap',
|
|
76
|
+
'padding-top': 4,
|
|
77
|
+
'vertical-align': 'top'
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
_.each(data.groupFields, function (field, fieldIdx) {
|
|
81
|
+
var fcc = self.colConfig.get(field) || {};
|
|
82
|
+
|
|
83
|
+
headingTr = jQuery('<tr>');
|
|
84
|
+
|
|
85
|
+
if (self.features.rowSelect) {
|
|
86
|
+
if (fieldIdx === 0) {
|
|
87
|
+
self.ui.checkAll_thead = jQuery('<input>', {
|
|
88
|
+
'name': 'checkAll',
|
|
89
|
+
'type': 'checkbox',
|
|
90
|
+
'class': 'wcdv_select_group',
|
|
91
|
+
'data-group-id': '0'
|
|
92
|
+
})
|
|
93
|
+
.on('change', function (evt) {
|
|
94
|
+
self.checkAll(evt);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
headingTh = jQuery('<th>', { scope: 'col' })
|
|
98
|
+
.addClass('wcdv_group_col_spacer')
|
|
99
|
+
.append(self.ui.checkAll_thead)
|
|
100
|
+
.appendTo(headingTr);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
jQuery('<th>')
|
|
104
|
+
.addClass('wcdv_group_col_spacer')
|
|
105
|
+
.appendTo(headingTr);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Add spacers for the previous group fields.
|
|
110
|
+
|
|
111
|
+
for (var i = 0; i < fieldIdx + 1; i += 1) {
|
|
112
|
+
jQuery('<th>')
|
|
113
|
+
.addClass('wcdv_group_col_spacer')
|
|
114
|
+
.appendTo(headingTr)
|
|
115
|
+
;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// headingTh <TH>
|
|
119
|
+
// headingThContainer <DIV>
|
|
120
|
+
// headingSpan <SPAN>
|
|
121
|
+
// headingThControls <DIV>
|
|
122
|
+
|
|
123
|
+
headingSpan = jQuery('<span>')
|
|
124
|
+
.attr({
|
|
125
|
+
'data-wcdv-field': field,
|
|
126
|
+
'data-wcdv-draggable-origin': 'GRID_TABLE_HEADER'
|
|
127
|
+
})
|
|
128
|
+
.addClass('wcdv_heading_title')
|
|
129
|
+
.text(fcc.displayText || field)
|
|
130
|
+
._makeDraggableField()
|
|
131
|
+
;
|
|
132
|
+
|
|
133
|
+
headingThControls = jQuery('<div>');
|
|
134
|
+
|
|
135
|
+
headingThContainer = jQuery('<div>')
|
|
136
|
+
.addClass('wcdv_heading_container')
|
|
137
|
+
.append(headingSpan, headingThControls);
|
|
138
|
+
|
|
139
|
+
headingTh = jQuery('<th>', { scope: 'col' })
|
|
140
|
+
.attr('colspan', columns.length - fieldIdx)
|
|
141
|
+
.css(headingThCss)
|
|
142
|
+
.append(headingThContainer)
|
|
143
|
+
;
|
|
144
|
+
|
|
145
|
+
self._addSortingToHeader(data, 'vertical', {groupFieldIndex: fieldIdx}, headingThControls.get(0));
|
|
146
|
+
|
|
147
|
+
self.setCss(headingTh, field);
|
|
148
|
+
|
|
149
|
+
self.ui.thMap[field] = headingTh;
|
|
150
|
+
|
|
151
|
+
headingTr.append(headingTh);
|
|
152
|
+
self.ui.thead.append(headingTr);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
headingTr = jQuery('<tr>');
|
|
156
|
+
|
|
157
|
+
// Add spacers for all the group fields.
|
|
158
|
+
|
|
159
|
+
if (self.features.rowSelect) {
|
|
160
|
+
jQuery('<th>')
|
|
161
|
+
.addClass('wcdv_group_col_spacer')
|
|
162
|
+
.appendTo(headingTr);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
for (var i = 0; i < data.groupFields.length + 1; i += 1) {
|
|
166
|
+
jQuery('<th>')
|
|
167
|
+
.addClass('wcdv_group_col_spacer')
|
|
168
|
+
.appendTo(headingTr)
|
|
169
|
+
;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Make headers for all the normal (non-grouped) columns.
|
|
173
|
+
|
|
174
|
+
_.each(columns, function (field, colIndex) {
|
|
175
|
+
var fcc = self.colConfig.get(field) || {};
|
|
176
|
+
|
|
177
|
+
if (data.groupFields.indexOf(field) >= 0) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
headingSpan = jQuery('<span>')
|
|
182
|
+
.attr({
|
|
183
|
+
'data-wcdv-field': field,
|
|
184
|
+
'data-wcdv-draggable-origin': 'GRID_TABLE_HEADER'
|
|
185
|
+
})
|
|
186
|
+
.addClass('wcdv_heading_title')
|
|
187
|
+
.text(fcc.displayText || field)
|
|
188
|
+
._makeDraggableField()
|
|
189
|
+
;
|
|
190
|
+
|
|
191
|
+
headingThControls = jQuery('<div>');
|
|
192
|
+
|
|
193
|
+
headingThContainer = jQuery('<div>')
|
|
194
|
+
.addClass('wcdv_heading_container')
|
|
195
|
+
.append(headingSpan, headingThControls);
|
|
196
|
+
|
|
197
|
+
headingTh = jQuery('<th>', { scope: 'col' })
|
|
198
|
+
.css(headingThCss)
|
|
199
|
+
.append(headingThContainer);
|
|
200
|
+
|
|
201
|
+
if (colIndex > 0) {
|
|
202
|
+
headingTh.addClass('wcdv_pivot_colval_boundary');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
self._addSortingToHeader(data, 'vertical', {field: field}, headingThControls.get(0));
|
|
206
|
+
|
|
207
|
+
self.setCss(headingTh, field);
|
|
208
|
+
self.setAlignment(headingTh, fcc, typeInfo.get(field));
|
|
209
|
+
|
|
210
|
+
self.ui.thMap[field] = headingTh;
|
|
211
|
+
headingTr.append(headingTh);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
self.ui.thead.append(headingTr);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// #drawBody {{{2
|
|
218
|
+
|
|
219
|
+
GridTableGroupDetail.prototype.drawBody = function (data, typeInfo, columns, cont, opts) {
|
|
220
|
+
var self = this;
|
|
221
|
+
// self.ui.tbl.append(self.ui.tbody);
|
|
222
|
+
|
|
223
|
+
// TYPES OF CHECKBOXES:
|
|
224
|
+
//
|
|
225
|
+
// .wcdv_select_row
|
|
226
|
+
// * data-row-num = What the rowNum for this data row is.
|
|
227
|
+
// * [tr] data-wcdv-rowValIndex = What rowVal this row is in.
|
|
228
|
+
//
|
|
229
|
+
// .wcdv_select_group
|
|
230
|
+
|
|
231
|
+
if (!data.isGroup) {
|
|
232
|
+
if (typeof cont === 'function') {
|
|
233
|
+
return cont();
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (self.opts.generateCsv) {
|
|
241
|
+
self.addDataToCsv(data);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// percolateUp() {{{3
|
|
245
|
+
|
|
246
|
+
function percolateUp(node /* groupInfo elt */) {
|
|
247
|
+
var disabled = false;
|
|
248
|
+
var checked = false;
|
|
249
|
+
var indeterminate = false;
|
|
250
|
+
|
|
251
|
+
// When a node has no children ...
|
|
252
|
+
//
|
|
253
|
+
// - it contains data rows in the UI
|
|
254
|
+
// - its height in the metadata tree is the # of group fields
|
|
255
|
+
// - it represents a complete rowval
|
|
256
|
+
//
|
|
257
|
+
// ... the number of selected rows is meant to be determined by the caller.
|
|
258
|
+
|
|
259
|
+
if (node.metadata.children != null) {
|
|
260
|
+
node.numSelected = 0;
|
|
261
|
+
_.each(node.metadata.children, function (child) {
|
|
262
|
+
node.numSelected += self.groupInfo[child.id].numSelected;
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (node.metadata.numRows === 0) {
|
|
267
|
+
disabled = true;
|
|
268
|
+
checked = false;
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
if (node.numSelected === 0) {
|
|
272
|
+
checked = false;
|
|
273
|
+
}
|
|
274
|
+
else if (node.numSelected === node.metadata.numRows) {
|
|
275
|
+
checked = true;
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
indeterminate = true;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
node.checkbox.prop('disabled', disabled);
|
|
283
|
+
node.checkbox.prop('checked', checked);
|
|
284
|
+
node.checkbox.prop('indeterminate', indeterminate);
|
|
285
|
+
|
|
286
|
+
if (node.metadata.parent) {
|
|
287
|
+
percolateUp(self.groupInfo[node.metadata.parent.id]);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// percolateDown() {{{3
|
|
292
|
+
|
|
293
|
+
function percolateDown(node /* groupInfo elt */, isChecked) {
|
|
294
|
+
node.checkbox.prop('disabled', false);
|
|
295
|
+
node.checkbox.prop('checked', isChecked);
|
|
296
|
+
node.checkbox.prop('indeterminate', false);
|
|
297
|
+
|
|
298
|
+
node.numSelected = isChecked ? node.metadata.numRows : 0;
|
|
299
|
+
|
|
300
|
+
if (node.metadata.children == null) {
|
|
301
|
+
self.ui.tbody
|
|
302
|
+
.find('tr[data-wcdv-in-group=' + node.metadata.id + ']')
|
|
303
|
+
.find('input[type="checkbox"].wcdv_select_row')
|
|
304
|
+
.prop('checked', isChecked);
|
|
305
|
+
_.each(data.data[node.metadata.rowValIndex], function (row) {
|
|
306
|
+
if (isChecked) {
|
|
307
|
+
self.select(row.rowNum);
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
self.unselect(row.rowNum);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
_.each(node.metadata.children, function (child) {
|
|
316
|
+
percolateDown(self.groupInfo[child.id], isChecked);
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// }}}3
|
|
322
|
+
|
|
323
|
+
/*
|
|
324
|
+
self.ui.tbody.on('change', 'input[type="checkbox"].wcdv_select_row', function () {
|
|
325
|
+
var elt = jQuery(this);
|
|
326
|
+
var tr = elt.closest('tr');
|
|
327
|
+
var isChecked = elt.prop('checked');
|
|
328
|
+
var rowNum = +tr.attr('data-row-num');
|
|
329
|
+
var rowValIndex = +tr.attr('data-wcdv-rowValIndex');
|
|
330
|
+
var rowValMetadata = data.groupMetadata.lookup.byRowValIndex[rowValIndex];
|
|
331
|
+
|
|
332
|
+
self.logDebug(self.makeLogTag() + ' DataVis // ' + 'GRID TABLE // GROUP - DETAIL // SELECT',
|
|
333
|
+
'Selecting data row: rowNum = %d, rowValIndex = %d, parentGroupId = %s, parentGroupInfo = %O',
|
|
334
|
+
rowNum, rowValIndex, rowValMetadata.id, self.groupInfo[rowValMetadata.id]);
|
|
335
|
+
|
|
336
|
+
self.groupInfo[rowValMetadata.id].numSelected += isChecked ? 1 : -1;
|
|
337
|
+
|
|
338
|
+
percolateUp(self.groupInfo[rowValMetadata.id]);
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
self.ui.tbody.on('change', 'input[type="checkbox"].wcdv_select_group', function () {
|
|
342
|
+
var elt = jQuery(this);
|
|
343
|
+
var tr = elt.closest('tr');
|
|
344
|
+
var isChecked = elt.prop('checked');
|
|
345
|
+
var groupMetadataId = +tr.attr('data-wcdv-toggles-group');
|
|
346
|
+
|
|
347
|
+
percolateDown(self.groupInfo[groupMetadataId], isChecked);
|
|
348
|
+
percolateUp(self.groupInfo[groupMetadataId]);
|
|
349
|
+
});
|
|
350
|
+
*/
|
|
351
|
+
|
|
352
|
+
var isRendered = {}; // isRendered[metadataId] => boolean
|
|
353
|
+
var lastRenderedTr = {}; // lastRenderedTr[metadataId] => jQuery <TR>
|
|
354
|
+
|
|
355
|
+
// groupInfo {{{3
|
|
356
|
+
|
|
357
|
+
// groupInfo[id] -> {
|
|
358
|
+
// metadata
|
|
359
|
+
// numSelected
|
|
360
|
+
// checkbox
|
|
361
|
+
// }
|
|
362
|
+
|
|
363
|
+
self.groupInfo = (function () {
|
|
364
|
+
var mapping = {};
|
|
365
|
+
|
|
366
|
+
function recur(node) {
|
|
367
|
+
mapping[node.id] = {
|
|
368
|
+
metadata: node,
|
|
369
|
+
numSelected: 0
|
|
370
|
+
};
|
|
371
|
+
if (node.children != null) {
|
|
372
|
+
_.each(node.children, recur);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
recur(data.groupMetadata);
|
|
377
|
+
mapping[0].checkbox = self.ui.checkAll_thead;
|
|
378
|
+
return mapping;
|
|
379
|
+
})();
|
|
380
|
+
|
|
381
|
+
// toggleGroup() {{{3
|
|
382
|
+
|
|
383
|
+
/*
|
|
384
|
+
* Toggle a sub-group open/closed. This is meant to be used as a jQuery event handler, e.g. for a
|
|
385
|
+
* click event.
|
|
386
|
+
*/
|
|
387
|
+
|
|
388
|
+
function toggleGroup() {
|
|
389
|
+
|
|
390
|
+
/*
|
|
391
|
+
* Toggle the visibility of the subgroup.
|
|
392
|
+
*
|
|
393
|
+
* - metadataId: number
|
|
394
|
+
* What group we are expanding/collapsing.
|
|
395
|
+
*
|
|
396
|
+
* - show: boolean
|
|
397
|
+
* If true, show the rows in the group; otherwise hide them.
|
|
398
|
+
*
|
|
399
|
+
* - tr: jQuery (TR)
|
|
400
|
+
* The table row for the subgroup header.
|
|
401
|
+
*/
|
|
402
|
+
|
|
403
|
+
function toggle(metadataId, show, tr) {
|
|
404
|
+
// Within the group metadata, the rowValIndex is only defined for things which are leaves in
|
|
405
|
+
// the grouping tree and therefore complete a rowVal.
|
|
406
|
+
|
|
407
|
+
var rowValIndex = self.data.groupMetadata.lookup.byId[metadataId].rowValIndex;
|
|
408
|
+
|
|
409
|
+
self.logDebug(self.makeLogTag() + ' show = %s, id = %s, rowValIndex = %s',
|
|
410
|
+
self.toString(), show, metadataId, rowValIndex);
|
|
411
|
+
|
|
412
|
+
// Check if we're expanding a leaf, thus fully expanding an entire group, and see if we need
|
|
413
|
+
// to render table rows for all the records in that group.
|
|
414
|
+
|
|
415
|
+
if (show && !isRendered[metadataId]) {
|
|
416
|
+
self.logDebug(self.makeLogTag() + ' Rendering: group metadata ID = %s',
|
|
417
|
+
self.toString(), metadataId);
|
|
418
|
+
render(metadataId, 0, tr);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Set the visibility for all affected table rows. These can be for children of the current
|
|
422
|
+
// node in the tree (i.e. when expanding the current node does not complete a group), or for
|
|
423
|
+
// records in a fully expanded group: we don't distinguish between these two when it comes to
|
|
424
|
+
// showing/hiding as the attributes used on the elements are the same.
|
|
425
|
+
|
|
426
|
+
self.ui.tbody
|
|
427
|
+
.find('tr')
|
|
428
|
+
.filter(function (i, elt) {
|
|
429
|
+
return jQuery(elt).attr('data-wcdv-in-group') === '' + metadataId;
|
|
430
|
+
})
|
|
431
|
+
.each(function (i, elt) {
|
|
432
|
+
elt = jQuery(elt);
|
|
433
|
+
if (elt.attr('data-wcdv-toggles-group')) {
|
|
434
|
+
toggle(+elt.attr('data-wcdv-toggles-group'), show && elt.attr('data-wcdv-expanded') === '1', elt);
|
|
435
|
+
}
|
|
436
|
+
if (show) {
|
|
437
|
+
elt.show();
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
elt.hide();
|
|
441
|
+
}
|
|
442
|
+
})
|
|
443
|
+
;
|
|
444
|
+
|
|
445
|
+
if (self.ui.tbl.floatThead) {
|
|
446
|
+
self.ui.tbl.floatThead('reflow');
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
var elt = jQuery(this);
|
|
451
|
+
var tr = elt.closest('tr');
|
|
452
|
+
|
|
453
|
+
var op = tr.attr('data-wcdv-expanded') === '0' ? 'show' : 'hide';
|
|
454
|
+
|
|
455
|
+
if (op === 'show') {
|
|
456
|
+
tr.find('.spinner').show();
|
|
457
|
+
}
|
|
458
|
+
window.setTimeout(function () {
|
|
459
|
+
toggle(+tr.attr('data-wcdv-toggles-group'), op === 'show', tr);
|
|
460
|
+
if (op === 'show') {
|
|
461
|
+
tr.find('.spinner').hide();
|
|
462
|
+
}
|
|
463
|
+
tr.attr('data-wcdv-expanded', op === 'show' ? '1' : '0');
|
|
464
|
+
elt.attr('data-wcdv-expanded', op === 'show' ? '1' : '0');
|
|
465
|
+
elt.html(icon(op === 'show' ? 'square-minus' : 'square-plus'));
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// render() {{{3
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* @param {number} [metadataId=0]
|
|
473
|
+
* @param {number} [startIndex=0]
|
|
474
|
+
* @param {jQuery} [afterElement]
|
|
475
|
+
*/
|
|
476
|
+
|
|
477
|
+
function render(metadataId, startIndex, afterElement, showAll) {
|
|
478
|
+
if (metadataId != null && typeof metadataId !== 'number') {
|
|
479
|
+
throw new Error('Call Error: `metadataId` must be null or a number');
|
|
480
|
+
}
|
|
481
|
+
if (startIndex != null && typeof startIndex !== 'number') {
|
|
482
|
+
throw new Error('Call Error: `startIndex` must be null or a number');
|
|
483
|
+
}
|
|
484
|
+
if (afterElement != null && !(afterElement instanceof jQuery)) {
|
|
485
|
+
throw new Error('Call Error: `afterElement` must be null or an instance of jQuery');
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (metadataId == null) metadataId = 0;
|
|
489
|
+
if (startIndex == null) startIndex = 0;
|
|
490
|
+
|
|
491
|
+
if (startIndex > 0 && afterElement == null)
|
|
492
|
+
throw new Error('Call Error: `afterElement` required when `startIndex` > 0');
|
|
493
|
+
|
|
494
|
+
var metadataNode = data.groupMetadata.lookup.byId[metadataId];
|
|
495
|
+
|
|
496
|
+
if (metadataNode == null)
|
|
497
|
+
throw new Error('No group metadata for specified ID: ' + metadataId);
|
|
498
|
+
|
|
499
|
+
var limitConfig = self.defn.table.limit;
|
|
500
|
+
|
|
501
|
+
var showMoreTr,lastInsertedTr;
|
|
502
|
+
|
|
503
|
+
if (afterElement != null && startIndex > 0) {
|
|
504
|
+
showMoreTr = afterElement.nextAll('tr.wcdvgrid_more[data-wcdv-in-group="' + metadataId + '"]');
|
|
505
|
+
afterElement = showMoreTr.prev();
|
|
506
|
+
showMoreTr.remove();
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
var isExpanded = self.defn.table.whenGroup.showExpandedGroups ? '1' : '0';
|
|
510
|
+
|
|
511
|
+
if (metadataNode.children) {
|
|
512
|
+
// We're rendering sub-groups.
|
|
513
|
+
|
|
514
|
+
var i, j;
|
|
515
|
+
var childMetadataNode;
|
|
516
|
+
var childTr;
|
|
517
|
+
var checkbox;
|
|
518
|
+
var expandBtn;
|
|
519
|
+
var infoText, infoTextSpan;
|
|
520
|
+
var fcc;
|
|
521
|
+
var t, v;
|
|
522
|
+
var rowValElt, rowValEltSpan, rowValEltTh;
|
|
523
|
+
var showMoreTd;
|
|
524
|
+
var colSpan;
|
|
525
|
+
|
|
526
|
+
var trans = {
|
|
527
|
+
'group:singular': 'group',
|
|
528
|
+
'group:plural': 'groups',
|
|
529
|
+
'row:singular': 'row',
|
|
530
|
+
'row:plural': 'rows'
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
var childRowValElts = mergeSort2(_.pluck(metadataNode.children, 'rowValElt'));
|
|
534
|
+
var childRowValEltsLen = childRowValElts.length;
|
|
535
|
+
|
|
536
|
+
var howMany = !self.features.limit || showAll ? childRowValEltsLen
|
|
537
|
+
: startIndex === 0 ? limitConfig.threshold
|
|
538
|
+
: limitConfig.chunkSize;
|
|
539
|
+
|
|
540
|
+
for (i = startIndex; i < childRowValEltsLen && i < startIndex + howMany; i += 1) {
|
|
541
|
+
childMetadataNode = metadataNode.children[childRowValElts[i]];
|
|
542
|
+
|
|
543
|
+
childTr = jQuery('<tr>')
|
|
544
|
+
.attr('data-wcdv-in-group', metadataNode.id)
|
|
545
|
+
.attr('data-wcdv-toggles-group', childMetadataNode.id)
|
|
546
|
+
.attr('data-wcdv-expanded', isExpanded)
|
|
547
|
+
;
|
|
548
|
+
|
|
549
|
+
// Insert spacer columns for previous group fields.
|
|
550
|
+
|
|
551
|
+
for (j = 0; j < childMetadataNode.groupFieldIndex; j += 1) {
|
|
552
|
+
jQuery('<th>', {'class': 'wcdv_group_col_spacer'})
|
|
553
|
+
.appendTo(childTr);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
var disabled = childMetadataNode.children == null && childMetadataNode.rows.length === 0;
|
|
557
|
+
|
|
558
|
+
expandBtn = jQuery('<button>', {
|
|
559
|
+
'type': 'button',
|
|
560
|
+
'class': 'wcdv_icon_button wcdv_expand_button',
|
|
561
|
+
'data-wcdv-expanded': isExpanded,
|
|
562
|
+
'disabled': disabled
|
|
563
|
+
}).html(icon(isExpanded === '1' ? (disabled ? 'square':'square-minus' ) : (disabled ? 'square' : 'square-plus')));
|
|
564
|
+
|
|
565
|
+
jQuery('<th>', {'class': 'wcdv_group_col_spacer'})
|
|
566
|
+
.append(expandBtn)
|
|
567
|
+
.appendTo(childTr);
|
|
568
|
+
|
|
569
|
+
// Create the check box which selects the row.
|
|
570
|
+
|
|
571
|
+
if (self.features.rowSelect) {
|
|
572
|
+
checkbox = jQuery('<input>', {
|
|
573
|
+
'type': 'checkbox',
|
|
574
|
+
'class': 'wcdv_select_group',
|
|
575
|
+
'data-group-id': childMetadataNode.id,
|
|
576
|
+
});
|
|
577
|
+
self.groupInfo[childMetadataNode.id].checkbox = checkbox;
|
|
578
|
+
jQuery('<th>', {'class': 'wcdv_group_col_spacer'})
|
|
579
|
+
.append(checkbox)
|
|
580
|
+
.appendTo(childTr);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
fcc = self.colConfig.get(childMetadataNode.groupField) || {};
|
|
584
|
+
t = self.typeInfo.get(childMetadataNode.groupField);
|
|
585
|
+
v = childMetadataNode.rowValCell || childMetadataNode.rowValElt;
|
|
586
|
+
|
|
587
|
+
if (childMetadataNode.groupSpec.fun != null) {
|
|
588
|
+
t = {
|
|
589
|
+
type: GROUP_FUNCTION_REGISTRY.get(childMetadataNode.groupSpec.fun).resultType
|
|
590
|
+
};
|
|
591
|
+
v = childMetadataNode.rowValElt;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
rowValElt = format(fcc, t, v);
|
|
595
|
+
rowValEltSpan = jQuery('<span>');
|
|
596
|
+
|
|
597
|
+
if (rowValElt instanceof Element || rowValElt instanceof jQuery) {
|
|
598
|
+
rowValEltSpan.append(rowValElt);
|
|
599
|
+
}
|
|
600
|
+
else if (fcc.allowHtml) {
|
|
601
|
+
rowValEltSpan.html(rowValElt);
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
rowValEltSpan.text(rowValElt);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
infoText = '(';
|
|
608
|
+
if (childMetadataNode.children != null) {
|
|
609
|
+
infoText += childMetadataNode.numChildren + ' ';
|
|
610
|
+
infoText += (childMetadataNode.numChildren === 1 ? trans['group:singular'] : trans['group:plural']) + ', ';
|
|
611
|
+
}
|
|
612
|
+
infoText += childMetadataNode.numRows + ' ';
|
|
613
|
+
infoText += childMetadataNode.numRows === 1 ? trans['row:singular'] : trans['row:plural'];
|
|
614
|
+
infoText += ')';
|
|
615
|
+
|
|
616
|
+
infoTextSpan = jQuery('<span>').css({'margin-left': '0.5em'}).text(infoText);
|
|
617
|
+
|
|
618
|
+
var spinnerDiv = jQuery('<div>', {'class': 'spinner'})
|
|
619
|
+
.append(jQuery('<div>', {'class': 'bounce1'}))
|
|
620
|
+
.append(jQuery('<div>', {'class': 'bounce2'}))
|
|
621
|
+
.append(jQuery('<div>', {'class': 'bounce3'}))
|
|
622
|
+
.hide();
|
|
623
|
+
|
|
624
|
+
jQuery('<th>', {
|
|
625
|
+
'class': 'wcdv_group_value',
|
|
626
|
+
'scope': 'row',
|
|
627
|
+
'data-wcdv-field': childMetadataNode.groupField,
|
|
628
|
+
'colspan': columns.length - childMetadataNode.groupFieldIndex
|
|
629
|
+
})
|
|
630
|
+
.append(rowValEltSpan)
|
|
631
|
+
.append(infoTextSpan)
|
|
632
|
+
.append(spinnerDiv)
|
|
633
|
+
.appendTo(childTr);
|
|
634
|
+
|
|
635
|
+
if (afterElement != null) {
|
|
636
|
+
afterElement.after(childTr);
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
self.ui.tbody.append(childTr);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
var rowRenderCb = getProp(self.opts, 'events', 'rowRender');
|
|
643
|
+
if (typeof rowRenderCb === 'function') {
|
|
644
|
+
rowRenderCb(childTr, {
|
|
645
|
+
isGroup: true,
|
|
646
|
+
groupMode: 'detail',
|
|
647
|
+
groupField: childMetadataNode.groupField,
|
|
648
|
+
rowValElt: childMetadataNode.rowValCell.value,
|
|
649
|
+
groupMetadata: childMetadataNode
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
if(self.defn.table.whenGroup.showExpandedGroups){
|
|
653
|
+
afterElement = render(childMetadataNode.id, 0, childTr);
|
|
654
|
+
|
|
655
|
+
} else {
|
|
656
|
+
afterElement = childTr;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
isRendered[metadataNode.id] = true;
|
|
661
|
+
|
|
662
|
+
if (i < childRowValEltsLen) {
|
|
663
|
+
// Not all children were rendered.
|
|
664
|
+
|
|
665
|
+
lastRenderedTr[metadataNode.id] = childTr;
|
|
666
|
+
for (var p = metadataNode.parent; p != null; p = p.parent) {
|
|
667
|
+
lastRenderedTr[p.id] = childTr;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
showMoreTr = jQuery('<tr>', {'class': 'wcdvgrid_more', 'data-wcdv-in-group': metadataNode.id});
|
|
671
|
+
|
|
672
|
+
// Insert spacer columns for previous group fields.
|
|
673
|
+
|
|
674
|
+
for (j = 0; j < childMetadataNode.groupFieldIndex; j += 1) {
|
|
675
|
+
jQuery('<th>', {'class': 'wcdv_group_col_spacer'})
|
|
676
|
+
.appendTo(showMoreTr);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
colSpan = columns.length
|
|
680
|
+
+ 1 // for the "expand" button column
|
|
681
|
+
+ (self.features.rowSelect ? 1 : 0)
|
|
682
|
+
+ (self.features.rowReorder ? 1 : 0)
|
|
683
|
+
- (metadataNode.groupFieldIndex || 0);
|
|
684
|
+
|
|
685
|
+
spinnerDiv = jQuery('<div>', {'class': 'spinner'})
|
|
686
|
+
.append(jQuery('<div>', {'class': 'bounce1'}))
|
|
687
|
+
.append(jQuery('<div>', {'class': 'bounce2'}))
|
|
688
|
+
.append(jQuery('<div>', {'class': 'bounce3'}))
|
|
689
|
+
.hide();
|
|
690
|
+
|
|
691
|
+
showMoreTd = jQuery('<td>', {
|
|
692
|
+
'class': 'wcdv_show_more',
|
|
693
|
+
'data-wcdv-in-group': metadataNode.id,
|
|
694
|
+
'data-wcdv-show-more-start': i,
|
|
695
|
+
'colspan': colSpan
|
|
696
|
+
})
|
|
697
|
+
.append(icon('circle-chevron-down'))
|
|
698
|
+
.append(jQuery('<span>Showing rows 1–' + i + ' of ' + childRowValEltsLen + '.</span>')
|
|
699
|
+
.css({'padding-left': '0.5em'}))
|
|
700
|
+
.append(jQuery('<button type="button">Load ' + limitConfig.chunkSize + ' more rows.</button>')
|
|
701
|
+
.css({'margin-left': '0.5em'}))
|
|
702
|
+
.append(jQuery('<button type="button" class="wcdv_show_all">Load all rows.</button>')
|
|
703
|
+
.css({'margin-left': '0.5em'})
|
|
704
|
+
)
|
|
705
|
+
.append(spinnerDiv)
|
|
706
|
+
.appendTo(showMoreTr);
|
|
707
|
+
|
|
708
|
+
childTr.after(showMoreTr);
|
|
709
|
+
}
|
|
710
|
+
lastInsertedTr = afterElement;
|
|
711
|
+
}
|
|
712
|
+
else if (metadataNode.rows) {
|
|
713
|
+
// We're rendering data rows.
|
|
714
|
+
|
|
715
|
+
var isSelected;
|
|
716
|
+
var checkbox;
|
|
717
|
+
var row;
|
|
718
|
+
var rowTr;
|
|
719
|
+
var showMoreTd;
|
|
720
|
+
var colSpan;
|
|
721
|
+
|
|
722
|
+
var howMany = (!self.features.limit || showAll) ? metadataNode.rows.length - startIndex
|
|
723
|
+
: startIndex === 0 ? limitConfig.threshold
|
|
724
|
+
: limitConfig.chunkSize;
|
|
725
|
+
|
|
726
|
+
for (i = startIndex; i < metadataNode.rows.length && i < startIndex + howMany; i += 1) {
|
|
727
|
+
row = metadataNode.rows[i];
|
|
728
|
+
|
|
729
|
+
rowTr = jQuery('<tr>', {
|
|
730
|
+
'id': self.defn.table.id + '_' + i,
|
|
731
|
+
'data-row-num': row.rowNum,
|
|
732
|
+
'data-wcdv-in-group': metadataNode.id,
|
|
733
|
+
'data-wcdv-rowValIndex': metadataNode.rowValIndex
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
// Insert some space to "indent" the data.
|
|
737
|
+
// TODO When does one of these work differently from the other?
|
|
738
|
+
|
|
739
|
+
//jQuery('<td>', {'colspan': data.groupFields.length + 1}).appendTo(rowTr);
|
|
740
|
+
for (var spacerIndex = 0; spacerIndex < data.groupFields.length + 1; spacerIndex += 1) {
|
|
741
|
+
jQuery('<td>', {'class': 'wcdv_group_col_spacer'}).appendTo(rowTr);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// Create the check box which selects the row.
|
|
745
|
+
|
|
746
|
+
if (self.features.rowSelect) {
|
|
747
|
+
isSelected = self.isSelected(row.rowNum);
|
|
748
|
+
checkbox = jQuery('<input>', {
|
|
749
|
+
'type': 'checkbox',
|
|
750
|
+
'data-row-num': row.rowNum,
|
|
751
|
+
'class': 'wcdv_select_row',
|
|
752
|
+
'checked': isSelected
|
|
753
|
+
});
|
|
754
|
+
jQuery('<td>', {'class': 'wcdv_group_col_spacer'}).append(checkbox).appendTo(rowTr);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Create the data cells.
|
|
758
|
+
|
|
759
|
+
_.each(columns, function (field, colIndex) {
|
|
760
|
+
if (data.groupFields.indexOf(field) >= 0) {
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
var fcc = self.colConfig.get(field) || {};
|
|
765
|
+
var cell = row.rowData[field];
|
|
766
|
+
|
|
767
|
+
var td = jQuery('<td>', {'data-wcdv-field': field});
|
|
768
|
+
if (colIndex > 0) {
|
|
769
|
+
td.addClass('wcdv_pivot_colval_boundary');
|
|
770
|
+
}
|
|
771
|
+
var value = format(fcc, typeInfo.get(field), cell);
|
|
772
|
+
|
|
773
|
+
if (value instanceof Element || value instanceof jQuery) {
|
|
774
|
+
td.append(value);
|
|
775
|
+
}
|
|
776
|
+
else if (fcc.allowHtml && typeInfo.get(field).type === 'string') {
|
|
777
|
+
td.html(value);
|
|
778
|
+
}
|
|
779
|
+
else if (value === '') {
|
|
780
|
+
td.html(' ');
|
|
781
|
+
}
|
|
782
|
+
else {
|
|
783
|
+
td.text(value);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
self.setCss(td, field);
|
|
787
|
+
self.setAlignment(td, fcc, typeInfo.get(field));
|
|
788
|
+
|
|
789
|
+
rowTr.append(td);
|
|
790
|
+
});
|
|
791
|
+
|
|
792
|
+
if (self.features.rowSelect && isSelected) {
|
|
793
|
+
rowTr.children('td').addClass('wcdv_selected_row');
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
self.ui.tr[i] = rowTr;
|
|
797
|
+
afterElement.after(rowTr);
|
|
798
|
+
afterElement = rowTr;
|
|
799
|
+
|
|
800
|
+
var rowRenderCb = getProp(self.opts, 'events', 'rowRender');
|
|
801
|
+
if (typeof rowRenderCb === 'function') {
|
|
802
|
+
rowRenderCb(rowTr, {
|
|
803
|
+
isGroup: true,
|
|
804
|
+
groupMode: 'details',
|
|
805
|
+
rowData: row.rowData,
|
|
806
|
+
rowNum: row.rowNum
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
isRendered[metadataNode.id] = true;
|
|
812
|
+
|
|
813
|
+
if (i < metadataNode.rows.length) {
|
|
814
|
+
// Not all children were rendered.
|
|
815
|
+
|
|
816
|
+
lastRenderedTr[metadataNode.id] = rowTr;
|
|
817
|
+
for (var p = metadataNode.parent; p != null; p = p.parent) {
|
|
818
|
+
lastRenderedTr[p.id] = rowTr;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
showMoreTr = jQuery('<tr>', {'class': 'wcdvgrid_more', 'data-wcdv-in-group': metadataNode.id});
|
|
822
|
+
|
|
823
|
+
// Insert spacer columns for previous group fields.
|
|
824
|
+
|
|
825
|
+
for (j = 0; j < metadataNode.groupFieldIndex + 1; j += 1) {
|
|
826
|
+
jQuery('<th>', {'class': 'wcdv_group_col_spacer'})
|
|
827
|
+
.appendTo(showMoreTr);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
colSpan = columns.length
|
|
831
|
+
+ 1 // for the "expand" button column
|
|
832
|
+
+ (self.features.rowSelect ? 1 : 0)
|
|
833
|
+
+ (self.features.rowReorder ? 1 : 0)
|
|
834
|
+
- (metadataNode.groupFieldIndex + 1);
|
|
835
|
+
|
|
836
|
+
spinnerDiv = jQuery('<div>', {'class': 'spinner'})
|
|
837
|
+
.append(jQuery('<div>', {'class': 'bounce1'}))
|
|
838
|
+
.append(jQuery('<div>', {'class': 'bounce2'}))
|
|
839
|
+
.append(jQuery('<div>', {'class': 'bounce3'}))
|
|
840
|
+
.hide();
|
|
841
|
+
|
|
842
|
+
showMoreTd = jQuery('<td>', {
|
|
843
|
+
'class': 'wcdv_show_more',
|
|
844
|
+
'data-wcdv-in-group': metadataNode.id,
|
|
845
|
+
'data-wcdv-show-more-start': i,
|
|
846
|
+
'colspan': colSpan
|
|
847
|
+
})
|
|
848
|
+
.append(icon('circle-chevron-down'))
|
|
849
|
+
.append(jQuery('<span>Showing rows 1–' + i + ' of ' + metadataNode.rows.length + '.</span>')
|
|
850
|
+
.css({'padding-left': '0.5em'}))
|
|
851
|
+
.append(jQuery('<button type="button">Load ' + limitConfig.chunkSize + ' more rows.</button>')
|
|
852
|
+
.css({'margin-left': '0.5em'}))
|
|
853
|
+
.append(jQuery('<button type="button" class="wcdv_show_all">Load all rows.</button>')
|
|
854
|
+
.css({'margin-left': '0.5em'})
|
|
855
|
+
)
|
|
856
|
+
.append(spinnerDiv)
|
|
857
|
+
.appendTo(showMoreTr);
|
|
858
|
+
|
|
859
|
+
rowTr.after(showMoreTr);
|
|
860
|
+
}
|
|
861
|
+
lastInsertedTr = rowTr;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
self._updateSelectionGui();
|
|
865
|
+
|
|
866
|
+
if (self.features.floatingHeader) {
|
|
867
|
+
switch (getProp(self.defn, 'table', 'floatingHeader', 'method')) {
|
|
868
|
+
case 'tabletool':
|
|
869
|
+
window.TableTool.update();
|
|
870
|
+
break;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
return lastInsertedTr;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// showMore() {{{3
|
|
877
|
+
|
|
878
|
+
function showMore(showAll) {
|
|
879
|
+
var elt = jQuery(this).closest('td');
|
|
880
|
+
var metadataId = +(elt.attr('data-wcdv-in-group'));
|
|
881
|
+
var startIndex = +(elt.attr('data-wcdv-show-more-start'));
|
|
882
|
+
var afterElement = lastRenderedTr[metadataId];
|
|
883
|
+
|
|
884
|
+
afterElement.nextAll('tr.wcdvgrid_more[data-wcdv-in-group="' + metadataId + '"]').find('.spinner').show();
|
|
885
|
+
|
|
886
|
+
window.setTimeout(function () {
|
|
887
|
+
render(metadataId, startIndex, afterElement, showAll);
|
|
888
|
+
// No need to hide the spinner because the "show more" row should be gone.
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// }}}3
|
|
893
|
+
|
|
894
|
+
render();
|
|
895
|
+
self.ui.tbody.on('click', 'button.wcdv_expand_button', toggleGroup);
|
|
896
|
+
self.ui.tbody.on('click', 'td.wcdv_show_more button.wcdv_show_all', function (evt) {
|
|
897
|
+
evt.stopPropagation();
|
|
898
|
+
showMore.call(this, true);
|
|
899
|
+
});
|
|
900
|
+
self.ui.tbody.on('click', 'td.wcdv_show_more', function (evt) {
|
|
901
|
+
showMore.call(this, false);
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
self._updateSelectionGui();
|
|
905
|
+
|
|
906
|
+
if (self.features.floatingHeader) {
|
|
907
|
+
switch (getProp(self.defn, 'table', 'floatingHeader', 'method')) {
|
|
908
|
+
case 'tabletool':
|
|
909
|
+
window.TableTool.update();
|
|
910
|
+
break;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
if (typeof cont === 'function') {
|
|
915
|
+
return cont();
|
|
916
|
+
}
|
|
917
|
+
};
|
|
918
|
+
|
|
919
|
+
// #drawFooter {{{2
|
|
920
|
+
|
|
921
|
+
GridTableGroupDetail.prototype.drawFooter = function (columns, data, typeInfo) {
|
|
922
|
+
var self = this;
|
|
923
|
+
|
|
924
|
+
var makeSelectAll = function (tr) {
|
|
925
|
+
self.ui.checkAll_tfoot = jQuery('<input>', {
|
|
926
|
+
'name': 'checkAll',
|
|
927
|
+
'type': 'checkbox',
|
|
928
|
+
'class': 'wcdv_select_group',
|
|
929
|
+
'data-group-id': '0'
|
|
930
|
+
})
|
|
931
|
+
.on('change', function (evt) {
|
|
932
|
+
self.checkAll(evt);
|
|
933
|
+
});
|
|
934
|
+
jQuery('<td>', {'class': 'wcdv_group_col_spacer'}).append(self.ui.checkAll_tfoot).appendTo(tr);
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
var makeAggregateRow = function () {
|
|
938
|
+
// Circumventing the correct logic here because TableTool requires an empty footer in order to
|
|
939
|
+
// implement horizontal scrolling; if you omit the footer (with a TR and all appropriate TD's in
|
|
940
|
+
// it) then you can't scroll horizontally.
|
|
941
|
+
if (false && getProp(self.defn, 'table', 'footer') == null) {
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
var tr = jQuery('<tr>');
|
|
946
|
+
|
|
947
|
+
// Add the "select all" checkbox when row selection is enabled.
|
|
948
|
+
|
|
949
|
+
if (self.features.rowSelect) {
|
|
950
|
+
makeSelectAll(tr);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
for (var spacerIndex = 0; spacerIndex < data.groupFields.length + 1; spacerIndex += 1) {
|
|
954
|
+
jQuery('<td>', {'class': 'wcdv_group_col_spacer'}).appendTo(tr);
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// Create the columns for the data fields, which contain aggregate function results over those
|
|
958
|
+
// fields.
|
|
959
|
+
|
|
960
|
+
var didFooterCell = false;
|
|
961
|
+
|
|
962
|
+
tr.append(_.map(columns, function (field, colIndex) {
|
|
963
|
+
if (data.groupFields.indexOf(field) >= 0) {
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
var fcc = self.colConfig.get(field) || {};
|
|
968
|
+
var colTypeInfo = typeInfo.get(field);
|
|
969
|
+
var td = jQuery('<td>');
|
|
970
|
+
var footerConfig = getProp(self.defn, 'table', 'footer', field);
|
|
971
|
+
var agg;
|
|
972
|
+
var aggFun;
|
|
973
|
+
var aggResult;
|
|
974
|
+
var footerVal;
|
|
975
|
+
|
|
976
|
+
self.setCss(td, field);
|
|
977
|
+
self.setAlignment(td, fcc, typeInfo.get(field));
|
|
978
|
+
|
|
979
|
+
if (footerConfig == null) {
|
|
980
|
+
if (didFooterCell) {
|
|
981
|
+
td.addClass('wcdv_divider');
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
didFooterCell = false;
|
|
985
|
+
}
|
|
986
|
+
else {
|
|
987
|
+
if (colIndex > 0) {
|
|
988
|
+
td.addClass('wcdv_divider');
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
didFooterCell = true;
|
|
992
|
+
|
|
993
|
+
// Although the footer config is an aggregate spec, there is one place we allow more
|
|
994
|
+
// flexibility. If the fields aren't set, use the field for the column in which we're
|
|
995
|
+
// displaying this footer. This is merely a convenience for the most common case.
|
|
996
|
+
|
|
997
|
+
if (footerConfig.fields == null) {
|
|
998
|
+
footerConfig.fields = [field];
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
self.logDebug(self.makeLogTag() + ' Creating footer using config: %O',
|
|
1002
|
+
self.toString(), field, footerConfig);
|
|
1003
|
+
|
|
1004
|
+
var aggInfo = new AggregateInfo('all', footerConfig, 0, self.colConfig, typeInfo, function (tag, fti) {
|
|
1005
|
+
if (fti.needsDecoding) {
|
|
1006
|
+
self.logDebug(self.makeLogTag() + ' Converting data: { field = "%s", type = "%s" }',
|
|
1007
|
+
self.toString(), field, tag, fti.field, fti.type);
|
|
1008
|
+
|
|
1009
|
+
Source.decodeAll(data.dataByRowId, fti.field);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
fti.deferDecoding = false;
|
|
1013
|
+
fti.needsDecoding = false;
|
|
1014
|
+
});
|
|
1015
|
+
aggResult = aggInfo.instance.calculate(data.groupMetadata.rows);
|
|
1016
|
+
var aggResult_formatted;
|
|
1017
|
+
|
|
1018
|
+
if (isElement(aggResult)) {
|
|
1019
|
+
footerVal = aggResult;
|
|
1020
|
+
}
|
|
1021
|
+
else {
|
|
1022
|
+
if (aggInfo.instance.inheritFormatting) {
|
|
1023
|
+
aggResult_formatted = format(aggInfo.colConfig[0], aggInfo.typeInfo[0], aggResult, {
|
|
1024
|
+
overrideType: aggInfo.instance.getType()
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
else {
|
|
1028
|
+
aggResult_formatted = format(null, null, aggResult, {
|
|
1029
|
+
overrideType: aggInfo.instance.getType(),
|
|
1030
|
+
decode: false
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
if (aggInfo.debug) {
|
|
1035
|
+
self.logDebug(self.makeLogTag() + ' Aggregate result: %s',
|
|
1036
|
+
self.toString(), field, JSON.stringify(aggResult));
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
switch (typeof footerConfig.format) {
|
|
1040
|
+
case 'function':
|
|
1041
|
+
footerVal = footerConfig.format(aggResult_formatted);
|
|
1042
|
+
break;
|
|
1043
|
+
case 'string':
|
|
1044
|
+
footerVal = sprintf.sprintf(footerConfig.format, aggResult_formatted);
|
|
1045
|
+
break;
|
|
1046
|
+
default:
|
|
1047
|
+
throw new Error('Footer config for field "' + field + '": `format` must be a function or a string');
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
if (footerVal instanceof Element || footerVal instanceof jQuery) {
|
|
1052
|
+
td.append(footerVal);
|
|
1053
|
+
}
|
|
1054
|
+
else {
|
|
1055
|
+
td.text(footerVal);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
return td;
|
|
1060
|
+
}));
|
|
1061
|
+
|
|
1062
|
+
// Finish the row that contains the aggregate functions.
|
|
1063
|
+
|
|
1064
|
+
self.ui.tfoot.append(tr);
|
|
1065
|
+
};
|
|
1066
|
+
|
|
1067
|
+
var makeExternalFooterRow = function () {
|
|
1068
|
+
// Create a new footer row for an external footer that we've absorbed into the grid.
|
|
1069
|
+
|
|
1070
|
+
if (self.opts.footer == null || !self.opts.stealGridFooter) {
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
var tr = jQuery('<tr>');
|
|
1075
|
+
|
|
1076
|
+
if (!isVisible(self.opts.footer)) {
|
|
1077
|
+
tr.hide();
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
if (self.features.rowSelect) {
|
|
1081
|
+
// Circumventing the correct logic here because TableTool requires an empty footer in order to
|
|
1082
|
+
// implement horizontal scrolling; if you omit the footer (with a TR and all appropriate TD's
|
|
1083
|
+
// in it) then you can't scroll horizontally.
|
|
1084
|
+
if (true || getProp(self.defn, 'table', 'footer')) {
|
|
1085
|
+
// There is an aggregate row, so it contains the "select all" checkbox.
|
|
1086
|
+
jQuery('<td>', {'class': 'wcdv_group_col_spacer'}).appendTo(tr);
|
|
1087
|
+
}
|
|
1088
|
+
else {
|
|
1089
|
+
// There is no aggregate row, so make the "select all" checkbox here.
|
|
1090
|
+
makeSelectAll(tr);
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
// colspan = (spacers: # groupFields + 1) + (columns: # fields - # groupFields) = (# fields) + 1
|
|
1094
|
+
jQuery('<td>', {'colspan': columns.length + 1}).append(self.opts.footer).appendTo(tr);
|
|
1095
|
+
self.ui.tfoot.append(tr);
|
|
1096
|
+
};
|
|
1097
|
+
|
|
1098
|
+
makeAggregateRow();
|
|
1099
|
+
makeExternalFooterRow();
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
// #addWorkHandler {{{2
|
|
1103
|
+
|
|
1104
|
+
GridTableGroupDetail.prototype.addWorkHandler = function () {
|
|
1105
|
+
var self = this;
|
|
1106
|
+
|
|
1107
|
+
self.view.on(ComputedView.events.workEnd, function (info, ops) {
|
|
1108
|
+
if (self._destroyed) { return; }
|
|
1109
|
+
self.logDebug(self.makeLogTag('handler(workEnd)') + ' ComputedView has finished doing work');
|
|
1110
|
+
|
|
1111
|
+
if (!ops.group || ops.pivot) {
|
|
1112
|
+
self.fire('unableToRender', null, ops);
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
self.logDebug(self.makeLogTag('handler(workEnd)') + ' Redrawing because the view has done work');
|
|
1117
|
+
self.draw(self.root);
|
|
1118
|
+
}, { who: self });
|
|
1119
|
+
};
|
|
1120
|
+
|
|
1121
|
+
// #_addRowSelectHandler {{{2
|
|
1122
|
+
|
|
1123
|
+
/**
|
|
1124
|
+
* Add an event handler for the row select checkboxes. The event is bound on `self.ui.tbody` and
|
|
1125
|
+
* looks for checkbox inputs inside TD elements with class `wcdv_group_col_spacer` to actually handle
|
|
1126
|
+
* the events. The handler calls `self.select(ROW_NUM)` or `self.unselect(ROW_NUM)` when the
|
|
1127
|
+
* checkbox is changed.
|
|
1128
|
+
*/
|
|
1129
|
+
|
|
1130
|
+
GridTableGroupDetail.prototype._addRowSelectHandler = function () {
|
|
1131
|
+
var self = this;
|
|
1132
|
+
|
|
1133
|
+
self.ui.tbody.on('change', 'input[type="checkbox"].wcdv_select_row', function () {
|
|
1134
|
+
var elt = jQuery(this);
|
|
1135
|
+
var rowNum = +elt.attr('data-row-num');
|
|
1136
|
+
var isChecked = elt.prop('checked');
|
|
1137
|
+
|
|
1138
|
+
if (isChecked) {
|
|
1139
|
+
self.select(rowNum);
|
|
1140
|
+
}
|
|
1141
|
+
else {
|
|
1142
|
+
self.unselect(rowNum);
|
|
1143
|
+
}
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
self.ui.tbody.on('change', 'input[type="checkbox"].wcdv_select_group', function () {
|
|
1147
|
+
var elt = jQuery(this);
|
|
1148
|
+
var isChecked = elt.prop('checked');
|
|
1149
|
+
var groupMetadataId = +elt.attr('data-group-id');
|
|
1150
|
+
var rowNums = [];
|
|
1151
|
+
|
|
1152
|
+
// Find all rows that are a descendant of the selected group.
|
|
1153
|
+
|
|
1154
|
+
function recur(node) {
|
|
1155
|
+
if (node.children == null) {
|
|
1156
|
+
rowNums = rowNums.concat(_.pluck(self.data.data[node.rowValIndex], 'rowNum'));
|
|
1157
|
+
}
|
|
1158
|
+
else {
|
|
1159
|
+
_.each(node.children, recur);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
recur(self.data.groupMetadata.lookup.byId[groupMetadataId]);
|
|
1164
|
+
|
|
1165
|
+
if (isChecked) {
|
|
1166
|
+
self.select(rowNums);
|
|
1167
|
+
}
|
|
1168
|
+
else {
|
|
1169
|
+
self.unselect(rowNums);
|
|
1170
|
+
}
|
|
1171
|
+
});
|
|
1172
|
+
};
|
|
1173
|
+
|
|
1174
|
+
// #_updateSelectionGui {{{2
|
|
1175
|
+
|
|
1176
|
+
/**
|
|
1177
|
+
* Update the checkboxes in the grid table to match what the current selection is.
|
|
1178
|
+
*/
|
|
1179
|
+
|
|
1180
|
+
GridTableGroupDetail.prototype._updateSelectionGui = function () {
|
|
1181
|
+
var self = this;
|
|
1182
|
+
|
|
1183
|
+
// First, deselect all rows (remove "selected" class and uncheck the box).
|
|
1184
|
+
|
|
1185
|
+
self.root.find('tbody td.wcdv_selected_row').removeClass('wcdv_selected_row');
|
|
1186
|
+
self.root.find('tbody input[type="checkbox"].wcdv_select_row').prop('checked', false);
|
|
1187
|
+
self.root.find('tbody input[type="checkbox"].wcdv_select_group').prop('checked', false);
|
|
1188
|
+
|
|
1189
|
+
// Next, find all the TR elements which correspond to selected rows.
|
|
1190
|
+
|
|
1191
|
+
var trs = self.root.find('tbody tr').filter(function (_idx, elt) {
|
|
1192
|
+
return self.selection.indexOf(+(jQuery(elt).attr('data-row-num'))) >= 0;
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
// Select appropriate rows (add "selected" class and check the box).
|
|
1196
|
+
|
|
1197
|
+
trs.children('td').addClass('wcdv_selected_row');
|
|
1198
|
+
trs.find('input[type="checkbox"].wcdv_select_row').prop('checked', true);
|
|
1199
|
+
|
|
1200
|
+
// ===============================================================================================
|
|
1201
|
+
//
|
|
1202
|
+
// DETERMINE GROUPING (HIERARCHICAL, PARENT) CHECKBOX STATES
|
|
1203
|
+
//
|
|
1204
|
+
// ===============================================================================================
|
|
1205
|
+
|
|
1206
|
+
// Initialize the structure with no rows selected in any leaf.
|
|
1207
|
+
|
|
1208
|
+
var numSelected = {};
|
|
1209
|
+
|
|
1210
|
+
_.each(_.keys(self.data.groupMetadata.lookup.byId), function (id) {
|
|
1211
|
+
numSelected[id] = 0;
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1214
|
+
// Determine how many are selected in each leaf of the tree.
|
|
1215
|
+
|
|
1216
|
+
for (var i = 0; i < self.selection.length; i += 1) {
|
|
1217
|
+
var s = self.selection[i];
|
|
1218
|
+
var id = getProp(self.data, 'groupMetadata', 'lookup', 'byRowNum', s, 'id');
|
|
1219
|
+
|
|
1220
|
+
if (id == null) {
|
|
1221
|
+
// This can happen when the selected row has been filtered out, so there's no group metadata
|
|
1222
|
+
// entry for that row number.
|
|
1223
|
+
|
|
1224
|
+
continue;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
if (numSelected[id] == null) {
|
|
1228
|
+
numSelected[id] = 0;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
numSelected[id] += 1;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// Determine how many are selected at all non-leaf nodes.
|
|
1235
|
+
|
|
1236
|
+
(function () {
|
|
1237
|
+
function postorder(node) {
|
|
1238
|
+
if (node.children != null) {
|
|
1239
|
+
numSelected[node.id] = 0;
|
|
1240
|
+
_.each(node.children, function (c) {
|
|
1241
|
+
postorder(c);
|
|
1242
|
+
numSelected[node.id] += numSelected[c.id];
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
postorder(self.data.groupMetadata);
|
|
1248
|
+
})();
|
|
1249
|
+
|
|
1250
|
+
_.each(numSelected, function (count, id) {
|
|
1251
|
+
var numRows = self.data.groupMetadata.lookup.byId[id].numRows;
|
|
1252
|
+
var checkbox = self.root.find('input[type="checkbox"][data-group-id="' + id + '"].wcdv_select_group');
|
|
1253
|
+
|
|
1254
|
+
if (checkbox.length === 0) {
|
|
1255
|
+
// This can happen when the rows for the sub-groups haven't been rendered yet.
|
|
1256
|
+
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
if (numRows === 0) {
|
|
1261
|
+
checkbox.prop({
|
|
1262
|
+
disabled: true,
|
|
1263
|
+
indeterminate: false,
|
|
1264
|
+
checked: false,
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
else if (count === 0) {
|
|
1268
|
+
checkbox.prop({
|
|
1269
|
+
disabled: false,
|
|
1270
|
+
indeterminate: false,
|
|
1271
|
+
checked: false,
|
|
1272
|
+
});
|
|
1273
|
+
}
|
|
1274
|
+
else if (numRows === count) {
|
|
1275
|
+
checkbox.prop({
|
|
1276
|
+
disabled: false,
|
|
1277
|
+
indeterminate: false,
|
|
1278
|
+
checked: true,
|
|
1279
|
+
});
|
|
1280
|
+
}
|
|
1281
|
+
else {
|
|
1282
|
+
checkbox.prop({
|
|
1283
|
+
disabled: false,
|
|
1284
|
+
indeterminate: true,
|
|
1285
|
+
checked: false,
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
};
|
|
1290
|
+
|
|
1291
|
+
// #checkAll {{{2
|
|
1292
|
+
|
|
1293
|
+
/**
|
|
1294
|
+
* Event handler for using the "check all" checkbox.
|
|
1295
|
+
*
|
|
1296
|
+
* @param {Event} evt
|
|
1297
|
+
* The event generated by the browser when the checkbox is changed.
|
|
1298
|
+
*/
|
|
1299
|
+
|
|
1300
|
+
GridTableGroupDetail.prototype.checkAll = function (evt) {
|
|
1301
|
+
var self = this;
|
|
1302
|
+
|
|
1303
|
+
// Synchronize with floating header clone.
|
|
1304
|
+
jQuery(evt.target).parents('div.tabletool').find('input[name="checkAll"]').prop('checked', evt.target.checked);
|
|
1305
|
+
|
|
1306
|
+
// Either select or unselect all rows.
|
|
1307
|
+
if (evt.target.checked) {
|
|
1308
|
+
self.select();
|
|
1309
|
+
}
|
|
1310
|
+
else {
|
|
1311
|
+
self.unselect();
|
|
1312
|
+
}
|
|
1313
|
+
};
|
|
1314
|
+
|
|
1315
|
+
// #addDataToCsv {{{2
|
|
1316
|
+
|
|
1317
|
+
/**
|
|
1318
|
+
* Add all data to the CSV file. Because plain tables frequently don't show all the data, it's not
|
|
1319
|
+
* enough to perform the CSV generation inside the `render()` method like we do with other GridTable
|
|
1320
|
+
* implementations.
|
|
1321
|
+
*
|
|
1322
|
+
* @param {object} data
|
|
1323
|
+
*/
|
|
1324
|
+
|
|
1325
|
+
GridTableGroupDetail.prototype.addDataToCsv = function (data) {
|
|
1326
|
+
var self = this;
|
|
1327
|
+
var columns = determineColumns(self.colConfig, data, self.typeInfo);
|
|
1328
|
+
|
|
1329
|
+
self.logDebug(self.makeLogTag() + ' Started generating CSV file', self.toString());
|
|
1330
|
+
self.fire('generateCsvProgress', null, 0);
|
|
1331
|
+
|
|
1332
|
+
self.csv.start();
|
|
1333
|
+
self.csv.addRow();
|
|
1334
|
+
|
|
1335
|
+
_.each(data.groupFields, function (fieldName) {
|
|
1336
|
+
var fcc = self.colConfig.get(fieldName) || {};
|
|
1337
|
+
self.csv.addCol(fcc.displayText || fieldName);
|
|
1338
|
+
});
|
|
1339
|
+
_.each(_.difference(columns, data.groupFields), function (fieldName) {
|
|
1340
|
+
var fcc = self.colConfig.get(fieldName) || {};
|
|
1341
|
+
self.csv.addCol(fcc.displayText || fieldName);
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
function recur(depth, metadataNode) {
|
|
1345
|
+
if (metadataNode.children != null) {
|
|
1346
|
+
_.each(_.keys(metadataNode.children).sort(), function (childName) {
|
|
1347
|
+
self.csv.addRow();
|
|
1348
|
+
for (var j = 0; j < depth; j += 1) {
|
|
1349
|
+
self.csv.addCol();
|
|
1350
|
+
}
|
|
1351
|
+
self.csv.addCol(childName);
|
|
1352
|
+
for (var j = depth + 1; j < columns.length; j += 1) {
|
|
1353
|
+
self.csv.addCol();
|
|
1354
|
+
}
|
|
1355
|
+
recur(depth + 1, metadataNode.children[childName]);
|
|
1356
|
+
});
|
|
1357
|
+
}
|
|
1358
|
+
else {
|
|
1359
|
+
_.each(metadataNode.rows, function (row) {
|
|
1360
|
+
self.csv.addRow();
|
|
1361
|
+
for (var j = 0; j < depth; j += 1) {
|
|
1362
|
+
self.csv.addCol();
|
|
1363
|
+
}
|
|
1364
|
+
_.each(_.difference(columns, data.groupFields), function (field, colIndex) {
|
|
1365
|
+
var fcc = self.colConfig.get(field) || {};
|
|
1366
|
+
var cell = row.rowData[field];
|
|
1367
|
+
var value = format(fcc, self.typeInfo.get(field), cell);
|
|
1368
|
+
|
|
1369
|
+
if (value instanceof Element) {
|
|
1370
|
+
self.csv.addCol(jQuery(value).text());
|
|
1371
|
+
}
|
|
1372
|
+
else if (value instanceof jQuery) {
|
|
1373
|
+
self.csv.addCol(value.text());
|
|
1374
|
+
}
|
|
1375
|
+
else if (fcc.allowHtml && self.typeInfo.get(field).type === 'string' && value.charAt(0) === '<') {
|
|
1376
|
+
self.csv.addCol(jQuery(value).text());
|
|
1377
|
+
}
|
|
1378
|
+
else {
|
|
1379
|
+
self.csv.addCol(value);
|
|
1380
|
+
}
|
|
1381
|
+
});
|
|
1382
|
+
});
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
recur(0, data.groupMetadata);
|
|
1387
|
+
|
|
1388
|
+
self.csv.finish(function () {
|
|
1389
|
+
self.logDebug(self.makeLogTag() + ' Finished generating CSV file', self.toString());
|
|
1390
|
+
self.csvLock.unlock();
|
|
1391
|
+
self.fire('generateCsvProgress', null, 100);
|
|
1392
|
+
self.fire('csvReady');
|
|
1393
|
+
});
|
|
1394
|
+
};
|
|
1395
|
+
|
|
1396
|
+
// Registry {{{1
|
|
1397
|
+
|
|
1398
|
+
GridRenderer.registry.set('table_group_detail', GridTableGroupDetail);
|
|
1399
|
+
|
|
1400
|
+
// Exports {{{1
|
|
1401
|
+
|
|
1402
|
+
export {
|
|
1403
|
+
GridTableGroupDetail
|
|
1404
|
+
};
|