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
package/src/grid.js
ADDED
|
@@ -0,0 +1,2777 @@
|
|
|
1
|
+
// Imports {{{1
|
|
2
|
+
|
|
3
|
+
import _ from 'underscore';
|
|
4
|
+
|
|
5
|
+
import jQuery from 'jquery';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
deepCopy,
|
|
9
|
+
deepDefaults,
|
|
10
|
+
delegate,
|
|
11
|
+
icon,
|
|
12
|
+
getProp,
|
|
13
|
+
getPropDef,
|
|
14
|
+
I,
|
|
15
|
+
makeSubclass,
|
|
16
|
+
mixinEventHandling,
|
|
17
|
+
mixinLogging,
|
|
18
|
+
mixinNameSetting,
|
|
19
|
+
presentDownload,
|
|
20
|
+
setProp,
|
|
21
|
+
setPropDef,
|
|
22
|
+
Timing,
|
|
23
|
+
} from './util/misc.js';
|
|
24
|
+
import { OrdMap, Lock, Prefs, ComputedView, MirageView, FileSource } from 'datavis-ace';
|
|
25
|
+
import {
|
|
26
|
+
AggregateControl,
|
|
27
|
+
FilterControl,
|
|
28
|
+
GroupControl,
|
|
29
|
+
PivotControl,
|
|
30
|
+
} from './grid_control.js';
|
|
31
|
+
import './prefs_modules.js';
|
|
32
|
+
import { GridRenderer } from './grid_renderer.js';
|
|
33
|
+
import './renderers/grid/handlebars.js';
|
|
34
|
+
import './renderers/grid/squirrelly.js';
|
|
35
|
+
import './renderers/grid/table/plain.js';
|
|
36
|
+
import './renderers/grid/table/group_detail.js';
|
|
37
|
+
import './renderers/grid/table/group_summary.js';
|
|
38
|
+
import './renderers/grid/table/pivot.js';
|
|
39
|
+
import { ColConfigWin } from './ui/windows/col_config.js';
|
|
40
|
+
import { DebugWin } from './ui/windows/debug.js';
|
|
41
|
+
import { TemplatesEditor } from './ui/templates.js';
|
|
42
|
+
import { PopupWindow } from './ui/popup_window.js';
|
|
43
|
+
import {
|
|
44
|
+
ComputedViewToolbar,
|
|
45
|
+
PlainToolbar,
|
|
46
|
+
GroupToolbar,
|
|
47
|
+
PivotToolbar,
|
|
48
|
+
PrefsToolbar,
|
|
49
|
+
RendererToolbar,
|
|
50
|
+
} from './ui/toolbars/grid.js';
|
|
51
|
+
import { OperationsPalette } from './operations_palette.js';
|
|
52
|
+
import { trans } from './trans.js';
|
|
53
|
+
|
|
54
|
+
// Server-Side Filter/Sort {{{1
|
|
55
|
+
|
|
56
|
+
/*
|
|
57
|
+
* Here's the list of filter conditions supported by jQWidgets:
|
|
58
|
+
*
|
|
59
|
+
* - NULL
|
|
60
|
+
* - NOT_NULL
|
|
61
|
+
* - EQUAL
|
|
62
|
+
*
|
|
63
|
+
* These only apply to strings:
|
|
64
|
+
*
|
|
65
|
+
* - EMPTY
|
|
66
|
+
* - NOT_EMPTY
|
|
67
|
+
* - CONTAINS
|
|
68
|
+
* - CONTAINS_CASE_SENSITIVE
|
|
69
|
+
* - DOES_NOT_CONTAIN
|
|
70
|
+
* - DOES_NOT_CONTAIN_CASE_SENSITIVE
|
|
71
|
+
* - STARTS_WITH
|
|
72
|
+
* - STARTS_WITH_CASE_SENSITIVE
|
|
73
|
+
* - ENDS_WITH
|
|
74
|
+
* - ENDS_WITH_CASE_SENSITIVE
|
|
75
|
+
* - EQUAL_CASE_SENSITIVE
|
|
76
|
+
*
|
|
77
|
+
* These only apply to numbers and dates:
|
|
78
|
+
*
|
|
79
|
+
* - NOT_EQUAL
|
|
80
|
+
* - LESS_THAN
|
|
81
|
+
* - LESS_THAN_OR_EQUAL
|
|
82
|
+
* - GREATER_THAN
|
|
83
|
+
* - GREATER_THAN_OR_EQUAL
|
|
84
|
+
*
|
|
85
|
+
* I find it weird that strings can't be NOT_EQUAL, but I'm just going by what their documentation
|
|
86
|
+
* says they do.
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
function makeJsonHaving(filters) {
|
|
90
|
+
var having = {};
|
|
91
|
+
var numClauses = 0;
|
|
92
|
+
_.each(filters, function (f) {
|
|
93
|
+
var h = having[f.datafield] = {};
|
|
94
|
+
var numItems = 0;
|
|
95
|
+
_.each(f.filter.getfilters(), function (filter) {
|
|
96
|
+
var isSupported = true;
|
|
97
|
+
switch (filter.condition) {
|
|
98
|
+
case 'EQUAL':
|
|
99
|
+
case 'EQUAL_CASE_SENSITIVE':
|
|
100
|
+
if (h['$eq']) {
|
|
101
|
+
h['$in'] = [h['$eq']];
|
|
102
|
+
delete h['$eq'];
|
|
103
|
+
}
|
|
104
|
+
if (h['$in']) {
|
|
105
|
+
h['$in'].push(filter.value);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
h['$eq'] = filter.value;
|
|
109
|
+
}
|
|
110
|
+
break;
|
|
111
|
+
case 'CONTAINS':
|
|
112
|
+
case 'CONTAINS_CASE_SENSITIVE':
|
|
113
|
+
h['$like'] = '%' + filter.value + '%';
|
|
114
|
+
break;
|
|
115
|
+
case 'EMPTY':
|
|
116
|
+
h['$eq'] = '';
|
|
117
|
+
break;
|
|
118
|
+
case 'NOT_EMPTY':
|
|
119
|
+
h['$ne'] = '';
|
|
120
|
+
break;
|
|
121
|
+
default:
|
|
122
|
+
self.logError(self.makeLogTag() + ' Unsupported filter condition "' + filter.condition + '" for type "' + filter.type + '"');
|
|
123
|
+
isSupported = false;
|
|
124
|
+
}
|
|
125
|
+
if (isSupported) {
|
|
126
|
+
numItems += 1;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
if (numItems > 0) {
|
|
130
|
+
numClauses += 1;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
delete having[f.datafield];
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
return numClauses > 0 ? having : null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Make a JSON ORDER BY object based on sort information from jQWidgets.
|
|
141
|
+
*
|
|
142
|
+
* @param {object} o A description of the sort from a jqxGrid.
|
|
143
|
+
*
|
|
144
|
+
* @return {object} A description of the sort that can be used by the system report code.
|
|
145
|
+
*/
|
|
146
|
+
|
|
147
|
+
function makeJsonOrderBy(o) {
|
|
148
|
+
if (o.sortcolumn === null) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
return [{
|
|
152
|
+
column: o.sortcolumn,
|
|
153
|
+
direction: o.sortdirection.ascending ? 'ASC' : 'DESC'
|
|
154
|
+
}];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Grid {{{1
|
|
158
|
+
// JSDoc Types {{{2
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @typedef {object} Grid~Defn
|
|
162
|
+
*
|
|
163
|
+
* @property {Object} table
|
|
164
|
+
*
|
|
165
|
+
* @property {string} table.id
|
|
166
|
+
*
|
|
167
|
+
* @property {Array.<Grid~ColConfig>} [table.columns]
|
|
168
|
+
* Specifies the order that fields are rendered in plain output. If not provided, all fields are
|
|
169
|
+
* rendered in the order received from the source; fields with names starting with an underscore are
|
|
170
|
+
* not shown. If provided, only those fields specified are rendered, and in the order indicated.
|
|
171
|
+
*
|
|
172
|
+
* @property {Grid~Features} [table.features]
|
|
173
|
+
* The features that are enabled for this grid.
|
|
174
|
+
*
|
|
175
|
+
* @property {object} [table.floatingHeader]
|
|
176
|
+
* Configuration for the "floating header" feature.
|
|
177
|
+
*
|
|
178
|
+
* @property {string} [table.floatingHeader.method]
|
|
179
|
+
* What library to use to create the floating table header. Must be one of the following:
|
|
180
|
+
*
|
|
181
|
+
* - `floatThead`
|
|
182
|
+
* - `fixedHeaderTable`
|
|
183
|
+
* - `tabletool`
|
|
184
|
+
*
|
|
185
|
+
* If this is not specified, the default is based on what library is available in the page, in the
|
|
186
|
+
* order listed above.
|
|
187
|
+
*
|
|
188
|
+
* @property {string} [table.groupMode]
|
|
189
|
+
* The starting mode for group output. Must be one of the following:
|
|
190
|
+
*
|
|
191
|
+
* - `summary`
|
|
192
|
+
* - `detail`
|
|
193
|
+
*
|
|
194
|
+
* The perspective will override this.
|
|
195
|
+
*
|
|
196
|
+
* @property {object} [table.incremental]
|
|
197
|
+
* Configuration for the "incremental" feature.
|
|
198
|
+
*
|
|
199
|
+
* @property {boolean} [table.incremental.appendBodyLast=false]
|
|
200
|
+
*
|
|
201
|
+
* @property {string} [table.incremental.method="setTimeout"]
|
|
202
|
+
* Must be one of the following:
|
|
203
|
+
*
|
|
204
|
+
* - `setTimeout`
|
|
205
|
+
* - `requestAnimationFrame`
|
|
206
|
+
*
|
|
207
|
+
* @property {number} [table.incremental.delay=10]
|
|
208
|
+
*
|
|
209
|
+
* @property {number} [table.incremental.chunkSize=100]
|
|
210
|
+
*
|
|
211
|
+
* @property {object} [table.limit]
|
|
212
|
+
* Configuration for the "limit" feature.
|
|
213
|
+
*
|
|
214
|
+
* @property {string} [table.limit.method="more"]
|
|
215
|
+
* How to limit the output. Must be one of the following:
|
|
216
|
+
*
|
|
217
|
+
* - `more` — Show a row at the bottom, which when clicked, loads more rows.
|
|
218
|
+
*
|
|
219
|
+
* @property {number} [table.limit.threshold=100]
|
|
220
|
+
* The total number of rows must exceed this in order to trigger using the limit method. If
|
|
221
|
+
* omitted, then the "limit" feature is effectively disabled.
|
|
222
|
+
*
|
|
223
|
+
* @property {number} [table.limit.chunkSize=50]
|
|
224
|
+
* When using the "more" limit method, how many additional rows to load each time.
|
|
225
|
+
*
|
|
226
|
+
* @property {object} [table.whenPlain]
|
|
227
|
+
* When the data has not been grouped, this is passed as the `opts` parameter to the GridRenderer
|
|
228
|
+
* constructor.
|
|
229
|
+
*
|
|
230
|
+
* @property {object} [table.whenGroup]
|
|
231
|
+
* When the data has been grouped, but not pivotted, this is passed as the `opts` parameter to the
|
|
232
|
+
* GridRenderer constructor.
|
|
233
|
+
*
|
|
234
|
+
* @property {object} [table.whenPivot]
|
|
235
|
+
* When the data has been pivotted, this is passed as the `opts` parameter to the GridRenderer
|
|
236
|
+
* constructor.
|
|
237
|
+
*
|
|
238
|
+
* @property {object} [table.activeRow]
|
|
239
|
+
* Configure the active row feature.
|
|
240
|
+
*
|
|
241
|
+
* @property {boolean} [table.activeRow.slider=true]
|
|
242
|
+
* If true, automatically deploy the slider when the active row is set.
|
|
243
|
+
*
|
|
244
|
+
* @property {function} [table.activeRow.callback]
|
|
245
|
+
* If set, a callback to invoke when the active row is changed. If the active row is set, the
|
|
246
|
+
* callback receives: (1) the active row ID, (2) the active row TR element. If the active row is
|
|
247
|
+
* cleared, the callback receives: (1) null.
|
|
248
|
+
*/
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* @typedef {object} Grid~FieldColConfig
|
|
252
|
+
* Represents the column configuration for a single field.
|
|
253
|
+
*
|
|
254
|
+
* @property {string} field
|
|
255
|
+
* We're configuring the output of this field.
|
|
256
|
+
*
|
|
257
|
+
* @property {string} [displayText]
|
|
258
|
+
* What to show as the name of the column; the default is to show the field name.
|
|
259
|
+
*
|
|
260
|
+
* @property {string} [format]
|
|
261
|
+
* If the value is a number or currency: a Numeral format string used to render the value. If the
|
|
262
|
+
* value is a date, datetime, or time: a Moment format string used to render the value. Otherwise,
|
|
263
|
+
* this option is not used. The default format strings are:
|
|
264
|
+
*
|
|
265
|
+
* - number: [none]
|
|
266
|
+
* - currency: `$0,0.00` (e.g. "$1,000.23")
|
|
267
|
+
* - date: `LL` (e.g. "September 4, 1986")
|
|
268
|
+
* - datetime: `LLL` (e.g. "September 4, 1986 8:30 PM")
|
|
269
|
+
*
|
|
270
|
+
* @property {string} [format_dateOnly="LL"]
|
|
271
|
+
* When `hideMidnight = true` this is the Moment format string used to display just the date
|
|
272
|
+
* component of the datetime. Note that the time component is still present in the value when it is
|
|
273
|
+
* formatted, so don't reference the hours/minutes/seconds from the format string.
|
|
274
|
+
*
|
|
275
|
+
* @property {boolean} [hideMidnight=false]
|
|
276
|
+
* If the value is a datetime, and this value is true, then the time component is not rendered when
|
|
277
|
+
* it's midnight (00:00:00). If the value is not a datetime, this option is not used.
|
|
278
|
+
*
|
|
279
|
+
* @property {string} [cellAlignment]
|
|
280
|
+
* How to align the value within the cell horizontally. Possible values:
|
|
281
|
+
*
|
|
282
|
+
* - `left`
|
|
283
|
+
* - `center`
|
|
284
|
+
* - `right`
|
|
285
|
+
*
|
|
286
|
+
* The default depends on the type of the field. Strings, dates, datetimes, and times are
|
|
287
|
+
* left-aligned by default. Numbers and currencies are right-aligned by default.
|
|
288
|
+
*
|
|
289
|
+
* @property {boolean} [allowHtml=false]
|
|
290
|
+
* If true and the type of the field is a string, the value is interpreted as HTML and the resulting
|
|
291
|
+
* nodes are inserted into the table result. When exporting to CSV, the value emitted will be the
|
|
292
|
+
* text nodes only.
|
|
293
|
+
*
|
|
294
|
+
* @property {string} [maxHeight]
|
|
295
|
+
* If present, sets the maximum height allowed for the cell, and puts a "fullscreen" icon button in
|
|
296
|
+
* the top-right which will pop open a window showing the full value. Useful for extremely long
|
|
297
|
+
* pieces of data that would otherwise blow up the table. Only works in plain output.
|
|
298
|
+
*/
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* @typedef {OrdMap.<string, Grid~FieldColConfig>} Grid~ColConfig
|
|
302
|
+
* A collection of configurations across all the available fields in the grid. If a field isn't in
|
|
303
|
+
* this object, then it might as well not exist.
|
|
304
|
+
*/
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* @typedef {object} Grid~Features
|
|
308
|
+
*
|
|
309
|
+
* @property {boolean} [footer=false] If true, then a footer is shown at the bottom of the table.
|
|
310
|
+
* This is automatically enabled if `defn.table.footer` is provided.
|
|
311
|
+
*
|
|
312
|
+
* @property {boolean} [sort=false] If true, the user is allowed to sort the data by clicking the
|
|
313
|
+
* column header.
|
|
314
|
+
*
|
|
315
|
+
* @property {boolean} [filter=false] If true, the user is allowed to filter the data by clicking
|
|
316
|
+
* the "add filter" button in the column header.
|
|
317
|
+
*
|
|
318
|
+
* @property {boolean} [group=false] If true, the user is allowed to group the data.
|
|
319
|
+
*
|
|
320
|
+
* @property {boolean} [pivot=false] If true, the user is allowed to pivot the data.
|
|
321
|
+
*
|
|
322
|
+
* @property {boolean} [rowSelect=false] If true, the user is allowed to select rows by using the
|
|
323
|
+
* checkbox in the first column.
|
|
324
|
+
*
|
|
325
|
+
* @property {boolean} [rowReorder=false] If true, the user is allowed to manually reorder the rows
|
|
326
|
+
* using the handle in the last column.
|
|
327
|
+
*
|
|
328
|
+
* @property {boolean} [add=false] Unused
|
|
329
|
+
*
|
|
330
|
+
* @property {boolean} [edit=false] Unused
|
|
331
|
+
*
|
|
332
|
+
* @property {boolean} [delete=false] Unused
|
|
333
|
+
*
|
|
334
|
+
* @property {boolean} [limit=false] If true, then limit the amount of rows output by some method.
|
|
335
|
+
*
|
|
336
|
+
* @property {boolean} [tabletool=false] If true, then use TableTool to create a floating header for
|
|
337
|
+
* the table.
|
|
338
|
+
*
|
|
339
|
+
* @property {boolean} [blockUI=false] If true, use BlockUI to prevent interaction with the table
|
|
340
|
+
* while the ComputedView is doing something.
|
|
341
|
+
*
|
|
342
|
+
* @property {boolean} [nprogress=false] If true, use nprogress to show the progress of sort/filter
|
|
343
|
+
* operations that the ComputedView is performing.
|
|
344
|
+
*
|
|
345
|
+
* @property {boolean} [incremental=false] If true, render rows in the table incrementally, which
|
|
346
|
+
* prevents UI freezes while doing so. However, the overall time required to finish rendering the
|
|
347
|
+
* table goes way up.
|
|
348
|
+
*
|
|
349
|
+
* @property {boolean} [columnResize=false] If true, allow the user to resize columns by dragging
|
|
350
|
+
* the column border. Column widths are persisted to the column configuration.
|
|
351
|
+
*
|
|
352
|
+
* @property {boolean} [columnReorder=false] If true, allow the user to reorder columns by dragging
|
|
353
|
+
* column headers to new positions. Column order is persisted to the column configuration.
|
|
354
|
+
*
|
|
355
|
+
* @property {boolean} [activeRow=false]
|
|
356
|
+
* If true, then clicking a row in plain output makes the row "active." An active row is highlighted
|
|
357
|
+
* and causes other configurable behavior to occur. By default, the slider appears on the right side
|
|
358
|
+
* of the page to show information about the active row.
|
|
359
|
+
*
|
|
360
|
+
* @property {boolean} [omnifilter=true]
|
|
361
|
+
* If true, a text input is shown above the titlebar that filters visible table rows in plain
|
|
362
|
+
* output. Typing into the omnifilter hides all rows whose cell values do not contain the search
|
|
363
|
+
* text. This is a visual-only filter and does not affect the underlying data in the view. The
|
|
364
|
+
* omnifilter is automatically hidden when the output is grouped or pivoted.
|
|
365
|
+
*
|
|
366
|
+
* @property {boolean} [pagination=false]
|
|
367
|
+
* If true, rows in plain output are divided into pages of 40 rows each. All rows are rendered
|
|
368
|
+
* into the DOM but only the rows belonging to the current page are visible. Navigation controls
|
|
369
|
+
* at the bottom of the table allow the user to switch pages. Because every row already exists
|
|
370
|
+
* in the DOM, page changes are near-instant (just show/hide TR elements).
|
|
371
|
+
*/
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* @typedef {object} Grid~Opts
|
|
375
|
+
* Various options for the grid.
|
|
376
|
+
*
|
|
377
|
+
* @param {string} [name]
|
|
378
|
+
* The name of the grid, used for logging and debugging. If not provided, one will be generated,
|
|
379
|
+
* but you won't like it.
|
|
380
|
+
*
|
|
381
|
+
* @param {boolean} [opts.runImmediately=true]
|
|
382
|
+
* If true, then show the grid immediately.
|
|
383
|
+
*
|
|
384
|
+
* @param {boolean} [opts.showOnDataChange=true]
|
|
385
|
+
* Whether or not to show the grid automatically when the view reports there's new data available.
|
|
386
|
+
* Useful when using push-oriented data flow, causing view updates to cascade to multiple outputs.
|
|
387
|
+
*
|
|
388
|
+
* @param {number} [opts.height]
|
|
389
|
+
* If present, sets the height of the grid.
|
|
390
|
+
*
|
|
391
|
+
* @param {string} [opts.title]
|
|
392
|
+
* If present, create a title bar for the grid.
|
|
393
|
+
*
|
|
394
|
+
* @param {string} [opts.helpText]
|
|
395
|
+
* If present, create a help bubble with this text.
|
|
396
|
+
*
|
|
397
|
+
* @param {boolean} [opts.showToolbar=true]
|
|
398
|
+
* Whether or not to show the toolbar by default.
|
|
399
|
+
*
|
|
400
|
+
* @param {boolean} [opts.showControls=false]
|
|
401
|
+
* Whether or not to show the controls by default.
|
|
402
|
+
*/
|
|
403
|
+
|
|
404
|
+
// Constructor {{{2
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Create a new Grid and place it somewhere in the page. A Grid consists of two major parts: the
|
|
408
|
+
* decoration (e.g. titlebar and toolbar), and the underlying grid (e.g. jQWidgets or Tablesaw).
|
|
409
|
+
*
|
|
410
|
+
* @param {string} id The ID of a DIV (which must already exist in the page) where we will put the
|
|
411
|
+
* grid and its decoration. This DIV is also known as the "tag container" because it's typically
|
|
412
|
+
* created by the <WCGRID> layout tag.
|
|
413
|
+
*
|
|
414
|
+
* @param {Grid~Defn} defn The definition of the grid itself.
|
|
415
|
+
*
|
|
416
|
+
* @param {Grid~Opts} [opts] Configuration of the decoration of the grid.
|
|
417
|
+
*
|
|
418
|
+
* @param {function} cb A function that will be called after the grid has finished rendering, with
|
|
419
|
+
* the underlying output method grid object (e.g. the jqxGrid instance) being passed.
|
|
420
|
+
*
|
|
421
|
+
* @class
|
|
422
|
+
*
|
|
423
|
+
* @property {string} id The ID of the div that contains the whole tag output.
|
|
424
|
+
* @property {Grid~Defn} defn The definition object used to create the grid.
|
|
425
|
+
* @property {Grid~Opts} opts Options for the grid's container.
|
|
426
|
+
* @property {object} grid The underlying grid object (e.g. a jqxGrid instance).
|
|
427
|
+
* @property {object} ui Contains various user interface components which are tracked for convenience.
|
|
428
|
+
* @property {Grid~Features} features
|
|
429
|
+
* @property {Timing} timing
|
|
430
|
+
*
|
|
431
|
+
* @property {boolean} rootHasFixedHeight
|
|
432
|
+
* If true, then the root DIV element has a fixed height (e.g. "600px") and the grid must fit within
|
|
433
|
+
* that size. Basically, this controls the "overflow" CSS property of the grid table, and also the
|
|
434
|
+
* scroll handler for when a grid table automatically shows more rows.
|
|
435
|
+
*
|
|
436
|
+
* @property {boolean} _isIdle
|
|
437
|
+
* If true, then the grid currently has no pending operations that would require the UI to change.
|
|
438
|
+
*
|
|
439
|
+
* @property {Grid~ColConfig} colConfig
|
|
440
|
+
*
|
|
441
|
+
* @property {string} colConfigSource
|
|
442
|
+
* Where the column configuration came from, recognized values are: `defn`, `typeinfo`.
|
|
443
|
+
*
|
|
444
|
+
* @property {boolean} colConfigRestricted
|
|
445
|
+
* If true, then the available columns in column configuration are restricted and cannot be added to
|
|
446
|
+
* via the source or user preferences. In other words, the set of available columns is restricted
|
|
447
|
+
* to the subset specified via the grid definition.
|
|
448
|
+
*
|
|
449
|
+
* @borrows GridTable#getSelection
|
|
450
|
+
* @borrows GridTable#setSelection
|
|
451
|
+
* @borrows GridTable#select
|
|
452
|
+
* @borrows GridTable#unselect
|
|
453
|
+
* @borrows GridTable#isSelected
|
|
454
|
+
*/
|
|
455
|
+
|
|
456
|
+
var Grid = makeSubclass('Grid', Object, function (defn, opts, cb) {
|
|
457
|
+
var self = this;
|
|
458
|
+
|
|
459
|
+
opts = deepDefaults(opts, {
|
|
460
|
+
runImmediately: true,
|
|
461
|
+
showOnDataChange: true,
|
|
462
|
+
showToolbar: true,
|
|
463
|
+
showControls: false,
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
self.setName(opts.name);
|
|
467
|
+
|
|
468
|
+
var rowCount = null; // Container span for the row counter.
|
|
469
|
+
var clearFilter = null; // Container span for the "clear filter" link.
|
|
470
|
+
var doingServerFilter = getProp(defn, 'server', 'filter') && getProp(defn, 'server', 'limit') !== -1;
|
|
471
|
+
var viewDropdown = null;
|
|
472
|
+
|
|
473
|
+
self._isIdle = false;
|
|
474
|
+
|
|
475
|
+
self.mode = 'plain';
|
|
476
|
+
|
|
477
|
+
self.generateCsv = false;
|
|
478
|
+
self.csvReady = false;
|
|
479
|
+
self.exportLock = new Lock('Export');
|
|
480
|
+
self.colConfigLock = new Lock('colConfig');
|
|
481
|
+
|
|
482
|
+
self.rootHasFixedHeight = false;
|
|
483
|
+
self.timing = new Timing();
|
|
484
|
+
|
|
485
|
+
self.colConfigWin = new ColConfigWin(self);
|
|
486
|
+
self.debugWin = new DebugWin();
|
|
487
|
+
|
|
488
|
+
self.defn = self._normalize(defn); // Definition used to retrieve data and output grid.
|
|
489
|
+
self.opts = opts; // Other tag options, not related to the grid.
|
|
490
|
+
self.grid = null; // List of all grids generated as a result.
|
|
491
|
+
self.ui = {}; // User interface elements.
|
|
492
|
+
self.selected = {}; // Information about what rows are selected.
|
|
493
|
+
|
|
494
|
+
self._validateFeatures();
|
|
495
|
+
self._validateId(self.defn.id);
|
|
496
|
+
|
|
497
|
+
self.logDebug(self.makeLogTag() + ' Definition: %O', defn);
|
|
498
|
+
self.logDebug(self.makeLogTag() + ' Options: %O', opts);
|
|
499
|
+
|
|
500
|
+
// Check the validity of the provided computed/mirage views and prefs.
|
|
501
|
+
|
|
502
|
+
if (defn.computedView != null && !(defn.computedView instanceof ComputedView)) {
|
|
503
|
+
throw new Error('Call Error: `defn.computedView` must be null or an instance of ComputedView');
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (defn.mirageView != null && !(defn.mirageView instanceof MirageView)) {
|
|
507
|
+
throw new Error('Call Error: `defn.mirageView` must be null or an instance of MirageView');
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (defn.prefs != null && !(defn.prefs instanceof Prefs)) {
|
|
511
|
+
throw new Error('Call Error: `defn.prefs` must be null or an instance of Prefs');
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
self.computedView = defn.computedView;
|
|
515
|
+
self.mirageView = defn.mirageView;
|
|
516
|
+
self.prefs = defn.prefs;
|
|
517
|
+
|
|
518
|
+
// Create default versions of the computed/mirage views and prefs if none were provided.
|
|
519
|
+
|
|
520
|
+
if (self.computedView == null) {
|
|
521
|
+
self.logDebug(self.makeLogTag() + ' No computed view specified, creating our own.');
|
|
522
|
+
self.computedView = new ComputedView();
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
if (self.mirageView == null) {
|
|
526
|
+
self.logDebug(self.makeLogTag() + ' No mirage view specified, creating our own.');
|
|
527
|
+
self.mirageView = new MirageView();
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
if (self.prefs == null) {
|
|
531
|
+
self.logDebug(self.makeLogTag() + ' No prefs specified, creating our own.');
|
|
532
|
+
self.prefs = new Prefs(self.id);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Make sure we're all using the same prefs.
|
|
536
|
+
|
|
537
|
+
self.computedView.setPrefs(self.prefs);
|
|
538
|
+
self.mirageView.setPrefs(self.prefs);
|
|
539
|
+
|
|
540
|
+
self.view = self.defn.computedView || self.defn.mirageView || self.computedView;
|
|
541
|
+
|
|
542
|
+
if (self.colConfig != null) {
|
|
543
|
+
self.view.setColConfig(self.colConfig);
|
|
544
|
+
}
|
|
545
|
+
self.view.addClient(self, 'grid');
|
|
546
|
+
|
|
547
|
+
self.defn.grid = self;
|
|
548
|
+
|
|
549
|
+
self.TemplatesEditor = new TemplatesEditor(self, function () {
|
|
550
|
+
self.redraw();
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
// Set up UI elements {{{3
|
|
554
|
+
|
|
555
|
+
self.ui.root = jQuery(document.getElementById(self.id))
|
|
556
|
+
.addClass('wcdv_grid')
|
|
557
|
+
.attr('data-title', self.id + '_title');
|
|
558
|
+
|
|
559
|
+
self.ui.root.children().remove();
|
|
560
|
+
|
|
561
|
+
if (self.ui.root.height() !== 0) {
|
|
562
|
+
self.rootHasFixedHeight = true;
|
|
563
|
+
self.rootHeight = self.ui.root.height();
|
|
564
|
+
// When using TableTool, we can't just set the height of the whole grid and use flex to control
|
|
565
|
+
// the height of the table automatically. See DV-196.
|
|
566
|
+
// Remove the height CSS property here, so the renderer can use it for data-ttheight instead.
|
|
567
|
+
if (self.features.floatingHeader &&
|
|
568
|
+
getProp(self.defn, 'table', 'floatingHeader', 'method') === 'tabletool' &&
|
|
569
|
+
window.TableTool != null) {
|
|
570
|
+
self.ui.root.css('height', '');
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (self.view.source.origin instanceof FileSource) {
|
|
575
|
+
self.ui.root._onFileDrop(function (files) {
|
|
576
|
+
self.view.source.origin.setFiles(files);
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Titlebar {{{4
|
|
581
|
+
|
|
582
|
+
self.ui.titlebar = jQuery('<div class="wcdv_grid_titlebar">')
|
|
583
|
+
.attr('title', trans('GRID.TITLEBAR.SHOW_HIDE'))
|
|
584
|
+
.on('click', function (evt) {
|
|
585
|
+
evt.stopPropagation();
|
|
586
|
+
self.toggle();
|
|
587
|
+
})
|
|
588
|
+
.droppable({
|
|
589
|
+
accept: '.wcdv_drag_handle',
|
|
590
|
+
over: function (evt, ui) {
|
|
591
|
+
self.showControls();
|
|
592
|
+
|
|
593
|
+
// Need to recalculate the position of the droppable targets, because they are now
|
|
594
|
+
// guaranteed to be visible (they may have been hidden within the grid control before).
|
|
595
|
+
|
|
596
|
+
ui.draggable.draggable('option', 'refreshPositions', true);
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
self._addTitleWidgets(self.ui.titlebar, doingServerFilter, self.id);
|
|
601
|
+
|
|
602
|
+
// Omnifilter {{{4
|
|
603
|
+
|
|
604
|
+
if (self.features.omnifilter) {
|
|
605
|
+
self.ui.omnifilter = jQuery('<div class="wcdv_omnifilter">')
|
|
606
|
+
.on('click', function (evt) {
|
|
607
|
+
evt.stopPropagation();
|
|
608
|
+
})
|
|
609
|
+
.hide();
|
|
610
|
+
|
|
611
|
+
self._omnifilterDebounceTimer = null;
|
|
612
|
+
|
|
613
|
+
self.ui.omnifilterInput = jQuery('<input>', {
|
|
614
|
+
'type': 'text',
|
|
615
|
+
'class': 'wcdv_omnifilter_input',
|
|
616
|
+
'placeholder': trans('GRID.OMNIFILTER.PLACEHOLDER'),
|
|
617
|
+
'aria-label': trans('GRID.OMNIFILTER.ARIA_LABEL')
|
|
618
|
+
})
|
|
619
|
+
.on('input', function () {
|
|
620
|
+
clearTimeout(self._omnifilterDebounceTimer);
|
|
621
|
+
self._omnifilterDebounceTimer = setTimeout(function () {
|
|
622
|
+
self._applyOmnifilter();
|
|
623
|
+
}, 500);
|
|
624
|
+
})
|
|
625
|
+
.on('keydown', function (evt) {
|
|
626
|
+
if (evt.key === 'Escape') {
|
|
627
|
+
clearTimeout(self._omnifilterDebounceTimer);
|
|
628
|
+
self.ui.omnifilterInput.val('');
|
|
629
|
+
self._applyOmnifilter();
|
|
630
|
+
self.ui.omnifilter.hide();
|
|
631
|
+
self.ui.omnifilterToggle.removeClass('wcdv_omnifilter_active');
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
self.ui.omnifilterClear = jQuery('<button>', {
|
|
636
|
+
'class': 'wcdv_omnifilter_clear wcdv_icon_button',
|
|
637
|
+
'type': 'button',
|
|
638
|
+
'aria-label': trans('GRID.OMNIFILTER.CLEAR')
|
|
639
|
+
})
|
|
640
|
+
.append(icon('x'))
|
|
641
|
+
.on('click', function () {
|
|
642
|
+
clearTimeout(self._omnifilterDebounceTimer);
|
|
643
|
+
self.ui.omnifilterInput.val('');
|
|
644
|
+
self._applyOmnifilter();
|
|
645
|
+
})
|
|
646
|
+
.hide();
|
|
647
|
+
|
|
648
|
+
self.ui.omnifilterInputWrap = jQuery('<div class="wcdv_omnifilter_input_wrap">')
|
|
649
|
+
.append(self.ui.omnifilterInput)
|
|
650
|
+
.append(self.ui.omnifilterClear);
|
|
651
|
+
|
|
652
|
+
self.ui.omnifilter
|
|
653
|
+
.append(self.ui.omnifilterInputWrap);
|
|
654
|
+
|
|
655
|
+
self.ui.omnifilterToggle = jQuery('<button>', {
|
|
656
|
+
'type': 'button',
|
|
657
|
+
'style': 'font-size: 18px',
|
|
658
|
+
'class': 'wcdv_icon_button wcdv_text-primary wcdv_omnifilter_toggle',
|
|
659
|
+
'aria-label': trans('GRID.OMNIFILTER.TOGGLE')
|
|
660
|
+
})
|
|
661
|
+
.append(icon('search'))
|
|
662
|
+
.on('click', function (evt) {
|
|
663
|
+
evt.stopPropagation();
|
|
664
|
+
if (self.ui.omnifilter.is(':visible')) {
|
|
665
|
+
clearTimeout(self._omnifilterDebounceTimer);
|
|
666
|
+
self.ui.omnifilterInput.val('');
|
|
667
|
+
self._applyOmnifilter();
|
|
668
|
+
self.ui.omnifilter.hide();
|
|
669
|
+
self.ui.omnifilterToggle.removeClass('wcdv_omnifilter_active');
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
self.ui.omnifilterToggle.addClass('wcdv_omnifilter_active');
|
|
673
|
+
self.ui.omnifilter.show();
|
|
674
|
+
self.ui.omnifilterInput.focus();
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
// Insert into titlebar controls, before the debug/export/refresh buttons.
|
|
679
|
+
self.ui.titlebar_controls
|
|
680
|
+
.prepend(self.ui.omnifilterToggle)
|
|
681
|
+
.prepend(self.ui.omnifilter);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
self.ui.autoLimit = jQuery('<div>', {
|
|
685
|
+
'class': 'wcdv_warning_banner auto_limit_warning'
|
|
686
|
+
})
|
|
687
|
+
.text(trans('GRID.TITLEBAR.DATA_LIMITED_WARNING'))
|
|
688
|
+
.on('click', function () {
|
|
689
|
+
self.ui.autoLimit.hide();
|
|
690
|
+
self.view.unlimit();
|
|
691
|
+
self.refresh();
|
|
692
|
+
})
|
|
693
|
+
.hide();
|
|
694
|
+
|
|
695
|
+
// Toolbar {{{4
|
|
696
|
+
|
|
697
|
+
self.ui.content = jQuery('<div>', {
|
|
698
|
+
'class': 'wcdv_grid_content'
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
self.ui.toolbar = jQuery('<div>')
|
|
702
|
+
.addClass('wcdv_grid_toolbar')
|
|
703
|
+
.droppable({
|
|
704
|
+
accept: '.wcdv_drag_handle',
|
|
705
|
+
over: function (evt, ui) {
|
|
706
|
+
self.showControls();
|
|
707
|
+
|
|
708
|
+
// Need to recalculate the position of the droppable targets, because they are now
|
|
709
|
+
// guaranteed to be visible (they may have been hidden within the grid control before).
|
|
710
|
+
|
|
711
|
+
ui.draggable.draggable('option', 'refreshPositions', true);
|
|
712
|
+
}
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
self.ui.toolbar_computedView = new ComputedViewToolbar(self);
|
|
716
|
+
self.ui.toolbar_computedView.attach(self.ui.toolbar);
|
|
717
|
+
|
|
718
|
+
self.ui.toolbar_plain = new PlainToolbar(self);
|
|
719
|
+
self.ui.toolbar_plain.attach(self.ui.toolbar);
|
|
720
|
+
self.ui.toolbar_plain.hide();
|
|
721
|
+
|
|
722
|
+
self.ui.toolbar_group = new GroupToolbar(self);
|
|
723
|
+
self.ui.toolbar_group.attach(self.ui.toolbar);
|
|
724
|
+
self.ui.toolbar_group.hide();
|
|
725
|
+
|
|
726
|
+
self.ui.toolbar_pivot = new PivotToolbar(self);
|
|
727
|
+
self.ui.toolbar_pivot.attach(self.ui.toolbar);
|
|
728
|
+
self.ui.toolbar_pivot.hide();
|
|
729
|
+
|
|
730
|
+
self.ui.toolbar_renderer = new RendererToolbar(self);
|
|
731
|
+
self.ui.toolbar_renderer.attach(self.ui.toolbar);
|
|
732
|
+
|
|
733
|
+
if (!self.opts.showToolbar) {
|
|
734
|
+
self.ui.toolbar.hide();
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// Controls {{{4
|
|
738
|
+
|
|
739
|
+
self.ui.controls = jQuery('<div>', { 'class': 'wcdv_grid_control' });
|
|
740
|
+
self.ui.filterControl = jQuery('<div>', { 'class': 'wcdv_control_pane wcdv_filter_control' });
|
|
741
|
+
self.ui.groupControl = jQuery('<div>', { 'class': 'wcdv_control_pane wcdv_group_control' });
|
|
742
|
+
self.ui.pivotControl = jQuery('<div>', { 'class': 'wcdv_control_pane wcdv_pivot_control' });
|
|
743
|
+
self.ui.aggregateControl = jQuery('<div>', { 'class': 'wcdv_control_pane wcdv_aggregate_control' });
|
|
744
|
+
self.ui.operationsPalette = jQuery('<div>', { 'class': 'wcdv_grid_control' }).css({
|
|
745
|
+
display: 'block'
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
// Filter Control {{{5
|
|
749
|
+
|
|
750
|
+
self.filterControl = new FilterControl(self, self.colConfig, self.view, self.features, self.timing);
|
|
751
|
+
self.ui.filterControl.children().remove();
|
|
752
|
+
self.filterControl.draw(self.ui.filterControl);
|
|
753
|
+
self.ui.filterControl.show();
|
|
754
|
+
|
|
755
|
+
// Group Control {{{5
|
|
756
|
+
|
|
757
|
+
self.groupControl = new GroupControl(self, self.colConfig, self.view, self.features, self.timing);
|
|
758
|
+
self.groupControl.draw(self.ui.groupControl);
|
|
759
|
+
|
|
760
|
+
self.groupControl.on('fieldAdded', function (fieldAdded, fields) {
|
|
761
|
+
self.ui.toolbar_computedView.ui.storeMirageBtn.attr('disabled', false);
|
|
762
|
+
self.ui.pivotControl.show();
|
|
763
|
+
self.ui.aggregateControl.show();
|
|
764
|
+
});
|
|
765
|
+
self.groupControl.on('fieldRemoved', function (fieldRemoved, fields) {
|
|
766
|
+
if (fields.length === 0) {
|
|
767
|
+
self.ui.toolbar_computedView.ui.storeMirageBtn.attr('disabled', true);
|
|
768
|
+
self.ui.pivotControl.hide();
|
|
769
|
+
self.ui.aggregateControl.hide();
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
self.groupControl.on('cleared', function () {
|
|
773
|
+
self.ui.toolbar_computedView.ui.storeMirageBtn.attr('disabled', true);
|
|
774
|
+
self.ui.pivotControl.hide();
|
|
775
|
+
self.ui.aggregateControl.hide();
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
// Pivot Control {{{5
|
|
779
|
+
|
|
780
|
+
self.pivotControl = new PivotControl(self, self.colConfig, self.view, self.features, self.timing);
|
|
781
|
+
self.pivotControl.draw(self.ui.pivotControl);
|
|
782
|
+
|
|
783
|
+
// Group <-> Pivot (Drag & Drop) {{{5
|
|
784
|
+
|
|
785
|
+
self.groupControl.getListElement().sortable({
|
|
786
|
+
connectWith: '#' + self.pivotControl.getListElement().attr('id'),
|
|
787
|
+
placeholder: 'ui-state-highlight',
|
|
788
|
+
forcePlaceholderSize: true,
|
|
789
|
+
cursor: 'move',
|
|
790
|
+
start: function (evt, ui) {
|
|
791
|
+
ui.placeholder.css('height', ui.item.get(0).offsetHeight);
|
|
792
|
+
ui.helper.addClass('wcdv_sortable_helper');
|
|
793
|
+
},
|
|
794
|
+
activate: function (evt, ui) {
|
|
795
|
+
// Leave room for item to be added to empty list.
|
|
796
|
+
jQuery(this).addClass('wcdv_sortable_sender');
|
|
797
|
+
},
|
|
798
|
+
deactivate: function (evt, ui) {
|
|
799
|
+
jQuery(this).removeClass('wcdv_sortable_sender');
|
|
800
|
+
ui.item.removeClass('wcdv_sortable_helper');
|
|
801
|
+
},
|
|
802
|
+
update: function (evt, ui) {
|
|
803
|
+
// If dragging from group list to pivot list, and there was only one item in the group list,
|
|
804
|
+
// prevent the action (because this would be pivot w/o group).
|
|
805
|
+
if (ui.sender === null && self.groupControl.controlFields.length === 1) {
|
|
806
|
+
jQuery(this).sortable('cancel');
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
jQuery(this).removeClass('wcdv_sortable_sender');
|
|
810
|
+
ui.item.removeClass('wcdv_sortable_helper');
|
|
811
|
+
self.groupControl.sortableSync();
|
|
812
|
+
},
|
|
813
|
+
});
|
|
814
|
+
self.pivotControl.getListElement().sortable({
|
|
815
|
+
connectWith: '#' + self.groupControl.getListElement().attr('id'),
|
|
816
|
+
placeholder: 'ui-state-highlight',
|
|
817
|
+
forcePlaceholderSize: true,
|
|
818
|
+
cursor: 'move',
|
|
819
|
+
start: function (evt, ui) {
|
|
820
|
+
ui.placeholder.css('height', ui.item.get(0).offsetHeight);
|
|
821
|
+
ui.helper.addClass('wcdv_sortable_helper');
|
|
822
|
+
},
|
|
823
|
+
activate: function (evt, ui) {
|
|
824
|
+
// Leave room for item to be added to empty list.
|
|
825
|
+
jQuery(this).addClass('wcdv_sortable_sender');
|
|
826
|
+
},
|
|
827
|
+
deactivate: function (evt, ui) {
|
|
828
|
+
jQuery(this).removeClass('wcdv_sortable_sender');
|
|
829
|
+
ui.item.removeClass('wcdv_sortable_helper');
|
|
830
|
+
},
|
|
831
|
+
update: function (evt, ui) {
|
|
832
|
+
jQuery(this).removeClass('wcdv_sortable_sender');
|
|
833
|
+
ui.item.removeClass('wcdv_sortable_helper');
|
|
834
|
+
self.pivotControl.sortableSync();
|
|
835
|
+
}
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
self.operationsPalette = new OperationsPalette(self);
|
|
839
|
+
self.operationsPalette.setOperations(self.defn.operations);
|
|
840
|
+
self.operationsPalette.draw(self.ui.operationsPalette);
|
|
841
|
+
|
|
842
|
+
// Aggregate Control {{{5
|
|
843
|
+
|
|
844
|
+
self.aggregateControl = new AggregateControl(self, self.colConfig, self.view, self.features, self.timing);
|
|
845
|
+
self.aggregateControl.draw(self.ui.aggregateControl);
|
|
846
|
+
|
|
847
|
+
// }}}5
|
|
848
|
+
|
|
849
|
+
if (!self.opts.showControls) {
|
|
850
|
+
self.ui.controls.hide();
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// }}}4
|
|
854
|
+
|
|
855
|
+
self.ui.grid = jQuery('<div>', { 'id': defn.table.id, 'class': 'wcdv_grid_table' });
|
|
856
|
+
|
|
857
|
+
// Apply the initial row mode class
|
|
858
|
+
var rowMode = getPropDef('wrapped', defn, 'table', 'rowMode');
|
|
859
|
+
self.ui.grid.addClass('wcdv_row_mode_' + rowMode);
|
|
860
|
+
|
|
861
|
+
if (self.rootHasFixedHeight) {
|
|
862
|
+
// When using TableTool, we can't just set the height of the whole grid and use flex to control
|
|
863
|
+
// the height of the table automatically. See DV-196.
|
|
864
|
+
// Don't use the height: 0px trick in this situation and let TableTool manage the table height.
|
|
865
|
+
// FIXME Is this needed with the CSS method?
|
|
866
|
+
if (!self.features.floatingHeader || getProp(self.defn, 'table', 'floatingHeader', 'method') !== 'tabletool') {
|
|
867
|
+
// This is a trick to make 'flex: 1 1 auto' work right in Firefox, IE, Edge.
|
|
868
|
+
// Otherwise, the table takes up as much space as it needs and doesn't scroll.
|
|
869
|
+
self.ui.grid.css('height', '0px');
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// The user has fixed the height of the containing grid, so we will need to have the browser put
|
|
874
|
+
// in some scrollbars for the overflow.
|
|
875
|
+
|
|
876
|
+
if (self.rootHasFixedHeight) {
|
|
877
|
+
self.ui.grid.css({ 'overflow': 'auto' });
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
if (document.getElementById(self.id + '_footer')) {
|
|
881
|
+
// There was a footer which was printed out by dashboard.c which we are now going to move
|
|
882
|
+
// inside the structure that we've been creating.
|
|
883
|
+
|
|
884
|
+
self.ui.footer = jQuery(document.getElementById(self.id + '_footer'));
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
self.ui.root
|
|
888
|
+
.append(self.ui.titlebar)
|
|
889
|
+
.append(self.ui.content
|
|
890
|
+
.append(self.ui.toolbar)
|
|
891
|
+
.append(self.ui.controls
|
|
892
|
+
.append(self.ui.filterControl)
|
|
893
|
+
.append(self.ui.groupControl)
|
|
894
|
+
.append(self.ui.pivotControl)
|
|
895
|
+
.append(self.ui.aggregateControl))
|
|
896
|
+
.append(self.ui.operationsPalette)
|
|
897
|
+
.append(self.ui.autoLimit)
|
|
898
|
+
.append(self.ui.grid)
|
|
899
|
+
.append(self.ui.footer))
|
|
900
|
+
;
|
|
901
|
+
|
|
902
|
+
if (self.defn.renderer != null) {
|
|
903
|
+
self.clearRenderers();
|
|
904
|
+
self.addRenderer(0, null, {
|
|
905
|
+
name: self.defn.renderer,
|
|
906
|
+
opts: self.defn.rendererOpts
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
else {
|
|
910
|
+
self.resetRenderers();
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
self.makeResponsive();
|
|
914
|
+
|
|
915
|
+
// }}}3
|
|
916
|
+
|
|
917
|
+
var initialRender = true;
|
|
918
|
+
|
|
919
|
+
self.tableDoneCont = function (grid, srcIndex) {
|
|
920
|
+
self.logDebug(self.makeLogTag() + ' Finished drawing grid table!');
|
|
921
|
+
|
|
922
|
+
if (initialRender) {
|
|
923
|
+
initialRender = false;
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// Invoke the callback for the Grid constructor, after the grid has been created. Sometimes
|
|
927
|
+
// people want to start manipulating the grid from JS right away.
|
|
928
|
+
|
|
929
|
+
if (typeof cb === 'function') {
|
|
930
|
+
cb();
|
|
931
|
+
}
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
self.view.on('fetchDataBegin', function () {
|
|
935
|
+
self._setSpinner('loading');
|
|
936
|
+
self._showSpinner();
|
|
937
|
+
if (self.opts.title) {
|
|
938
|
+
self.ui.title._addTrailing(',');
|
|
939
|
+
self.ui.statusSpan.show().text(trans('GRID.TITLEBAR.LOADING'));
|
|
940
|
+
self.ui.rowCount.hide();
|
|
941
|
+
}
|
|
942
|
+
if (self.view.source.isCancellable()) {
|
|
943
|
+
self.ui.cancelFetchBtn.show();
|
|
944
|
+
}
|
|
945
|
+
});
|
|
946
|
+
self.view.on('fetchDataEnd', function () {
|
|
947
|
+
self._hideSpinner();
|
|
948
|
+
self.ui.cancelFetchBtn.hide();
|
|
949
|
+
self.ui.statusSpan.show().text(trans('GRID.TITLEBAR.LOADED'));
|
|
950
|
+
});
|
|
951
|
+
self.view.source.on('fetchDataCancel', function () {
|
|
952
|
+
self.ui.cancelFetchBtn.hide();
|
|
953
|
+
if (initialRender) {
|
|
954
|
+
if (self.opts.title) {
|
|
955
|
+
self.ui.title._addTrailing(',');
|
|
956
|
+
self.ui.statusSpan.show().text(trans('GRID.TITLEBAR.NOT_LOADED'));
|
|
957
|
+
self.ui.rowCount.hide();
|
|
958
|
+
}
|
|
959
|
+
self._setSpinner('not-loaded');
|
|
960
|
+
self.hasRun = false;
|
|
961
|
+
self.hide();
|
|
962
|
+
}
|
|
963
|
+
else {
|
|
964
|
+
if (self.opts.title) {
|
|
965
|
+
self.ui.title._addTrailing(',');
|
|
966
|
+
self.ui.statusSpan.hide();
|
|
967
|
+
self.ui.rowCount.show();
|
|
968
|
+
}
|
|
969
|
+
self._hideSpinner();
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
self.view.on('workBegin', function () {
|
|
974
|
+
self._isIdle = false;
|
|
975
|
+
self._setSpinner('working');
|
|
976
|
+
self._showSpinner();
|
|
977
|
+
if (self.opts.title) {
|
|
978
|
+
self.ui.title._addTrailing(',');
|
|
979
|
+
self.ui.statusSpan.show().text(trans('GRID.TITLEBAR.WORKING'));
|
|
980
|
+
self.ui.rowCount.hide();
|
|
981
|
+
}
|
|
982
|
+
});
|
|
983
|
+
self.view.on('workEnd', function (info, ops) {
|
|
984
|
+
self._isIdle = true;
|
|
985
|
+
self._hideSpinner();
|
|
986
|
+
self.ui.title._stripTrailing(',');
|
|
987
|
+
self.ui.statusSpan.hide();
|
|
988
|
+
self.ui.rowCount.show();
|
|
989
|
+
self._updateRowCount(info, ops);
|
|
990
|
+
self.mode = info.isPlain ? 'plain' : info.isGroup ? 'group' : info.isPivot ? 'pivot' : null;
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
self.view.on('dataUpdated', function () {
|
|
994
|
+
if (self.opts.showOnDataChange && !self.isVisible()) {
|
|
995
|
+
self.show({ redraw: false });
|
|
996
|
+
}
|
|
997
|
+
self.redraw();
|
|
998
|
+
});
|
|
999
|
+
|
|
1000
|
+
self.view.on('getTypeInfo', function (typeInfo) {
|
|
1001
|
+
self.colConfigFromTypeInfo(typeInfo);
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
self.prefs.prime(function () {
|
|
1005
|
+
// Create a way to switch back and forth between the two types of views depending on if a
|
|
1006
|
+
// perspective is live or not.
|
|
1007
|
+
|
|
1008
|
+
self.prefs.on('perspectiveChanged', function (id, p) {
|
|
1009
|
+
self.setView();
|
|
1010
|
+
self.redraw();
|
|
1011
|
+
}, {
|
|
1012
|
+
info: 'Changing view type to match new perspective'
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
if (self.opts.runImmediately) {
|
|
1016
|
+
self.setView();
|
|
1017
|
+
self.redraw();
|
|
1018
|
+
}
|
|
1019
|
+
else {
|
|
1020
|
+
self.hasRun = false;
|
|
1021
|
+
self.hide();
|
|
1022
|
+
}
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
/*
|
|
1026
|
+
* Store self object so it can be accessed from other JavaScript in the page.
|
|
1027
|
+
*/
|
|
1028
|
+
|
|
1029
|
+
setProp(self, window, 'MIE', 'WC_DataVis', 'grids', self.id);
|
|
1030
|
+
});
|
|
1031
|
+
|
|
1032
|
+
// Mixins {{{2
|
|
1033
|
+
|
|
1034
|
+
mixinEventHandling(Grid, [
|
|
1035
|
+
'showControls'
|
|
1036
|
+
, 'hideControls'
|
|
1037
|
+
, 'renderBegin'
|
|
1038
|
+
, 'renderEnd'
|
|
1039
|
+
, 'colConfigUpdate'
|
|
1040
|
+
, 'selectionChange'
|
|
1041
|
+
, 'rowModeChange'
|
|
1042
|
+
]);
|
|
1043
|
+
|
|
1044
|
+
delegate(Grid, 'renderer', ['setSelection', 'getSelection', 'select', 'unselect', 'isSelected']);
|
|
1045
|
+
|
|
1046
|
+
mixinLogging(Grid);
|
|
1047
|
+
mixinNameSetting(Grid);
|
|
1048
|
+
|
|
1049
|
+
// Events JSDoc {{{3
|
|
1050
|
+
|
|
1051
|
+
/**
|
|
1052
|
+
* Fired when controls are shown in the grid.
|
|
1053
|
+
*
|
|
1054
|
+
* @event Grid#showControls
|
|
1055
|
+
*/
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
* Fired when controls are hidden in the grid.
|
|
1059
|
+
*
|
|
1060
|
+
* @event Grid#hideControls
|
|
1061
|
+
*/
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* Fired when rendering has started.
|
|
1065
|
+
*
|
|
1066
|
+
* @event Grid#renderBegin
|
|
1067
|
+
*/
|
|
1068
|
+
|
|
1069
|
+
/**
|
|
1070
|
+
* Fired when rendering has finished.
|
|
1071
|
+
*
|
|
1072
|
+
* @event Grid#renderEnd
|
|
1073
|
+
*/
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* Fired when column configuration has changed.
|
|
1077
|
+
*
|
|
1078
|
+
* @event Grid#colConfigUpdate
|
|
1079
|
+
*/
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* Fired when selection is changed.
|
|
1083
|
+
*
|
|
1084
|
+
* @event Grid#selectionChange
|
|
1085
|
+
*
|
|
1086
|
+
* @param {Array.<ComputedView~Data_Row>} selected
|
|
1087
|
+
* Data from rows that are selected.
|
|
1088
|
+
*/
|
|
1089
|
+
|
|
1090
|
+
// #toString {{{2
|
|
1091
|
+
|
|
1092
|
+
Grid.prototype.toString = function () {
|
|
1093
|
+
var self = this;
|
|
1094
|
+
return 'Grid(' + self.id + ')';
|
|
1095
|
+
};
|
|
1096
|
+
|
|
1097
|
+
// #_validateFeatures {{{2
|
|
1098
|
+
|
|
1099
|
+
Grid.prototype._validateFeatures = function () {
|
|
1100
|
+
var self = this;
|
|
1101
|
+
|
|
1102
|
+
self.features = {};
|
|
1103
|
+
|
|
1104
|
+
var availableFeatures = [
|
|
1105
|
+
'footer',
|
|
1106
|
+
'sort',
|
|
1107
|
+
'filter',
|
|
1108
|
+
'group',
|
|
1109
|
+
'pivot',
|
|
1110
|
+
'rowSelect',
|
|
1111
|
+
'rowReorder',
|
|
1112
|
+
'add',
|
|
1113
|
+
'edit',
|
|
1114
|
+
'delete',
|
|
1115
|
+
'limit',
|
|
1116
|
+
'floatingHeader',
|
|
1117
|
+
'block',
|
|
1118
|
+
'progress',
|
|
1119
|
+
'incremental',
|
|
1120
|
+
'operations',
|
|
1121
|
+
'columnResize',
|
|
1122
|
+
'columnReorder',
|
|
1123
|
+
'activeRow',
|
|
1124
|
+
'omnifilter',
|
|
1125
|
+
'pagination'
|
|
1126
|
+
];
|
|
1127
|
+
|
|
1128
|
+
// When the user has specified the `footer` option, enable the footer feature (if it hasn't
|
|
1129
|
+
// already been set by the user - in other words, the user can override this automatic behavior).
|
|
1130
|
+
|
|
1131
|
+
if (getProp(self.defn, 'table', 'footer') !== undefined) {
|
|
1132
|
+
setPropDef(true, self.defn, 'table', 'features', 'footer');
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
_.each(availableFeatures, function (feat) {
|
|
1136
|
+
self.features[feat] = getPropDef(false, self.defn, 'table', 'features', feat);
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
self.logDebug(self.makeLogTag() + ' Features =', self.features);
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
// #_validateId {{{2
|
|
1143
|
+
|
|
1144
|
+
Grid.prototype._validateId = function (id) {
|
|
1145
|
+
var self = this;
|
|
1146
|
+
|
|
1147
|
+
// If the ID was specified as a jQuery object, extract the ID from the element.
|
|
1148
|
+
|
|
1149
|
+
if (_.isArray(id) && id[0] instanceof jQuery) {
|
|
1150
|
+
id = id[0];
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
if (id instanceof jQuery) {
|
|
1154
|
+
id = id.attr('id');
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
if (typeof id !== 'string') {
|
|
1158
|
+
throw '<grid> "id" is not a string';
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
if (document.getElementById(id) === null) {
|
|
1162
|
+
throw 'No element exists with given ID: ' + id;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
self.id = id;
|
|
1166
|
+
setProp(id + '_gridContainer', self.defn, 'table', 'id');
|
|
1167
|
+
};
|
|
1168
|
+
|
|
1169
|
+
// #setView {{{2
|
|
1170
|
+
|
|
1171
|
+
Grid.prototype.setView = function () {
|
|
1172
|
+
var self = this;
|
|
1173
|
+
|
|
1174
|
+
var p = self.prefs.currentPerspective;
|
|
1175
|
+
|
|
1176
|
+
// If the perspective is meant for live data then configure the grid to use a ComputedView.
|
|
1177
|
+
// Otherwise, configure the grid to use a MirageView.
|
|
1178
|
+
|
|
1179
|
+
if (p.isMirage()) {
|
|
1180
|
+
self.logDebug(self.makeLogTag('setView') + ' Switching to Mirage View for pre-computed data for perspective "%s"', p.name);
|
|
1181
|
+
self.view = self.prefs.modules['mirage'].target;
|
|
1182
|
+
self.view.setPerspectiveName(p.name);
|
|
1183
|
+
}
|
|
1184
|
+
else {
|
|
1185
|
+
self.logDebug(self.makeLogTag('setView') + ' Switching to Computed View for live data for perspective "%s"', p.name);
|
|
1186
|
+
self.view = self.prefs.modules['view'].target;
|
|
1187
|
+
}
|
|
1188
|
+
};
|
|
1189
|
+
// #_addTitleWidgets {{{2
|
|
1190
|
+
|
|
1191
|
+
/**
|
|
1192
|
+
* Add widgets to the header of the grid.
|
|
1193
|
+
*
|
|
1194
|
+
* @method
|
|
1195
|
+
* @memberof Grid
|
|
1196
|
+
* @private
|
|
1197
|
+
*
|
|
1198
|
+
* @param {object} header
|
|
1199
|
+
* @param {boolean} doingServerFilter If true, then we are filtering and sorting on the server.
|
|
1200
|
+
* @param {string} id
|
|
1201
|
+
*/
|
|
1202
|
+
|
|
1203
|
+
Grid.prototype._addTitleWidgets = function (titlebar, doingServerFilter, id) {
|
|
1204
|
+
var self = this;
|
|
1205
|
+
|
|
1206
|
+
self.ui.spinner = jQuery('<div>', {
|
|
1207
|
+
'class': 'wcdv_loading_spinner'
|
|
1208
|
+
})
|
|
1209
|
+
.appendTo(titlebar)
|
|
1210
|
+
;
|
|
1211
|
+
|
|
1212
|
+
self._setSpinner(self.opts.runImmediately ? 'loading' : 'not-loaded');
|
|
1213
|
+
|
|
1214
|
+
self.ui.title = jQuery('<strong>', {'id': id + '_title', 'data-parent': id})
|
|
1215
|
+
.addClass('wcdv_title')
|
|
1216
|
+
.text(self.opts.title)
|
|
1217
|
+
.appendTo(titlebar);
|
|
1218
|
+
|
|
1219
|
+
var notHeader = jQuery('<span>', {'class': 'headingInfo'})
|
|
1220
|
+
.on('click', function (evt) {
|
|
1221
|
+
evt.stopPropagation();
|
|
1222
|
+
})
|
|
1223
|
+
.appendTo(titlebar);
|
|
1224
|
+
|
|
1225
|
+
notHeader.append(' ');
|
|
1226
|
+
|
|
1227
|
+
self.ui.statusSpan = jQuery('<span>').appendTo(notHeader);
|
|
1228
|
+
self.ui.rowCount = jQuery('<span>').appendTo(notHeader);
|
|
1229
|
+
|
|
1230
|
+
self.ui.selectionInfo = jQuery('<span>').appendTo(notHeader);
|
|
1231
|
+
|
|
1232
|
+
self.ui.clearFilter = jQuery('<span>')
|
|
1233
|
+
.hide()
|
|
1234
|
+
.append(' (')
|
|
1235
|
+
.append(jQuery('<span>', {'class': 'link'})
|
|
1236
|
+
.text(trans('GRID.TITLEBAR.CLEAR_FILTER'))
|
|
1237
|
+
.on('click', function (evt) {
|
|
1238
|
+
evt.stopPropagation();
|
|
1239
|
+
self.ui.clearFilter.hide();
|
|
1240
|
+
self.view.clearFilter({ notify: true });
|
|
1241
|
+
}))
|
|
1242
|
+
.append(')')
|
|
1243
|
+
.appendTo(notHeader);
|
|
1244
|
+
|
|
1245
|
+
self.ui.cancelFetchBtn = jQuery('<button>', {
|
|
1246
|
+
'type': 'button',
|
|
1247
|
+
'title': trans('GRID.TITLEBAR.CANCEL')
|
|
1248
|
+
})
|
|
1249
|
+
.css({'margin-left': '0.5em'})
|
|
1250
|
+
.text(trans('GRID.TITLEBAR.CANCEL'))
|
|
1251
|
+
.on('click', function (evt) {
|
|
1252
|
+
evt.stopPropagation();
|
|
1253
|
+
self.view.source.cancel();
|
|
1254
|
+
})
|
|
1255
|
+
.hide()
|
|
1256
|
+
.appendTo(notHeader);
|
|
1257
|
+
|
|
1258
|
+
if (typeof self.opts.helpText === 'string' && self.opts.helpText !== '') {
|
|
1259
|
+
notHeader.append(' ');
|
|
1260
|
+
jQuery('<span>', {
|
|
1261
|
+
'data-tooltip': self.opts.helpText
|
|
1262
|
+
})
|
|
1263
|
+
.append(icon('circle-help').css({
|
|
1264
|
+
'margin-bottom': '-4px'
|
|
1265
|
+
}))
|
|
1266
|
+
.appendTo(notHeader);
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
self.ui.toolbar_prefs = new PrefsToolbar(self);
|
|
1270
|
+
self.ui.toolbar_prefs.attach(titlebar);
|
|
1271
|
+
|
|
1272
|
+
self.prefs.bind('grid', self, {
|
|
1273
|
+
toolbar: self.ui.toolbar_prefs.ui.root
|
|
1274
|
+
});
|
|
1275
|
+
|
|
1276
|
+
// Create container to hold all the controls in the titlebar
|
|
1277
|
+
|
|
1278
|
+
self.ui.titlebar_controls = jQuery('<div>')
|
|
1279
|
+
.addClass('wcdv_titlebar_controls pull-right')
|
|
1280
|
+
.appendTo(titlebar);
|
|
1281
|
+
|
|
1282
|
+
// Create the Debug Info button.
|
|
1283
|
+
|
|
1284
|
+
if (window.MIE && window.MIE.DEBUGGING) {
|
|
1285
|
+
jQuery('<button>', {
|
|
1286
|
+
'type': 'button',
|
|
1287
|
+
'style': 'font-size: 18px',
|
|
1288
|
+
'class': 'wcdv_icon_button wcdv_text-primary'
|
|
1289
|
+
})
|
|
1290
|
+
.attr('title', trans('GRID.TITLEBAR.SHOW_DEBUG_INFO'))
|
|
1291
|
+
.click(function (evt) {
|
|
1292
|
+
evt.stopPropagation();
|
|
1293
|
+
self.debugWin.show(self, self.view, self.view.source);
|
|
1294
|
+
})
|
|
1295
|
+
.append(icon('bug'))
|
|
1296
|
+
.appendTo(self.ui.titlebar_controls);
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
// Create the Export button
|
|
1300
|
+
|
|
1301
|
+
self.ui.exportBtn = jQuery('<button>', {
|
|
1302
|
+
'type': 'button',
|
|
1303
|
+
'style': 'font-size: 18px',
|
|
1304
|
+
'class': 'wcdv_icon_button wcdv_text-primary'
|
|
1305
|
+
})
|
|
1306
|
+
.on('click', function (evt) {
|
|
1307
|
+
evt.stopPropagation();
|
|
1308
|
+
self.export();
|
|
1309
|
+
})
|
|
1310
|
+
.appendTo(self.ui.titlebar_controls)
|
|
1311
|
+
;
|
|
1312
|
+
|
|
1313
|
+
self._setExportStatus('notReady');
|
|
1314
|
+
|
|
1315
|
+
// Create the Refresh button
|
|
1316
|
+
|
|
1317
|
+
self.ui.refreshBtn = jQuery('<button>', {
|
|
1318
|
+
'type': 'button',
|
|
1319
|
+
'style': 'font-size: 18px',
|
|
1320
|
+
'class': 'wcdv_icon_button wcdv_text-primary'
|
|
1321
|
+
})
|
|
1322
|
+
.attr('title', trans('GRID.TITLEBAR.REFRESH'))
|
|
1323
|
+
.on('click', function (evt) {
|
|
1324
|
+
evt.stopPropagation();
|
|
1325
|
+
self.refresh();
|
|
1326
|
+
})
|
|
1327
|
+
.append(icon('refresh-cw'))
|
|
1328
|
+
.appendTo(self.ui.titlebar_controls)
|
|
1329
|
+
;
|
|
1330
|
+
|
|
1331
|
+
var pWin = new PopupWindow({
|
|
1332
|
+
title: trans('GRID.PERSPECTIVE_WIN.TITLE'),
|
|
1333
|
+
width: 500,
|
|
1334
|
+
position: {
|
|
1335
|
+
my: 'top',
|
|
1336
|
+
at: 'bottom',
|
|
1337
|
+
of: titlebar
|
|
1338
|
+
}
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
var pWinContentDiv = jQuery('<div>');
|
|
1342
|
+
|
|
1343
|
+
var pWinWarning = jQuery('<div>')
|
|
1344
|
+
.addClass('wcdv_dlg_warning_banner')
|
|
1345
|
+
.appendTo(pWinContentDiv);
|
|
1346
|
+
|
|
1347
|
+
var pWinTextArea = jQuery('<textarea>', {'style': 'font-family: monospace; font-size: 10pt; width: 100%', 'rows': '20', 'readonly': true})
|
|
1348
|
+
.appendTo(pWinContentDiv);
|
|
1349
|
+
|
|
1350
|
+
pWin.setContent(pWinContentDiv);
|
|
1351
|
+
|
|
1352
|
+
// This is the "gear" icon that shows/hides the controls below the toolbar. The controls are used
|
|
1353
|
+
// to set the group, pivot, aggregate, and filters. Ideally the user only has to utilize these
|
|
1354
|
+
// once, and then switches between perspectives to get the same effect.
|
|
1355
|
+
|
|
1356
|
+
jQuery('<button>', {
|
|
1357
|
+
'type': 'button',
|
|
1358
|
+
'style': 'font-size: 18px',
|
|
1359
|
+
'class': 'wcdv_icon_button wcdv_text-primary'
|
|
1360
|
+
})
|
|
1361
|
+
.attr('title', trans('GRID.TITLEBAR.SHOW_HIDE_CONTROLS'))
|
|
1362
|
+
.click(function (evt) {
|
|
1363
|
+
evt.stopPropagation();
|
|
1364
|
+
if (evt.shiftKey) {
|
|
1365
|
+
if (self.prefs.currentPerspective.opts.isTemporary) {
|
|
1366
|
+
pWinWarning.text(trans('GRID.PERSPECTIVE_WIN.TEMP_PERSPECTIVE_WARNING'));
|
|
1367
|
+
pWinWarning.show();
|
|
1368
|
+
}
|
|
1369
|
+
else {
|
|
1370
|
+
pWinWarning.hide();
|
|
1371
|
+
}
|
|
1372
|
+
pWinTextArea.val(JSON.stringify(self.prefs.currentPerspective.config, null, 2));
|
|
1373
|
+
pWin.open();
|
|
1374
|
+
}
|
|
1375
|
+
else {
|
|
1376
|
+
self.toggleControls();
|
|
1377
|
+
}
|
|
1378
|
+
})
|
|
1379
|
+
.append(jQuery(icon('settings')))
|
|
1380
|
+
.appendTo(self.ui.titlebar_controls)
|
|
1381
|
+
;
|
|
1382
|
+
|
|
1383
|
+
// Create the down-chevron button that shows/hides everything under the titlebar.
|
|
1384
|
+
|
|
1385
|
+
self.ui.showHideButton = jQuery('<button>', {
|
|
1386
|
+
'type': 'button',
|
|
1387
|
+
'style': 'font-size: 18px',
|
|
1388
|
+
'class': 'wcdv_icon_button wcdv_text-primary showhide'
|
|
1389
|
+
})
|
|
1390
|
+
.attr('title', trans('GRID.TITLEBAR.SHOW_HIDE'))
|
|
1391
|
+
.click(function (evt) {
|
|
1392
|
+
evt.stopPropagation();
|
|
1393
|
+
self.toggle();
|
|
1394
|
+
})
|
|
1395
|
+
.append(jQuery(icon('chevron-down')))
|
|
1396
|
+
.appendTo(self.ui.titlebar_controls)
|
|
1397
|
+
;
|
|
1398
|
+
};
|
|
1399
|
+
|
|
1400
|
+
// #clear {{{2
|
|
1401
|
+
|
|
1402
|
+
Grid.prototype.clear = function () {
|
|
1403
|
+
var self = this;
|
|
1404
|
+
|
|
1405
|
+
if (self.resizeObserver != null) {
|
|
1406
|
+
self.resizeObserver.disconnect();
|
|
1407
|
+
self.resizeObserver = null;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
self.ui.root.children().remove();
|
|
1411
|
+
};
|
|
1412
|
+
// #redraw {{{2
|
|
1413
|
+
|
|
1414
|
+
/**
|
|
1415
|
+
* Redraw the data shown in a grid. If the grid is not visible, this function does nothing (i.e.
|
|
1416
|
+
* you cannot use it to retrieve data for an invisible grid).
|
|
1417
|
+
*
|
|
1418
|
+
* @method
|
|
1419
|
+
* @memberof Grid
|
|
1420
|
+
*
|
|
1421
|
+
* @param {function} [contOk]
|
|
1422
|
+
* Function to call on success.
|
|
1423
|
+
*
|
|
1424
|
+
* @param {function} [contFail]
|
|
1425
|
+
* Function to call on failure.
|
|
1426
|
+
*/
|
|
1427
|
+
|
|
1428
|
+
Grid.prototype.redraw = function (contOk, contFail) {
|
|
1429
|
+
var self = this;
|
|
1430
|
+
|
|
1431
|
+
if (contOk != null && typeof contOk !== 'function') {
|
|
1432
|
+
throw new Error('Call Error: `contOk` must be null or a function');
|
|
1433
|
+
}
|
|
1434
|
+
if (contFail != null && typeof contFail != 'function') {
|
|
1435
|
+
throw new Error('Call Error: `contFail` must be null or a function');
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
contOk = contOk || I;
|
|
1439
|
+
contFail = contFail || I;
|
|
1440
|
+
|
|
1441
|
+
self.logDebug(self.makeLogTag() + ' Redrawing...');
|
|
1442
|
+
|
|
1443
|
+
var rendererCtor
|
|
1444
|
+
, rendererCtorOpts;
|
|
1445
|
+
|
|
1446
|
+
self.colConfigLock.lock('redrawing grid; prevent colConfig changes from notifying existing renderer');
|
|
1447
|
+
|
|
1448
|
+
self.view.getData(function (ok, data) {
|
|
1449
|
+
if (!ok) {
|
|
1450
|
+
return contFail();
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
var mode = data.isPlain ? 'plain' : data.isGroup ? 'group' : data.isPivot ? 'pivot' : null;
|
|
1454
|
+
var renderer = self.findRenderer(self.ui.root.get(0).getBoundingClientRect().width, mode);
|
|
1455
|
+
|
|
1456
|
+
self.rendererName = renderer.name;
|
|
1457
|
+
self.rendererId = renderer.id;
|
|
1458
|
+
|
|
1459
|
+
var rendererCtor = GridRenderer.registry.get(self.rendererName);
|
|
1460
|
+
var rendererCtorOpts = deepCopy(renderer.opts);
|
|
1461
|
+
|
|
1462
|
+
if (self.ui.footer) {
|
|
1463
|
+
rendererCtorOpts.footer = self.ui.footer;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
if (self.renderer) {
|
|
1467
|
+
self.renderer.destroy();
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
rendererCtorOpts.generateCsv = self.generateCsv;
|
|
1471
|
+
rendererCtorOpts.fixedHeight = self.rootHasFixedHeight;
|
|
1472
|
+
|
|
1473
|
+
self.ui.exportBtn.attr('disabled', true);
|
|
1474
|
+
self.renderer = new rendererCtor(self, self.defn, self.view, self.features, rendererCtorOpts, self.timing, self.id, self.colConfig);
|
|
1475
|
+
|
|
1476
|
+
// Update the toolbar sections. This needs to be done after creating the renderer because the
|
|
1477
|
+
// renderer validates (and possibly changes) the supported features, and that changes what parts
|
|
1478
|
+
// of the toolbar we show. Obviously, we shouldn't show buttons for features that the current
|
|
1479
|
+
// renderer doesn't implement.
|
|
1480
|
+
|
|
1481
|
+
if (data.isPlain) {
|
|
1482
|
+
self.ui.toolbar_plain.show();
|
|
1483
|
+
self.ui.toolbar_group.hide();
|
|
1484
|
+
self.ui.toolbar_pivot.hide();
|
|
1485
|
+
if (self.features.omnifilter) {
|
|
1486
|
+
self.ui.omnifilterToggle.show();
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
else if (data.isGroup) {
|
|
1490
|
+
self.ui.toolbar_plain.hide();
|
|
1491
|
+
self.ui.toolbar_group.show();
|
|
1492
|
+
self.ui.toolbar_pivot.hide();
|
|
1493
|
+
if (self.features.omnifilter) {
|
|
1494
|
+
self.ui.omnifilterToggle.removeClass('wcdv_omnifilter_active');
|
|
1495
|
+
self.ui.omnifilterToggle.hide();
|
|
1496
|
+
self.ui.omnifilter.hide();
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
else if (data.isPivot) {
|
|
1500
|
+
self.ui.toolbar_plain.hide();
|
|
1501
|
+
self.ui.toolbar_group.hide();
|
|
1502
|
+
self.ui.toolbar_pivot.show();
|
|
1503
|
+
if (self.features.omnifilter) {
|
|
1504
|
+
self.ui.omnifilterToggle.removeClass('wcdv_omnifilter_active');
|
|
1505
|
+
self.ui.omnifilterToggle.hide();
|
|
1506
|
+
self.ui.omnifilter.hide();
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
self.renderer.on('renderBegin', function () {
|
|
1511
|
+
self._isIdle = false;
|
|
1512
|
+
self.fire('renderBegin');
|
|
1513
|
+
});
|
|
1514
|
+
self.renderer.on('renderEnd', function () {
|
|
1515
|
+
self.fire('renderEnd');
|
|
1516
|
+
self._isIdle = true;
|
|
1517
|
+
});
|
|
1518
|
+
|
|
1519
|
+
self.renderer.on('unableToRender', function () {
|
|
1520
|
+
self._setExportStatus('notReady');
|
|
1521
|
+
self.redraw();
|
|
1522
|
+
});
|
|
1523
|
+
|
|
1524
|
+
self.renderer.on('csvReady', function () {
|
|
1525
|
+
if (self.exportLock.isLocked()) {
|
|
1526
|
+
self.exportLock.unlock();
|
|
1527
|
+
}
|
|
1528
|
+
self._setExportStatus('ready');
|
|
1529
|
+
});
|
|
1530
|
+
self.renderer.on('generateCsvProgress', function (progress) {
|
|
1531
|
+
if (progress === 0) {
|
|
1532
|
+
self.ui.exportBtn.children('svg.wcdv_icon').remove();
|
|
1533
|
+
self.ui.exportBtn.append(icon('loader-circle', ['wcdv_icon_pulse']));
|
|
1534
|
+
}
|
|
1535
|
+
});
|
|
1536
|
+
|
|
1537
|
+
if (self.features.limit) {
|
|
1538
|
+
self.renderer.on('limited', function () {
|
|
1539
|
+
self.ui.limit_div.show();
|
|
1540
|
+
});
|
|
1541
|
+
self.renderer.on('unlimited', function () {
|
|
1542
|
+
self.ui.limit_div.hide();
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
if (self.features.rowSelect) {
|
|
1547
|
+
self.renderer.on('selectionChange', function (selection) {
|
|
1548
|
+
if (selection.length === 0) {
|
|
1549
|
+
self.ui.selectionInfo.text('');
|
|
1550
|
+
}
|
|
1551
|
+
else {
|
|
1552
|
+
var addComma = self.ui.rowCount.text().length > 0;
|
|
1553
|
+
var str = addComma ? ', ' : '';
|
|
1554
|
+
str += trans(selection.length === 1 ? 'GRID.TITLEBAR.SELECTED_COUNT_SINGULAR' : 'GRID.TITLEBAR.SELECTED_COUNT_PLURAL', selection.length);
|
|
1555
|
+
self.ui.selectionInfo.text(str);
|
|
1556
|
+
}
|
|
1557
|
+
self.fire('selectionChange', null, selection);
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
self.renderer.draw(self.ui.grid, null, function () {
|
|
1562
|
+
if (self.colConfigLock.isLocked()) {
|
|
1563
|
+
self.colConfigLock.unlock('renderer finished drawing');
|
|
1564
|
+
}
|
|
1565
|
+
self.setSelection();
|
|
1566
|
+
self.ui.exportBtn.attr('disabled', false);
|
|
1567
|
+
if (self.features.omnifilter) {
|
|
1568
|
+
self._applyOmnifilter();
|
|
1569
|
+
}
|
|
1570
|
+
self.tableDoneCont();
|
|
1571
|
+
});
|
|
1572
|
+
});
|
|
1573
|
+
};
|
|
1574
|
+
|
|
1575
|
+
// #_applyOmnifilter {{{2
|
|
1576
|
+
|
|
1577
|
+
/**
|
|
1578
|
+
* Apply the omnifilter to the currently rendered table. Hides all rows in the table body that do
|
|
1579
|
+
* not contain the search text in any cell. This is a visual-only operation and does not affect the
|
|
1580
|
+
* underlying data in the view.
|
|
1581
|
+
*
|
|
1582
|
+
* @method
|
|
1583
|
+
* @memberof Grid
|
|
1584
|
+
* @private
|
|
1585
|
+
*/
|
|
1586
|
+
|
|
1587
|
+
Grid.prototype._applyOmnifilter = function () {
|
|
1588
|
+
var self = this;
|
|
1589
|
+
|
|
1590
|
+
if (!self.features.omnifilter) {
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
var query = (self.ui.omnifilterInput.val() || '').toLowerCase();
|
|
1595
|
+
var tbody = self.ui.grid.find('tbody');
|
|
1596
|
+
|
|
1597
|
+
if (!tbody.length) {
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
// Show or hide the clear button based on whether there is text in the input.
|
|
1602
|
+
|
|
1603
|
+
if (query.length > 0) {
|
|
1604
|
+
self.ui.omnifilterClear.show();
|
|
1605
|
+
}
|
|
1606
|
+
else {
|
|
1607
|
+
self.ui.omnifilterClear.hide();
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
// Determine which column indices contain string data by inspecting the TH elements for the
|
|
1611
|
+
// data-wcdv-field-type attribute output by the renderer.
|
|
1612
|
+
|
|
1613
|
+
var stringColIndices = [];
|
|
1614
|
+
var thead = self.ui.grid.find('thead');
|
|
1615
|
+
|
|
1616
|
+
if (thead.length) {
|
|
1617
|
+
var ths = thead.find('tr:first th');
|
|
1618
|
+
|
|
1619
|
+
ths.each(function (i) {
|
|
1620
|
+
var fieldType = this.getAttribute('data-wcdv-field-type');
|
|
1621
|
+
|
|
1622
|
+
if (fieldType === 'string') {
|
|
1623
|
+
stringColIndices.push(i);
|
|
1624
|
+
}
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
// Iterate through all data rows (not "show more" rows) and toggle visibility based on whether
|
|
1629
|
+
// any cell text in the row contains the query string.
|
|
1630
|
+
|
|
1631
|
+
var even = false;
|
|
1632
|
+
var hasStringCols = stringColIndices.length > 0;
|
|
1633
|
+
|
|
1634
|
+
tbody.children('tr').each(function () {
|
|
1635
|
+
var tr = jQuery(this);
|
|
1636
|
+
|
|
1637
|
+
// Skip non-data rows (e.g. "show more rows" button).
|
|
1638
|
+
if (tr.hasClass('wcdvgrid_more')) {
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
if (query.length === 0) {
|
|
1643
|
+
tr.show();
|
|
1644
|
+
tr.removeClass('even odd').addClass(even ? 'even' : 'odd');
|
|
1645
|
+
even = !even;
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
var matched = false;
|
|
1650
|
+
var cells = this.children;
|
|
1651
|
+
var cellText;
|
|
1652
|
+
|
|
1653
|
+
if (hasStringCols) {
|
|
1654
|
+
for (var i = 0; i < stringColIndices.length; i++) {
|
|
1655
|
+
var cell = cells[stringColIndices[i]];
|
|
1656
|
+
|
|
1657
|
+
if (cell != null) {
|
|
1658
|
+
cellText = cell.textContent || cell.innerText || '';
|
|
1659
|
+
|
|
1660
|
+
if (cellText.toLowerCase().indexOf(query) >= 0) {
|
|
1661
|
+
matched = true;
|
|
1662
|
+
break;
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
else {
|
|
1668
|
+
// Fallback: if no string columns were identified, search the entire row.
|
|
1669
|
+
cellText = this.textContent || this.innerText || '';
|
|
1670
|
+
matched = cellText.toLowerCase().indexOf(query) >= 0;
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
if (matched) {
|
|
1674
|
+
tr.show();
|
|
1675
|
+
tr.removeClass('even odd').addClass(even ? 'even' : 'odd');
|
|
1676
|
+
even = !even;
|
|
1677
|
+
}
|
|
1678
|
+
else {
|
|
1679
|
+
tr.hide();
|
|
1680
|
+
}
|
|
1681
|
+
});
|
|
1682
|
+
};
|
|
1683
|
+
|
|
1684
|
+
// #refresh {{{2
|
|
1685
|
+
|
|
1686
|
+
/**
|
|
1687
|
+
* Refreshes the data from the data view in the grid.
|
|
1688
|
+
*
|
|
1689
|
+
* @method
|
|
1690
|
+
* @memberof Grid
|
|
1691
|
+
*/
|
|
1692
|
+
|
|
1693
|
+
Grid.prototype.refresh = function () {
|
|
1694
|
+
var self = this;
|
|
1695
|
+
|
|
1696
|
+
if (!self.isVisible()) {
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
self.logDebug(self.makeLogTag() + ' Refreshing...');
|
|
1701
|
+
|
|
1702
|
+
self._isIdle = false;
|
|
1703
|
+
self.view.refresh();
|
|
1704
|
+
};
|
|
1705
|
+
|
|
1706
|
+
// #clearRenderCache {{{2
|
|
1707
|
+
|
|
1708
|
+
/**
|
|
1709
|
+
* Clear the cache of the render on each cell.
|
|
1710
|
+
*/
|
|
1711
|
+
|
|
1712
|
+
Grid.prototype.clearRenderCache = function (cols) {
|
|
1713
|
+
var self = this;
|
|
1714
|
+
|
|
1715
|
+
if (self.renderer != null) {
|
|
1716
|
+
self.renderer.clearRenderCache(cols);
|
|
1717
|
+
}
|
|
1718
|
+
};
|
|
1719
|
+
|
|
1720
|
+
// #_updateRowCount {{{2
|
|
1721
|
+
|
|
1722
|
+
/**
|
|
1723
|
+
* Set the number of rows shown in the titlebar. You can provider the number yourself!
|
|
1724
|
+
*
|
|
1725
|
+
* @method
|
|
1726
|
+
* @memberof Grid
|
|
1727
|
+
*
|
|
1728
|
+
* @param {object} info
|
|
1729
|
+
* @param {number} info.numRows
|
|
1730
|
+
* @param {number} info.totalRows
|
|
1731
|
+
* @param {number} info.numGroups
|
|
1732
|
+
* @param {number} info.numPivots
|
|
1733
|
+
* @param {boolean} info.isPlain
|
|
1734
|
+
* @param {boolean} info.isGroup
|
|
1735
|
+
* @param {boolean} info.isPivot
|
|
1736
|
+
*
|
|
1737
|
+
* @param {object} ops
|
|
1738
|
+
* Describes what the view did.
|
|
1739
|
+
*
|
|
1740
|
+
* @param {boolean} ops.filter
|
|
1741
|
+
* If true, then the view filtered data.
|
|
1742
|
+
*
|
|
1743
|
+
* @param {boolean} ops.group
|
|
1744
|
+
* If true, then the view grouped data.
|
|
1745
|
+
*
|
|
1746
|
+
* @param {boolean} ops.pivot
|
|
1747
|
+
* If true, then the view pivotted data.
|
|
1748
|
+
*
|
|
1749
|
+
* @param {boolean} ops.sort
|
|
1750
|
+
* If true, then the view sorted data.
|
|
1751
|
+
*/
|
|
1752
|
+
|
|
1753
|
+
Grid.prototype._updateRowCount = function (info, ops) {
|
|
1754
|
+
var self = this;
|
|
1755
|
+
var doingServerFilter = getProp(self.defn, 'server', 'filter') && getProp(self.defn, 'server', 'limit') !== -1;
|
|
1756
|
+
var text = [];
|
|
1757
|
+
|
|
1758
|
+
self.logDebug(self.makeLogTag() + ' Updating row count');
|
|
1759
|
+
|
|
1760
|
+
// When there's no titlebar, there's nothing for us to do here.
|
|
1761
|
+
|
|
1762
|
+
if (!self.opts.title) {
|
|
1763
|
+
return;
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
self._hideSpinner();
|
|
1767
|
+
|
|
1768
|
+
if (info.numRows != null) {
|
|
1769
|
+
if (info.totalRows != null) {
|
|
1770
|
+
text.push(info.numRows + ' / ' + trans(info.totalRows === 1 ? 'GRID.TITLEBAR.RECORD_COUNT_SINGULAR' : 'GRID.TITLEBAR.RECORD_COUNT_PLURAL', info.totalRows));
|
|
1771
|
+
}
|
|
1772
|
+
else {
|
|
1773
|
+
text.push(trans(info.numRows === 1 ? 'GRID.TITLEBAR.RECORD_COUNT_SINGULAR' : 'GRID.TITLEBAR.RECORD_COUNT_PLURAL', info.numRows));
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
if (info.isGroup || info.isPivot) {
|
|
1778
|
+
text.push(trans(info.numGroups === 1 ? 'GRID.TITLEBAR.GROUP_COUNT_SINGULAR' : 'GRID.TITLEBAR.GROUP_COUNT_PLURAL', info.numGroups));
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
self.ui.rowCount.text(text.join(', '));
|
|
1782
|
+
|
|
1783
|
+
// When we have been auto-limited, show the banner message showing as much and prevent people from
|
|
1784
|
+
// grouping (because we don't have all the data, grouping / pivotting is misleading).
|
|
1785
|
+
|
|
1786
|
+
if (getProp(self.view, 'source', 'origin', 'isLimited')) {
|
|
1787
|
+
self.ui.autoLimit.show();
|
|
1788
|
+
self.ui.groupControl.hide();
|
|
1789
|
+
self.ui.toolbar_computedView.ui.storeMirageBtn.attr('disabled', true);
|
|
1790
|
+
}
|
|
1791
|
+
else {
|
|
1792
|
+
self.ui.autoLimit.hide();
|
|
1793
|
+
self.ui.groupControl.show();
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
if (self.ui.clearFilter) {
|
|
1797
|
+
if (info.totalRows) {
|
|
1798
|
+
self.ui.clearFilter.show();
|
|
1799
|
+
}
|
|
1800
|
+
else {
|
|
1801
|
+
self.ui.clearFilter.hide();
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
self.ui.title._addTrailing(',');
|
|
1806
|
+
};
|
|
1807
|
+
|
|
1808
|
+
// #hide {{{2
|
|
1809
|
+
|
|
1810
|
+
/**
|
|
1811
|
+
* Hide the grid.
|
|
1812
|
+
*
|
|
1813
|
+
* @method
|
|
1814
|
+
* @memberof Grid
|
|
1815
|
+
*/
|
|
1816
|
+
|
|
1817
|
+
Grid.prototype.hide = function () {
|
|
1818
|
+
var self = this;
|
|
1819
|
+
|
|
1820
|
+
self.logDebug(self.makeLogTag() + ' Hiding...');
|
|
1821
|
+
|
|
1822
|
+
self.ui.content.hide({
|
|
1823
|
+
duration: 0,
|
|
1824
|
+
done: function () {
|
|
1825
|
+
if (self.opts.title) {
|
|
1826
|
+
self.ui.showHideButton.removeClass('open');
|
|
1827
|
+
self.ui.showHideButton.children('svg.wcdv_icon').removeClass('wcdv_icon_rotate_180');
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
});
|
|
1831
|
+
};
|
|
1832
|
+
|
|
1833
|
+
// #show {{{2
|
|
1834
|
+
|
|
1835
|
+
/**
|
|
1836
|
+
* Make the grid visible. If the grid has not been "run" yet, it will be done now.
|
|
1837
|
+
*
|
|
1838
|
+
* @param {object} [opts]
|
|
1839
|
+
*
|
|
1840
|
+
* @param {boolean} [opts.redraw=true]
|
|
1841
|
+
* If true, automatically redraw the grid after it has been shown. This is almost always what you
|
|
1842
|
+
* want, unless you intend to manually call `redraw()` or `refresh()` immediately after showing it.
|
|
1843
|
+
*/
|
|
1844
|
+
|
|
1845
|
+
Grid.prototype.show = function (opts) {
|
|
1846
|
+
var self = this;
|
|
1847
|
+
|
|
1848
|
+
opts = deepDefaults(opts, {
|
|
1849
|
+
redraw: true
|
|
1850
|
+
});
|
|
1851
|
+
|
|
1852
|
+
self.logDebug(self.makeLogTag() + ' Showing...');
|
|
1853
|
+
|
|
1854
|
+
self.ui.content.show({
|
|
1855
|
+
duration: 0,
|
|
1856
|
+
done: function () {
|
|
1857
|
+
if (self.opts.title) {
|
|
1858
|
+
self.ui.showHideButton.addClass('open');
|
|
1859
|
+
self.ui.showHideButton.children('svg.wcdv_icon').addClass('wcdv_icon_rotate_180');
|
|
1860
|
+
}
|
|
1861
|
+
if (!self.hasRun && opts.redraw) {
|
|
1862
|
+
self.hasRun = true;
|
|
1863
|
+
self.redraw();
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
});
|
|
1867
|
+
};
|
|
1868
|
+
|
|
1869
|
+
// #toggle {{{2
|
|
1870
|
+
|
|
1871
|
+
/**
|
|
1872
|
+
* Toggle grid visibility.
|
|
1873
|
+
*/
|
|
1874
|
+
|
|
1875
|
+
Grid.prototype.toggle = function () {
|
|
1876
|
+
var self = this;
|
|
1877
|
+
|
|
1878
|
+
if (self.ui.content.css('display') === 'none') {
|
|
1879
|
+
self.show();
|
|
1880
|
+
}
|
|
1881
|
+
else {
|
|
1882
|
+
self.hide();
|
|
1883
|
+
}
|
|
1884
|
+
};
|
|
1885
|
+
|
|
1886
|
+
// #isVisible {{{2
|
|
1887
|
+
|
|
1888
|
+
/**
|
|
1889
|
+
* Determine if the grid is currently visible.
|
|
1890
|
+
*
|
|
1891
|
+
* @returns {boolean}
|
|
1892
|
+
* True if the grid is currently visible, false if it is not.
|
|
1893
|
+
*/
|
|
1894
|
+
|
|
1895
|
+
Grid.prototype.isVisible = function () {
|
|
1896
|
+
var self = this;
|
|
1897
|
+
|
|
1898
|
+
return self.ui.content.css('display') !== 'none';
|
|
1899
|
+
};
|
|
1900
|
+
|
|
1901
|
+
// hideControls {{{2
|
|
1902
|
+
|
|
1903
|
+
Grid.prototype.hideControls = function () {
|
|
1904
|
+
var self = this;
|
|
1905
|
+
|
|
1906
|
+
if (self.ui.controls._isHidden()) {
|
|
1907
|
+
return;
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
// We need this to happen after both of the async functions (to hide the
|
|
1911
|
+
// controls & toolbar) happen below.
|
|
1912
|
+
|
|
1913
|
+
var l = new Lock('Hide Controls', {start: 2});
|
|
1914
|
+
l.onUnlock(function () {
|
|
1915
|
+
if (window.Tabletool) {
|
|
1916
|
+
window.Tabletool.update();
|
|
1917
|
+
}
|
|
1918
|
+
}, 'Update Tabletool');
|
|
1919
|
+
|
|
1920
|
+
self.ui.controls.hide({
|
|
1921
|
+
duration: 0,
|
|
1922
|
+
complete: function () {
|
|
1923
|
+
self.fire(Grid.events.hideControls);
|
|
1924
|
+
l.unlock();
|
|
1925
|
+
}
|
|
1926
|
+
});
|
|
1927
|
+
|
|
1928
|
+
self.ui.toolbar.hide({
|
|
1929
|
+
duration: 0,
|
|
1930
|
+
complete: function () {
|
|
1931
|
+
//self.fire(Grid.events.hideToolbar);
|
|
1932
|
+
l.unlock();
|
|
1933
|
+
}
|
|
1934
|
+
});
|
|
1935
|
+
};
|
|
1936
|
+
|
|
1937
|
+
// showControls {{{2
|
|
1938
|
+
|
|
1939
|
+
Grid.prototype.showControls = function () {
|
|
1940
|
+
var self = this;
|
|
1941
|
+
|
|
1942
|
+
if (!self.ui.controls._isHidden()) {
|
|
1943
|
+
return;
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
// We need this to happen after both of the async functions (to show the
|
|
1947
|
+
// controls & toolbar) happen below.
|
|
1948
|
+
|
|
1949
|
+
var l = new Lock('Show Controls', {start: 2});
|
|
1950
|
+
l.onUnlock(function () {
|
|
1951
|
+
if (window.Tabletool) {
|
|
1952
|
+
window.Tabletool.update();
|
|
1953
|
+
}
|
|
1954
|
+
}, 'Update Tabletool');
|
|
1955
|
+
|
|
1956
|
+
self.ui.controls.show({
|
|
1957
|
+
duration: 0,
|
|
1958
|
+
complete: function () {
|
|
1959
|
+
self.fire(Grid.events.showControls);
|
|
1960
|
+
l.unlock();
|
|
1961
|
+
}
|
|
1962
|
+
});
|
|
1963
|
+
|
|
1964
|
+
self.ui.toolbar.show({
|
|
1965
|
+
duration: 0,
|
|
1966
|
+
complete: function () {
|
|
1967
|
+
//self.fire(Grid.events.showToolbar);
|
|
1968
|
+
l.unlock();
|
|
1969
|
+
}
|
|
1970
|
+
});
|
|
1971
|
+
};
|
|
1972
|
+
|
|
1973
|
+
// toggleControls {{{2
|
|
1974
|
+
|
|
1975
|
+
Grid.prototype.toggleControls = function () {
|
|
1976
|
+
var self = this;
|
|
1977
|
+
|
|
1978
|
+
if (self.ui.controls._isHidden()) {
|
|
1979
|
+
self.showControls();
|
|
1980
|
+
}
|
|
1981
|
+
else {
|
|
1982
|
+
self.hideControls();
|
|
1983
|
+
}
|
|
1984
|
+
};
|
|
1985
|
+
|
|
1986
|
+
// #_setSpinner {{{2
|
|
1987
|
+
|
|
1988
|
+
/**
|
|
1989
|
+
* Set the type of the spinner icon.
|
|
1990
|
+
*
|
|
1991
|
+
* @param {string} what
|
|
1992
|
+
* The kind of spinner icon to show. Must be one of: loading, not-loaded, working.
|
|
1993
|
+
*/
|
|
1994
|
+
|
|
1995
|
+
Grid.prototype._setSpinner = function (what) {
|
|
1996
|
+
var self = this;
|
|
1997
|
+
|
|
1998
|
+
switch (what) {
|
|
1999
|
+
case 'loading':
|
|
2000
|
+
self.ui.spinner.html(icon('refresh-cw', ['wcdv_icon_spin'], trans('GRID.TITLEBAR.LOADING')));
|
|
2001
|
+
break;
|
|
2002
|
+
case 'not-loaded':
|
|
2003
|
+
self.ui.spinner.html(icon('ban', null, trans('GRID.TITLEBAR.NOT_LOADED')));
|
|
2004
|
+
break;
|
|
2005
|
+
case 'working':
|
|
2006
|
+
self.ui.spinner.html(icon('loader-circle', ['wcdv_icon_spin'], trans('GRID.TITLEBAR.WORKING')));
|
|
2007
|
+
break;
|
|
2008
|
+
}
|
|
2009
|
+
};
|
|
2010
|
+
|
|
2011
|
+
// #_showSpinner {{{2
|
|
2012
|
+
|
|
2013
|
+
/**
|
|
2014
|
+
* Show the spinner icon.
|
|
2015
|
+
*/
|
|
2016
|
+
|
|
2017
|
+
Grid.prototype._showSpinner = function () {
|
|
2018
|
+
var self = this;
|
|
2019
|
+
|
|
2020
|
+
if (self.opts.title) {
|
|
2021
|
+
self.ui.spinner.show();
|
|
2022
|
+
}
|
|
2023
|
+
};
|
|
2024
|
+
|
|
2025
|
+
// #_hideSpinner {{{2
|
|
2026
|
+
|
|
2027
|
+
/**
|
|
2028
|
+
* Hide the spinner icon.
|
|
2029
|
+
*/
|
|
2030
|
+
|
|
2031
|
+
Grid.prototype._hideSpinner = function () {
|
|
2032
|
+
var self = this;
|
|
2033
|
+
|
|
2034
|
+
if (self.opts.title) {
|
|
2035
|
+
self.ui.spinner.hide();
|
|
2036
|
+
}
|
|
2037
|
+
};
|
|
2038
|
+
|
|
2039
|
+
// #_normalize {{{2
|
|
2040
|
+
|
|
2041
|
+
/**
|
|
2042
|
+
* The point of "normalizing" a definition is to expand shortcut configurations. For example, lots
|
|
2043
|
+
* of properties can be a string (the shortcut) or an object which contains the same info plus some
|
|
2044
|
+
* additional configuration. This function would convert the string into the object. This way,
|
|
2045
|
+
* later code only has to check for the object version. It also adds a layer of backwards
|
|
2046
|
+
* compatibility.
|
|
2047
|
+
*
|
|
2048
|
+
* You only need to normalize a definition once; after doing so, we flag it so we won't mess with it
|
|
2049
|
+
* again, even though it should be possible to normalize something that's already been done.
|
|
2050
|
+
*/
|
|
2051
|
+
|
|
2052
|
+
Grid.prototype._normalize = function (defn) {
|
|
2053
|
+
var self = this;
|
|
2054
|
+
|
|
2055
|
+
if (defn == null) {
|
|
2056
|
+
defn = {};
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
if (defn.normalized) {
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
defn.normalized = true;
|
|
2064
|
+
|
|
2065
|
+
deepDefaults(true, defn, {
|
|
2066
|
+
prefs: null,
|
|
2067
|
+
table: {
|
|
2068
|
+
groupMode: 'detail',
|
|
2069
|
+
rowMode: 'wrapped',
|
|
2070
|
+
features: {
|
|
2071
|
+
sort: true,
|
|
2072
|
+
filter: true,
|
|
2073
|
+
group: true,
|
|
2074
|
+
pivot: true,
|
|
2075
|
+
rowSelect: false,
|
|
2076
|
+
rowReorder: false,
|
|
2077
|
+
add: false,
|
|
2078
|
+
edit: false,
|
|
2079
|
+
delete: false,
|
|
2080
|
+
limit: true,
|
|
2081
|
+
floatingHeader: true,
|
|
2082
|
+
block: false,
|
|
2083
|
+
progress: false,
|
|
2084
|
+
columnResize: false,
|
|
2085
|
+
columnReorder: false,
|
|
2086
|
+
activeRow: false,
|
|
2087
|
+
omnifilter: true
|
|
2088
|
+
},
|
|
2089
|
+
limit: {
|
|
2090
|
+
appendBodyLast: false,
|
|
2091
|
+
method: 'more',
|
|
2092
|
+
threshold: 100,
|
|
2093
|
+
chunkSize: 50
|
|
2094
|
+
},
|
|
2095
|
+
floatingHeader: {
|
|
2096
|
+
method: 'tabletool'
|
|
2097
|
+
},
|
|
2098
|
+
incremental: {
|
|
2099
|
+
method: 'setTimeout',
|
|
2100
|
+
delay: 10,
|
|
2101
|
+
chunkSize: 100
|
|
2102
|
+
},
|
|
2103
|
+
activeRow: {
|
|
2104
|
+
slider: true
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
});
|
|
2108
|
+
|
|
2109
|
+
self._normalizeColumns(defn);
|
|
2110
|
+
|
|
2111
|
+
return defn;
|
|
2112
|
+
};
|
|
2113
|
+
|
|
2114
|
+
// #_normalizeColumns {{{2
|
|
2115
|
+
|
|
2116
|
+
Grid.prototype._normalizeColumns = function (defn) {
|
|
2117
|
+
var self = this;
|
|
2118
|
+
|
|
2119
|
+
// When the developer did not provider column configuration, take it from the ComputedView via typeInfo.
|
|
2120
|
+
// Potentially the source could change what fields it contains (e.g. add/remove a field to/from a
|
|
2121
|
+
// report) and this would all still work OK, we would stay up-to-date because every time the ComputedView
|
|
2122
|
+
// got new typeInfo we would update our colConfig.
|
|
2123
|
+
|
|
2124
|
+
if (getProp(defn, 'table', 'columns') == null) {
|
|
2125
|
+
self.initColConfig = null;
|
|
2126
|
+
self.colConfig = null;
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
var colConfig = new OrdMap();
|
|
2131
|
+
|
|
2132
|
+
for (var i = 0; i < defn.table.columns.length; i += 1) {
|
|
2133
|
+
var cc = defn.table.columns[i];
|
|
2134
|
+
|
|
2135
|
+
if (_.isString(cc)) {
|
|
2136
|
+
cc = { field: cc };
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2139
|
+
if (typeof cc.field !== 'string') {
|
|
2140
|
+
self.logWarning(self.makeLogTag() + ' Column Configuration: `field` must be a string');
|
|
2141
|
+
continue;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
cc = deepDefaults(cc, {
|
|
2145
|
+
hideMidnight: false,
|
|
2146
|
+
format_dateOnly: 'LL',
|
|
2147
|
+
allowHtml: false,
|
|
2148
|
+
allowFormatting: false,
|
|
2149
|
+
canHide: true
|
|
2150
|
+
});
|
|
2151
|
+
|
|
2152
|
+
colConfig.set(cc.field, cc);
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
self.initColConfig = colConfig.clone();
|
|
2156
|
+
|
|
2157
|
+
_.each(getPropDef([], defn, 'table', 'columnConfig'), function (cc, colName) {
|
|
2158
|
+
|
|
2159
|
+
// When you want to show a checkbox to represent the value, it only makes sense to have a
|
|
2160
|
+
// checkbox for the filter widget.
|
|
2161
|
+
|
|
2162
|
+
if (cc.widget === 'checkbox') {
|
|
2163
|
+
if (cc.filter !== undefined && cc.filter !== 'checkbox') {
|
|
2164
|
+
self.logWarning(self.makeLogTag() + ' Overriding configuration to use filter type "' + cc.filter + '" for checkbox widgets.');
|
|
2165
|
+
}
|
|
2166
|
+
cc.filter = 'checkbox';
|
|
2167
|
+
}
|
|
2168
|
+
});
|
|
2169
|
+
|
|
2170
|
+
self.setColConfig(colConfig, {
|
|
2171
|
+
from: 'defn',
|
|
2172
|
+
savePrefs: false
|
|
2173
|
+
});
|
|
2174
|
+
};
|
|
2175
|
+
|
|
2176
|
+
// #export {{{2
|
|
2177
|
+
|
|
2178
|
+
/**
|
|
2179
|
+
* Export whatever this grid is currently showing as a CSV file for the user to download.
|
|
2180
|
+
*/
|
|
2181
|
+
|
|
2182
|
+
Grid.prototype.export = function () {
|
|
2183
|
+
var self = this;
|
|
2184
|
+
|
|
2185
|
+
if (self.exportLock.isLocked()) {
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
if (self.csvReady) {
|
|
2190
|
+
var fileName = (self.opts.title || self.id) + '.csv';
|
|
2191
|
+
var csv = self.renderer.getCsv();
|
|
2192
|
+
var contentType = 'text/csv';
|
|
2193
|
+
var blob = new Blob([csv], {'type': contentType});
|
|
2194
|
+
|
|
2195
|
+
presentDownload(blob, fileName);
|
|
2196
|
+
}
|
|
2197
|
+
else {
|
|
2198
|
+
self.exportLock.lock(); // Unlocked in `csvReady` event handler.
|
|
2199
|
+
self.generateCsv = true;
|
|
2200
|
+
self.redraw();
|
|
2201
|
+
}
|
|
2202
|
+
};
|
|
2203
|
+
|
|
2204
|
+
// #_setExportStatus {{{2
|
|
2205
|
+
|
|
2206
|
+
Grid.prototype._setExportStatus = function (status) {
|
|
2207
|
+
var self = this;
|
|
2208
|
+
|
|
2209
|
+
switch (status) {
|
|
2210
|
+
case 'notReady':
|
|
2211
|
+
self.csvReady = false;
|
|
2212
|
+
self.ui.exportBtn.attr('title', trans('GRID.TITLEBAR.GENERATE_CSV'));
|
|
2213
|
+
self.ui.exportBtn.children('svg.wcdv_icon').remove();
|
|
2214
|
+
self.ui.exportBtn.append(icon('file'));
|
|
2215
|
+
break;
|
|
2216
|
+
case 'ready':
|
|
2217
|
+
self.csvReady = true;
|
|
2218
|
+
self.ui.exportBtn.attr('title', trans('GRID.TITLEBAR.DOWNLOAD_CSV'));
|
|
2219
|
+
self.ui.exportBtn.children('svg.wcdv_icon').remove();
|
|
2220
|
+
self.ui.exportBtn.append(icon('download'));
|
|
2221
|
+
break;
|
|
2222
|
+
default:
|
|
2223
|
+
throw new Error('Call Error: invalid status "' + status + '"');
|
|
2224
|
+
}
|
|
2225
|
+
};
|
|
2226
|
+
|
|
2227
|
+
// #setColConfig {{{2
|
|
2228
|
+
|
|
2229
|
+
/**
|
|
2230
|
+
* Set the column configuration.
|
|
2231
|
+
*
|
|
2232
|
+
* @param {OrdMap} colConfig
|
|
2233
|
+
* @param {Object} opts
|
|
2234
|
+
* @param {string} opts.from
|
|
2235
|
+
* @param {boolean} [opts.sendEvent=true]
|
|
2236
|
+
* @param {Array.<Object>} [opts.dontSendEventTo]
|
|
2237
|
+
* @param {boolean} [opts.redraw=true]
|
|
2238
|
+
* @param {boolean} [opts.savePrefs=true]
|
|
2239
|
+
*/
|
|
2240
|
+
|
|
2241
|
+
Grid.prototype.setColConfig = function (colConfig, opts) {
|
|
2242
|
+
var self = this;
|
|
2243
|
+
var updated = false;
|
|
2244
|
+
|
|
2245
|
+
if (['defn', 'prefs', 'typeInfo', 'ui', 'reset', 'autoResizeCols'].indexOf(opts.from) < 0) {
|
|
2246
|
+
throw new Error('Call Error: `opts.from` must be one of: [defn, prefs, typeInfo, ui, reset]');
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
opts = deepDefaults(opts, {
|
|
2250
|
+
sendEvent: true,
|
|
2251
|
+
dontSendEventTo: [],
|
|
2252
|
+
redraw: true,
|
|
2253
|
+
savePrefs: true
|
|
2254
|
+
});
|
|
2255
|
+
|
|
2256
|
+
// We use the colConfig lock so that we don't have a bunch of processes updating the colConfig
|
|
2257
|
+
// when we're trying to redraw the grid. If we already have a renderer, it's going to be get
|
|
2258
|
+
// replaced by `Grid#redraw()` so we shouldn't send an event to the renderer to have it redraw.
|
|
2259
|
+
|
|
2260
|
+
if (self.colConfigLock.isLocked() && self.renderer) {
|
|
2261
|
+
opts.dontSendEventTo.push(self.renderer);
|
|
2262
|
+
}
|
|
2263
|
+
|
|
2264
|
+
var setCurrent = function () {
|
|
2265
|
+
self.logDebug(self.makeLogTag('colConfig') + ' Setting from %s: %O', opts.from || '[unknown]', colConfig);
|
|
2266
|
+
self.colConfig = colConfig;
|
|
2267
|
+
self.colConfigSource = opts.from;
|
|
2268
|
+
|
|
2269
|
+
if (self.renderer != null) {
|
|
2270
|
+
self.renderer.colConfig = self.colConfig;
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
self.logDebug(self.makeLogTag('colConfig') + ' Setting shadow from %s: %O', opts.from || '[unknown]', colConfig);
|
|
2274
|
+
self.shadowColConfig = colConfig.clone();
|
|
2275
|
+
updated = true;
|
|
2276
|
+
};
|
|
2277
|
+
|
|
2278
|
+
var setInitial = function () {
|
|
2279
|
+
self.logDebug(self.makeLogTag('colConfig') + ' Setting initial from %s: %O', opts.from || '[unknown]', colConfig);
|
|
2280
|
+
self.initColConfig = colConfig.clone();
|
|
2281
|
+
};
|
|
2282
|
+
|
|
2283
|
+
/**
|
|
2284
|
+
* Add elements (that are absent in `dst`) from `src` to `dst`.
|
|
2285
|
+
*
|
|
2286
|
+
* @param {OrdMap} src
|
|
2287
|
+
* @param {string} srcMsg
|
|
2288
|
+
* @param {OrdMap} dst
|
|
2289
|
+
* @param {string} dstMsg
|
|
2290
|
+
*/
|
|
2291
|
+
|
|
2292
|
+
var addMissing = function (src, srcMsg, dst, dstMsg) {
|
|
2293
|
+
var count = dst.mergeWith(src);
|
|
2294
|
+
self.logDebug(self.makeLogTag('colConfig') + ' Merged %d fields from %s into %s', count, srcMsg, dstMsg);
|
|
2295
|
+
return count;
|
|
2296
|
+
};
|
|
2297
|
+
|
|
2298
|
+
/**
|
|
2299
|
+
* Remove elements from `dst` that are absent from `src`.
|
|
2300
|
+
*
|
|
2301
|
+
* @param {OrdMap} src
|
|
2302
|
+
* @param {string} srcMsg
|
|
2303
|
+
* @param {OrdMap} dst
|
|
2304
|
+
* @param {string} dstMsg
|
|
2305
|
+
*/
|
|
2306
|
+
|
|
2307
|
+
var removeMissing = function (src, srcMsg, dst, dstMsg) {
|
|
2308
|
+
var absent = [];
|
|
2309
|
+
|
|
2310
|
+
dst.each(function (fcc, fieldName) {
|
|
2311
|
+
if (!src.isSet(fieldName)) {
|
|
2312
|
+
absent.push(fieldName);
|
|
2313
|
+
}
|
|
2314
|
+
});
|
|
2315
|
+
|
|
2316
|
+
if (absent.length > 0) {
|
|
2317
|
+
self.logDebug(self.makeLogTag('colConfig') + ' Removing %d fields from %s which are absent from %s: %O',
|
|
2318
|
+
absent.length, dstMsg, srcMsg, absent);
|
|
2319
|
+
_.each(absent, function (fieldName) {
|
|
2320
|
+
dst.unset(fieldName);
|
|
2321
|
+
});
|
|
2322
|
+
return true;
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
return false;
|
|
2326
|
+
};
|
|
2327
|
+
|
|
2328
|
+
if (typeof getProp(self.defn, 'advice', 'setColConfig', 'before') === 'function') {
|
|
2329
|
+
self.defn.advice.setColConfig.before(colConfig, opts.from, self);
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
switch (opts.from) {
|
|
2333
|
+
case 'defn':
|
|
2334
|
+
setCurrent();
|
|
2335
|
+
setInitial();
|
|
2336
|
+
self.colConfigRestricted = true;
|
|
2337
|
+
break;
|
|
2338
|
+
case 'prefs':
|
|
2339
|
+
if (self.colConfigRestricted) {
|
|
2340
|
+
self.colConfig.each(function (v, k) {
|
|
2341
|
+
if (colConfig.isSet(k)) {
|
|
2342
|
+
_.defaults(colConfig.get(k), v);
|
|
2343
|
+
}
|
|
2344
|
+
});
|
|
2345
|
+
|
|
2346
|
+
// The column configuration is restricted by defn, so remove anything from prefs that's
|
|
2347
|
+
// missing from defn.
|
|
2348
|
+
|
|
2349
|
+
removeMissing(self.colConfig, 'defn', colConfig, 'prefs');
|
|
2350
|
+
|
|
2351
|
+
// Add anything that's in defn but not in prefs.
|
|
2352
|
+
|
|
2353
|
+
addMissing(self.colConfig, 'defn', colConfig, 'prefs');
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
setCurrent();
|
|
2357
|
+
break;
|
|
2358
|
+
case 'reset':
|
|
2359
|
+
case 'ui':
|
|
2360
|
+
case 'autoResizeCols':
|
|
2361
|
+
setCurrent();
|
|
2362
|
+
break;
|
|
2363
|
+
case 'typeInfo':
|
|
2364
|
+
// Column configuration derived from typeInfo merges with existing config (by removing config on
|
|
2365
|
+
// columns that don't exist in the source, and by adding defaults for columns that exist in the
|
|
2366
|
+
// source but aren't specified in the current config). It can also set the initial, filling in
|
|
2367
|
+
// when no defn is specified.
|
|
2368
|
+
|
|
2369
|
+
if (self.colConfig == null) {
|
|
2370
|
+
setCurrent();
|
|
2371
|
+
}
|
|
2372
|
+
else {
|
|
2373
|
+
self.colConfig = self.shadowColConfig.clone();
|
|
2374
|
+
if (self.renderer != null) {
|
|
2375
|
+
self.renderer.colConfig = self.colConfig;
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
// Delete fields from existing colConfig which aren't in the source.
|
|
2379
|
+
|
|
2380
|
+
if (removeMissing(colConfig, 'source', self.colConfig, 'existing')) {
|
|
2381
|
+
updated = true;
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
// Add fields from source that are missing from existing colConfig. Columns set explicitly in
|
|
2385
|
+
// the grid's definition are there to limit what we see, so don't try to add to them.
|
|
2386
|
+
|
|
2387
|
+
if (!self.colConfigRestricted) {
|
|
2388
|
+
if (addMissing(colConfig, 'source', self.colConfig, 'existing')) {
|
|
2389
|
+
updated = true;
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
if (self.initColConfig == null) {
|
|
2394
|
+
setInitial();
|
|
2395
|
+
}
|
|
2396
|
+
break;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
if (!updated) {
|
|
2400
|
+
return;
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
if (opts.savePrefs) {
|
|
2404
|
+
self.prefs.save();
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
if (opts.sendEvent) {
|
|
2408
|
+
self.fire('colConfigUpdate', {
|
|
2409
|
+
notTo: opts.dontSendEventTo
|
|
2410
|
+
}, self.colConfig, self.initColConfig, ['autoResizeCols'].indexOf(opts.from) >= 0 ? false : true);
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
if (opts.redraw) {
|
|
2414
|
+
//self.redraw();
|
|
2415
|
+
}
|
|
2416
|
+
};
|
|
2417
|
+
|
|
2418
|
+
// #getColConfig {{{2
|
|
2419
|
+
|
|
2420
|
+
Grid.prototype.getColConfig = function (colConfig) {
|
|
2421
|
+
var self = this;
|
|
2422
|
+
|
|
2423
|
+
return self.colConfig;
|
|
2424
|
+
};
|
|
2425
|
+
|
|
2426
|
+
// #resetColConfig {{{2
|
|
2427
|
+
|
|
2428
|
+
Grid.prototype.resetColConfig = function (opts) {
|
|
2429
|
+
var self = this;
|
|
2430
|
+
|
|
2431
|
+
self.logDebug(self.makeLogTag('colConfig') + ' Resetting to: %O', self.initColConfig);
|
|
2432
|
+
|
|
2433
|
+
opts = deepDefaults(opts, {
|
|
2434
|
+
from: 'reset',
|
|
2435
|
+
savePrefs: false
|
|
2436
|
+
});
|
|
2437
|
+
|
|
2438
|
+
self.setColConfig(self.initColConfig.clone(), opts);
|
|
2439
|
+
};
|
|
2440
|
+
|
|
2441
|
+
// #setRowMode {{{2
|
|
2442
|
+
|
|
2443
|
+
/**
|
|
2444
|
+
* Set the row display mode for the grid.
|
|
2445
|
+
*
|
|
2446
|
+
* @param {string} mode
|
|
2447
|
+
* The row mode to use. Must be either "wrapped" (default) or "clipped".
|
|
2448
|
+
* - "wrapped": Cells can wrap text to multiple lines (default behavior)
|
|
2449
|
+
* - "clipped": Single-line cells with text truncated (similar to AG Grid)
|
|
2450
|
+
*/
|
|
2451
|
+
|
|
2452
|
+
Grid.prototype.setRowMode = function (mode) {
|
|
2453
|
+
var self = this;
|
|
2454
|
+
|
|
2455
|
+
if (['wrapped', 'clipped'].indexOf(mode) < 0) {
|
|
2456
|
+
self.logWarn(self.makeLogTag('setRowMode') + ' Invalid row mode "' + mode + '". Using "wrapped" as default.');
|
|
2457
|
+
mode = 'wrapped';
|
|
2458
|
+
}
|
|
2459
|
+
|
|
2460
|
+
self.defn.table.rowMode = mode;
|
|
2461
|
+
self.logDebug(self.makeLogTag('setRowMode') + ' Setting row mode to: %s', mode);
|
|
2462
|
+
|
|
2463
|
+
// Update the CSS class on the grid table container
|
|
2464
|
+
if (self.ui && self.ui.grid) {
|
|
2465
|
+
self.ui.grid.removeClass('wcdv_row_mode_wrapped wcdv_row_mode_clipped');
|
|
2466
|
+
self.ui.grid.addClass('wcdv_row_mode_' + mode);
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2469
|
+
// Fire an event for any listeners
|
|
2470
|
+
self.fire('rowModeChange', mode);
|
|
2471
|
+
};
|
|
2472
|
+
|
|
2473
|
+
// #getRowMode {{{2
|
|
2474
|
+
|
|
2475
|
+
/**
|
|
2476
|
+
* Get the current row display mode for the grid.
|
|
2477
|
+
*
|
|
2478
|
+
* @returns {string} The current row mode ("wrapped" or "clipped").
|
|
2479
|
+
*/
|
|
2480
|
+
|
|
2481
|
+
Grid.prototype.getRowMode = function () {
|
|
2482
|
+
var self = this;
|
|
2483
|
+
|
|
2484
|
+
return getPropDef('wrapped', self.defn, 'table', 'rowMode');
|
|
2485
|
+
};
|
|
2486
|
+
|
|
2487
|
+
// #isIdle {{{2
|
|
2488
|
+
|
|
2489
|
+
/**
|
|
2490
|
+
* Ask the grid whether there are currently any pending operations that would change the UI.
|
|
2491
|
+
*
|
|
2492
|
+
* Caveats:
|
|
2493
|
+
*
|
|
2494
|
+
* - If you yield after checking this, then it's no longer guaranteed to be true; some other
|
|
2495
|
+
* asynchronous event could cause the grid to become active.
|
|
2496
|
+
*
|
|
2497
|
+
* - If you have `renderEnd` event handlers that yield, it is possible that those event handlers
|
|
2498
|
+
* will continue executing after the grid has been marked idle.
|
|
2499
|
+
*
|
|
2500
|
+
* @returns {boolean} True if the grid is currently idle, false if there are changes pending which
|
|
2501
|
+
* might cause the grid to be redrawn.
|
|
2502
|
+
*/
|
|
2503
|
+
|
|
2504
|
+
Grid.prototype.isIdle = function () {
|
|
2505
|
+
var self = this;
|
|
2506
|
+
|
|
2507
|
+
return self._isIdle;
|
|
2508
|
+
};
|
|
2509
|
+
|
|
2510
|
+
// #colConfigFromTypeInfo {{{2
|
|
2511
|
+
|
|
2512
|
+
Grid.prototype.colConfigFromTypeInfo = function (typeInfo, opts) {
|
|
2513
|
+
var self = this;
|
|
2514
|
+
|
|
2515
|
+
opts = deepDefaults(opts, {
|
|
2516
|
+
from: 'typeInfo',
|
|
2517
|
+
savePrefs: false
|
|
2518
|
+
});
|
|
2519
|
+
|
|
2520
|
+
if (!(typeInfo instanceof OrdMap)) {
|
|
2521
|
+
throw new Error('Call Error: `typeInfo` must be an OrdMap');
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2524
|
+
var typeInfoColConfig = new OrdMap();
|
|
2525
|
+
|
|
2526
|
+
typeInfo.each(function (fti, fieldName) {
|
|
2527
|
+
typeInfoColConfig.set(fieldName, {
|
|
2528
|
+
field: fieldName
|
|
2529
|
+
});
|
|
2530
|
+
});
|
|
2531
|
+
|
|
2532
|
+
self.logDebug(self.makeLogTag() + ' Creating colConfig from typeInfo: %O -> %O', typeInfo.asMap(), typeInfoColConfig.asMap());
|
|
2533
|
+
|
|
2534
|
+
//self.setColConfig(self.colConfig == null
|
|
2535
|
+
// ? typeInfoColConfig
|
|
2536
|
+
// : OrdMap.fromMerge([self.colConfig, typeInfoColConfig]), opts);
|
|
2537
|
+
self.setColConfig(typeInfoColConfig, opts);
|
|
2538
|
+
};
|
|
2539
|
+
|
|
2540
|
+
// #setOperations {{{2
|
|
2541
|
+
|
|
2542
|
+
Grid.prototype.setOperations = function (ops) {
|
|
2543
|
+
var self = this;
|
|
2544
|
+
|
|
2545
|
+
if (self.operationsPalette != null) {
|
|
2546
|
+
self.operationsPalette.setOperations(ops);
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
self.defn.operations = ops;
|
|
2550
|
+
|
|
2551
|
+
// We need to redraw the grid because operations that affect one row at a time might change,
|
|
2552
|
+
// therefore the buttons in the row need to be redrawn.
|
|
2553
|
+
|
|
2554
|
+
self.redraw();
|
|
2555
|
+
};
|
|
2556
|
+
|
|
2557
|
+
// #makeResponsive {{{2
|
|
2558
|
+
|
|
2559
|
+
Grid.prototype.makeResponsive = function () {
|
|
2560
|
+
var self = this;
|
|
2561
|
+
|
|
2562
|
+
if (window.ResizeObserver == null) {
|
|
2563
|
+
self.logWarning(self.makeLogTag() + ' ResizeObserver is not supported; grid will not be responsive.');
|
|
2564
|
+
return;
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
var timer;
|
|
2568
|
+
|
|
2569
|
+
// We use a timer to create a delay, so the page has to be "still" for 500ms before we'll try to
|
|
2570
|
+
// redraw the grid with a different renderer.
|
|
2571
|
+
|
|
2572
|
+
self.resizeObserver = new ResizeObserver(function (elts) {
|
|
2573
|
+
if (timer != null) {
|
|
2574
|
+
clearTimeout(timer);
|
|
2575
|
+
}
|
|
2576
|
+
timer = setTimeout(function () {
|
|
2577
|
+
timer = null;
|
|
2578
|
+
var renderer = self.findRenderer(elts[0].contentRect.width, self.mode);
|
|
2579
|
+
if (renderer.id !== self.rendererId) {
|
|
2580
|
+
self.logDebug(self.makeLogTag() + ' Resized to ' + elts[0].contentRect.width + '; using renderer: %O', renderer);
|
|
2581
|
+
self.redraw();
|
|
2582
|
+
}
|
|
2583
|
+
}, 500);
|
|
2584
|
+
});
|
|
2585
|
+
|
|
2586
|
+
self.resizeObserver.observe(self.ui.root.get(0));
|
|
2587
|
+
};
|
|
2588
|
+
|
|
2589
|
+
// #addRenderer {{{2
|
|
2590
|
+
|
|
2591
|
+
/**
|
|
2592
|
+
* @typedef RendererSpec
|
|
2593
|
+
* Either `name` or `fn` must exist.
|
|
2594
|
+
*
|
|
2595
|
+
* @prop {string} [name]
|
|
2596
|
+
* Name of the renderer to use; must be registered in {@see GridRenderer.registry}.
|
|
2597
|
+
*
|
|
2598
|
+
* @prop {function} [fn]
|
|
2599
|
+
* A nullary function that returns the name of the name of a renderer registered in
|
|
2600
|
+
* {@see GridRenderer.registry}.
|
|
2601
|
+
*
|
|
2602
|
+
* @prop {object} [opts]
|
|
2603
|
+
* Additional options to pass to the renderer contructor.
|
|
2604
|
+
*/
|
|
2605
|
+
|
|
2606
|
+
/**
|
|
2607
|
+
* Adds a new renderer to the grid.
|
|
2608
|
+
*
|
|
2609
|
+
* @param {number} minWidth
|
|
2610
|
+
* The minimum width at which this renderer will work.
|
|
2611
|
+
*
|
|
2612
|
+
* @param {string[]} modes
|
|
2613
|
+
* List of modes for which this renderer will work.
|
|
2614
|
+
*
|
|
2615
|
+
* @param {RendererSpec} renderer
|
|
2616
|
+
* Specification of the renderer to add.
|
|
2617
|
+
*/
|
|
2618
|
+
|
|
2619
|
+
Grid.prototype.addRenderer = (function () {
|
|
2620
|
+
var id = 1;
|
|
2621
|
+
|
|
2622
|
+
return function (minWidth, modes, renderer) {
|
|
2623
|
+
var self = this
|
|
2624
|
+
, i;
|
|
2625
|
+
|
|
2626
|
+
renderer.id = 'CUSTOM.' + id++;
|
|
2627
|
+
|
|
2628
|
+
for (i = 0; i < self.widthBreaks.length; i += 1) {
|
|
2629
|
+
if (minWidth < self.widthBreaks[i].minWidth) {
|
|
2630
|
+
// Insert at the appropriate place in the list.
|
|
2631
|
+
|
|
2632
|
+
self.widthBreaks.splice(i, 0, {
|
|
2633
|
+
minWidth: minWidth,
|
|
2634
|
+
modes: modes,
|
|
2635
|
+
renderer: renderer
|
|
2636
|
+
});
|
|
2637
|
+
|
|
2638
|
+
return;
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
// New entry has the largest minWidth in the list, put it at the end.
|
|
2643
|
+
|
|
2644
|
+
self.widthBreaks.splice(-1, 0, {
|
|
2645
|
+
minWidth: minWidth,
|
|
2646
|
+
modes: modes,
|
|
2647
|
+
renderer: renderer
|
|
2648
|
+
});
|
|
2649
|
+
};
|
|
2650
|
+
})();
|
|
2651
|
+
|
|
2652
|
+
// #clearRenderers {{{2
|
|
2653
|
+
|
|
2654
|
+
/**
|
|
2655
|
+
* Completely clears all grid renderers.
|
|
2656
|
+
*/
|
|
2657
|
+
|
|
2658
|
+
Grid.prototype.clearRenderers = function () {
|
|
2659
|
+
var self = this;
|
|
2660
|
+
|
|
2661
|
+
self.widthBreaks = [];
|
|
2662
|
+
};
|
|
2663
|
+
|
|
2664
|
+
// #resetRenderers {{{2
|
|
2665
|
+
|
|
2666
|
+
/**
|
|
2667
|
+
* Resets the list of grid renderers to the initial state.
|
|
2668
|
+
*/
|
|
2669
|
+
|
|
2670
|
+
Grid.prototype.resetRenderers = function () {
|
|
2671
|
+
var self = this;
|
|
2672
|
+
|
|
2673
|
+
self.widthBreaks = [{
|
|
2674
|
+
minWidth: 1024,
|
|
2675
|
+
modes: ['plain'],
|
|
2676
|
+
renderer: {
|
|
2677
|
+
name: 'table_plain',
|
|
2678
|
+
opts: getPropDef({}, self.defn, 'table', 'whenPlain')
|
|
2679
|
+
}
|
|
2680
|
+
}, {
|
|
2681
|
+
minWidth: 1024,
|
|
2682
|
+
modes: ['group'],
|
|
2683
|
+
renderer: {
|
|
2684
|
+
fn: function () {
|
|
2685
|
+
switch (self.defn.table.groupMode) {
|
|
2686
|
+
case 'summary':
|
|
2687
|
+
return {
|
|
2688
|
+
name: 'table_group_summary',
|
|
2689
|
+
opts: getPropDef({}, self.defn, 'table', 'whenGroup')
|
|
2690
|
+
};
|
|
2691
|
+
case 'detail':
|
|
2692
|
+
return {
|
|
2693
|
+
name: 'table_group_detail',
|
|
2694
|
+
opts: getPropDef({}, self.defn, 'table', 'whenGroup')
|
|
2695
|
+
};
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
}
|
|
2699
|
+
}, {
|
|
2700
|
+
minWidth: 1024,
|
|
2701
|
+
modes: ['pivot'],
|
|
2702
|
+
renderer: {
|
|
2703
|
+
name: 'table_pivot',
|
|
2704
|
+
opts: getPropDef({}, self.defn, 'table', 'whenPivot')
|
|
2705
|
+
}
|
|
2706
|
+
}];
|
|
2707
|
+
};
|
|
2708
|
+
|
|
2709
|
+
// #findRenderer {{{2
|
|
2710
|
+
|
|
2711
|
+
/**
|
|
2712
|
+
* Find a renderer suitable for drawing the grid. A "suitable" renderer is one that (1) can handle
|
|
2713
|
+
* the data `mode`, and (2) has a `minWidth` property less than the current width. If no such
|
|
2714
|
+
* renderer exists, we pick one that can handle the data, at the smallest `minWidth` available. If
|
|
2715
|
+
* there still aren't any renderers available (e.g. if the developer cleared the list) then null is
|
|
2716
|
+
* returned.
|
|
2717
|
+
*
|
|
2718
|
+
* @param {number} width
|
|
2719
|
+
* The width of the grid.
|
|
2720
|
+
*
|
|
2721
|
+
* @param {string} mode
|
|
2722
|
+
* What type of data we're displaying. Must be one of: plain, group, pivot.
|
|
2723
|
+
*
|
|
2724
|
+
* @returns {RendererSpec}
|
|
2725
|
+
* A renderer that can be used to display the grid. Returns null if there aren't any options.
|
|
2726
|
+
*/
|
|
2727
|
+
|
|
2728
|
+
Grid.prototype.findRenderer = function (width, mode) {
|
|
2729
|
+
var self = this,
|
|
2730
|
+
i, b;
|
|
2731
|
+
|
|
2732
|
+
var processRenderer = function (r) {
|
|
2733
|
+
var x = deepCopy(r);
|
|
2734
|
+
delete x.fn;
|
|
2735
|
+
|
|
2736
|
+
// If the `fn` property exists, call it to get properties that can override (or supplement)
|
|
2737
|
+
// those in the "main" object. This is how you can set the group renderer depending on whether
|
|
2738
|
+
// the grid is in summary or details mode.
|
|
2739
|
+
|
|
2740
|
+
if (typeof r.fn === 'function') {
|
|
2741
|
+
var spec = r.fn();
|
|
2742
|
+
x = deepDefaults(spec, x);
|
|
2743
|
+
}
|
|
2744
|
+
|
|
2745
|
+
return x;
|
|
2746
|
+
};
|
|
2747
|
+
|
|
2748
|
+
if (self.widthBreaks == null || self.widthBreaks.length === 0) {
|
|
2749
|
+
return null;
|
|
2750
|
+
}
|
|
2751
|
+
|
|
2752
|
+
// Find the entry with the largest minWidth that's still less than the current width, which also
|
|
2753
|
+
// supports the mode we're currently in.
|
|
2754
|
+
|
|
2755
|
+
for (i = self.widthBreaks.length - 1; i >= 0; i -= 1) {
|
|
2756
|
+
b = self.widthBreaks[i];
|
|
2757
|
+
if (b.minWidth <= width && (b.modes == null || b.modes.indexOf(mode) >= 0)) {
|
|
2758
|
+
return processRenderer(b.renderer);
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
// There aren't any renderers with a minWidth less than the current width; start at the bottom and
|
|
2763
|
+
// find the smallest that can handle the data.
|
|
2764
|
+
|
|
2765
|
+
for (i = 0; i < self.widthBreaks.length; i += 1) {
|
|
2766
|
+
b = self.widthBreaks[i];
|
|
2767
|
+
if (b.modes == null || b.modes.indexOf(mode) >= 0) {
|
|
2768
|
+
return processRenderer(b.renderer);
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
};
|
|
2772
|
+
|
|
2773
|
+
// Exports {{{1
|
|
2774
|
+
|
|
2775
|
+
export {
|
|
2776
|
+
Grid
|
|
2777
|
+
};
|