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.
Files changed (62) hide show
  1. package/LICENSE +45 -0
  2. package/README.md +129 -0
  3. package/datavis.js +101 -0
  4. package/dist/wcdatavis.css +1957 -0
  5. package/dist/wcdatavis.min.js +1 -0
  6. package/global-jquery.js +4 -0
  7. package/ie-fixes.js +13 -0
  8. package/index.js +70 -0
  9. package/meteor.js +1 -0
  10. package/package.json +102 -0
  11. package/src/flags.js +6 -0
  12. package/src/graph.js +1079 -0
  13. package/src/graph_renderer.js +85 -0
  14. package/src/grid.js +2777 -0
  15. package/src/grid_control.js +1957 -0
  16. package/src/grid_filter.js +1073 -0
  17. package/src/grid_renderer.js +276 -0
  18. package/src/group_fun_win.js +121 -0
  19. package/src/lang/en-US.js +188 -0
  20. package/src/lang/es-MX.js +188 -0
  21. package/src/lang/fr-FR.js +188 -0
  22. package/src/lang/id-ID.js +188 -0
  23. package/src/lang/nl-NL.js +188 -0
  24. package/src/lang/pt-BR.js +188 -0
  25. package/src/lang/ru-RU.js +188 -0
  26. package/src/lang/th-TH.js +188 -0
  27. package/src/lang/vi-VN.js +188 -0
  28. package/src/lang/zh-Hans-CN.js +188 -0
  29. package/src/operations_palette.js +176 -0
  30. package/src/prefs_modules.js +132 -0
  31. package/src/reg/graph_renderer.js +17 -0
  32. package/src/renderers/graph/chartjs.js +457 -0
  33. package/src/renderers/graph/google.js +584 -0
  34. package/src/renderers/graph/jit.js +61 -0
  35. package/src/renderers/graph/svelte-gantt.js +168 -0
  36. package/src/renderers/grid/dummy.js +79 -0
  37. package/src/renderers/grid/handlebars.js +217 -0
  38. package/src/renderers/grid/squirrelly.js +215 -0
  39. package/src/renderers/grid/table/group_detail.js +1404 -0
  40. package/src/renderers/grid/table/group_summary.js +380 -0
  41. package/src/renderers/grid/table/pivot.js +915 -0
  42. package/src/renderers/grid/table/plain.js +1592 -0
  43. package/src/renderers/grid/table.js +2510 -0
  44. package/src/trans.js +101 -0
  45. package/src/ui/collapsible.js +234 -0
  46. package/src/ui/filters/date.js +283 -0
  47. package/src/ui/grid_filter.js +398 -0
  48. package/src/ui/popup_menu.js +224 -0
  49. package/src/ui/popup_window.js +572 -0
  50. package/src/ui/slider.js +156 -0
  51. package/src/ui/tabs.js +202 -0
  52. package/src/ui/templates.js +131 -0
  53. package/src/ui/toolbar.js +63 -0
  54. package/src/ui/toolbars/grid.js +873 -0
  55. package/src/ui/windows/col_config.js +341 -0
  56. package/src/ui/windows/debug.js +164 -0
  57. package/src/ui/windows/grid_table_opts.js +139 -0
  58. package/src/util/handlebars.js +158 -0
  59. package/src/util/jquery.js +630 -0
  60. package/src/util/misc.js +1058 -0
  61. package/src/util/squirrelly.js +155 -0
  62. package/wcdatavis.css +1601 -0
@@ -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
+ }