datavis-glide 4.0.0-PRE.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +45 -0
- package/README.md +129 -0
- package/datavis.js +101 -0
- package/dist/wcdatavis.css +1957 -0
- package/dist/wcdatavis.min.js +1 -0
- package/global-jquery.js +4 -0
- package/ie-fixes.js +13 -0
- package/index.js +70 -0
- package/meteor.js +1 -0
- package/package.json +102 -0
- package/src/flags.js +6 -0
- package/src/graph.js +1079 -0
- package/src/graph_renderer.js +85 -0
- package/src/grid.js +2777 -0
- package/src/grid_control.js +1957 -0
- package/src/grid_filter.js +1073 -0
- package/src/grid_renderer.js +276 -0
- package/src/group_fun_win.js +121 -0
- package/src/lang/en-US.js +188 -0
- package/src/lang/es-MX.js +188 -0
- package/src/lang/fr-FR.js +188 -0
- package/src/lang/id-ID.js +188 -0
- package/src/lang/nl-NL.js +188 -0
- package/src/lang/pt-BR.js +188 -0
- package/src/lang/ru-RU.js +188 -0
- package/src/lang/th-TH.js +188 -0
- package/src/lang/vi-VN.js +188 -0
- package/src/lang/zh-Hans-CN.js +188 -0
- package/src/operations_palette.js +176 -0
- package/src/prefs_modules.js +132 -0
- package/src/reg/graph_renderer.js +17 -0
- package/src/renderers/graph/chartjs.js +457 -0
- package/src/renderers/graph/google.js +584 -0
- package/src/renderers/graph/jit.js +61 -0
- package/src/renderers/graph/svelte-gantt.js +168 -0
- package/src/renderers/grid/dummy.js +79 -0
- package/src/renderers/grid/handlebars.js +217 -0
- package/src/renderers/grid/squirrelly.js +215 -0
- package/src/renderers/grid/table/group_detail.js +1404 -0
- package/src/renderers/grid/table/group_summary.js +380 -0
- package/src/renderers/grid/table/pivot.js +915 -0
- package/src/renderers/grid/table/plain.js +1592 -0
- package/src/renderers/grid/table.js +2510 -0
- package/src/trans.js +101 -0
- package/src/ui/collapsible.js +234 -0
- package/src/ui/filters/date.js +283 -0
- package/src/ui/grid_filter.js +398 -0
- package/src/ui/popup_menu.js +224 -0
- package/src/ui/popup_window.js +572 -0
- package/src/ui/slider.js +156 -0
- package/src/ui/tabs.js +202 -0
- package/src/ui/templates.js +131 -0
- package/src/ui/toolbar.js +63 -0
- package/src/ui/toolbars/grid.js +873 -0
- package/src/ui/windows/col_config.js +341 -0
- package/src/ui/windows/debug.js +164 -0
- package/src/ui/windows/grid_table_opts.js +139 -0
- package/src/util/handlebars.js +158 -0
- package/src/util/jquery.js +630 -0
- package/src/util/misc.js +1058 -0
- package/src/util/squirrelly.js +155 -0
- package/wcdatavis.css +1601 -0
|
@@ -0,0 +1,1957 @@
|
|
|
1
|
+
import _ from 'underscore';
|
|
2
|
+
|
|
3
|
+
import jQuery from 'jquery';
|
|
4
|
+
|
|
5
|
+
import { trans } from './trans.js';
|
|
6
|
+
import {
|
|
7
|
+
deepCopy,
|
|
8
|
+
deepDefaults,
|
|
9
|
+
determineColumns,
|
|
10
|
+
icon,
|
|
11
|
+
gensym,
|
|
12
|
+
getProp,
|
|
13
|
+
makeSubclass,
|
|
14
|
+
mapLimit,
|
|
15
|
+
mixinEventHandling,
|
|
16
|
+
mixinLogging,
|
|
17
|
+
objFromArray,
|
|
18
|
+
} from './util/misc.js';
|
|
19
|
+
|
|
20
|
+
import './util/jquery.js';
|
|
21
|
+
import {AGGREGATE_REGISTRY, ComputedView, GROUP_FUNCTION_REGISTRY, types} from 'datavis-ace';
|
|
22
|
+
import {Grid} from './grid.js';
|
|
23
|
+
import {GridFilterSet} from './grid_filter.js';
|
|
24
|
+
import {GroupFunWin} from './group_fun_win.js';
|
|
25
|
+
import {PopupWindow} from './ui/popup_window.js';
|
|
26
|
+
|
|
27
|
+
/*
|
|
28
|
+
* Grid controls are the rounded boxes that appear between the toolbar and the grid. They allow
|
|
29
|
+
* dynamic configuration of the view to which the grid is bound.
|
|
30
|
+
*
|
|
31
|
+
* - Filters
|
|
32
|
+
* - Group Fields
|
|
33
|
+
* - Pivot Fields
|
|
34
|
+
* - Aggregates
|
|
35
|
+
*
|
|
36
|
+
* Each control is basically a list of things that have been added to it, e.g. for grouping, it's a
|
|
37
|
+
* list of fields to group by. Internally, the control is an instance of a subclass of GridControl,
|
|
38
|
+
* and the items are corresponding instances of a subclass of GridControlField. The name "Field"
|
|
39
|
+
* here is historical, before aggregates were specified this way, all controls managed fields from
|
|
40
|
+
* the source data.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
var GRID_CONTROL_FIELD_POOL = {};
|
|
44
|
+
|
|
45
|
+
// GridControlField {{{1
|
|
46
|
+
|
|
47
|
+
// Constructor {{{2
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Create a new GridControlField instance.
|
|
51
|
+
*
|
|
52
|
+
* @param {GridControl} control
|
|
53
|
+
*
|
|
54
|
+
* @param {string} field
|
|
55
|
+
*
|
|
56
|
+
* @param {string} displayText
|
|
57
|
+
*
|
|
58
|
+
* @param {object} colConfig
|
|
59
|
+
*
|
|
60
|
+
* @class
|
|
61
|
+
*
|
|
62
|
+
* Represents an individual field added to a control. In an older iteration, this literally
|
|
63
|
+
* corresponded to a field in the data (e.g. because the control was a filter, group, or pivot).
|
|
64
|
+
* Now that aggregate functions are also managed through a GridControl subclass, the "field" name is
|
|
65
|
+
* no longer strictly accurate.
|
|
66
|
+
*
|
|
67
|
+
* @property {GridControl} control
|
|
68
|
+
*
|
|
69
|
+
* @property {string|object} spec
|
|
70
|
+
* If a string, simply the field to add. If an object, should contain a `field` property along with
|
|
71
|
+
* anything else that this instance needs to carry.
|
|
72
|
+
*
|
|
73
|
+
* @property {string} displayText
|
|
74
|
+
*
|
|
75
|
+
* @property {object} colConfig
|
|
76
|
+
*
|
|
77
|
+
* @property {object} [opts]
|
|
78
|
+
*
|
|
79
|
+
* @property {object} ui
|
|
80
|
+
* Refers to all user interface constructs that we might need to use later.
|
|
81
|
+
*
|
|
82
|
+
* @property {Element} ui.root
|
|
83
|
+
* The DIV that completely contains the control field.
|
|
84
|
+
*
|
|
85
|
+
* @property {Element} ui.removeButton
|
|
86
|
+
* A button that is used to remove the control field.
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
var GridControlField = (function () {
|
|
90
|
+
var CONTROL_FIELD_ID = 0;
|
|
91
|
+
return makeSubclass('GridControlField', Object, function (control, spec, displayText, colConfig, opts) {
|
|
92
|
+
var self = this;
|
|
93
|
+
|
|
94
|
+
self.control = control;
|
|
95
|
+
|
|
96
|
+
if (typeof spec === 'string') {
|
|
97
|
+
self.field = {
|
|
98
|
+
field: spec
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
self.field = deepCopy(spec);
|
|
103
|
+
}
|
|
104
|
+
self.displayText = displayText;
|
|
105
|
+
self.colConfig = colConfig;
|
|
106
|
+
self.opts = opts;
|
|
107
|
+
|
|
108
|
+
self.fti = self.control.typeInfo.get(self.field.field);
|
|
109
|
+
|
|
110
|
+
self.ui = {};
|
|
111
|
+
self.id = CONTROL_FIELD_ID++;
|
|
112
|
+
});
|
|
113
|
+
})();
|
|
114
|
+
|
|
115
|
+
mixinLogging(GridControlField);
|
|
116
|
+
|
|
117
|
+
// #draw {{{2
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Renders the control field into a DIV.
|
|
121
|
+
*
|
|
122
|
+
* @returns {Element}
|
|
123
|
+
* A newly created DIV that contains everything needed by the control field.
|
|
124
|
+
*/
|
|
125
|
+
|
|
126
|
+
GridControlField.prototype.draw = function () {
|
|
127
|
+
var self = this;
|
|
128
|
+
var label = self.displayText || (self.colConfig && self.colConfig.displayText) || self.field.field;
|
|
129
|
+
|
|
130
|
+
self.ui.removeButton = jQuery('<button>', {'type': 'button'})
|
|
131
|
+
.append(icon('square-minus'))
|
|
132
|
+
.attr('title', trans('GRID_CONTROL.FIELD.REMOVE'))
|
|
133
|
+
.addClass('wcdv_icon_button wcdv_remove wcdv_text-primary')
|
|
134
|
+
.on('click', function () {
|
|
135
|
+
self.control.removeField(self);
|
|
136
|
+
})
|
|
137
|
+
;
|
|
138
|
+
|
|
139
|
+
self.ui.fieldLabel = jQuery('<span>', {
|
|
140
|
+
'class': 'wcdv_field_name',
|
|
141
|
+
'title': label
|
|
142
|
+
})
|
|
143
|
+
.text(label);
|
|
144
|
+
|
|
145
|
+
self.ui.root = jQuery('<div>', { 'class': 'wcdv_field' })
|
|
146
|
+
.append(self.ui.removeButton)
|
|
147
|
+
.append(self.ui.fieldLabel)
|
|
148
|
+
;
|
|
149
|
+
|
|
150
|
+
self._addErrorIndicator(self.ui.root, 'wcdv_aggregate_control_error');
|
|
151
|
+
|
|
152
|
+
return self.ui.root;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// #getElement {{{2
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Gets the DIV that contains the UI for this control field.
|
|
159
|
+
*
|
|
160
|
+
* @returns {Element}
|
|
161
|
+
* The DIV that this control field was rendered into.
|
|
162
|
+
*/
|
|
163
|
+
|
|
164
|
+
GridControlField.prototype.getElement = function () {
|
|
165
|
+
var self = this;
|
|
166
|
+
|
|
167
|
+
return self.ui.root;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// #destroy {{{2
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Called when the control field is removed; should be used to clean up resources like DOM nodes and
|
|
174
|
+
* event handlers.
|
|
175
|
+
*/
|
|
176
|
+
|
|
177
|
+
GridControlField.prototype.destroy = function () {
|
|
178
|
+
// DO NOTHING
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// #showError {{{2
|
|
182
|
+
|
|
183
|
+
GridControlField.prototype.showError = function (errMsg) {
|
|
184
|
+
var self = this;
|
|
185
|
+
|
|
186
|
+
self.logDebug(self.makeLogTag() + ' GRID // CONTROL', errMsg);
|
|
187
|
+
|
|
188
|
+
if (self.ui.error) {
|
|
189
|
+
self.ui.error.attr('data-tooltip', errMsg);
|
|
190
|
+
self.ui.error.show();
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
self.logError(self.makeLogTag() + ' Call Error: Attempted to call `showError()` on a ControlField subclass instance that does not provide a way of indicating errors in the user interface.');
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// #_addErrorIndicator {{{2
|
|
198
|
+
|
|
199
|
+
GridControlField.prototype._addErrorIndicator = function (parent, cls) {
|
|
200
|
+
var self = this;
|
|
201
|
+
|
|
202
|
+
self.ui.error = icon('triangle-alert', cls)
|
|
203
|
+
.hide()
|
|
204
|
+
.appendTo(parent);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// #getSpec {{{2
|
|
208
|
+
|
|
209
|
+
GridControlField.prototype.getSpec = function () {
|
|
210
|
+
var self = this;
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
field: self.field.field
|
|
214
|
+
};
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
// FunGridControlField {{{1
|
|
218
|
+
|
|
219
|
+
// Constructor {{{2
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* @class
|
|
223
|
+
* @extends GridControlField
|
|
224
|
+
*/
|
|
225
|
+
|
|
226
|
+
var FunGridControlField = makeSubclass('FunGridControlField', GridControlField);
|
|
227
|
+
|
|
228
|
+
// #draw {{{2
|
|
229
|
+
|
|
230
|
+
FunGridControlField.prototype.draw = function () {
|
|
231
|
+
var self = this;
|
|
232
|
+
|
|
233
|
+
self.super['GridControlField'].draw();
|
|
234
|
+
|
|
235
|
+
// Let's find out what group functions there are that work on the type of the field that we
|
|
236
|
+
// represent, e.g. if we are a date, find out what group functions work on dates.
|
|
237
|
+
|
|
238
|
+
var applicableGroupFuns = GROUP_FUNCTION_REGISTRY.filter(function (gf) {
|
|
239
|
+
if (self.fti == null) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
return gf.allowedTypes.indexOf(self.fti.type) >= 0;
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (applicableGroupFuns.size() > 0) {
|
|
246
|
+
// When there are some group functions for the type of this field, we need to create a window to
|
|
247
|
+
// choose between them, plus a button to show the window.
|
|
248
|
+
|
|
249
|
+
self.ui.groupFunWin = new GroupFunWin(trans('GRID.GROUP_FUN.DIALOG.TITLE', self.field.field), applicableGroupFuns);
|
|
250
|
+
|
|
251
|
+
self.ui.groupFunWinBtn = jQuery('<button>', {
|
|
252
|
+
'type': 'button',
|
|
253
|
+
'data-wcdv-role': 'set-group-fun',
|
|
254
|
+
title: trans('GRID_CONTROL.FIELD.SHOW_FUNCTIONS')
|
|
255
|
+
})
|
|
256
|
+
.addClass('wcdv_icon_button wcdv_button_left wcdv_text-primary')
|
|
257
|
+
.on('click', function () {
|
|
258
|
+
self.showFunWin();
|
|
259
|
+
})
|
|
260
|
+
.append(icon('zap'))
|
|
261
|
+
.appendTo(self.ui.root)
|
|
262
|
+
;
|
|
263
|
+
|
|
264
|
+
if (self.field.fun != null) {
|
|
265
|
+
var gf = GROUP_FUNCTION_REGISTRY.get(self.field.fun);
|
|
266
|
+
self.ui.fieldLabel.text(self.field.field + ' (' + gf.getTransName() + ')');
|
|
267
|
+
self.ui.fieldLabel.attr('title', self.field.field + ' (' + gf.getTransName() + ')');
|
|
268
|
+
}
|
|
269
|
+
self.ui.fieldLabel.after(self.ui.groupFunWinBtn);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return self.ui.root;
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// #getSpec {{{2
|
|
276
|
+
|
|
277
|
+
FunGridControlField.prototype.getSpec = function () {
|
|
278
|
+
var self = this;
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
field: self.field.field,
|
|
282
|
+
fun: self.field.fun
|
|
283
|
+
};
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
// #showFunWin {{{2
|
|
287
|
+
|
|
288
|
+
FunGridControlField.prototype.showFunWin = function () {
|
|
289
|
+
var self = this;
|
|
290
|
+
|
|
291
|
+
self.ui.groupFunWin.show(self.field.fun || 'none', function (groupFunName) {
|
|
292
|
+
if (groupFunName != null) {
|
|
293
|
+
if (groupFunName === 'none') {
|
|
294
|
+
self.field.fun = null;
|
|
295
|
+
self.ui.fieldLabel.text(self.field.field);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
self.field.fun = groupFunName;
|
|
299
|
+
var gf = GROUP_FUNCTION_REGISTRY.get(self.field.fun);
|
|
300
|
+
self.ui.fieldLabel.text(self.field.field + ' (' + gf.getTransName() + ')');
|
|
301
|
+
}
|
|
302
|
+
self.control.updateView();
|
|
303
|
+
}
|
|
304
|
+
else if (self.field.fun === undefined) {
|
|
305
|
+
self.field.fun = null;
|
|
306
|
+
self.control.updateView();
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// GroupControlField {{{1
|
|
312
|
+
|
|
313
|
+
// Constructor {{{2
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* @class
|
|
317
|
+
* @extends FunGridControlField
|
|
318
|
+
*/
|
|
319
|
+
|
|
320
|
+
var GroupControlField = makeSubclass('GroupControlField', FunGridControlField);
|
|
321
|
+
|
|
322
|
+
// PivotControlField {{{1
|
|
323
|
+
|
|
324
|
+
// Constructor {{{2
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* @class
|
|
328
|
+
* @extends FunGridControlField
|
|
329
|
+
*/
|
|
330
|
+
|
|
331
|
+
var PivotControlField = makeSubclass('PivotControlField', FunGridControlField);
|
|
332
|
+
|
|
333
|
+
// FilterControlField {{{1
|
|
334
|
+
// Constructor {{{2
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* @class
|
|
338
|
+
* @extends GridControlField
|
|
339
|
+
*/
|
|
340
|
+
|
|
341
|
+
var FilterControlField = makeSubclass('FilterControlField', GridControlField);
|
|
342
|
+
|
|
343
|
+
// #draw {{{2
|
|
344
|
+
|
|
345
|
+
FilterControlField.prototype.draw = function () {
|
|
346
|
+
var self = this;
|
|
347
|
+
|
|
348
|
+
self.super['GridControlField'].draw();
|
|
349
|
+
self.ui.filterContainer = jQuery('<div>')
|
|
350
|
+
.addClass('wcdv_filter_control_filter_container')
|
|
351
|
+
.appendTo(self.ui.root);
|
|
352
|
+
self.control.gfs.add(self.field.field, self.ui.filterContainer, {
|
|
353
|
+
filterType: self.colConfig && self.colConfig.filter
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
return self.ui.root;
|
|
357
|
+
};
|
|
358
|
+
// AggregateControlField {{{1
|
|
359
|
+
// Constructor {{{2
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* @class
|
|
363
|
+
* @extends GridControlField
|
|
364
|
+
*
|
|
365
|
+
* @property {object} [opts]
|
|
366
|
+
*
|
|
367
|
+
* @property {string[]} [opts.fields]
|
|
368
|
+
* List of the fields used by the aggregate function.
|
|
369
|
+
*
|
|
370
|
+
* @property {object} [aggFunOpts]
|
|
371
|
+
* Options passed to the aggregate function.
|
|
372
|
+
*/
|
|
373
|
+
|
|
374
|
+
var AggregateControlField = makeSubclass('AggregateControlField', GridControlField, function () {
|
|
375
|
+
var self = this;
|
|
376
|
+
|
|
377
|
+
self.super['GridControlField'].ctor.apply(self, arguments);
|
|
378
|
+
self.fieldDropdowns = [];
|
|
379
|
+
self.shouldGraph = false;
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// #draw {{{2
|
|
383
|
+
|
|
384
|
+
AggregateControlField.prototype.draw = function () {
|
|
385
|
+
var self = this;
|
|
386
|
+
|
|
387
|
+
self.super['GridControlField'].draw();
|
|
388
|
+
|
|
389
|
+
self._addErrorIndicator(self.ui.root, 'wcdv_aggregate_control_error');
|
|
390
|
+
|
|
391
|
+
var aggDefn = AGGREGATE_REGISTRY.get(self.field.field);
|
|
392
|
+
|
|
393
|
+
var fieldList = jQuery('<ul>', {
|
|
394
|
+
'class': 'wcdv_aggregate_control_fieldlist'
|
|
395
|
+
}).appendTo(self.ui.root);
|
|
396
|
+
|
|
397
|
+
for (var i = 0; i < aggDefn.prototype.fieldCount; i += 1) {
|
|
398
|
+
var li = jQuery('<li>').addClass('wcdv_aggregate_field').appendTo(fieldList);
|
|
399
|
+
if (getProp(aggDefn.prototype, 'fieldInfo', i, 'transLabel')) {
|
|
400
|
+
var label = jQuery('<label>').text(trans(aggDefn.prototype.fieldInfo[i].transLabel) + ':').appendTo(li);
|
|
401
|
+
}
|
|
402
|
+
var select = jQuery('<select>')
|
|
403
|
+
.on('change', function (evt) {
|
|
404
|
+
select.children('option[data-wcdv-bad-field]').filter(function (eltIndex, elt) {
|
|
405
|
+
return jQuery(elt).attr('value') !== select.val();
|
|
406
|
+
}).remove();
|
|
407
|
+
self.control.updateView();
|
|
408
|
+
})
|
|
409
|
+
.appendTo(li);
|
|
410
|
+
self.fieldDropdowns.push(select);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
_.each(determineColumns(self.control.colConfig, null, self.control.typeInfo), function (fieldName) {
|
|
414
|
+
var text = getProp(self.control.colConfig.get(fieldName), 'displayText') || fieldName;
|
|
415
|
+
_.each(self.fieldDropdowns, function (dropdown, i) {
|
|
416
|
+
jQuery('<option>', { 'value': fieldName }).text(text).appendTo(dropdown);
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// For each field dropdown, set its value to whatever we received. This has the effect of making
|
|
421
|
+
// the user interface match the internal aggregate configuration.
|
|
422
|
+
|
|
423
|
+
_.each(self.fieldDropdowns, function (dropdown, i) {
|
|
424
|
+
if (getProp(self.opts, 'fields', i)) {
|
|
425
|
+
var matchingOption = dropdown.children('option').filter(function (eltIndex, elt) {
|
|
426
|
+
return jQuery(elt).attr('value') === self.opts.fields[i];
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// When the field in the configuration isn't in the dropdown (i.e. it's not in colConfig) then
|
|
430
|
+
// we need to make an entry for it. This happens when the aggregate spec from prefs refers to
|
|
431
|
+
// a field that no longer exists in the data.
|
|
432
|
+
|
|
433
|
+
if (matchingOption.length === 0) {
|
|
434
|
+
jQuery('<option>', {
|
|
435
|
+
'value': self.opts.fields[i],
|
|
436
|
+
'data-wcdv-bad-field': 'yup'
|
|
437
|
+
})
|
|
438
|
+
// FIXME: i18n
|
|
439
|
+
.text(self.opts.fields[i] + ' — Invalid')
|
|
440
|
+
.appendTo(dropdown);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
dropdown.val(self.opts.fields[i]);
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
if (aggDefn.prototype.options != null) {
|
|
448
|
+
jQuery('<button>', {
|
|
449
|
+
'type': 'button',
|
|
450
|
+
title: trans('GRID_CONTROL.AGGREGATE.EDIT_OPTIONS')
|
|
451
|
+
})
|
|
452
|
+
.addClass('wcdv_icon_button wcdv_button_left wcdv_text-primary')
|
|
453
|
+
.on('click', function () {
|
|
454
|
+
self.ui.optionsDialog.open();
|
|
455
|
+
})
|
|
456
|
+
.append(icon('square-pen'))
|
|
457
|
+
.appendTo(self.ui.root)
|
|
458
|
+
;
|
|
459
|
+
self._makeOptionsDialog(aggDefn);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// if (self.control.view.hasClientKind('graph')) {
|
|
463
|
+
// self.ui.graphBtn = jQuery('<button>', {
|
|
464
|
+
// 'type': 'button'
|
|
465
|
+
// })
|
|
466
|
+
// .addClass('wcdv_icon_button wcdv_text-primary')
|
|
467
|
+
// .on('click', function () {
|
|
468
|
+
// // TODO Think of a better way to do this. I feel like the coupling here is too high.
|
|
469
|
+
// self.control.clearGraphFlag();
|
|
470
|
+
// self.shouldGraph = true;
|
|
471
|
+
// self.control.updateView();
|
|
472
|
+
// })
|
|
473
|
+
// .append(icon('bar-chart-2'))
|
|
474
|
+
// .appendTo(self.ui.root)
|
|
475
|
+
// ;
|
|
476
|
+
// }
|
|
477
|
+
|
|
478
|
+
self.ui.isHiddenCheckbox = jQuery('<input>', {
|
|
479
|
+
'type': 'checkbox'
|
|
480
|
+
})
|
|
481
|
+
.prop('checked', getProp(self.opts, 'isHidden'))
|
|
482
|
+
.on('change', function () {
|
|
483
|
+
self.control.updateView();
|
|
484
|
+
})
|
|
485
|
+
.appendTo(self.ui.root)
|
|
486
|
+
._makeIconCheckbox({
|
|
487
|
+
on: {
|
|
488
|
+
icon: 'eye-off',
|
|
489
|
+
classes: ['wcdv_text-primary']
|
|
490
|
+
},
|
|
491
|
+
off: {
|
|
492
|
+
icon: 'eye',
|
|
493
|
+
classes: ['wcdv_text-primary']
|
|
494
|
+
}
|
|
495
|
+
})
|
|
496
|
+
;
|
|
497
|
+
|
|
498
|
+
return self.ui.root;
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
// #_makeOptionsDialog {{{2
|
|
502
|
+
|
|
503
|
+
AggregateControlField.prototype._makeOptionsDialog = function (aggDefn) {
|
|
504
|
+
var self = this;
|
|
505
|
+
|
|
506
|
+
var contentDiv = jQuery('<div>');
|
|
507
|
+
|
|
508
|
+
var table = jQuery('<table>').appendTo(contentDiv);
|
|
509
|
+
var opts = {};
|
|
510
|
+
|
|
511
|
+
_.each(aggDefn.prototype.options, function (optConfig, optName) {
|
|
512
|
+
optConfig = deepDefaults(optConfig, {
|
|
513
|
+
type: 'string',
|
|
514
|
+
widget: 'text',
|
|
515
|
+
displayText: optName
|
|
516
|
+
});
|
|
517
|
+
var id = gensym();
|
|
518
|
+
var input = jQuery('<input>', {
|
|
519
|
+
'type': 'text',
|
|
520
|
+
'id': id
|
|
521
|
+
});
|
|
522
|
+
opts[optName] = input;
|
|
523
|
+
var label = jQuery('<label>', {
|
|
524
|
+
'for': id
|
|
525
|
+
}).text(optConfig.displayText);
|
|
526
|
+
jQuery('<tr>')
|
|
527
|
+
.append(jQuery('<td>').append(label))
|
|
528
|
+
.append(jQuery('<td>').append(input))
|
|
529
|
+
.appendTo(table);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
self.ui.optionsDialog = new PopupWindow({
|
|
533
|
+
title: trans('GRID_CONTROL.AGGREGATE.OPTIONS_DIALOG.TITLE', aggDefn.prototype.getTransName()),
|
|
534
|
+
content: contentDiv,
|
|
535
|
+
buttons: [{
|
|
536
|
+
icon: 'check',
|
|
537
|
+
label: trans('DIALOG.OK'),
|
|
538
|
+
callback: function () {
|
|
539
|
+
self.aggFunOpts = opts;
|
|
540
|
+
self.control.updateView();
|
|
541
|
+
self.ui.optionsDialog.close();
|
|
542
|
+
}
|
|
543
|
+
}, {
|
|
544
|
+
icon: 'ban',
|
|
545
|
+
label: trans('DIALOG.CANCEL'),
|
|
546
|
+
callback: function () {
|
|
547
|
+
self.ui.optionsDialog.close();
|
|
548
|
+
}
|
|
549
|
+
}]
|
|
550
|
+
});
|
|
551
|
+
};
|
|
552
|
+
|
|
553
|
+
// #destroy {{{2
|
|
554
|
+
|
|
555
|
+
AggregateControlField.prototype.destroy = function () {
|
|
556
|
+
var self = this;
|
|
557
|
+
|
|
558
|
+
if (self.ui.optionsDialog != null) {
|
|
559
|
+
self.ui.optionsDialog.destroy();
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
self.super['GridControlField'].destroy();
|
|
563
|
+
};
|
|
564
|
+
|
|
565
|
+
// #getInfo {{{2
|
|
566
|
+
|
|
567
|
+
AggregateControlField.prototype.getInfo = function () {
|
|
568
|
+
var self = this;
|
|
569
|
+
|
|
570
|
+
return {
|
|
571
|
+
fun: self.field.field,
|
|
572
|
+
name: null,
|
|
573
|
+
fields: _.map(self.fieldDropdowns, function (dropdown) {
|
|
574
|
+
return dropdown.val();
|
|
575
|
+
}),
|
|
576
|
+
isHidden: self.ui.isHiddenCheckbox._isChecked(),
|
|
577
|
+
shouldGraph: self.shouldGraph,
|
|
578
|
+
opts: _.mapObject(self.aggFunOpts, function (input, optName) {
|
|
579
|
+
return input.val();
|
|
580
|
+
}),
|
|
581
|
+
debug: true
|
|
582
|
+
};
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
// GridControl {{{1
|
|
586
|
+
|
|
587
|
+
// Constructor {{{2
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Creates a new GridControl instance.
|
|
591
|
+
*
|
|
592
|
+
* @param {Grid} grid
|
|
593
|
+
* @param {OrdMap.<Grid~ColConfig>} colConfig
|
|
594
|
+
* @param {ComputedView} view
|
|
595
|
+
* @param {object} features
|
|
596
|
+
* @param {Timing} timing
|
|
597
|
+
*
|
|
598
|
+
* @class
|
|
599
|
+
*
|
|
600
|
+
* An abstract class that represents some kind of interface that the user can operate over the
|
|
601
|
+
* available fields.
|
|
602
|
+
*
|
|
603
|
+
* Subclasses should implement the following functions:
|
|
604
|
+
*
|
|
605
|
+
* - `draw(TARGET)`
|
|
606
|
+
* Called to create all required user interface components.
|
|
607
|
+
*
|
|
608
|
+
* - `updateView()`
|
|
609
|
+
* Use `self.fields` to set whatever properties are needed on the view.
|
|
610
|
+
*
|
|
611
|
+
* @property {Grid} grid
|
|
612
|
+
* @property {ComputedView} view
|
|
613
|
+
* @property {object} features
|
|
614
|
+
* @property {Timing} timing
|
|
615
|
+
* @property {OrdMap.<Grid~ColConfig>} colConfig
|
|
616
|
+
*
|
|
617
|
+
* @property {Array.<string>} fields
|
|
618
|
+
* List of all the fields selected by the user.
|
|
619
|
+
*
|
|
620
|
+
* @property {Array.<ControlField>} controlFields
|
|
621
|
+
* List of all the control fields currently in the UI.
|
|
622
|
+
*
|
|
623
|
+
* @property {Object.<string, Array.<ControlField>>} controlFieldsByField
|
|
624
|
+
* Object for looking up control fields by name.
|
|
625
|
+
*
|
|
626
|
+
* @property {Object.<string, ControlField>} controlFieldsById
|
|
627
|
+
* Object for looking up control fields by ID.
|
|
628
|
+
*
|
|
629
|
+
* @property {object} ui
|
|
630
|
+
* Object containing different user interface components.
|
|
631
|
+
*
|
|
632
|
+
* @property {jQuery} ui.dropdown
|
|
633
|
+
* The SELECT element containing the available fields.
|
|
634
|
+
*
|
|
635
|
+
* @property {boolean} [prototype.isHorizontal=false]
|
|
636
|
+
* If true, display the list horizontally rather than vertically.
|
|
637
|
+
*
|
|
638
|
+
* @property {boolean} [prototype.isReorderable=true]
|
|
639
|
+
* If true, display an arrow for reordering the items in the list (when `isHorizontal=false`).
|
|
640
|
+
*
|
|
641
|
+
* @property {boolean} [prototype.showColumns=true]
|
|
642
|
+
* If true, display a dropdown with field names to choose from.
|
|
643
|
+
*
|
|
644
|
+
* @property {boolean} [prototype.disableUsedItems=false]
|
|
645
|
+
* If true, items that are added will be disabled in the columns dropdown.
|
|
646
|
+
*
|
|
647
|
+
* @property {boolean} [prototype.useColConfig=true]
|
|
648
|
+
* If true, pass colConfig for the item to the appropriate `Field` subclass.
|
|
649
|
+
*
|
|
650
|
+
* @property {boolean} [prototype.updateCanHide=true]
|
|
651
|
+
* If true, automatically update colConfig to show (and prohibit hiding of) the column being added.
|
|
652
|
+
*/
|
|
653
|
+
|
|
654
|
+
var GridControl = makeSubclass('GridControl', Object, function (grid, colConfig, view, features, timing) {
|
|
655
|
+
var self = this;
|
|
656
|
+
|
|
657
|
+
if (!(grid instanceof Grid)) {
|
|
658
|
+
throw new Error('Call Error: `grid` must be an instance of MIE.WC_DataVis.Grid');
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
self.grid = grid;
|
|
662
|
+
self.colConfig = colConfig;
|
|
663
|
+
self.view = view;
|
|
664
|
+
self.features = features;
|
|
665
|
+
self.timing = timing;
|
|
666
|
+
self.fields = [];
|
|
667
|
+
self.controlFields = [];
|
|
668
|
+
self.controlFieldsByField = {};
|
|
669
|
+
self.controlFieldsById = {};
|
|
670
|
+
|
|
671
|
+
self.ui = {};
|
|
672
|
+
|
|
673
|
+
self.grid.on('colConfigUpdate', function (colConfig) {
|
|
674
|
+
self.colConfig = colConfig;
|
|
675
|
+
});
|
|
676
|
+
}, {
|
|
677
|
+
isHorizontal: false,
|
|
678
|
+
isReorderable: true,
|
|
679
|
+
showColumns: true,
|
|
680
|
+
disableUsedItems: false,
|
|
681
|
+
useColConfig: true,
|
|
682
|
+
updateCanHide: true
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
mixinLogging(GridControl);
|
|
686
|
+
|
|
687
|
+
// Events {{{2
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Fired when a field has been added to the control.
|
|
691
|
+
*
|
|
692
|
+
* @event GridControl#fieldAdded
|
|
693
|
+
*
|
|
694
|
+
* @param {string} fieldAdded
|
|
695
|
+
* The field that was added.
|
|
696
|
+
*
|
|
697
|
+
* @param {Array.<string>} allFields
|
|
698
|
+
* All fields in the control, after the addition.
|
|
699
|
+
*/
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Fired when a field has been removed from the control.
|
|
703
|
+
*
|
|
704
|
+
* @event GridControl#fieldRemoved
|
|
705
|
+
*
|
|
706
|
+
* @param {string} fieldRemoved
|
|
707
|
+
* The field that was removed.
|
|
708
|
+
*
|
|
709
|
+
* @param {Array.<string>} allFields
|
|
710
|
+
* All fields in the control, after the removal.
|
|
711
|
+
*/
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Fired when the control has been cleared (reset).
|
|
715
|
+
*
|
|
716
|
+
* @event GridControl#cleared
|
|
717
|
+
*/
|
|
718
|
+
|
|
719
|
+
mixinEventHandling(GridControl, [
|
|
720
|
+
'fieldAdded'
|
|
721
|
+
, 'fieldRemoved'
|
|
722
|
+
, 'cleared'
|
|
723
|
+
]);
|
|
724
|
+
|
|
725
|
+
// #makeClearButton {{{2
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Make a button that calls the `clear` method when clicked.
|
|
729
|
+
*
|
|
730
|
+
* @param {jQuery} target
|
|
731
|
+
* Where to append the button.
|
|
732
|
+
*
|
|
733
|
+
* @returns {jQuery}
|
|
734
|
+
* The button created.
|
|
735
|
+
*/
|
|
736
|
+
|
|
737
|
+
GridControl.prototype.makeClearButton = function (target) {
|
|
738
|
+
var self = this;
|
|
739
|
+
|
|
740
|
+
return jQuery('<button>')
|
|
741
|
+
.addClass('wcdv_icon_button wcdv_text-primary wcdv_control_clear_button')
|
|
742
|
+
.append(icon('ban'))
|
|
743
|
+
.hide()
|
|
744
|
+
.on('click', function () {
|
|
745
|
+
jQuery(this).hide();
|
|
746
|
+
self.clear();
|
|
747
|
+
})
|
|
748
|
+
.appendTo(target);
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
// #addField {{{2
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Add a field to this control. Automatically updates the view afterwards.
|
|
755
|
+
*
|
|
756
|
+
* @param {string} field
|
|
757
|
+
* Name of the field to add.
|
|
758
|
+
*
|
|
759
|
+
* @param {string} displayText
|
|
760
|
+
*
|
|
761
|
+
* @param {object} opts
|
|
762
|
+
*
|
|
763
|
+
* @param {object} controlFieldOpts
|
|
764
|
+
*
|
|
765
|
+
* @param {function} next
|
|
766
|
+
*/
|
|
767
|
+
|
|
768
|
+
GridControl.prototype.addField = function (field, displayText, opts, controlFieldOpts, next) {
|
|
769
|
+
var self = this
|
|
770
|
+
, args = Array.prototype.slice.call(arguments)
|
|
771
|
+
, fieldName;
|
|
772
|
+
|
|
773
|
+
opts = deepDefaults(opts, {
|
|
774
|
+
updateView: true,
|
|
775
|
+
silent: false,
|
|
776
|
+
openControls: false
|
|
777
|
+
});
|
|
778
|
+
|
|
779
|
+
if (field == null || field === '') {
|
|
780
|
+
return typeof next === 'function' ? next(false) : undefined;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
fieldName = typeof field === 'string' ? field : field.field;
|
|
784
|
+
|
|
785
|
+
if (fieldName == null || fieldName === '') {
|
|
786
|
+
return typeof next === 'function' ? next(false) : undefined;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Make sure we have access to typeinfo before continuing. The typeinfo is used for:
|
|
790
|
+
//
|
|
791
|
+
// 1. Making sure aggregates are only applied to certain fields.
|
|
792
|
+
// 2. Showing group/pivot functions for applicable fields only.
|
|
793
|
+
|
|
794
|
+
if (self.typeInfo == null) {
|
|
795
|
+
return self.view.getTypeInfo(function (ok, typeInfo) {
|
|
796
|
+
if (!ok) {
|
|
797
|
+
return typeof next === 'function' ? next(false) : undefined;
|
|
798
|
+
}
|
|
799
|
+
self.typeInfo = typeInfo;
|
|
800
|
+
return GridControl.prototype.addField.apply(self, args);
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
if (opts.openControls) {
|
|
805
|
+
self.grid.showControls();
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
if (self.disableUsedItems && self.fields.indexOf(fieldName) >= 0) {
|
|
809
|
+
return typeof next === 'function' ? next(false) : undefined;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Check to see if we are supposed to update the 'canHide' property of the column config. Since
|
|
813
|
+
// we're adding the field, we mark it so that the field can't be hidden.
|
|
814
|
+
|
|
815
|
+
if (self.updateCanHide && self.colConfig != null && self.colConfig.isSet(fieldName)) {
|
|
816
|
+
self.colConfig.get(fieldName).isHidden = false;
|
|
817
|
+
self.colConfig.get(fieldName).canHide = false;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
var cf = new self.controlFieldCtor(self, field, displayText, self.useColConfig ? self.colConfig.get(fieldName) : null, controlFieldOpts);
|
|
821
|
+
|
|
822
|
+
self.controlFields.push(cf);
|
|
823
|
+
self.controlFieldsById[cf.id] = cf;
|
|
824
|
+
GRID_CONTROL_FIELD_POOL[cf.id] = cf;
|
|
825
|
+
|
|
826
|
+
if (self.controlFieldsByField[fieldName] == null) {
|
|
827
|
+
self.controlFieldsByField[fieldName] = [];
|
|
828
|
+
}
|
|
829
|
+
self.controlFieldsByField[fieldName].push(cf);
|
|
830
|
+
|
|
831
|
+
self.ui.clearBtn.show();
|
|
832
|
+
|
|
833
|
+
var li = jQuery('<li>')
|
|
834
|
+
.attr({
|
|
835
|
+
'data-wcdv-field': fieldName,
|
|
836
|
+
'data-wcdv-control-field-id': cf.id,
|
|
837
|
+
'data-wcdv-draggable-origin': 'GRID_CONTROL_FIELD'
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
if (self.isHorizontal) {
|
|
841
|
+
li.append(icon('arrow-right'));
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
li.append(cf.draw());
|
|
845
|
+
li.appendTo(self.ui.fields); // Add it to the DOM.
|
|
846
|
+
|
|
847
|
+
if (self.disableUsedItems) {
|
|
848
|
+
self.ui.dropdown.find('option').filter(function () {
|
|
849
|
+
return jQuery(this).val() === fieldName;
|
|
850
|
+
}).prop('disabled', true);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
self.ui.dropdown.val('');
|
|
854
|
+
self.fields.push(fieldName); // Add it to the fields array.
|
|
855
|
+
|
|
856
|
+
if (typeof self.updateView === 'function' && opts.updateView) {
|
|
857
|
+
self.updateView();
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
if (!opts.silent) {
|
|
861
|
+
self.fire('fieldAdded', null, fieldName, self.fields);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
return typeof next === 'function' ? next(true, cf) : undefined;
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
// #removeField {{{2
|
|
868
|
+
|
|
869
|
+
/**
|
|
870
|
+
* Remove a field from this control. Automatically updates the view afterwards.
|
|
871
|
+
*
|
|
872
|
+
* @param {ControlField} cf
|
|
873
|
+
* The field to remove.
|
|
874
|
+
*/
|
|
875
|
+
|
|
876
|
+
GridControl.prototype.removeField = function (cf) {
|
|
877
|
+
var self = this
|
|
878
|
+
, fieldName = cf.field.field;
|
|
879
|
+
|
|
880
|
+
// Check to see if we are supposed to update the 'canHide' property of the column config. Since
|
|
881
|
+
// we're removing the field, we mark it so that the field can be hidden.
|
|
882
|
+
|
|
883
|
+
if (self.updateCanHide && self.colConfig != null && self.colConfig.isSet(fieldName)) {
|
|
884
|
+
self.colConfig.get(fieldName).canHide = true;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// Remove it from the UI.
|
|
888
|
+
|
|
889
|
+
cf.destroy();
|
|
890
|
+
cf.getElement().parent('li').remove();
|
|
891
|
+
|
|
892
|
+
// Remove it from the internal data structures.
|
|
893
|
+
|
|
894
|
+
self.controlFields = _.without(self.controlFields, cf);
|
|
895
|
+
self.controlFieldsByField[fieldName] = _.without(self.controlFieldsByField[fieldName], cf);
|
|
896
|
+
|
|
897
|
+
delete self.controlFieldsById[cf.id];
|
|
898
|
+
delete GRID_CONTROL_FIELD_POOL[cf.id];
|
|
899
|
+
|
|
900
|
+
// Re-enable the option in the dropdown, if necessary.
|
|
901
|
+
|
|
902
|
+
self.fields.splice(self.fields.indexOf(fieldName), 1);
|
|
903
|
+
|
|
904
|
+
if (self.disableUsedItems) {
|
|
905
|
+
self.ui.dropdown.find('option').filter(function () {
|
|
906
|
+
return jQuery(this).val() === fieldName;
|
|
907
|
+
}).prop('disabled', false);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// Hide the "clear" button if there's nothing to clear.
|
|
911
|
+
|
|
912
|
+
if (self.controlFields.length === 0) {
|
|
913
|
+
self.ui.clearBtn.hide();
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
self.updateView();
|
|
917
|
+
self.fire(GridControl.events.fieldRemoved, null, fieldName, self.fields);
|
|
918
|
+
};
|
|
919
|
+
|
|
920
|
+
// #clear {{{2
|
|
921
|
+
|
|
922
|
+
/**
|
|
923
|
+
* Removes all fields from the control. Automatically updates the view afterwards.
|
|
924
|
+
*/
|
|
925
|
+
|
|
926
|
+
GridControl.prototype.clear = function (opts) {
|
|
927
|
+
var self = this;
|
|
928
|
+
|
|
929
|
+
opts = opts || {};
|
|
930
|
+
_.defaults(opts, {
|
|
931
|
+
updateView: true
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
// Check to see if we are supposed to update the 'canHide' property of the column config. Since
|
|
935
|
+
// we're removing all fields, we mark it so that they can all be hidden.
|
|
936
|
+
|
|
937
|
+
if (self.updateCanHide && self.colConfig != null) {
|
|
938
|
+
self.colConfig.each(function (cc) {
|
|
939
|
+
cc.canHide = true;
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
self.fields = [];
|
|
944
|
+
self.controlFields = [];
|
|
945
|
+
self.controlFieldsById = {};
|
|
946
|
+
self.controlFieldsByField = {};
|
|
947
|
+
self.ui.fields.children().remove();
|
|
948
|
+
self.ui.dropdown.find('option:disabled').filter(function () {
|
|
949
|
+
return jQuery(this).val() !== '';
|
|
950
|
+
}).prop('disabled', false);
|
|
951
|
+
self.ui.clearBtn.hide();
|
|
952
|
+
|
|
953
|
+
if (opts.updateView) {
|
|
954
|
+
self.updateView();
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
self.fire(GridControl.events.cleared);
|
|
958
|
+
};
|
|
959
|
+
|
|
960
|
+
// #destroy {{{2
|
|
961
|
+
|
|
962
|
+
GridControl.prototype.destroy = function () {
|
|
963
|
+
var self = this;
|
|
964
|
+
|
|
965
|
+
self.logDebug(self.makeLogTag() + ' Good-bye, cruel world!');
|
|
966
|
+
|
|
967
|
+
self.view.off('*', self);
|
|
968
|
+
self.grid.off('*', self);
|
|
969
|
+
self.ui.root.remove();
|
|
970
|
+
};
|
|
971
|
+
|
|
972
|
+
// #addViewConfigChangeHandler {{{2
|
|
973
|
+
|
|
974
|
+
/**
|
|
975
|
+
* Registers an event handler on the view to update the UI when the view is changed (typically by
|
|
976
|
+
* loading preferences, but also possibly by another grid connected to the same view).
|
|
977
|
+
*
|
|
978
|
+
* @param {string} event
|
|
979
|
+
* Name of the event to register on in the view.
|
|
980
|
+
*
|
|
981
|
+
* @param {function} sync
|
|
982
|
+
* Event handler for the specified event.
|
|
983
|
+
*/
|
|
984
|
+
|
|
985
|
+
GridControl.prototype.addViewConfigChangeHandler = function (event, sync) {
|
|
986
|
+
var self = this;
|
|
987
|
+
|
|
988
|
+
var clearDropdown = function () {
|
|
989
|
+
self.ui.dropdown.children().remove();
|
|
990
|
+
jQuery('<option>', {
|
|
991
|
+
'value': '',
|
|
992
|
+
'disabled': true,
|
|
993
|
+
'selected': true
|
|
994
|
+
})
|
|
995
|
+
.text(trans('GRID_CONTROL.SELECT_FIELD'))
|
|
996
|
+
.appendTo(self.ui.dropdown);
|
|
997
|
+
};
|
|
998
|
+
|
|
999
|
+
// There are two main things that we sync:
|
|
1000
|
+
//
|
|
1001
|
+
// 1. The dropdown that shows all the fields. (Not used by aggregate control.) This is done when
|
|
1002
|
+
// the column configuration is updated. Interactive column configuration can change the names
|
|
1003
|
+
// shown for the fields in the dropdown.
|
|
1004
|
+
//
|
|
1005
|
+
// 2. The list of elements applied in the control; for group & pivot these are the fields with
|
|
1006
|
+
// arrows connecting them; for filter it's the list of filters; for aggregate it's the list of
|
|
1007
|
+
// aggregate functions. It's up to the caller (i.e. the subclass) to provide a function that
|
|
1008
|
+
// does this synchronization.
|
|
1009
|
+
|
|
1010
|
+
var sync_colConfig = function (colConfig) {
|
|
1011
|
+
self.logDebug(self.makeLogTag() + ' Synchronizing column configuration with grid', self.grid.toString(), self.controlType.toUpperCase());
|
|
1012
|
+
self.colConfig = colConfig;
|
|
1013
|
+
if (self.showColumns) {
|
|
1014
|
+
clearDropdown();
|
|
1015
|
+
colConfig.each(function (fcc) {
|
|
1016
|
+
jQuery('<option>', { 'value': fcc.field }).text(fcc.displayText || fcc.field).appendTo(self.ui.dropdown);
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
var sync_view = function () {
|
|
1022
|
+
self.logDebug(self.makeLogTag() + ' Synchronizing user interface with view', self.grid.toString(), self.controlType.toUpperCase());
|
|
1023
|
+
sync();
|
|
1024
|
+
};
|
|
1025
|
+
|
|
1026
|
+
// To fully sync, you need column configuration and type info. Obviously you need column config
|
|
1027
|
+
// because that says what all the available fields' names are. Type info is only needed right now
|
|
1028
|
+
// for the filter control, to determine what type of control to show (e.g. the widget used for
|
|
1029
|
+
// numbers is different from that used for dates).
|
|
1030
|
+
//
|
|
1031
|
+
// We need to do things in that order: sync #1 (column config) first, then #2 (view). The reason
|
|
1032
|
+
// is that synchronizing #2 may cause us to modify the dropdown, i.e. to disable a field that must
|
|
1033
|
+
// already exist due to synchronizing #1.
|
|
1034
|
+
//
|
|
1035
|
+
// BUT we don't know that any of this code will necessarily execute *before* the column config
|
|
1036
|
+
// and/or type info has been determined. This code may run before either of those are known, or
|
|
1037
|
+
// it may be afterwards (because column config could come directly from the JS instantiating the
|
|
1038
|
+
// grid, from prefs, or from the source itself). So we need to always take that info account ---
|
|
1039
|
+
// if the column config is already known, use it; otherwise register an event handler to capture
|
|
1040
|
+
// it when it's decided. Similarly with type info.
|
|
1041
|
+
|
|
1042
|
+
if (self.grid.colConfig != null) {
|
|
1043
|
+
sync_colConfig(self.grid.colConfig);
|
|
1044
|
+
self.grid.on('colConfigUpdate', sync_colConfig);
|
|
1045
|
+
if (self.view.typeInfo != null) {
|
|
1046
|
+
sync_view();
|
|
1047
|
+
self.view.on(event, sync_view, { who: self });
|
|
1048
|
+
}
|
|
1049
|
+
else {
|
|
1050
|
+
self.view.on('getTypeInfo', function () {
|
|
1051
|
+
sync_view();
|
|
1052
|
+
self.view.on(event, sync_view, { who: self });
|
|
1053
|
+
}, { limit: 1 });
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
else {
|
|
1057
|
+
// This setup of event handlers forces us to receive one `colConfigUpdate` event before we allow
|
|
1058
|
+
// any `*Set` events to come through. This is important because the `*Set` events will cause us
|
|
1059
|
+
// to disable elements in the dropdown, so we need to have populated it first.
|
|
1060
|
+
|
|
1061
|
+
self.grid.on('colConfigUpdate', function (colConfig) {
|
|
1062
|
+
sync_colConfig(colConfig);
|
|
1063
|
+
self.grid.on('colConfigUpdate', sync_colConfig);
|
|
1064
|
+
if (self.view.typeInfo != null) {
|
|
1065
|
+
sync_view();
|
|
1066
|
+
self.view.on(event, sync_view, { who: self });
|
|
1067
|
+
}
|
|
1068
|
+
else {
|
|
1069
|
+
self.view.on('getTypeInfo', function (ok) {
|
|
1070
|
+
sync_view();
|
|
1071
|
+
self.view.on(event, sync_view, { who: self });
|
|
1072
|
+
}, { limit: 1 });
|
|
1073
|
+
}
|
|
1074
|
+
}, { limit: 1 });
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
|
|
1078
|
+
// #getListElement {{{2
|
|
1079
|
+
|
|
1080
|
+
GridControl.prototype.getListElement = function () {
|
|
1081
|
+
var self = this;
|
|
1082
|
+
|
|
1083
|
+
return self.ui.fields;
|
|
1084
|
+
};
|
|
1085
|
+
|
|
1086
|
+
// #draw {{{2
|
|
1087
|
+
|
|
1088
|
+
/**
|
|
1089
|
+
* Render this grid control and attach it to the specified parent element.
|
|
1090
|
+
*
|
|
1091
|
+
* @abstract
|
|
1092
|
+
*
|
|
1093
|
+
* @param {jQuery} parent
|
|
1094
|
+
* Element to append this grid control to.
|
|
1095
|
+
*/
|
|
1096
|
+
|
|
1097
|
+
GridControl.prototype.draw = function (parent) {
|
|
1098
|
+
throw new Error('ABSTRACT');
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
// #updateView {{{2
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* Update the view with the configuration entered using this grid control.
|
|
1105
|
+
*
|
|
1106
|
+
* @abstract
|
|
1107
|
+
*/
|
|
1108
|
+
|
|
1109
|
+
GridControl.prototype.updateView = function () {
|
|
1110
|
+
throw new Error('ABSTRACT');
|
|
1111
|
+
};
|
|
1112
|
+
|
|
1113
|
+
// GroupControl {{{1
|
|
1114
|
+
|
|
1115
|
+
// Constructor {{{2
|
|
1116
|
+
|
|
1117
|
+
/**
|
|
1118
|
+
* Part of the user interface which governs the fields that are part of the group, including
|
|
1119
|
+
* filtering.
|
|
1120
|
+
*
|
|
1121
|
+
* @class
|
|
1122
|
+
* @extends GridControl
|
|
1123
|
+
*/
|
|
1124
|
+
|
|
1125
|
+
var GroupControl = makeSubclass('GroupControl', GridControl, function () {
|
|
1126
|
+
var self = this;
|
|
1127
|
+
|
|
1128
|
+
self.super['GridControl'].ctor.apply(self, arguments);
|
|
1129
|
+
|
|
1130
|
+
self.view.on(ComputedView.events.invalidGroupField, function (field) {
|
|
1131
|
+
_.each(self.controlFieldsByField[field], function (cf) {
|
|
1132
|
+
cf.showError('This field does not exist in the data.');
|
|
1133
|
+
});
|
|
1134
|
+
});
|
|
1135
|
+
}, {
|
|
1136
|
+
controlFieldCtor: GroupControlField,
|
|
1137
|
+
controlType: 'Group'
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
// #draw {{{2
|
|
1141
|
+
|
|
1142
|
+
/**
|
|
1143
|
+
* Create a DIV element that can be placed within the Grid instance to hold the user interface for
|
|
1144
|
+
* the GroupControl. The caller must add the result to the DOM somewhere.
|
|
1145
|
+
*
|
|
1146
|
+
* @returns {jQuery} The DIV element that holds the entire UI.
|
|
1147
|
+
*/
|
|
1148
|
+
|
|
1149
|
+
GroupControl.prototype.draw = function (parent) {
|
|
1150
|
+
var self = this;
|
|
1151
|
+
|
|
1152
|
+
parent.droppable({
|
|
1153
|
+
classes: {
|
|
1154
|
+
'ui-droppable-hover': 'wcdv_drop_target_hover'
|
|
1155
|
+
},
|
|
1156
|
+
drop: function (evt, ui) {
|
|
1157
|
+
// Turn this off for the sake of efficiency.
|
|
1158
|
+
//ui.draggable.draggable('option', 'refreshPositions', false);
|
|
1159
|
+
|
|
1160
|
+
// The problem is, this event gets triggered both (1) when dropping a field from the grid
|
|
1161
|
+
// table's header, and (2) when shuffling fields between the group & pivot controls. In the
|
|
1162
|
+
// case of (1) we need to make an <LI>. But in the case of (2), we don't need to modify the
|
|
1163
|
+
// DOM in any way, jQuery UI sortable does that for us. To tell the difference, we use the
|
|
1164
|
+
// `wcdv-draggable-origin` data attribute, which tells where the draggable came from.
|
|
1165
|
+
|
|
1166
|
+
if (ui.draggable.attr('data-wcdv-draggable-origin') === 'GRID_TABLE_HEADER') {
|
|
1167
|
+
var field = ui.draggable.attr('data-wcdv-field');
|
|
1168
|
+
self.addField(field, getProp(self.colConfig.get(field), 'displayText'), {
|
|
1169
|
+
autoShowFunWin: true
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
self.ui.root = jQuery('<div>').appendTo(parent);
|
|
1176
|
+
self.ui.title = jQuery('<div>')
|
|
1177
|
+
.addClass('wcdv_control_title_bar')
|
|
1178
|
+
.appendTo(self.ui.root);
|
|
1179
|
+
jQuery('<span>', { 'class': 'wcdv_control_title' })
|
|
1180
|
+
.text(trans('GRID_CONTROL.GROUP.TITLE'))
|
|
1181
|
+
.appendTo(self.ui.title);
|
|
1182
|
+
self.ui.clearBtn = self.makeClearButton(self.ui.title);
|
|
1183
|
+
self.ui.fields = jQuery('<ul>', {
|
|
1184
|
+
id: gensym(),
|
|
1185
|
+
'class': self.isHorizontal ? 'wcdv_control_horizontal' : 'wcdv_control_vertical'
|
|
1186
|
+
}).appendTo(self.ui.root);
|
|
1187
|
+
|
|
1188
|
+
var dropdownContainer = jQuery('<div>').appendTo(self.ui.root);
|
|
1189
|
+
self.ui.dropdown = jQuery('<select>', { 'class': 'wcdv_control_addField' }).appendTo(dropdownContainer);
|
|
1190
|
+
self.ui.dropdown.on('change', function () {
|
|
1191
|
+
self.addField(self.ui.dropdown.val(), self.ui.dropdown.find('option:selected').text(), {
|
|
1192
|
+
autoShowFunWin: true
|
|
1193
|
+
});
|
|
1194
|
+
});
|
|
1195
|
+
|
|
1196
|
+
self.addViewConfigChangeHandler('groupSet', function () {
|
|
1197
|
+
var spec = self.view.getGroup();
|
|
1198
|
+
var fields = (!self.view.source.origin.isLimited && spec && spec.fieldNames) || [];
|
|
1199
|
+
self.clear({ updateView: false });
|
|
1200
|
+
self.logDebug(self.makeLogTag() + ' View set group fields to: %s', self.grid.toString(), JSON.stringify(fields));
|
|
1201
|
+
_.each(fields, function (field) {
|
|
1202
|
+
self.addField(field, getProp(self.colConfig.get(field), 'displayText'), { updateView: false });
|
|
1203
|
+
});
|
|
1204
|
+
});
|
|
1205
|
+
|
|
1206
|
+
return self.ui.root;
|
|
1207
|
+
};
|
|
1208
|
+
|
|
1209
|
+
// #updateView {{{2
|
|
1210
|
+
|
|
1211
|
+
GroupControl.prototype.updateView = function () {
|
|
1212
|
+
var self = this;
|
|
1213
|
+
var fieldNames = _.map(self.controlFields, function (cf) {
|
|
1214
|
+
return cf.getSpec();
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
if (fieldNames.length > 0) {
|
|
1218
|
+
self.view.setGroup({fieldNames: fieldNames}, {
|
|
1219
|
+
dontSendEventTo: self
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
else {
|
|
1223
|
+
self.view.clearGroup();
|
|
1224
|
+
}
|
|
1225
|
+
};
|
|
1226
|
+
|
|
1227
|
+
// #toString {{{2
|
|
1228
|
+
|
|
1229
|
+
GroupControl.prototype.toString = function () {
|
|
1230
|
+
var self = this;
|
|
1231
|
+
|
|
1232
|
+
return self.grid.id + ', Group';
|
|
1233
|
+
};
|
|
1234
|
+
|
|
1235
|
+
// #sortableSync {{{2
|
|
1236
|
+
|
|
1237
|
+
GroupControl.prototype.sortableSync = function () {
|
|
1238
|
+
var self = this;
|
|
1239
|
+
|
|
1240
|
+
var controlFieldIds = self.ui.fields.children('li').map(function (index, elt) {
|
|
1241
|
+
return jQuery(elt).attr('data-wcdv-control-field-id');
|
|
1242
|
+
}).get();
|
|
1243
|
+
|
|
1244
|
+
self.controlFields = [];
|
|
1245
|
+
_.each(controlFieldIds, function (id) {
|
|
1246
|
+
self.controlFields.push(GRID_CONTROL_FIELD_POOL[id]);
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1249
|
+
if (self.controlFields.length > 0) {
|
|
1250
|
+
self.ui.clearBtn.show();
|
|
1251
|
+
}
|
|
1252
|
+
else {
|
|
1253
|
+
self.ui.clearBtn.hide();
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
return self.updateView();
|
|
1257
|
+
};
|
|
1258
|
+
|
|
1259
|
+
// #addField {{{2
|
|
1260
|
+
|
|
1261
|
+
GroupControl.prototype.addField = function (field, displayText, opts) {
|
|
1262
|
+
var self = this;
|
|
1263
|
+
|
|
1264
|
+
// Make sure we have typeInfo. We need that so we can detect when the user is dragging a field
|
|
1265
|
+
// with a type that doesn't permit grouping (e.g. JSON).
|
|
1266
|
+
|
|
1267
|
+
if (self.typeInfo == null) {
|
|
1268
|
+
return self.view.getTypeInfo(function (ok, typeInfo) {
|
|
1269
|
+
if (!ok) {
|
|
1270
|
+
self.logError(self.makeLogTag() + ' Failed to retrieve typeInfo');
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
self.typeInfo = typeInfo;
|
|
1274
|
+
return self.addField(field, displayText, opts);
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
var fieldName = typeof field === 'string' ? field : field.field;
|
|
1279
|
+
var fti = self.typeInfo.get(fieldName);
|
|
1280
|
+
if (fti == null) {
|
|
1281
|
+
self.logError(self.makeLogTag() + ' Field not in typeInfo: %s', fieldName);
|
|
1282
|
+
self.ui.dropdown.val('');
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
var tc = types.registry.get(fti.type);
|
|
1287
|
+
if (tc == null) {
|
|
1288
|
+
self.logError(self.makeLogTag() + ' Field "%s" type "%s" not in registry', fieldName, fti.type);
|
|
1289
|
+
self.ui.dropdown.val('');
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
if (!tc.supports.group) {
|
|
1294
|
+
self.logError(self.makeLogTag() + ' Field "%s" type "%s" does not support grouping', fieldName, fti.type);
|
|
1295
|
+
self.ui.dropdown.val('');
|
|
1296
|
+
return;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
opts = deepDefaults(opts, {
|
|
1300
|
+
autoShowFunWin: false,
|
|
1301
|
+
updateView: true
|
|
1302
|
+
});
|
|
1303
|
+
var updateView = opts.updateView;
|
|
1304
|
+
opts.updateView = false;
|
|
1305
|
+
|
|
1306
|
+
self.super['GridControl'].addField(field, displayText, opts, null, function (ok, cf) {
|
|
1307
|
+
if (!ok) {
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
if (opts.autoShowFunWin && cf.fti != null && ['date', 'datetime'].indexOf(cf.fti.type) >= 0 && cf.field.fun === undefined) {
|
|
1311
|
+
cf.showFunWin();
|
|
1312
|
+
}
|
|
1313
|
+
else if (updateView) {
|
|
1314
|
+
self.updateView();
|
|
1315
|
+
}
|
|
1316
|
+
});
|
|
1317
|
+
};
|
|
1318
|
+
|
|
1319
|
+
// PivotControl {{{1
|
|
1320
|
+
|
|
1321
|
+
// Constructor {{{2
|
|
1322
|
+
|
|
1323
|
+
/**
|
|
1324
|
+
* Part of the user interface which governs: (1) the fields that are part of the pivot, including
|
|
1325
|
+
* filtering; (2) the aggregate function [and potentially its arguments] that produces the values in
|
|
1326
|
+
* the pivot table.
|
|
1327
|
+
*
|
|
1328
|
+
* @class
|
|
1329
|
+
* @extends GridControl
|
|
1330
|
+
*
|
|
1331
|
+
* @property {GridControl} super
|
|
1332
|
+
* Proxy to call prototype ("superclass") methods even if we override them.
|
|
1333
|
+
*
|
|
1334
|
+
* @property {string[]} fields
|
|
1335
|
+
* Names of the fields
|
|
1336
|
+
*/
|
|
1337
|
+
|
|
1338
|
+
var PivotControl = makeSubclass('PivotControl', GridControl, function () {
|
|
1339
|
+
var self = this;
|
|
1340
|
+
|
|
1341
|
+
self.super['GridControl'].ctor.apply(self, arguments);
|
|
1342
|
+
|
|
1343
|
+
self.view.on(ComputedView.events.invalidPivotField, function (field) {
|
|
1344
|
+
_.each(self.controlFieldsByField[field], function (cf) {
|
|
1345
|
+
cf.showError('This field does not exist in the data.');
|
|
1346
|
+
});
|
|
1347
|
+
});
|
|
1348
|
+
}, {
|
|
1349
|
+
controlFieldCtor: PivotControlField,
|
|
1350
|
+
controlType: 'Pivot'
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
// #draw {{{2
|
|
1354
|
+
|
|
1355
|
+
/**
|
|
1356
|
+
* Create a DIV element that can be placed within the Grid instance to hold the user interface for
|
|
1357
|
+
* the PivotControl. The caller must add the result to the DOM somewhere.
|
|
1358
|
+
*
|
|
1359
|
+
* @returns {jQuery} The DIV element that holds the entire UI.
|
|
1360
|
+
*/
|
|
1361
|
+
|
|
1362
|
+
PivotControl.prototype.draw = function (parent) {
|
|
1363
|
+
var self = this;
|
|
1364
|
+
|
|
1365
|
+
parent.droppable({
|
|
1366
|
+
classes: {
|
|
1367
|
+
'ui-droppable-hover': 'wcdv_drop_target_hover'
|
|
1368
|
+
},
|
|
1369
|
+
drop: function (evt, ui) {
|
|
1370
|
+
// Turn this off for the sake of efficiency.
|
|
1371
|
+
//ui.draggable.draggable('option', 'refreshPositions', false);
|
|
1372
|
+
|
|
1373
|
+
// The problem is, this event gets triggered both (1) when dropping a field from the grid
|
|
1374
|
+
// table's header, and (2) when shuffling fields between the group & pivot controls. In the
|
|
1375
|
+
// case of (1) we need to make an <LI>. But in the case of (2), we don't need to modify the
|
|
1376
|
+
// DOM in any way, jQuery UI sortable does that for us. To tell the difference, we use the
|
|
1377
|
+
// `wcdv-draggable-origin` data attribute, which tells where the draggable came from.
|
|
1378
|
+
|
|
1379
|
+
if (ui.draggable.attr('data-wcdv-draggable-origin') === 'GRID_TABLE_HEADER') {
|
|
1380
|
+
var field = ui.draggable.attr('data-wcdv-field');
|
|
1381
|
+
self.addField(field, getProp(self.colConfig.get(field), 'displayText'), {
|
|
1382
|
+
autoShowFunWin: true
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
});
|
|
1387
|
+
|
|
1388
|
+
self.ui.root = jQuery('<div>').appendTo(parent);
|
|
1389
|
+
self.ui.title = jQuery('<div>')
|
|
1390
|
+
.addClass('wcdv_control_title_bar')
|
|
1391
|
+
.appendTo(self.ui.root);
|
|
1392
|
+
jQuery('<span>')
|
|
1393
|
+
.addClass('wcdv_control_title')
|
|
1394
|
+
.text(trans('GRID_CONTROL.PIVOT.TITLE'))
|
|
1395
|
+
.appendTo(self.ui.title);
|
|
1396
|
+
self.ui.clearBtn = self.makeClearButton(self.ui.title);
|
|
1397
|
+
self.ui.fields = jQuery('<ul>', {
|
|
1398
|
+
id: gensym(),
|
|
1399
|
+
'class': self.isHorizontal ? 'wcdv_control_horizontal' : 'wcdv_control_vertical'
|
|
1400
|
+
}).appendTo(self.ui.root);
|
|
1401
|
+
|
|
1402
|
+
var dropdownContainer = jQuery('<div>').appendTo(self.ui.root);
|
|
1403
|
+
self.ui.dropdown = jQuery('<select>', { 'class': 'wcdv_control_addField' }).appendTo(dropdownContainer);
|
|
1404
|
+
self.ui.dropdown.on('change', function () {
|
|
1405
|
+
self.addField(self.ui.dropdown.val(), self.ui.dropdown.find('option:selected').text(), {
|
|
1406
|
+
autoShowFunWin: true
|
|
1407
|
+
});
|
|
1408
|
+
});
|
|
1409
|
+
|
|
1410
|
+
self.addViewConfigChangeHandler('pivotSet', function (spec) {
|
|
1411
|
+
spec = self.view.getPivot();
|
|
1412
|
+
var fields = (!self.view.source.origin.isLimited && spec && spec.fieldNames) || [];
|
|
1413
|
+
self.clear({ updateView: false });
|
|
1414
|
+
self.logDebug(self.makeLogTag() + ' View set pivot fields to: %s', self.grid.toString(), JSON.stringify(fields));
|
|
1415
|
+
_.each(fields, function (field) {
|
|
1416
|
+
self.addField(field, getProp(self.colConfig.get(field), 'displayText'), { updateView: false });
|
|
1417
|
+
});
|
|
1418
|
+
});
|
|
1419
|
+
|
|
1420
|
+
return self.ui.root;
|
|
1421
|
+
};
|
|
1422
|
+
|
|
1423
|
+
// #updateView {{{2
|
|
1424
|
+
|
|
1425
|
+
/**
|
|
1426
|
+
* Set the pivot configuration on the ComputedView. The pivot configuration consists of:
|
|
1427
|
+
*
|
|
1428
|
+
* - Fields that are part of the pivot.
|
|
1429
|
+
*/
|
|
1430
|
+
|
|
1431
|
+
PivotControl.prototype.updateView = function () {
|
|
1432
|
+
var self = this;
|
|
1433
|
+
var fieldNames = _.map(self.controlFields, function (cf) {
|
|
1434
|
+
return cf.getSpec();
|
|
1435
|
+
});
|
|
1436
|
+
|
|
1437
|
+
if (fieldNames.length > 0) {
|
|
1438
|
+
self.view.setPivot({fieldNames: fieldNames}, {
|
|
1439
|
+
dontSendEventTo: self
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
else {
|
|
1443
|
+
self.view.clearPivot();
|
|
1444
|
+
}
|
|
1445
|
+
};
|
|
1446
|
+
|
|
1447
|
+
// #toString {{{2
|
|
1448
|
+
|
|
1449
|
+
PivotControl.prototype.toString = function () {
|
|
1450
|
+
var self = this;
|
|
1451
|
+
|
|
1452
|
+
return self.grid.id + ', Pivot';
|
|
1453
|
+
};
|
|
1454
|
+
|
|
1455
|
+
// #sortableSync {{{2
|
|
1456
|
+
|
|
1457
|
+
PivotControl.prototype.sortableSync = function () {
|
|
1458
|
+
var self = this;
|
|
1459
|
+
|
|
1460
|
+
var controlFieldIds = self.ui.fields.children('li').map(function (index, elt) {
|
|
1461
|
+
return jQuery(elt).attr('data-wcdv-control-field-id');
|
|
1462
|
+
}).get();
|
|
1463
|
+
|
|
1464
|
+
self.controlFields = [];
|
|
1465
|
+
_.each(controlFieldIds, function (id) {
|
|
1466
|
+
self.controlFields.push(GRID_CONTROL_FIELD_POOL[id]);
|
|
1467
|
+
});
|
|
1468
|
+
|
|
1469
|
+
return self.updateView();
|
|
1470
|
+
};
|
|
1471
|
+
|
|
1472
|
+
// #addField {{{2
|
|
1473
|
+
|
|
1474
|
+
PivotControl.prototype.addField = function (field, displayText, opts) {
|
|
1475
|
+
var self = this;
|
|
1476
|
+
|
|
1477
|
+
// Make sure we have typeInfo. We need that so we can detect when the user is dragging a field
|
|
1478
|
+
// with a type that doesn't permit grouping (e.g. JSON).
|
|
1479
|
+
|
|
1480
|
+
if (self.typeInfo == null) {
|
|
1481
|
+
return self.view.getTypeInfo(function (ok, typeInfo) {
|
|
1482
|
+
if (!ok) {
|
|
1483
|
+
self.logError(self.makeLogTag() + ' Failed to retrieve typeInfo');
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
self.typeInfo = typeInfo;
|
|
1487
|
+
return self.addField(field, displayText, opts);
|
|
1488
|
+
});
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
var fieldName = typeof field === 'string' ? field : field.field;
|
|
1492
|
+
var fti = self.typeInfo.get(fieldName);
|
|
1493
|
+
if (fti == null) {
|
|
1494
|
+
self.logError(self.makeLogTag() + ' Field not in typeInfo: %s', fieldName);
|
|
1495
|
+
self.ui.dropdown.val('');
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
var tc = types.registry.get(fti.type);
|
|
1500
|
+
if (tc == null) {
|
|
1501
|
+
self.logError(self.makeLogTag() + ' Field "%s" type "%s" not in registry', fieldName, fti.type);
|
|
1502
|
+
self.ui.dropdown.val('');
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
if (!tc.supports.group) {
|
|
1507
|
+
self.logError(self.makeLogTag() + ' Field "%s" type "%s" does not support grouping', fieldName, fti.type);
|
|
1508
|
+
self.ui.dropdown.val('');
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
opts = deepDefaults(opts, {
|
|
1513
|
+
autoShowFunWin: false,
|
|
1514
|
+
updateView: true
|
|
1515
|
+
});
|
|
1516
|
+
var updateView = opts.updateView;
|
|
1517
|
+
opts.updateView = false;
|
|
1518
|
+
|
|
1519
|
+
self.super['GridControl'].addField(field, displayText, opts, null, function (ok, cf) {
|
|
1520
|
+
if (!ok) {
|
|
1521
|
+
return;
|
|
1522
|
+
}
|
|
1523
|
+
if (opts.autoShowFunWin && cf.fti != null && ['date', 'datetime'].indexOf(cf.fti.type) >= 0 && cf.field.fun === undefined) {
|
|
1524
|
+
cf.showFunWin();
|
|
1525
|
+
}
|
|
1526
|
+
else if (updateView) {
|
|
1527
|
+
self.updateView();
|
|
1528
|
+
}
|
|
1529
|
+
});
|
|
1530
|
+
};
|
|
1531
|
+
|
|
1532
|
+
// AggregateControl {{{1
|
|
1533
|
+
|
|
1534
|
+
// Constructor {{{2
|
|
1535
|
+
|
|
1536
|
+
/**
|
|
1537
|
+
* Part of the user interface which governs the aggregate function (and potentially its arguments)
|
|
1538
|
+
* that produces the values in (1) group summary columns, (2) pivot cells.
|
|
1539
|
+
*
|
|
1540
|
+
* @class
|
|
1541
|
+
* @extends GridControl
|
|
1542
|
+
*
|
|
1543
|
+
* @property {string[]} fields
|
|
1544
|
+
* Names of the fields
|
|
1545
|
+
*/
|
|
1546
|
+
|
|
1547
|
+
var AggregateControl = makeSubclass('AggregateControl', GridControl, function () {
|
|
1548
|
+
var self = this;
|
|
1549
|
+
|
|
1550
|
+
self.super['GridControl'].ctor.apply(self, arguments);
|
|
1551
|
+
|
|
1552
|
+
self.view.on(ComputedView.events.invalidAggregate, function (aggNum, errMsg) {
|
|
1553
|
+
self.controlFields[aggNum].showError(errMsg);
|
|
1554
|
+
});
|
|
1555
|
+
}, {
|
|
1556
|
+
disableUsedItems: false,
|
|
1557
|
+
showColumns: false,
|
|
1558
|
+
updateCanHide: false,
|
|
1559
|
+
controlFieldCtor: AggregateControlField,
|
|
1560
|
+
controlType: 'Aggregate'
|
|
1561
|
+
});
|
|
1562
|
+
|
|
1563
|
+
// #draw {{{2
|
|
1564
|
+
|
|
1565
|
+
/**
|
|
1566
|
+
* Create a DIV element that can be placed within the Grid instance to hold the user interface for
|
|
1567
|
+
* the AggregateControl. The caller must add the result to the DOM somewhere.
|
|
1568
|
+
*
|
|
1569
|
+
* @returns {jQuery} The DIV element that holds the entire UI.
|
|
1570
|
+
*/
|
|
1571
|
+
|
|
1572
|
+
AggregateControl.prototype.draw = function (parent) {
|
|
1573
|
+
var self = this;
|
|
1574
|
+
|
|
1575
|
+
self.ui.root = jQuery('<div>').appendTo(parent);
|
|
1576
|
+
|
|
1577
|
+
self.ui.title = jQuery('<div>')
|
|
1578
|
+
.addClass('wcdv_control_title_bar')
|
|
1579
|
+
.appendTo(self.ui.root);
|
|
1580
|
+
jQuery('<span>')
|
|
1581
|
+
.addClass('wcdv_control_title')
|
|
1582
|
+
.text(trans('GRID_CONTROL.AGGREGATE.TITLE'))
|
|
1583
|
+
.appendTo(self.ui.title);
|
|
1584
|
+
self.ui.clearBtn = self.makeClearButton(self.ui.title);
|
|
1585
|
+
self.ui.fields = jQuery('<ul>', {
|
|
1586
|
+
id: gensym(),
|
|
1587
|
+
'class': self.isHorizontal ? 'wcdv_control_horizontal' : 'wcdv_control_vertical'
|
|
1588
|
+
}).appendTo(self.ui.root);
|
|
1589
|
+
var dropdownContainer = jQuery('<div>').appendTo(self.ui.root);
|
|
1590
|
+
self.ui.dropdown = jQuery('<select>', { 'class': 'wcdv_control_addField' }).appendTo(dropdownContainer);
|
|
1591
|
+
self.ui.dropdown.on('change', function () {
|
|
1592
|
+
self.addField(self.ui.dropdown.val(), self.ui.dropdown.find('option:selected').text());
|
|
1593
|
+
});
|
|
1594
|
+
|
|
1595
|
+
jQuery('<option>', { 'value': '', 'disabled': true, 'selected': true })
|
|
1596
|
+
.text(trans('GRID_CONTROL.SELECT_AGGREGATE'))
|
|
1597
|
+
.appendTo(self.ui.dropdown);
|
|
1598
|
+
|
|
1599
|
+
AGGREGATE_REGISTRY.each(function (aggFunDefn, aggFunShortName) {
|
|
1600
|
+
jQuery('<option>', { 'value': aggFunShortName }).text(aggFunDefn.prototype.getTransName()).appendTo(self.ui.dropdown);
|
|
1601
|
+
});
|
|
1602
|
+
/*
|
|
1603
|
+
self.ui.fun = jQuery('<div>').css({'margin-top': '7px'}).appendTo(self.ui.root);
|
|
1604
|
+
jQuery('<label>').text('Function:').appendTo(self.ui.fun);
|
|
1605
|
+
self.ui.funDropdown = jQuery('<select>')
|
|
1606
|
+
.appendTo(self.ui.fun)
|
|
1607
|
+
.on('change', function () {
|
|
1608
|
+
self.triggerAggChange();
|
|
1609
|
+
})
|
|
1610
|
+
;
|
|
1611
|
+
|
|
1612
|
+
// Create a dropdown containing all the aggregate functions that are allowed to be used for
|
|
1613
|
+
// calculating pivot cells. Right now that's everything that needs no external parameters aside
|
|
1614
|
+
// from the field.
|
|
1615
|
+
|
|
1616
|
+
AGGREGATE_REGISTRY.each(function (aggClass, aggFunName) {
|
|
1617
|
+
if (aggClass.prototype.enabled && aggClass.prototype.enabled) {
|
|
1618
|
+
jQuery('<option>', {
|
|
1619
|
+
value: aggFunName
|
|
1620
|
+
})
|
|
1621
|
+
.text(aggClass.prototype.name || aggFunName)
|
|
1622
|
+
.appendTo(self.ui.funDropdown);
|
|
1623
|
+
}
|
|
1624
|
+
});
|
|
1625
|
+
|
|
1626
|
+
// When we receive type information, use that to populate the "fields" dropdown.
|
|
1627
|
+
//
|
|
1628
|
+
// TODO This needs to be expanded to the possibility of having multiple fields.
|
|
1629
|
+
|
|
1630
|
+
self.view.on('getTypeInfo', function (typeInfo) {
|
|
1631
|
+
self.typeInfo = typeInfo;
|
|
1632
|
+
self.updateFieldDropdowns();
|
|
1633
|
+
}, { limit: 1 });
|
|
1634
|
+
|
|
1635
|
+
var syncAgg = function (spec) {
|
|
1636
|
+
var agg;
|
|
1637
|
+
if (getProp(spec, 'cell', 0, 'fun')) {
|
|
1638
|
+
self.ui.funDropdown.val(spec.cell[0].fun);
|
|
1639
|
+
agg = AGGREGATE_REGISTRY.get(spec.cell[0].fun);
|
|
1640
|
+
if (agg.prototype.fieldCount >= self.ui.fields.length) {
|
|
1641
|
+
self.addFieldDropdowns(agg);
|
|
1642
|
+
}
|
|
1643
|
+
self.showHideFields(agg);
|
|
1644
|
+
}
|
|
1645
|
+
if (getProp(spec, 'cell', 0, 'fields')) {
|
|
1646
|
+
_.each(spec.cell[0].fields, function (f, i) {
|
|
1647
|
+
self.ui.fields[i].dropdown.val(f);
|
|
1648
|
+
});
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
debug.info('GRID // AGGREGATE CONTROL',
|
|
1652
|
+
'ComputedView set aggregate to: ' + JSON.stringify(spec));
|
|
1653
|
+
};
|
|
1654
|
+
|
|
1655
|
+
self.view.on(ComputedView.events.aggregateSet, function (spec) {
|
|
1656
|
+
syncAgg(spec)
|
|
1657
|
+
}, { who: self });
|
|
1658
|
+
*/
|
|
1659
|
+
|
|
1660
|
+
self.addViewConfigChangeHandler('aggregateSet', function () {
|
|
1661
|
+
var spec = self.view.getAggregate();
|
|
1662
|
+
self.clear({ updateView: false });
|
|
1663
|
+
if (spec != null) {
|
|
1664
|
+
self.logDebug(self.makeLogTag() + ' View set aggregate to: %s', self.grid.toString(), JSON.stringify(spec.all));
|
|
1665
|
+
|
|
1666
|
+
_.each(spec.all, function (agg) {
|
|
1667
|
+
self.addField(agg.fun, AGGREGATE_REGISTRY.get(agg.fun).prototype.getTransName(), { updateView: false }, {
|
|
1668
|
+
fields: agg.fields,
|
|
1669
|
+
isHidden: agg.isHidden
|
|
1670
|
+
});
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
});
|
|
1674
|
+
return self.ui.root;
|
|
1675
|
+
};
|
|
1676
|
+
|
|
1677
|
+
// #updateView {{{2
|
|
1678
|
+
|
|
1679
|
+
AggregateControl.prototype.updateView = function () {
|
|
1680
|
+
var self = this;
|
|
1681
|
+
var info = _.map(self.controlFields, function (cf) {
|
|
1682
|
+
return cf.getInfo();
|
|
1683
|
+
});
|
|
1684
|
+
self.ui.root.find('.wcdv_aggregate_control_error').hide();
|
|
1685
|
+
self.view.setAggregate(objFromArray(['group', 'pivot', 'cell', 'all'], [info]), {
|
|
1686
|
+
dontSendEventTo: self
|
|
1687
|
+
});
|
|
1688
|
+
};
|
|
1689
|
+
|
|
1690
|
+
// #clearGraphFlag {{{2
|
|
1691
|
+
|
|
1692
|
+
AggregateControl.prototype.clearGraphFlag = function () {
|
|
1693
|
+
var self = this;
|
|
1694
|
+
|
|
1695
|
+
_.each(self.controlFields, function (cf) {
|
|
1696
|
+
cf.shouldGraph = false;
|
|
1697
|
+
});
|
|
1698
|
+
};
|
|
1699
|
+
|
|
1700
|
+
// #triggerAggChange (PROTOTYPE) {{{2
|
|
1701
|
+
|
|
1702
|
+
/**
|
|
1703
|
+
* Perform necessary actions when the aggregate function is changed.
|
|
1704
|
+
*
|
|
1705
|
+
* - Update the UI to show/hide field argument.
|
|
1706
|
+
*/
|
|
1707
|
+
|
|
1708
|
+
AggregateControl.prototype.triggerAggChange = function () {
|
|
1709
|
+
var self = this;
|
|
1710
|
+
var agg = AGGREGATE_REGISTRY.get(self.ui.funDropdown.val());
|
|
1711
|
+
|
|
1712
|
+
if (agg.prototype.fieldCount > self.ui.fields.length) {
|
|
1713
|
+
self.addFieldDropdowns(agg);
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
self.showHideFields(agg);
|
|
1717
|
+
|
|
1718
|
+
var aggSpec = objFromArray(['group', 'pivot', 'cell', 'all'], [[{
|
|
1719
|
+
fun: self.ui.funDropdown.val(),
|
|
1720
|
+
fields: agg.prototype.fieldCount > 0 && mapLimit(self.ui.fields, function (f) {
|
|
1721
|
+
return f.dropdown.val();
|
|
1722
|
+
}, agg.prototype.fieldCount)
|
|
1723
|
+
}]]);
|
|
1724
|
+
var i;
|
|
1725
|
+
var div;
|
|
1726
|
+
|
|
1727
|
+
self.view.setAggregate(aggSpec, {
|
|
1728
|
+
dontSendEventTo: self
|
|
1729
|
+
});
|
|
1730
|
+
};
|
|
1731
|
+
|
|
1732
|
+
// #showHideFields (PROTOTYPE) {{{2
|
|
1733
|
+
|
|
1734
|
+
AggregateControl.prototype.showHideFields = function (agg) {
|
|
1735
|
+
var self = this;
|
|
1736
|
+
var i;
|
|
1737
|
+
|
|
1738
|
+
for (i = 0; i < self.ui.fields.length; i += 1) {
|
|
1739
|
+
if (i < agg.prototype.fieldCount) {
|
|
1740
|
+
self.ui.fields[i].div.show();
|
|
1741
|
+
}
|
|
1742
|
+
else {
|
|
1743
|
+
self.ui.fields[i].div.hide();
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
};
|
|
1747
|
+
|
|
1748
|
+
// #addFieldDropdowns (PROTOTYPE) {{{2
|
|
1749
|
+
|
|
1750
|
+
/**
|
|
1751
|
+
* For each field that an aggregate function requires, add a dropdown for it to the user interface.
|
|
1752
|
+
* This is used by some prototype code that allows changing the aggregate function dynamically. If
|
|
1753
|
+
* the new aggregate function needs more fields than the old one (e.g. going from "count" to "sum")
|
|
1754
|
+
* then this function adds the extra UI elements needed to get those fields from the user.
|
|
1755
|
+
*/
|
|
1756
|
+
|
|
1757
|
+
AggregateControl.prototype.addFieldDropdowns = function (agg) {
|
|
1758
|
+
var self = this;
|
|
1759
|
+
|
|
1760
|
+
self.logDebug(self.makeLogTag() + ' Adding %s extra field dropdowns for the %s aggregate function',
|
|
1761
|
+
self.grid.toString(), agg.prototype.fieldCount - self.ui.fields.length, agg.prototype.name);
|
|
1762
|
+
|
|
1763
|
+
// Create the extra dropdowns that we need to get all the fields required by the aggregate
|
|
1764
|
+
// function selected.
|
|
1765
|
+
|
|
1766
|
+
while (self.ui.fields.length < agg.prototype.fieldCount) {
|
|
1767
|
+
var x = {};
|
|
1768
|
+
x.div = jQuery('<div>').css({'margin-top': '4px'}).appendTo(self.ui.root);
|
|
1769
|
+
x.label = jQuery('<label>').text(trans('GRID_CONTROL.FIELD') + ':').appendTo(x.div);
|
|
1770
|
+
x.dropdown = jQuery('<select>').on('change', function () { self.triggerAggChange(); }).appendTo(x.div);
|
|
1771
|
+
self.ui.fields.push(x);
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
self.updateFieldDropdowns();
|
|
1775
|
+
};
|
|
1776
|
+
|
|
1777
|
+
// #updateFieldDropdowns (PROTOTYPE) {{{2
|
|
1778
|
+
|
|
1779
|
+
/**
|
|
1780
|
+
* Populate the field dropdowns with the list of fields that are available in the view. This is
|
|
1781
|
+
* used by prototype code that allows changing the aggregate function dynamically.
|
|
1782
|
+
*/
|
|
1783
|
+
|
|
1784
|
+
AggregateControl.prototype.updateFieldDropdowns = function () {
|
|
1785
|
+
var self = this;
|
|
1786
|
+
|
|
1787
|
+
// Clear out the fields that are already in the dropdown (in case anything was removed, and to
|
|
1788
|
+
// prevent duplicates from being added).
|
|
1789
|
+
|
|
1790
|
+
_.each(self.ui.fields, function (f) {
|
|
1791
|
+
f.dropdown.children().remove();
|
|
1792
|
+
});
|
|
1793
|
+
|
|
1794
|
+
// Add <OPTION> elements for all the fields.
|
|
1795
|
+
|
|
1796
|
+
_.each(determineColumns(self.colConfig, null, self.typeInfo), function (fieldName) {
|
|
1797
|
+
var text = getProp(self.colConfig.get(fieldName), 'displayText') || fieldName;
|
|
1798
|
+
_.each(self.ui.fields, function (f) {
|
|
1799
|
+
jQuery('<option>', { 'value': fieldName }).text(text).appendTo(f.dropdown);
|
|
1800
|
+
});
|
|
1801
|
+
});
|
|
1802
|
+
};
|
|
1803
|
+
|
|
1804
|
+
// #toString {{{2
|
|
1805
|
+
|
|
1806
|
+
AggregateControl.prototype.toString = function () {
|
|
1807
|
+
var self = this;
|
|
1808
|
+
|
|
1809
|
+
return self.grid.id + ', Aggregate';
|
|
1810
|
+
};
|
|
1811
|
+
|
|
1812
|
+
// FilterControl {{{1
|
|
1813
|
+
|
|
1814
|
+
// Constructor {{{2
|
|
1815
|
+
|
|
1816
|
+
/**
|
|
1817
|
+
* Part of the user interface which lets users filter columns.
|
|
1818
|
+
*
|
|
1819
|
+
* @param {object} defn
|
|
1820
|
+
*
|
|
1821
|
+
* @param {ComputedView} view
|
|
1822
|
+
*
|
|
1823
|
+
* @param {Grid~Features} features
|
|
1824
|
+
*
|
|
1825
|
+
* @param {object} timing
|
|
1826
|
+
*
|
|
1827
|
+
* @class
|
|
1828
|
+
* @extends GridControl
|
|
1829
|
+
*/
|
|
1830
|
+
|
|
1831
|
+
var FilterControl = makeSubclass('FilterControl', GridControl, function () {
|
|
1832
|
+
var self = this;
|
|
1833
|
+
|
|
1834
|
+
self.super['GridControl'].ctor.apply(self, arguments);
|
|
1835
|
+
self.gfs = new GridFilterSet(self.view, null, null, null, {
|
|
1836
|
+
dontSendEventTo: self
|
|
1837
|
+
});
|
|
1838
|
+
}, {
|
|
1839
|
+
isReorderable: false,
|
|
1840
|
+
disableUsedItems: true,
|
|
1841
|
+
controlFieldCtor: FilterControlField,
|
|
1842
|
+
controlType: 'Filter'
|
|
1843
|
+
});
|
|
1844
|
+
|
|
1845
|
+
// #draw {{{2
|
|
1846
|
+
|
|
1847
|
+
/**
|
|
1848
|
+
* Create a DIV element that can be placed within the Grid instance to hold the user interface for
|
|
1849
|
+
* the FilterControl. The caller must add the result to the DOM somewhere.
|
|
1850
|
+
*
|
|
1851
|
+
* @returns {jQuery} The DIV element that holds the entire UI.
|
|
1852
|
+
*/
|
|
1853
|
+
|
|
1854
|
+
FilterControl.prototype.draw = function (parent) {
|
|
1855
|
+
var self = this;
|
|
1856
|
+
|
|
1857
|
+
/*
|
|
1858
|
+
parent.resizable({
|
|
1859
|
+
handles: 'e',
|
|
1860
|
+
minWidth: 100
|
|
1861
|
+
});
|
|
1862
|
+
*/
|
|
1863
|
+
|
|
1864
|
+
parent.droppable({
|
|
1865
|
+
classes: {
|
|
1866
|
+
'ui-droppable-hover': 'wcdv_drop_target_hover'
|
|
1867
|
+
},
|
|
1868
|
+
drop: function (evt, ui) {
|
|
1869
|
+
// Turn this off for the sake of efficiency.
|
|
1870
|
+
//ui.draggable.draggable('option', 'refreshPositions', false);
|
|
1871
|
+
var field = ui.draggable.attr('data-wcdv-field');
|
|
1872
|
+
|
|
1873
|
+
self.addField(field, getProp(self.colConfig.get(field), 'displayText'));
|
|
1874
|
+
}
|
|
1875
|
+
});
|
|
1876
|
+
|
|
1877
|
+
self.ui.root = jQuery('<div>').appendTo(parent);
|
|
1878
|
+
self.ui.title = jQuery('<div>')
|
|
1879
|
+
.addClass('wcdv_control_title_bar')
|
|
1880
|
+
.appendTo(self.ui.root);
|
|
1881
|
+
jQuery('<span>', { 'class': 'wcdv_control_title' })
|
|
1882
|
+
.text(trans('GRID_CONTROL.FILTER.TITLE'))
|
|
1883
|
+
.appendTo(self.ui.title);
|
|
1884
|
+
self.ui.clearBtn = self.makeClearButton(self.ui.title);
|
|
1885
|
+
self.ui.fields = jQuery('<ul>', {
|
|
1886
|
+
id: gensym(),
|
|
1887
|
+
'class': self.isHorizontal ? 'wcdv_control_horizontal' : 'wcdv_control_vertical'
|
|
1888
|
+
}).appendTo(self.ui.root);
|
|
1889
|
+
|
|
1890
|
+
var dropdownContainer = jQuery('<div>').appendTo(self.ui.root);
|
|
1891
|
+
self.ui.dropdown = jQuery('<select>', { 'class': 'wcdv_control_addField' }).appendTo(dropdownContainer);
|
|
1892
|
+
self.ui.dropdown.on('change', function () {
|
|
1893
|
+
self.addField(self.ui.dropdown.val(), self.ui.dropdown.find('option:selected').text());
|
|
1894
|
+
});
|
|
1895
|
+
|
|
1896
|
+
self.addViewConfigChangeHandler('filterSet', function () {
|
|
1897
|
+
var spec = self.view.getFilter();
|
|
1898
|
+
self.logDebug(self.makeLogTag() + 'View set filter to: %s', self.grid.toString(), JSON.stringify(spec));
|
|
1899
|
+
self.clear({ updateView: false });
|
|
1900
|
+
_.each(spec, function (fieldSpec, field) {
|
|
1901
|
+
self.addField(field, getProp(self.colConfig.get(field), 'displayText'), { updateView: false });
|
|
1902
|
+
self.gfs.set(field, fieldSpec, { updateView: false });
|
|
1903
|
+
});
|
|
1904
|
+
});
|
|
1905
|
+
|
|
1906
|
+
return self.ui.root;
|
|
1907
|
+
};
|
|
1908
|
+
|
|
1909
|
+
// #addField {{{2
|
|
1910
|
+
|
|
1911
|
+
FilterControl.prototype.addField = function (field, displayText, opts) {
|
|
1912
|
+
var self = this;
|
|
1913
|
+
|
|
1914
|
+
self.super['GridControl'].addField(field, displayText || getProp(self.colConfig.get(field), 'displayText'), opts);
|
|
1915
|
+
};
|
|
1916
|
+
|
|
1917
|
+
// #removeField {{{2
|
|
1918
|
+
|
|
1919
|
+
FilterControl.prototype.removeField = function (cf) {
|
|
1920
|
+
var self = this;
|
|
1921
|
+
|
|
1922
|
+
self.gfs.removeField(cf.field.field);
|
|
1923
|
+
self.super['GridControl'].removeField(cf);
|
|
1924
|
+
};
|
|
1925
|
+
|
|
1926
|
+
// #clear {{{2
|
|
1927
|
+
|
|
1928
|
+
FilterControl.prototype.clear = function (opts) {
|
|
1929
|
+
var self = this;
|
|
1930
|
+
|
|
1931
|
+
self.gfs.reset(opts);
|
|
1932
|
+
self.super['GridControl'].clear(opts);
|
|
1933
|
+
};
|
|
1934
|
+
|
|
1935
|
+
// #updateView {{{2
|
|
1936
|
+
|
|
1937
|
+
FilterControl.prototype.updateView = function () {
|
|
1938
|
+
// NOTE This function intentionally does nothing!
|
|
1939
|
+
// It overrides the behavior of the superclass' method.
|
|
1940
|
+
};
|
|
1941
|
+
|
|
1942
|
+
// #toString {{{2
|
|
1943
|
+
|
|
1944
|
+
FilterControl.prototype.toString = function () {
|
|
1945
|
+
var self = this;
|
|
1946
|
+
|
|
1947
|
+
return self.grid.id + ', Filter';
|
|
1948
|
+
};
|
|
1949
|
+
|
|
1950
|
+
// Exports {{{1
|
|
1951
|
+
|
|
1952
|
+
export {
|
|
1953
|
+
FilterControl,
|
|
1954
|
+
GroupControl,
|
|
1955
|
+
PivotControl,
|
|
1956
|
+
AggregateControl,
|
|
1957
|
+
};
|