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/util/misc.js
ADDED
|
@@ -0,0 +1,1058 @@
|
|
|
1
|
+
import jQuery from 'jquery';
|
|
2
|
+
import _ from 'underscore';
|
|
3
|
+
import sprintf from 'sprintf-js';
|
|
4
|
+
import JSONFormatter from 'json-formatter-js';
|
|
5
|
+
import { icons as lucideIcons } from 'lucide';
|
|
6
|
+
|
|
7
|
+
import { OrdMap, Lock, Util } from 'datavis-ace';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Partial application of a function. Returns a new function that is a version of the argument
|
|
11
|
+
* with some parameters already bound. Also called Schönfinkelization.
|
|
12
|
+
*
|
|
13
|
+
* @param {function} f The function to curry.
|
|
14
|
+
* @param {...any} args Arguments to bind in `f`.
|
|
15
|
+
*
|
|
16
|
+
* @returns {function} A function with free parameters corresponding to the parameters of `f`
|
|
17
|
+
* which weren't bound by `args`.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export function curry() {
|
|
21
|
+
var curryArgs = Array.prototype.slice.call(arguments);
|
|
22
|
+
var fn = curryArgs.shift();
|
|
23
|
+
var placeholderIndex = curryArgs.indexOf('#');
|
|
24
|
+
return function () {
|
|
25
|
+
var args = Array.prototype.slice.call(arguments);
|
|
26
|
+
var fnArgs = curryArgs.slice();
|
|
27
|
+
var spliceArgs = placeholderIndex === -1 ? [fnArgs.length, 0] : [placeholderIndex, 1];
|
|
28
|
+
Array.prototype.splice.apply(fnArgs, spliceArgs.concat(args));
|
|
29
|
+
return fn.apply(this, fnArgs);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Conversion {{{1
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @namespace util.conversion
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
export var parseNumber = (function () {
|
|
40
|
+
var re_number = new RegExp(/(^-?[1-9]{1}[0-9]{0,2}(,?\d{3})*(\.\d+)?(e[+-]?\d+)?$)|(^0(e[+-]?\d+)?$)|(^-?0?\.\d+(e[+-]?\d+)?$)/);
|
|
41
|
+
var re_comma = new RegExp(/,/g);
|
|
42
|
+
return function p(s, resultType) {
|
|
43
|
+
if (typeof s !== 'string') {
|
|
44
|
+
throw new Error('Call Error: `s` must be a string');
|
|
45
|
+
}
|
|
46
|
+
if (resultType != null && typeof resultType !== 'string') {
|
|
47
|
+
throw new Error('Call Error: `resultType` must be null or a string');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (resultType == null) {
|
|
51
|
+
resultType = 'number';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (['number', 'string'].indexOf(resultType) < 0) {
|
|
55
|
+
throw new Error('Call Error: `resultType` must be one of: ["number", "string"]');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (s.charAt(0) === '$') {
|
|
59
|
+
return p(s.substring(1));
|
|
60
|
+
}
|
|
61
|
+
else if (s.charAt(0) === '(' && s.charAt(-1) === ')') {
|
|
62
|
+
return p(s.substring(1, s.length - 1)) * -1;
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
return !re_number.test(s) ? null
|
|
66
|
+
: s.indexOf('.') >= 0 || s.indexOf('e') >= 0 ? (resultType === 'number' ? parseFloat : Util.I)(s.replace(re_comma, ''))
|
|
67
|
+
: (resultType === 'number' ? parseInt : Util.I)(s.replace(re_comma, ''));
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
})();
|
|
71
|
+
|
|
72
|
+
// Data Structures {{{1
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @namespace util.data_structures
|
|
76
|
+
*/
|
|
77
|
+
|
|
78
|
+
export function moveArrayElement(a, fromIdx, toIdx) {
|
|
79
|
+
var elt = a[fromIdx];
|
|
80
|
+
a.splice(fromIdx, 1);
|
|
81
|
+
a.splice(toIdx, 0, elt);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Map a function over an array, stopping after a preset number of elements.
|
|
86
|
+
*
|
|
87
|
+
* @memberof util.data_structures
|
|
88
|
+
* @inner
|
|
89
|
+
*
|
|
90
|
+
* @param {any[]} a
|
|
91
|
+
* An array of items.
|
|
92
|
+
*
|
|
93
|
+
* @param {function} f
|
|
94
|
+
* The function to map.
|
|
95
|
+
*
|
|
96
|
+
* @param {number} l
|
|
97
|
+
* Maximum number of elements to process.
|
|
98
|
+
*
|
|
99
|
+
* @return {any[]}
|
|
100
|
+
* An array of size `min(a.length, l)` containing the mapped results.
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
export function mapLimit(a, f, l) {
|
|
104
|
+
var result = [];
|
|
105
|
+
for (var i = 0; i < Math.min(a.length, l); i += 1) {
|
|
106
|
+
result.push(f(a[i], i));
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function mergeSort2(data, cmp) {
|
|
112
|
+
cmp = cmp || function (a, b) { return a < b; };
|
|
113
|
+
|
|
114
|
+
var merge = function (left, right) {
|
|
115
|
+
var result = []
|
|
116
|
+
, leftLen = left.length
|
|
117
|
+
, leftIdx = 0
|
|
118
|
+
, rightLen = right.length
|
|
119
|
+
, rightIdx = 0;
|
|
120
|
+
while (leftIdx < leftLen && rightIdx < rightLen) {
|
|
121
|
+
var cmpResult = cmp(left[leftIdx], right[rightIdx]);
|
|
122
|
+
result.push(cmpResult ? left[leftIdx++] : right[rightIdx++]);
|
|
123
|
+
}
|
|
124
|
+
return result.concat(leftIdx < leftLen ? left.slice(leftIdx) : right.slice(rightIdx));
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
if (data.length <= 1) {
|
|
128
|
+
return data;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
var pivot = Math.floor(data.length / 2)
|
|
132
|
+
, left = mergeSort2(data.slice(0, pivot), cmp)
|
|
133
|
+
, right = mergeSort2(data.slice(pivot), cmp);
|
|
134
|
+
return merge(left, right);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// HTML {{{1
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @namespace util.html
|
|
142
|
+
*/
|
|
143
|
+
|
|
144
|
+
// outerHtml {{{2
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Returns the HTML used to construct the argument.
|
|
148
|
+
*/
|
|
149
|
+
|
|
150
|
+
export function outerHtml(elt) {
|
|
151
|
+
return jQuery('<div>').append(elt).html();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// isVisible {{{2
|
|
155
|
+
|
|
156
|
+
export function isVisible(elt) {
|
|
157
|
+
return elt.css('display') !== 'none' && elt.css('visibility') === 'visible';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// isElement {{{2
|
|
161
|
+
|
|
162
|
+
export function isElement(x) {
|
|
163
|
+
return x instanceof Element || x instanceof jQuery;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// getElement {{{2
|
|
167
|
+
|
|
168
|
+
export function getElement(x) {
|
|
169
|
+
return x instanceof Element ? x
|
|
170
|
+
: x instanceof jQuery ? x.get(0)
|
|
171
|
+
: null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// isElementInViewport {{{2
|
|
175
|
+
|
|
176
|
+
/*
|
|
177
|
+
* Taken from --
|
|
178
|
+
* https://stackoverflow.com/a/7557433/5628
|
|
179
|
+
*/
|
|
180
|
+
|
|
181
|
+
export function isElementInViewport (parent, elt) {
|
|
182
|
+
if (elt instanceof jQuery) {
|
|
183
|
+
elt = elt.get(0);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
var eltRect = elt.getBoundingClientRect();
|
|
187
|
+
|
|
188
|
+
if (eltRect.top < 0 || eltRect.left < 0) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (parent !== window) {
|
|
193
|
+
if (parent instanceof Element) {
|
|
194
|
+
parent = jQuery(parent);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
var parentRect = parent.get(0).getBoundingClientRect();
|
|
198
|
+
//console.log('top=' + eltRect.top + ', ' +
|
|
199
|
+
// 'left=' + eltRect.left + ', ' +
|
|
200
|
+
// 'bottom=' + eltRect.bottom + ', ' +
|
|
201
|
+
// 'height=' + (parent.innerHeight() + parentRect.top));
|
|
202
|
+
return eltRect.bottom <= parent.innerHeight() + parentRect.top;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
//console.log('top=' + eltRect.top + ', ' +
|
|
206
|
+
// 'left=' + eltRect.left + ', ' +
|
|
207
|
+
// 'bottom=' + eltRect.bottom + ', ' +
|
|
208
|
+
// 'height=' + window.innerHeight);
|
|
209
|
+
return eltRect.bottom <= window.innerHeight;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// onVisibilityChange {{{2
|
|
214
|
+
|
|
215
|
+
export function onVisibilityChange(parent, elt, callback) {
|
|
216
|
+
var old_visible;
|
|
217
|
+
return function () {
|
|
218
|
+
var visible = isElementInViewport(parent, elt);
|
|
219
|
+
if (visible !== old_visible) {
|
|
220
|
+
if (old_visible !== undefined && typeof callback == 'function') {
|
|
221
|
+
callback(visible);
|
|
222
|
+
}
|
|
223
|
+
old_visible = visible;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// addFocusHandler {{{2
|
|
229
|
+
|
|
230
|
+
export function addFocusHandler(elt, id, cb) {
|
|
231
|
+
jQuery(document).on('click.focus-' + id, function (evt) {
|
|
232
|
+
if (jQuery(evt.target).closest(document).length === 0) {
|
|
233
|
+
// Clicked element no longer on the page; ignore! This could happen when another click
|
|
234
|
+
// handler on the element (e.g. a button) caused it to be removed (e.g. the clicked button
|
|
235
|
+
// closed a dialog). In this case, there's nothing for us to do.
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (jQuery(evt.target).closest(elt).length === 1) {
|
|
239
|
+
elt.addClass('wcdv-focus');
|
|
240
|
+
cb(true);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
elt.removeClass('wcdv-focus');
|
|
244
|
+
cb(false);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// removeFocusHandler {{{2
|
|
250
|
+
|
|
251
|
+
export function removeFocusHandler(id) {
|
|
252
|
+
jQuery(document).off('click.focus-' + id);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// icon {{{2
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Convert a kebab-case Lucide icon name to PascalCase for lookup in the icons object.
|
|
259
|
+
*
|
|
260
|
+
* @param {string} name
|
|
261
|
+
* @returns {string}
|
|
262
|
+
*/
|
|
263
|
+
|
|
264
|
+
function lucideNameToPascal(name) {
|
|
265
|
+
return name.split('-').map(function (part) {
|
|
266
|
+
return part.charAt(0).toUpperCase() + part.slice(1);
|
|
267
|
+
}).join('');
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
var SVG_NS = 'http://www.w3.org/2000/svg';
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Create an SVG element from Lucide icon data.
|
|
274
|
+
*
|
|
275
|
+
* @param {string} name Lucide icon name in kebab-case.
|
|
276
|
+
* @returns {SVGSVGElement|null} The SVG element, or null if the icon was not found.
|
|
277
|
+
*/
|
|
278
|
+
|
|
279
|
+
export function createLucideSvg(name) {
|
|
280
|
+
var pascalName = lucideNameToPascal(name);
|
|
281
|
+
var iconData = lucideIcons[pascalName]; // eslint-disable-line import/namespace
|
|
282
|
+
|
|
283
|
+
if (!iconData) {
|
|
284
|
+
console.warn('[DataVis] Unknown Lucide icon: ' + name + ' (looked up as ' + pascalName + ')');
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
var svg = document.createElementNS(SVG_NS, 'svg');
|
|
289
|
+
svg.setAttribute('xmlns', SVG_NS);
|
|
290
|
+
svg.setAttribute('width', '24');
|
|
291
|
+
svg.setAttribute('height', '24');
|
|
292
|
+
svg.setAttribute('viewBox', '0 0 24 24');
|
|
293
|
+
svg.setAttribute('fill', 'none');
|
|
294
|
+
svg.setAttribute('stroke', 'currentColor');
|
|
295
|
+
svg.setAttribute('stroke-width', '2');
|
|
296
|
+
svg.setAttribute('stroke-linecap', 'round');
|
|
297
|
+
svg.setAttribute('stroke-linejoin', 'round');
|
|
298
|
+
|
|
299
|
+
for (var i = 0; i < iconData.length; i++) {
|
|
300
|
+
var tag = iconData[i][0];
|
|
301
|
+
var attrs = iconData[i][1];
|
|
302
|
+
var child = document.createElementNS(SVG_NS, tag);
|
|
303
|
+
for (var key in attrs) {
|
|
304
|
+
if (Object.prototype.hasOwnProperty.call(attrs, key)) {
|
|
305
|
+
child.setAttribute(key, attrs[key]);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
svg.appendChild(child);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return svg;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Create an icon element wrapped in jQuery.
|
|
316
|
+
*
|
|
317
|
+
* Accepts either a Lucide icon name (kebab-case, e.g. 'check') or a FontAwesome icon name
|
|
318
|
+
* (e.g. 'fa-check'), which will be mapped to its Lucide equivalent.
|
|
319
|
+
*
|
|
320
|
+
* @param {string} icon The icon name.
|
|
321
|
+
* @param {string} [cls] Additional CSS classes to add.
|
|
322
|
+
* @param {string} [title] A title/tooltip for the icon.
|
|
323
|
+
* @returns {jQuery} A jQuery-wrapped SVG element.
|
|
324
|
+
*/
|
|
325
|
+
|
|
326
|
+
export function icon(icon, cls, title) {
|
|
327
|
+
var svg = createLucideSvg(icon);
|
|
328
|
+
if (!svg) {
|
|
329
|
+
// Fallback: create an empty span so callers don't break.
|
|
330
|
+
return jQuery('<span>');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
svg.classList.add('wcdv_icon');
|
|
334
|
+
svg.setAttribute('data-icon', icon);
|
|
335
|
+
|
|
336
|
+
if (cls != null) {
|
|
337
|
+
_.each(cls, function (c) {
|
|
338
|
+
svg.classList.add(c);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (title != undefined) {
|
|
343
|
+
svg.setAttribute('title', title);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return jQuery(svg);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Create a FontAwesome icon element (for backward compatibility with external consumers
|
|
351
|
+
* that set iconType: 'fontawesome' on their operations).
|
|
352
|
+
*
|
|
353
|
+
* @param {string} faIcon The FA icon class name (e.g. 'fa-check').
|
|
354
|
+
* @param {string} [cls] Additional CSS classes.
|
|
355
|
+
* @param {string} [title] A title/tooltip for the icon.
|
|
356
|
+
* @returns {jQuery}
|
|
357
|
+
*/
|
|
358
|
+
|
|
359
|
+
export function fontAwesome(icon, cls, title) {
|
|
360
|
+
var span = jQuery('<span>')
|
|
361
|
+
.addClass('fa');
|
|
362
|
+
|
|
363
|
+
if (icon.substr(0, 3) === 'fa-') {
|
|
364
|
+
span.addClass(icon);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (cls != undefined) {
|
|
368
|
+
span.addClass(cls);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (title != undefined) {
|
|
372
|
+
span.attr('title', title);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return span;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// loadScript {{{2
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* @function loadScript
|
|
382
|
+
* @description
|
|
383
|
+
*
|
|
384
|
+
* Dynamically load JavaScript from a URL into the page.
|
|
385
|
+
*
|
|
386
|
+
* Here's an example of using the `needAsyncSetup` option:
|
|
387
|
+
*
|
|
388
|
+
* ```
|
|
389
|
+
* return loadScript('https://www.gstatic.com/charts/loader.js', function (wasAlreadyLoaded, k) {
|
|
390
|
+
* if (!wasAlreadyLoaded) {
|
|
391
|
+
* google.charts.load('current', {'packages':['corechart']});
|
|
392
|
+
* google.charts.setOnLoadCallback(k);
|
|
393
|
+
* }
|
|
394
|
+
* else {
|
|
395
|
+
* k();
|
|
396
|
+
* }
|
|
397
|
+
* }, {
|
|
398
|
+
* needAsyncSetup: true
|
|
399
|
+
* });
|
|
400
|
+
* ```
|
|
401
|
+
*
|
|
402
|
+
* Calling `k()` *must* be done to properly unlock the loading code (only one file is loaded at a
|
|
403
|
+
* time) but only after everything is fully set up.
|
|
404
|
+
*
|
|
405
|
+
* @param {string} url
|
|
406
|
+
* The URL to load a script file from.
|
|
407
|
+
*
|
|
408
|
+
* @param {function} callback
|
|
409
|
+
* A function that receives at least one argument, a boolean which is true if the script was already
|
|
410
|
+
* loaded in the page. The callback will not be called until the browser has finished executing the
|
|
411
|
+
* script, so it can safely use anything that the script provides. If the callback needs to perform
|
|
412
|
+
* any additional setup before the loading is considered "complete" then use the `needAsyncSetup`
|
|
413
|
+
* option as shown below.
|
|
414
|
+
*
|
|
415
|
+
* @param {object} [opts]
|
|
416
|
+
* Additional options (see below).
|
|
417
|
+
*
|
|
418
|
+
* @param {boolean} [opts.needAsyncSetup = false]
|
|
419
|
+
* If true, then the callback function receives an additional argument, another function *which it
|
|
420
|
+
* must call* when finished. This is specifically to support Google's JS API "loader" script, which
|
|
421
|
+
* requires additional (asynchronous) setup.
|
|
422
|
+
*/
|
|
423
|
+
|
|
424
|
+
export var loadScript = (function () {
|
|
425
|
+
var alreadyLoaded = {};
|
|
426
|
+
var lock = new Lock('LOAD SCRIPT');
|
|
427
|
+
return function (url, callback, opts) {
|
|
428
|
+
_.defaults(opts, {
|
|
429
|
+
needAsyncSetup: false
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// https://stackoverflow.com/a/950146
|
|
433
|
+
|
|
434
|
+
var load = function (url, callback) {
|
|
435
|
+
// Adding the script tag to the head as suggested before
|
|
436
|
+
var head = document.getElementsByTagName('head')[0];
|
|
437
|
+
var script = document.createElement('script');
|
|
438
|
+
script.type = 'text/javascript';
|
|
439
|
+
script.src = url;
|
|
440
|
+
|
|
441
|
+
// Then bind the event to the callback function.
|
|
442
|
+
// There are several events for cross browser compatibility.
|
|
443
|
+
script.onreadystatechange = callback;
|
|
444
|
+
script.onload = callback;
|
|
445
|
+
|
|
446
|
+
// Fire the loading
|
|
447
|
+
head.appendChild(script);
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
var makeCb = function (isAlreadyLoaded) {
|
|
451
|
+
var showLoadMsg = function () {
|
|
452
|
+
if (isAlreadyLoaded) {
|
|
453
|
+
console.debug('[DataVis // Load Script] [url = %s] Already loaded', url);
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
console.debug('[DataVis // Load Script] [url = %s] Finished executing loaded script', url);
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
if (opts.needAsyncSetup) {
|
|
461
|
+
return function () {
|
|
462
|
+
showLoadMsg();
|
|
463
|
+
callback(isAlreadyLoaded, function () {
|
|
464
|
+
console.debug('[DataVis // Load Script] [url = %s] Exiting control of the script loader', url);
|
|
465
|
+
if (!isAlreadyLoaded) {
|
|
466
|
+
alreadyLoaded[url] = true;
|
|
467
|
+
lock.unlock();
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
return function () {
|
|
474
|
+
showLoadMsg();
|
|
475
|
+
console.debug('[DataVis // Load Script] [url = %s] Exiting control of the script loader', url);
|
|
476
|
+
if (!isAlreadyLoaded) {
|
|
477
|
+
alreadyLoaded[url] = true;
|
|
478
|
+
lock.unlock();
|
|
479
|
+
}
|
|
480
|
+
callback(isAlreadyLoaded);
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
lock.onUnlock(function () {
|
|
486
|
+
if (alreadyLoaded[url]) {
|
|
487
|
+
makeCb(true)();
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
lock.lock();
|
|
491
|
+
load(url, makeCb(false));
|
|
492
|
+
}
|
|
493
|
+
}, sprintf.sprintf('Waiting to load [url = %s]', url));
|
|
494
|
+
};
|
|
495
|
+
})();
|
|
496
|
+
|
|
497
|
+
// setTableCell {{{2
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Set the value of a table cell.
|
|
501
|
+
*
|
|
502
|
+
* @param {jQuery|HTMLTableCellElement} cell
|
|
503
|
+
* @param {Element|jQuery|string|number} value
|
|
504
|
+
* @param {object} opts
|
|
505
|
+
* @param {string} opts.field
|
|
506
|
+
* @param {OrdMap} opts.colConfig
|
|
507
|
+
* @param {OrdMap} opts.typeInfo
|
|
508
|
+
*/
|
|
509
|
+
|
|
510
|
+
export function setTableCell(cell, value, opts) {
|
|
511
|
+
opts = opts || {};
|
|
512
|
+
|
|
513
|
+
var fcc = (opts.colConfig instanceof OrdMap && opts.colConfig.get(opts.field)) || opts.colConfig || {};
|
|
514
|
+
var fti = (opts.typeInfo instanceof OrdMap && opts.typeInfo.get(opts.field)) || opts.typeInfo || {};
|
|
515
|
+
var ops = opts.operations || [];
|
|
516
|
+
|
|
517
|
+
if (cell instanceof jQuery) {
|
|
518
|
+
cell = cell.get(0);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (!(cell instanceof HTMLTableCellElement)) {
|
|
522
|
+
throw new Error('Call Error: `cell` must be a HTMLTableCellElement instance');
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
var container = cell
|
|
526
|
+
, wrapper
|
|
527
|
+
, operationDiv;
|
|
528
|
+
|
|
529
|
+
if (fcc.maxHeight != null && value !== '') {
|
|
530
|
+
wrapper = document.createElement('div');
|
|
531
|
+
wrapper.classList.add('wcdv_maxheight_wrapper');
|
|
532
|
+
wrapper.style.maxHeight = fcc.maxHeight;
|
|
533
|
+
|
|
534
|
+
if (fcc.width) {
|
|
535
|
+
wrapper.classList.add('wcdv_maxheight_wrapper_withwidth');
|
|
536
|
+
wrapper.style.width = fcc.width;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
var showValueBtn = document.createElement('button');
|
|
540
|
+
showValueBtn.setAttribute('title', 'Full value has been truncated; click to show it.');
|
|
541
|
+
showValueBtn.classList.add('wcdv_icon_button');
|
|
542
|
+
showValueBtn.classList.add('wcdv_icon_button_incell');
|
|
543
|
+
showValueBtn.classList.add('wcdv_icon_button_nolabel');
|
|
544
|
+
showValueBtn.classList.add('wcdv_show_full_value');
|
|
545
|
+
|
|
546
|
+
var showValueIcon = createLucideSvg('asterisk');
|
|
547
|
+
showValueIcon.classList.add('wcdv_icon');
|
|
548
|
+
showValueIcon.setAttribute('data-icon', 'asterisk');
|
|
549
|
+
|
|
550
|
+
operationDiv = document.createElement('div');
|
|
551
|
+
operationDiv.style.display = 'inline-block';
|
|
552
|
+
operationDiv.style.float = 'right';
|
|
553
|
+
|
|
554
|
+
_.each(ops, function (op, index) {
|
|
555
|
+
operationDiv.appendChild(makeOperationButton('cell', op, index, {inCell: true}));
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
container = document.createElement('div');
|
|
559
|
+
|
|
560
|
+
// cell (td)
|
|
561
|
+
// wrapper (div)
|
|
562
|
+
// showValueBtn (button)
|
|
563
|
+
// showValueIcon (svg.wcdv_icon)
|
|
564
|
+
// operationDiv (div)
|
|
565
|
+
// (button) (button) (button) ...
|
|
566
|
+
// container (div) <-- holds the actual data value
|
|
567
|
+
|
|
568
|
+
cell.appendChild(wrapper);
|
|
569
|
+
wrapper.appendChild(showValueBtn);
|
|
570
|
+
showValueBtn.appendChild(showValueIcon);
|
|
571
|
+
wrapper.appendChild(operationDiv);
|
|
572
|
+
wrapper.appendChild(container);
|
|
573
|
+
}
|
|
574
|
+
else if (ops.length > 0) {
|
|
575
|
+
wrapper = document.createElement('div');
|
|
576
|
+
|
|
577
|
+
operationDiv = document.createElement('div');
|
|
578
|
+
operationDiv.style.display = 'inline-block';
|
|
579
|
+
operationDiv.style.float = 'right';
|
|
580
|
+
|
|
581
|
+
_.each(ops, function (op, index) {
|
|
582
|
+
var opBtn = makeOperationButton('cell', op, index, {inCell: true});
|
|
583
|
+
if (op.disableWhen && op.disableWhen(value)) {
|
|
584
|
+
opBtn.disabled = true;
|
|
585
|
+
}
|
|
586
|
+
if (op.hideWhen && op.hideWhen(value)) {
|
|
587
|
+
opBtn.style.display = 'none';
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
operationDiv.appendChild(opBtn);
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
container = document.createElement('div');
|
|
594
|
+
|
|
595
|
+
// cell (td)
|
|
596
|
+
// wrapper (div)
|
|
597
|
+
// operationDiv (div)
|
|
598
|
+
// (button) (button) (button) ...
|
|
599
|
+
// container (div) <-- holds the actual data value
|
|
600
|
+
|
|
601
|
+
cell.appendChild(wrapper);
|
|
602
|
+
wrapper.appendChild(operationDiv);
|
|
603
|
+
wrapper.appendChild(container);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
setElement(container, value, opts);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// setElement {{{2
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Set the value of an element.
|
|
613
|
+
*
|
|
614
|
+
* @param {jQuery|Element} container
|
|
615
|
+
* @param {Element|jQuery|string|number} value
|
|
616
|
+
* @param {object} opts
|
|
617
|
+
* @param {string} opts.field
|
|
618
|
+
* @param {OrdMap} opts.colConfig
|
|
619
|
+
* @param {OrdMap} opts.typeInfo
|
|
620
|
+
*/
|
|
621
|
+
|
|
622
|
+
export function setElement(container, value, opts) {
|
|
623
|
+
opts = opts || {};
|
|
624
|
+
|
|
625
|
+
var fcc = (opts.colConfig instanceof OrdMap && opts.colConfig.get(opts.field)) || opts.colConfig || {};
|
|
626
|
+
var fti = (opts.typeInfo instanceof OrdMap && opts.typeInfo.get(opts.field)) || opts.typeInfo || {};
|
|
627
|
+
|
|
628
|
+
if (container instanceof jQuery) {
|
|
629
|
+
container = container.get(0);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (!(container instanceof Element)) {
|
|
633
|
+
throw new Error('Call Error: `container` must be an Element instance');
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (value instanceof Element) {
|
|
637
|
+
container.appendChild(value);
|
|
638
|
+
}
|
|
639
|
+
else if (value instanceof jQuery) {
|
|
640
|
+
container.appendChild(value.get(0));
|
|
641
|
+
}
|
|
642
|
+
else if (fcc.allowHtml && fti.type === 'string') {
|
|
643
|
+
container.innerHTML = value;
|
|
644
|
+
}
|
|
645
|
+
else if (value === '') {
|
|
646
|
+
container.innerText = '\u00A0';
|
|
647
|
+
}
|
|
648
|
+
else {
|
|
649
|
+
container.innerText = value;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// makeOperationButton {{{2
|
|
654
|
+
|
|
655
|
+
function makeOperationIcon(op) {
|
|
656
|
+
if (op.iconType === 'fontawesome') {
|
|
657
|
+
return fontAwesome(op.icon).get(0);
|
|
658
|
+
}
|
|
659
|
+
return icon(op.icon).get(0);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
export function makeOperationButton(type, op, index, opts) {
|
|
663
|
+
opts = opts || {};
|
|
664
|
+
|
|
665
|
+
_.defaults(opts, {
|
|
666
|
+
inCell: false
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
var btn = document.createElement('button');
|
|
670
|
+
btn.setAttribute('type', 'button');
|
|
671
|
+
btn.setAttribute('data-operation-type', type);
|
|
672
|
+
btn.setAttribute('data-operation-index', index);
|
|
673
|
+
btn.classList.add('wcdv_operation');
|
|
674
|
+
// Cell operations don't get labels, because they would take up too much space.
|
|
675
|
+
if (type === 'cell') {
|
|
676
|
+
btn.classList.add('wcdv_icon_button');
|
|
677
|
+
btn.classList.add('wcdv_icon_button_incell');
|
|
678
|
+
btn.classList.add('wcdv_icon_button_nolabel');
|
|
679
|
+
btn.style.float = 'initial';
|
|
680
|
+
btn.appendChild(makeOperationIcon(op));
|
|
681
|
+
}
|
|
682
|
+
else {
|
|
683
|
+
if (op.icon) {
|
|
684
|
+
btn.appendChild(makeOperationIcon(op));
|
|
685
|
+
}
|
|
686
|
+
if (op.label) {
|
|
687
|
+
btn.classList.add('wcdv_nowrap');
|
|
688
|
+
btn.append(op.label);
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
btn.classList.add('no_label');
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
if (op.tooltip) {
|
|
695
|
+
btn.setAttribute('title', op.tooltip);
|
|
696
|
+
}
|
|
697
|
+
return btn;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
// makeCheckbox {{{2
|
|
701
|
+
|
|
702
|
+
export function makeCheckbox(startChecked, onChange, text, parent) {
|
|
703
|
+
var label = jQuery('<label>');
|
|
704
|
+
var input = jQuery('<input>', { 'type': 'checkbox', 'checked': startChecked }).on('change', onChange);
|
|
705
|
+
|
|
706
|
+
label.append(input).append(text).appendTo(parent);
|
|
707
|
+
|
|
708
|
+
return input;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// makeToggleCheckbox {{{2
|
|
712
|
+
|
|
713
|
+
export function makeToggleCheckbox(rootObj, path, startChecked, text, parent, after) {
|
|
714
|
+
if (rootObj != null) {
|
|
715
|
+
Util.setPropDef(startChecked, rootObj, path);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
return makeCheckbox(rootObj != null ? Util.getProp(rootObj, path) : startChecked, function () {
|
|
719
|
+
var isChecked = jQuery(this).prop('checked');
|
|
720
|
+
if (rootObj != null) {
|
|
721
|
+
console.debug('[DataVis // Grid // Toolbar] Setting `' + path.join('.') + '` to ' + isChecked);
|
|
722
|
+
Util.setProp(isChecked, rootObj, path);
|
|
723
|
+
}
|
|
724
|
+
if (typeof after === 'function') {
|
|
725
|
+
after(isChecked);
|
|
726
|
+
}
|
|
727
|
+
}, text, parent);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
// makeRadioButtons {{{2
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* @typedef makeRadioButtons_values
|
|
734
|
+
*
|
|
735
|
+
* @property {string} label
|
|
736
|
+
*
|
|
737
|
+
* @property {string} value
|
|
738
|
+
*/
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* @param {Object} rootObj
|
|
742
|
+
* Object to update when radio button is selected.
|
|
743
|
+
*
|
|
744
|
+
* @param {string[]} path
|
|
745
|
+
* Path within the object to set the value of the selected radio button.
|
|
746
|
+
*
|
|
747
|
+
* @param {string} def
|
|
748
|
+
* Default value to set in the object.
|
|
749
|
+
*
|
|
750
|
+
* @param {string} [label]
|
|
751
|
+
* Label to put before the group of radio buttons.
|
|
752
|
+
*
|
|
753
|
+
* @param {string} name
|
|
754
|
+
* Name of the form variable.
|
|
755
|
+
*
|
|
756
|
+
* @param {Array.<string|makeRadioButtons_values>} values
|
|
757
|
+
* Possible values to create radio buttons for.
|
|
758
|
+
*
|
|
759
|
+
* @param {function} [conv]
|
|
760
|
+
* Pass selected value through this function to convert it (e.g. "true" -> 1).
|
|
761
|
+
*
|
|
762
|
+
* @param {function} [onChange]
|
|
763
|
+
* Function to call when the value is changed.
|
|
764
|
+
*
|
|
765
|
+
* @param {Element|jQuery} parent
|
|
766
|
+
* Element to place the radio buttons within.
|
|
767
|
+
*/
|
|
768
|
+
|
|
769
|
+
export function makeRadioButtons(rootObj, path, def, label, name, values, conv, onChange, parent) {
|
|
770
|
+
Util.setPropDef(def, rootObj, path);
|
|
771
|
+
var initial = Util.getProp(rootObj, path);
|
|
772
|
+
|
|
773
|
+
var root = jQuery('<div>').css('display', 'inline-block').appendTo(parent);
|
|
774
|
+
|
|
775
|
+
var handler = function () {
|
|
776
|
+
var selected = root.find('input[type=radio]:checked').val();
|
|
777
|
+
if (typeof conv === 'function') {
|
|
778
|
+
selected = conv(selected);
|
|
779
|
+
}
|
|
780
|
+
console.debug('[DataVis // Grid // Toolbar] Setting `' + path.join('.') + '` to ' + selected);
|
|
781
|
+
Util.setProp(selected, rootObj, path);
|
|
782
|
+
if (typeof onChange === 'function') {
|
|
783
|
+
onChange(selected);
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
|
|
787
|
+
if (label) {
|
|
788
|
+
jQuery('<label>').text(label).appendTo(root);
|
|
789
|
+
}
|
|
790
|
+
_.each(values, function (v) {
|
|
791
|
+
var label = _.isString(v) ? v : v.label;
|
|
792
|
+
var value = _.isString(v) ? v : v.value;
|
|
793
|
+
jQuery('<label>')
|
|
794
|
+
.append(jQuery('<input>', { 'type': 'radio', 'name': name, 'value': value })
|
|
795
|
+
.on('change', handler))
|
|
796
|
+
.append(label)
|
|
797
|
+
.appendTo(root);
|
|
798
|
+
});
|
|
799
|
+
root.find('input[type=radio]').val([initial]);
|
|
800
|
+
return root;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Initialize date and time format strings from user preferences. There doesn't seem to be a
|
|
804
|
+
// builtin way to convert the magick numbers into format strings, but since they're stored in the
|
|
805
|
+
// database it seems safe to assume that they won't change.
|
|
806
|
+
|
|
807
|
+
// Downloading {{{1
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Present a blob as a download. This works even in IE10!
|
|
811
|
+
*
|
|
812
|
+
* @param {Blob} blob
|
|
813
|
+
* The content to download.
|
|
814
|
+
*
|
|
815
|
+
* @param {string} fileName
|
|
816
|
+
* Default name to use for the file.
|
|
817
|
+
*/
|
|
818
|
+
|
|
819
|
+
export function presentDownload(blob, fileName) {
|
|
820
|
+
if (!(blob instanceof Blob)) {
|
|
821
|
+
throw new Error('Call Error: `blob` must be a Blob');
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// IE11 supports Blob, but doesn't allow you to fake a click on the download link. Fortunately
|
|
825
|
+
// for us, it has a function which does all of that for you in one step!
|
|
826
|
+
|
|
827
|
+
if (window.navigator.msSaveBlob != null) {
|
|
828
|
+
window.navigator.msSaveBlob(blob, fileName);
|
|
829
|
+
}
|
|
830
|
+
else {
|
|
831
|
+
var a = document.createElement('a');
|
|
832
|
+
a.download = fileName;
|
|
833
|
+
a.href = URL.createObjectURL(blob);
|
|
834
|
+
jQuery(document.body).append(a);
|
|
835
|
+
a.click();
|
|
836
|
+
a.remove();
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// https://stackoverflow.com/a/12300351
|
|
841
|
+
|
|
842
|
+
/**
|
|
843
|
+
* Convert a data URI to a blob which can be downloaded. This works even in IE10!
|
|
844
|
+
*
|
|
845
|
+
* @param {string} dataURI
|
|
846
|
+
* The URI to convert into a blob.
|
|
847
|
+
*
|
|
848
|
+
* @return {Blob}
|
|
849
|
+
* A blob that can be downloaded.
|
|
850
|
+
*/
|
|
851
|
+
|
|
852
|
+
export function dataURItoBlob(dataURI) {
|
|
853
|
+
// convert base64 to raw binary data held in a string
|
|
854
|
+
// doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this
|
|
855
|
+
var byteString = atob(dataURI.split(',')[1]);
|
|
856
|
+
|
|
857
|
+
// separate out the mime component
|
|
858
|
+
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
|
|
859
|
+
|
|
860
|
+
// write the bytes of the string to an ArrayBuffer
|
|
861
|
+
var ab = new ArrayBuffer(byteString.length);
|
|
862
|
+
|
|
863
|
+
// create a view into the buffer
|
|
864
|
+
var ia = new Uint8Array(ab);
|
|
865
|
+
|
|
866
|
+
// set the bytes of the buffer to the correct values
|
|
867
|
+
for (var i = 0; i < byteString.length; i++) {
|
|
868
|
+
ia[i] = byteString.charCodeAt(i);
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// write the ArrayBuffer to a blob, and you're done
|
|
872
|
+
var blob = new Blob([ab], {type: mimeString});
|
|
873
|
+
return blob;
|
|
874
|
+
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
export function ordmapAsHtmlDefnList(o) {
|
|
878
|
+
var dl = jQuery('<dl>');
|
|
879
|
+
o.each(function (v, k) {
|
|
880
|
+
var dt = jQuery('<dt>').text(k);
|
|
881
|
+
var dd = jQuery('<dd>');
|
|
882
|
+
if (v instanceof jQuery || v instanceof Element) {
|
|
883
|
+
dd.append(v);
|
|
884
|
+
}
|
|
885
|
+
else if (_.isObject(v)) {
|
|
886
|
+
dd.append(new JSONFormatter(v, 0).render());
|
|
887
|
+
}
|
|
888
|
+
else {
|
|
889
|
+
dd.text(v);
|
|
890
|
+
}
|
|
891
|
+
jQuery('<div>')
|
|
892
|
+
.append(dt)
|
|
893
|
+
.append(dd)
|
|
894
|
+
.appendTo(dl);
|
|
895
|
+
});
|
|
896
|
+
return dl;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// EagerPipeline {{{1
|
|
900
|
+
|
|
901
|
+
export var EagerPipeline = Util.makeSubclass('EagerPipeline', Object, function (x) {
|
|
902
|
+
this.x = x;
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
// #andThen {{{2
|
|
906
|
+
|
|
907
|
+
EagerPipeline.prototype.andThen = function (f) {
|
|
908
|
+
var x = this.x;
|
|
909
|
+
return new EagerPipeline(f(x));
|
|
910
|
+
};
|
|
911
|
+
|
|
912
|
+
// #andThenCurry {{{2
|
|
913
|
+
|
|
914
|
+
EagerPipeline.prototype.andThenCurry = function () {
|
|
915
|
+
var f = curry.apply(null, arguments);
|
|
916
|
+
return this.andThen(f);
|
|
917
|
+
};
|
|
918
|
+
|
|
919
|
+
// #done {{{2
|
|
920
|
+
|
|
921
|
+
EagerPipeline.prototype.done = function () {
|
|
922
|
+
return this.x;
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
// Re-export Util members from datavis-ace for API compatibility.
|
|
926
|
+
export var {
|
|
927
|
+
gensym,
|
|
928
|
+
I,
|
|
929
|
+
universalCmp,
|
|
930
|
+
getComparisonFn,
|
|
931
|
+
getNatRep,
|
|
932
|
+
car,
|
|
933
|
+
cdr,
|
|
934
|
+
isInt,
|
|
935
|
+
isFloat,
|
|
936
|
+
toInt,
|
|
937
|
+
toFloat,
|
|
938
|
+
stringValueType,
|
|
939
|
+
arrayCompare,
|
|
940
|
+
arrayEqual,
|
|
941
|
+
eachUntilObj,
|
|
942
|
+
asyncEach,
|
|
943
|
+
shallowCopy,
|
|
944
|
+
deepCopy,
|
|
945
|
+
arrayCopy,
|
|
946
|
+
isNothing,
|
|
947
|
+
isEmpty,
|
|
948
|
+
deepDefaults,
|
|
949
|
+
getProp,
|
|
950
|
+
getPropDef,
|
|
951
|
+
setProp,
|
|
952
|
+
setPropDef,
|
|
953
|
+
copyProps,
|
|
954
|
+
interleaveWith,
|
|
955
|
+
mergeSort4,
|
|
956
|
+
pigeonHoleSort,
|
|
957
|
+
objFromArray,
|
|
958
|
+
walkObj,
|
|
959
|
+
makeSubclass,
|
|
960
|
+
makeSuper,
|
|
961
|
+
mixinEventHandling,
|
|
962
|
+
mixinLogging,
|
|
963
|
+
makeSetters,
|
|
964
|
+
delegate,
|
|
965
|
+
mixinNameSetting,
|
|
966
|
+
escapeHtml,
|
|
967
|
+
logAsync,
|
|
968
|
+
format,
|
|
969
|
+
Timing,
|
|
970
|
+
getParamsFromUrl,
|
|
971
|
+
validateColConfig,
|
|
972
|
+
determineColumns,
|
|
973
|
+
uuid
|
|
974
|
+
} = Util;
|
|
975
|
+
|
|
976
|
+
// Polyfills {{{1
|
|
977
|
+
|
|
978
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
|
|
979
|
+
|
|
980
|
+
if (!String.prototype.repeat) {
|
|
981
|
+
String.prototype.repeat = function(count) {
|
|
982
|
+
'use strict';
|
|
983
|
+
if (this == null) { // check if `this` is null or undefined
|
|
984
|
+
throw new TypeError('can\'t convert ' + this + ' to object');
|
|
985
|
+
}
|
|
986
|
+
var str = '' + this;
|
|
987
|
+
// To convert string to integer.
|
|
988
|
+
count = +count;
|
|
989
|
+
if (count < 0) {
|
|
990
|
+
throw new RangeError('repeat count must be non-negative');
|
|
991
|
+
}
|
|
992
|
+
if (count == Infinity) {
|
|
993
|
+
throw new RangeError('repeat count must be less than infinity');
|
|
994
|
+
}
|
|
995
|
+
count |= 0; // floors and rounds-down it.
|
|
996
|
+
if (str.length == 0 || count == 0) {
|
|
997
|
+
return '';
|
|
998
|
+
}
|
|
999
|
+
// Ensuring count is a 31-bit integer allows us to heavily optimize the
|
|
1000
|
+
// main part. But anyway, most current (August 2014) browsers can't handle
|
|
1001
|
+
// strings 1 << 28 chars or longer, so:
|
|
1002
|
+
if (str.length * count >= (1 << 28)) {
|
|
1003
|
+
throw new RangeError('repeat count must not overflow maximum string size');
|
|
1004
|
+
}
|
|
1005
|
+
// eslint-disable-next-line no-cond-assign
|
|
1006
|
+
while (count >>= 1) { // shift it by multiple of 2 because this is binary summation of series
|
|
1007
|
+
str += str; // binary summation
|
|
1008
|
+
}
|
|
1009
|
+
str += str.substring(0, str.length * count - str.length);
|
|
1010
|
+
return str;
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith
|
|
1015
|
+
|
|
1016
|
+
if (!String.prototype.startsWith) {
|
|
1017
|
+
Object.defineProperty(String.prototype, 'startsWith', {
|
|
1018
|
+
value: function(search, rawPos) {
|
|
1019
|
+
var pos = rawPos > 0 ? rawPos|0 : 0;
|
|
1020
|
+
return this.substring(pos, pos + search.length) === search;
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith
|
|
1026
|
+
|
|
1027
|
+
if (!String.prototype.endsWith) {
|
|
1028
|
+
String.prototype.endsWith = function(search, this_len) {
|
|
1029
|
+
if (this_len === undefined || this_len > this.length) {
|
|
1030
|
+
this_len = this.length;
|
|
1031
|
+
}
|
|
1032
|
+
return this.substring(this_len - search.length, this_len) === search;
|
|
1033
|
+
};
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN
|
|
1037
|
+
|
|
1038
|
+
Number.isNaN = Number.isNaN || function(value) {
|
|
1039
|
+
return value !== value;
|
|
1040
|
+
};
|
|
1041
|
+
|
|
1042
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
|
|
1043
|
+
|
|
1044
|
+
if (!Element.prototype.matches) {
|
|
1045
|
+
Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
if (!Element.prototype.closest) {
|
|
1049
|
+
Element.prototype.closest = function(s) {
|
|
1050
|
+
var el = this;
|
|
1051
|
+
|
|
1052
|
+
do {
|
|
1053
|
+
if (el.matches(s)) return el;
|
|
1054
|
+
el = el.parentElement || el.parentNode;
|
|
1055
|
+
} while (el !== null && el.nodeType === 1);
|
|
1056
|
+
return null;
|
|
1057
|
+
};
|
|
1058
|
+
}
|