oxidized-web 0.13.1 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of oxidized-web might be problematic. Click here for more details.

Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.github/PULL_REQUEST_TEMPLATE.md +12 -0
  3. data/.github/dependabot.yml +25 -0
  4. data/.github/workflows/codeql.yml +76 -0
  5. data/.github/workflows/ruby.yml +41 -0
  6. data/.github/workflows/stale.yml +20 -0
  7. data/.gitignore +3 -0
  8. data/.rubocop.yml +38 -3
  9. data/.rubocop_todo.yml +27 -211
  10. data/CHANGELOG.md +46 -0
  11. data/README.md +8 -5
  12. data/Rakefile +49 -5
  13. data/docs/development.md +182 -0
  14. data/lib/oxidized/web/public/css/oxidized.css +59 -0
  15. data/lib/oxidized/web/public/scripts/oxidized.js +1 -48
  16. data/lib/oxidized/web/public/weblibs/bootstrap-icons.css +2078 -0
  17. data/lib/oxidized/web/public/weblibs/bootstrap.bundle.js +6314 -0
  18. data/lib/oxidized/web/public/weblibs/bootstrap.bundle.js.map +1 -0
  19. data/lib/oxidized/web/public/weblibs/bootstrap.css +12057 -0
  20. data/lib/oxidized/web/public/weblibs/bootstrap.css.map +1 -0
  21. data/lib/oxidized/web/public/weblibs/bootstrap.js +4494 -0
  22. data/lib/oxidized/web/public/weblibs/bootstrap.js.map +1 -0
  23. data/lib/oxidized/web/public/weblibs/buttons.bootstrap5.css +395 -0
  24. data/lib/oxidized/web/public/weblibs/buttons.bootstrap5.js +114 -0
  25. data/lib/oxidized/web/public/weblibs/buttons.colVis.js +256 -0
  26. data/lib/oxidized/web/public/weblibs/dataTables.bootstrap5.css +517 -0
  27. data/lib/oxidized/web/public/weblibs/dataTables.bootstrap5.js +122 -0
  28. data/lib/oxidized/web/public/weblibs/dataTables.buttons.js +2936 -0
  29. data/lib/oxidized/web/public/weblibs/dataTables.js +13843 -0
  30. data/lib/oxidized/web/public/weblibs/fonts/bootstrap-icons.woff +0 -0
  31. data/lib/oxidized/web/public/weblibs/fonts/bootstrap-icons.woff2 +0 -0
  32. data/lib/oxidized/web/public/weblibs/jquery.js +10716 -0
  33. data/lib/oxidized/web/version.rb +7 -0
  34. data/lib/oxidized/web/views/conf_search.haml +14 -13
  35. data/lib/oxidized/web/views/diffs.haml +5 -5
  36. data/lib/oxidized/web/views/footer.haml +5 -4
  37. data/lib/oxidized/web/views/head.haml +21 -7
  38. data/lib/oxidized/web/views/layout.haml +25 -34
  39. data/lib/oxidized/web/views/node.haml +10 -8
  40. data/lib/oxidized/web/views/nodes.haml +45 -35
  41. data/lib/oxidized/web/views/stats.haml +32 -24
  42. data/lib/oxidized/web/views/version.haml +8 -6
  43. data/lib/oxidized/web/views/versions.haml +23 -24
  44. data/lib/oxidized/web/webapp.rb +117 -113
  45. data/lib/oxidized/web.rb +10 -7
  46. data/oxidized-web.gemspec +32 -18
  47. data/package-lock.json +104 -0
  48. data/package.json +21 -0
  49. data/spec/node_spec.rb +99 -0
  50. data/spec/node_version_spec.rb +102 -0
  51. data/spec/nodes_spec.rb +57 -0
  52. data/spec/root_spec.rb +18 -0
  53. data/spec/spec_helper.rb +11 -0
  54. data/spec/webapp_spec.rb +28 -0
  55. metadata +183 -74
  56. data/lib/oxidized/web/mig.rb +0 -150
  57. data/lib/oxidized/web/public/css/bootstrap.min.css +0 -5
  58. data/lib/oxidized/web/public/css/buttons.bootstrap.min.css +0 -1
  59. data/lib/oxidized/web/public/css/dataTables.bootstrap.css +0 -299
  60. data/lib/oxidized/web/public/css/dataTables.colVis.css +0 -171
  61. data/lib/oxidized/web/public/css/oxidized_custom.css +0 -19
  62. data/lib/oxidized/web/public/fonts/glyphicons-halflings-regular.eot +0 -0
  63. data/lib/oxidized/web/public/fonts/glyphicons-halflings-regular.svg +0 -229
  64. data/lib/oxidized/web/public/fonts/glyphicons-halflings-regular.ttf +0 -0
  65. data/lib/oxidized/web/public/fonts/glyphicons-halflings-regular.woff +0 -0
  66. data/lib/oxidized/web/public/images/diff_15x17.png +0 -0
  67. data/lib/oxidized/web/public/images/sort_asc.png +0 -0
  68. data/lib/oxidized/web/public/images/sort_asc_disabled.png +0 -0
  69. data/lib/oxidized/web/public/images/sort_both.png +0 -0
  70. data/lib/oxidized/web/public/images/sort_desc.png +0 -0
  71. data/lib/oxidized/web/public/images/sort_desc_disabled.png +0 -0
  72. data/lib/oxidized/web/public/images/versioning_18px.png +0 -0
  73. data/lib/oxidized/web/public/scripts/bootstrap.min.js +0 -6
  74. data/lib/oxidized/web/public/scripts/dataTables.bootstrap.js +0 -186
  75. data/lib/oxidized/web/public/scripts/dataTables.colVis.js +0 -1123
  76. data/lib/oxidized/web/public/scripts/jquery-2.1.1.min.js +0 -4
  77. data/lib/oxidized/web/public/scripts/jquery.dataTables.min.js +0 -157
  78. data/lib/oxidized/web/public/scripts/jquery.min.js +0 -6
  79. data/lib/oxidized/web/public/scripts/script-migration.js +0 -15
  80. data/lib/oxidized/web/views/migration.haml +0 -46
  81. data/lib/oxidized/web/views/sass/oxidized.sass +0 -113
@@ -0,0 +1,2936 @@
1
+ /*! Buttons for DataTables 3.2.2
2
+ * © SpryMedia Ltd - datatables.net/license
3
+ */
4
+
5
+ (function( factory ){
6
+ if ( typeof define === 'function' && define.amd ) {
7
+ // AMD
8
+ define( ['jquery', 'datatables.net'], function ( $ ) {
9
+ return factory( $, window, document );
10
+ } );
11
+ }
12
+ else if ( typeof exports === 'object' ) {
13
+ // CommonJS
14
+ var jq = require('jquery');
15
+ var cjsRequires = function (root, $) {
16
+ if ( ! $.fn.dataTable ) {
17
+ require('datatables.net')(root, $);
18
+ }
19
+ };
20
+
21
+ if (typeof window === 'undefined') {
22
+ module.exports = function (root, $) {
23
+ if ( ! root ) {
24
+ // CommonJS environments without a window global must pass a
25
+ // root. This will give an error otherwise
26
+ root = window;
27
+ }
28
+
29
+ if ( ! $ ) {
30
+ $ = jq( root );
31
+ }
32
+
33
+ cjsRequires( root, $ );
34
+ return factory( $, root, root.document );
35
+ };
36
+ }
37
+ else {
38
+ cjsRequires( window, jq );
39
+ module.exports = factory( jq, window, window.document );
40
+ }
41
+ }
42
+ else {
43
+ // Browser
44
+ factory( jQuery, window, document );
45
+ }
46
+ }(function( $, window, document ) {
47
+ 'use strict';
48
+ var DataTable = $.fn.dataTable;
49
+
50
+
51
+
52
+ // Used for namespacing events added to the document by each instance, so they
53
+ // can be removed on destroy
54
+ var _instCounter = 0;
55
+
56
+ // Button namespacing counter for namespacing events on individual buttons
57
+ var _buttonCounter = 0;
58
+
59
+ var _dtButtons = DataTable.ext.buttons;
60
+
61
+ // Custom entity decoder for data export
62
+ var _entityDecoder = null;
63
+
64
+ // Allow for jQuery slim
65
+ function _fadeIn(el, duration, fn) {
66
+ if ($.fn.animate) {
67
+ el.stop().fadeIn(duration, fn);
68
+ }
69
+ else {
70
+ el.css('display', 'block');
71
+
72
+ if (fn) {
73
+ fn.call(el);
74
+ }
75
+ }
76
+ }
77
+
78
+ function _fadeOut(el, duration, fn) {
79
+ if ($.fn.animate) {
80
+ el.stop().fadeOut(duration, fn);
81
+ }
82
+ else {
83
+ el.css('display', 'none');
84
+
85
+ if (fn) {
86
+ fn.call(el);
87
+ }
88
+ }
89
+ }
90
+
91
+ /**
92
+ * [Buttons description]
93
+ * @param {[type]}
94
+ * @param {[type]}
95
+ */
96
+ var Buttons = function (dt, config) {
97
+ if (!DataTable.versionCheck('2')) {
98
+ throw 'Warning: Buttons requires DataTables 2 or newer';
99
+ }
100
+
101
+ // If not created with a `new` keyword then we return a wrapper function that
102
+ // will take the settings object for a DT. This allows easy use of new instances
103
+ // with the `layout` option - e.g. `topLeft: $.fn.dataTable.Buttons( ... )`.
104
+ if (!(this instanceof Buttons)) {
105
+ return function (settings) {
106
+ return new Buttons(settings, dt).container();
107
+ };
108
+ }
109
+
110
+ // If there is no config set it to an empty object
111
+ if (typeof config === 'undefined') {
112
+ config = {};
113
+ }
114
+
115
+ // Allow a boolean true for defaults
116
+ if (config === true) {
117
+ config = {};
118
+ }
119
+
120
+ // For easy configuration of buttons an array can be given
121
+ if (Array.isArray(config)) {
122
+ config = { buttons: config };
123
+ }
124
+
125
+ this.c = $.extend(true, {}, Buttons.defaults, config);
126
+
127
+ // Don't want a deep copy for the buttons
128
+ if (config.buttons) {
129
+ this.c.buttons = config.buttons;
130
+ }
131
+
132
+ this.s = {
133
+ dt: new DataTable.Api(dt),
134
+ buttons: [],
135
+ listenKeys: '',
136
+ namespace: 'dtb' + _instCounter++
137
+ };
138
+
139
+ this.dom = {
140
+ container: $('<' + this.c.dom.container.tag + '/>').addClass(
141
+ this.c.dom.container.className
142
+ )
143
+ };
144
+
145
+ this._constructor();
146
+ };
147
+
148
+ $.extend(Buttons.prototype, {
149
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
150
+ * Public methods
151
+ */
152
+
153
+ /**
154
+ * Get the action of a button
155
+ * @param {int|string} Button index
156
+ * @return {function}
157
+ */ /**
158
+ * Set the action of a button
159
+ * @param {node} node Button element
160
+ * @param {function} action Function to set
161
+ * @return {Buttons} Self for chaining
162
+ */
163
+ action: function (node, action) {
164
+ var button = this._nodeToButton(node);
165
+
166
+ if (action === undefined) {
167
+ return button.conf.action;
168
+ }
169
+
170
+ button.conf.action = action;
171
+
172
+ return this;
173
+ },
174
+
175
+ /**
176
+ * Add an active class to the button to make to look active or get current
177
+ * active state.
178
+ * @param {node} node Button element
179
+ * @param {boolean} [flag] Enable / disable flag
180
+ * @return {Buttons} Self for chaining or boolean for getter
181
+ */
182
+ active: function (node, flag) {
183
+ var button = this._nodeToButton(node);
184
+ var klass = this.c.dom.button.active;
185
+ var jqNode = $(button.node);
186
+
187
+ if (
188
+ button.inCollection &&
189
+ this.c.dom.collection.button &&
190
+ this.c.dom.collection.button.active !== undefined
191
+ ) {
192
+ klass = this.c.dom.collection.button.active;
193
+ }
194
+
195
+ if (flag === undefined) {
196
+ return jqNode.hasClass(klass);
197
+ }
198
+
199
+ jqNode.toggleClass(klass, flag === undefined ? true : flag);
200
+
201
+ return this;
202
+ },
203
+
204
+ /**
205
+ * Add a new button
206
+ * @param {object} config Button configuration object, base string name or function
207
+ * @param {int|string} [idx] Button index for where to insert the button
208
+ * @param {boolean} [draw=true] Trigger a draw. Set a false when adding
209
+ * lots of buttons, until the last button.
210
+ * @return {Buttons} Self for chaining
211
+ */
212
+ add: function (config, idx, draw) {
213
+ var buttons = this.s.buttons;
214
+
215
+ if (typeof idx === 'string') {
216
+ var split = idx.split('-');
217
+ var base = this.s;
218
+
219
+ for (var i = 0, ien = split.length - 1; i < ien; i++) {
220
+ base = base.buttons[split[i] * 1];
221
+ }
222
+
223
+ buttons = base.buttons;
224
+ idx = split[split.length - 1] * 1;
225
+ }
226
+
227
+ this._expandButton(
228
+ buttons,
229
+ config,
230
+ config !== undefined ? config.split : undefined,
231
+ (config === undefined ||
232
+ config.split === undefined ||
233
+ config.split.length === 0) &&
234
+ base !== undefined,
235
+ false,
236
+ idx
237
+ );
238
+
239
+ if (draw === undefined || draw === true) {
240
+ this._draw();
241
+ }
242
+
243
+ return this;
244
+ },
245
+
246
+ /**
247
+ * Clear buttons from a collection and then insert new buttons
248
+ */
249
+ collectionRebuild: function (node, newButtons) {
250
+ var button = this._nodeToButton(node);
251
+
252
+ if (newButtons !== undefined) {
253
+ var i;
254
+ // Need to reverse the array
255
+ for (i = button.buttons.length - 1; i >= 0; i--) {
256
+ this.remove(button.buttons[i].node);
257
+ }
258
+
259
+ // If the collection has prefix and / or postfix buttons we need to add them in
260
+ if (button.conf.prefixButtons) {
261
+ newButtons.unshift.apply(newButtons, button.conf.prefixButtons);
262
+ }
263
+
264
+ if (button.conf.postfixButtons) {
265
+ newButtons.push.apply(newButtons, button.conf.postfixButtons);
266
+ }
267
+
268
+ for (i = 0; i < newButtons.length; i++) {
269
+ var newBtn = newButtons[i];
270
+
271
+ this._expandButton(
272
+ button.buttons,
273
+ newBtn,
274
+ newBtn !== undefined &&
275
+ newBtn.config !== undefined &&
276
+ newBtn.config.split !== undefined,
277
+ true,
278
+ newBtn.parentConf !== undefined &&
279
+ newBtn.parentConf.split !== undefined,
280
+ null,
281
+ newBtn.parentConf
282
+ );
283
+ }
284
+ }
285
+
286
+ this._draw(button.collection, button.buttons);
287
+ },
288
+
289
+ /**
290
+ * Get the container node for the buttons
291
+ * @return {jQuery} Buttons node
292
+ */
293
+ container: function () {
294
+ return this.dom.container;
295
+ },
296
+
297
+ /**
298
+ * Disable a button
299
+ * @param {node} node Button node
300
+ * @return {Buttons} Self for chaining
301
+ */
302
+ disable: function (node) {
303
+ var button = this._nodeToButton(node);
304
+
305
+ if (button.isSplit) {
306
+ $(button.node.childNodes[0])
307
+ .addClass(this.c.dom.button.disabled)
308
+ .prop('disabled', true);
309
+ }
310
+ else {
311
+ $(button.node)
312
+ .addClass(this.c.dom.button.disabled)
313
+ .prop('disabled', true);
314
+ }
315
+
316
+ button.disabled = true;
317
+
318
+ this._checkSplitEnable();
319
+
320
+ return this;
321
+ },
322
+
323
+ /**
324
+ * Destroy the instance, cleaning up event handlers and removing DOM
325
+ * elements
326
+ * @return {Buttons} Self for chaining
327
+ */
328
+ destroy: function () {
329
+ // Key event listener
330
+ $('body').off('keyup.' + this.s.namespace);
331
+
332
+ // Individual button destroy (so they can remove their own events if
333
+ // needed). Take a copy as the array is modified by `remove`
334
+ var buttons = this.s.buttons.slice();
335
+ var i, ien;
336
+
337
+ for (i = 0, ien = buttons.length; i < ien; i++) {
338
+ this.remove(buttons[i].node);
339
+ }
340
+
341
+ // Container
342
+ this.dom.container.remove();
343
+
344
+ // Remove from the settings object collection
345
+ var buttonInsts = this.s.dt.settings()[0];
346
+
347
+ for (i = 0, ien = buttonInsts.length; i < ien; i++) {
348
+ if (buttonInsts.inst === this) {
349
+ buttonInsts.splice(i, 1);
350
+ break;
351
+ }
352
+ }
353
+
354
+ return this;
355
+ },
356
+
357
+ /**
358
+ * Enable / disable a button
359
+ * @param {node} node Button node
360
+ * @param {boolean} [flag=true] Enable / disable flag
361
+ * @return {Buttons} Self for chaining
362
+ */
363
+ enable: function (node, flag) {
364
+ if (flag === false) {
365
+ return this.disable(node);
366
+ }
367
+
368
+ var button = this._nodeToButton(node);
369
+
370
+ if (button.isSplit) {
371
+ $(button.node.childNodes[0])
372
+ .removeClass(this.c.dom.button.disabled)
373
+ .prop('disabled', false);
374
+ }
375
+ else {
376
+ $(button.node)
377
+ .removeClass(this.c.dom.button.disabled)
378
+ .prop('disabled', false);
379
+ }
380
+
381
+ button.disabled = false;
382
+
383
+ this._checkSplitEnable();
384
+
385
+ return this;
386
+ },
387
+
388
+ /**
389
+ * Get a button's index
390
+ *
391
+ * This is internally recursive
392
+ * @param {element} node Button to get the index of
393
+ * @return {string} Button index
394
+ */
395
+ index: function (node, nested, buttons) {
396
+ if (!nested) {
397
+ nested = '';
398
+ buttons = this.s.buttons;
399
+ }
400
+
401
+ for (var i = 0, ien = buttons.length; i < ien; i++) {
402
+ var inner = buttons[i].buttons;
403
+
404
+ if (buttons[i].node === node) {
405
+ return nested + i;
406
+ }
407
+
408
+ if (inner && inner.length) {
409
+ var match = this.index(node, i + '-', inner);
410
+
411
+ if (match !== null) {
412
+ return match;
413
+ }
414
+ }
415
+ }
416
+
417
+ return null;
418
+ },
419
+
420
+ /**
421
+ * Get the instance name for the button set selector
422
+ * @return {string} Instance name
423
+ */
424
+ name: function () {
425
+ return this.c.name;
426
+ },
427
+
428
+ /**
429
+ * Get a button's node of the buttons container if no button is given
430
+ * @param {node} [node] Button node
431
+ * @return {jQuery} Button element, or container
432
+ */
433
+ node: function (node) {
434
+ if (!node) {
435
+ return this.dom.container;
436
+ }
437
+
438
+ var button = this._nodeToButton(node);
439
+ return $(button.node);
440
+ },
441
+
442
+ /**
443
+ * Set / get a processing class on the selected button
444
+ * @param {element} node Triggering button node
445
+ * @param {boolean} flag true to add, false to remove, undefined to get
446
+ * @return {boolean|Buttons} Getter value or this if a setter.
447
+ */
448
+ processing: function (node, flag) {
449
+ var dt = this.s.dt;
450
+ var button = this._nodeToButton(node);
451
+
452
+ if (flag === undefined) {
453
+ return $(button.node).hasClass('processing');
454
+ }
455
+
456
+ $(button.node).toggleClass('processing', flag);
457
+
458
+ $(dt.table().node()).triggerHandler('buttons-processing.dt', [
459
+ flag,
460
+ dt.button(node),
461
+ dt,
462
+ $(node),
463
+ button.conf
464
+ ]);
465
+
466
+ return this;
467
+ },
468
+
469
+ /**
470
+ * Remove a button.
471
+ * @param {node} node Button node
472
+ * @return {Buttons} Self for chaining
473
+ */
474
+ remove: function (node) {
475
+ var button = this._nodeToButton(node);
476
+ var host = this._nodeToHost(node);
477
+ var dt = this.s.dt;
478
+
479
+ // Remove any child buttons first
480
+ if (button.buttons.length) {
481
+ for (var i = button.buttons.length - 1; i >= 0; i--) {
482
+ this.remove(button.buttons[i].node);
483
+ }
484
+ }
485
+
486
+ button.conf.destroying = true;
487
+
488
+ // Allow the button to remove event handlers, etc
489
+ if (button.conf.destroy) {
490
+ button.conf.destroy.call(dt.button(node), dt, $(node), button.conf);
491
+ }
492
+
493
+ this._removeKey(button.conf);
494
+
495
+ $(button.node).remove();
496
+
497
+ var idx = $.inArray(button, host);
498
+ host.splice(idx, 1);
499
+
500
+ return this;
501
+ },
502
+
503
+ /**
504
+ * Get the text for a button
505
+ * @param {int|string} node Button index
506
+ * @return {string} Button text
507
+ */ /**
508
+ * Set the text for a button
509
+ * @param {int|string|function} node Button index
510
+ * @param {string} label Text
511
+ * @return {Buttons} Self for chaining
512
+ */
513
+ text: function (node, label) {
514
+ var button = this._nodeToButton(node);
515
+ var textNode = button.textNode;
516
+ var dt = this.s.dt;
517
+ var jqNode = $(button.node);
518
+ var text = function (opt) {
519
+ return typeof opt === 'function'
520
+ ? opt(dt, jqNode, button.conf)
521
+ : opt;
522
+ };
523
+
524
+ if (label === undefined) {
525
+ return text(button.conf.text);
526
+ }
527
+
528
+ button.conf.text = label;
529
+ textNode.html(text(label));
530
+
531
+ return this;
532
+ },
533
+
534
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
535
+ * Constructor
536
+ */
537
+
538
+ /**
539
+ * Buttons constructor
540
+ * @private
541
+ */
542
+ _constructor: function () {
543
+ var that = this;
544
+ var dt = this.s.dt;
545
+ var dtSettings = dt.settings()[0];
546
+ var buttons = this.c.buttons;
547
+
548
+ if (!dtSettings._buttons) {
549
+ dtSettings._buttons = [];
550
+ }
551
+
552
+ dtSettings._buttons.push({
553
+ inst: this,
554
+ name: this.c.name
555
+ });
556
+
557
+ for (var i = 0, ien = buttons.length; i < ien; i++) {
558
+ this.add(buttons[i]);
559
+ }
560
+
561
+ dt.on('destroy', function (e, settings) {
562
+ if (settings === dtSettings) {
563
+ that.destroy();
564
+ }
565
+ });
566
+
567
+ // Global key event binding to listen for button keys
568
+ $('body').on('keyup.' + this.s.namespace, function (e) {
569
+ if (
570
+ !document.activeElement ||
571
+ document.activeElement === document.body
572
+ ) {
573
+ // SUse a string of characters for fast lookup of if we need to
574
+ // handle this
575
+ var character = String.fromCharCode(e.keyCode).toLowerCase();
576
+
577
+ if (that.s.listenKeys.toLowerCase().indexOf(character) !== -1) {
578
+ that._keypress(character, e);
579
+ }
580
+ }
581
+ });
582
+ },
583
+
584
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
585
+ * Private methods
586
+ */
587
+
588
+ /**
589
+ * Add a new button to the key press listener
590
+ * @param {object} conf Resolved button configuration object
591
+ * @private
592
+ */
593
+ _addKey: function (conf) {
594
+ if (conf.key) {
595
+ this.s.listenKeys += $.isPlainObject(conf.key)
596
+ ? conf.key.key
597
+ : conf.key;
598
+ }
599
+ },
600
+
601
+ /**
602
+ * Insert the buttons into the container. Call without parameters!
603
+ * @param {node} [container] Recursive only - Insert point
604
+ * @param {array} [buttons] Recursive only - Buttons array
605
+ * @private
606
+ */
607
+ _draw: function (container, buttons) {
608
+ if (!container) {
609
+ container = this.dom.container;
610
+ buttons = this.s.buttons;
611
+ }
612
+
613
+ container.children().detach();
614
+
615
+ for (var i = 0, ien = buttons.length; i < ien; i++) {
616
+ container.append(buttons[i].inserter);
617
+ container.append(' ');
618
+
619
+ if (buttons[i].buttons && buttons[i].buttons.length) {
620
+ this._draw(buttons[i].collection, buttons[i].buttons);
621
+ }
622
+ }
623
+ },
624
+
625
+ /**
626
+ * Create buttons from an array of buttons
627
+ * @param {array} attachTo Buttons array to attach to
628
+ * @param {object} button Button definition
629
+ * @param {boolean} inCollection true if the button is in a collection
630
+ * @private
631
+ */
632
+ _expandButton: function (
633
+ attachTo,
634
+ button,
635
+ split,
636
+ inCollection,
637
+ inSplit,
638
+ attachPoint,
639
+ parentConf
640
+ ) {
641
+ var dt = this.s.dt;
642
+ var isSplit = false;
643
+ var domCollection = this.c.dom.collection;
644
+ var buttons = !Array.isArray(button) ? [button] : button;
645
+
646
+ if (button === undefined) {
647
+ buttons = !Array.isArray(split) ? [split] : split;
648
+ }
649
+
650
+ for (var i = 0, ien = buttons.length; i < ien; i++) {
651
+ var conf = this._resolveExtends(buttons[i]);
652
+
653
+ if (!conf) {
654
+ continue;
655
+ }
656
+
657
+ isSplit = conf.config && conf.config.split ? true : false;
658
+
659
+ // If the configuration is an array, then expand the buttons at this
660
+ // point
661
+ if (Array.isArray(conf)) {
662
+ this._expandButton(
663
+ attachTo,
664
+ conf,
665
+ built !== undefined && built.conf !== undefined
666
+ ? built.conf.split
667
+ : undefined,
668
+ inCollection,
669
+ parentConf !== undefined && parentConf.split !== undefined,
670
+ attachPoint,
671
+ parentConf
672
+ );
673
+ continue;
674
+ }
675
+
676
+ var built = this._buildButton(
677
+ conf,
678
+ inCollection,
679
+ conf.split !== undefined ||
680
+ (conf.config !== undefined &&
681
+ conf.config.split !== undefined),
682
+ inSplit
683
+ );
684
+ if (!built) {
685
+ continue;
686
+ }
687
+
688
+ if (attachPoint !== undefined && attachPoint !== null) {
689
+ attachTo.splice(attachPoint, 0, built);
690
+ attachPoint++;
691
+ }
692
+ else {
693
+ attachTo.push(built);
694
+ }
695
+
696
+ // Any button type can have a drop icon set
697
+ if (built.conf.dropIcon && ! built.conf.split) {
698
+ $(built.node)
699
+ .addClass(this.c.dom.button.dropClass)
700
+ .append(this.c.dom.button.dropHtml);
701
+ }
702
+
703
+ // Create the dropdown for a collection
704
+ if (built.conf.buttons) {
705
+ built.collection = $(
706
+ '<' + domCollection.container.content.tag + '/>'
707
+ );
708
+ built.conf._collection = built.collection;
709
+
710
+ this._expandButton(
711
+ built.buttons,
712
+ built.conf.buttons,
713
+ built.conf.split,
714
+ !isSplit,
715
+ isSplit,
716
+ attachPoint,
717
+ built.conf
718
+ );
719
+ }
720
+
721
+ // And the split collection
722
+ if (built.conf.split) {
723
+ built.collection = $('<' + domCollection.container.tag + '/>');
724
+ built.conf._collection = built.collection;
725
+
726
+ for (var j = 0; j < built.conf.split.length; j++) {
727
+ var item = built.conf.split[j];
728
+
729
+ if (typeof item === 'object') {
730
+ item.parent = parentConf;
731
+
732
+ if (item.collectionLayout === undefined) {
733
+ item.collectionLayout = built.conf.collectionLayout;
734
+ }
735
+
736
+ if (item.dropup === undefined) {
737
+ item.dropup = built.conf.dropup;
738
+ }
739
+
740
+ if (item.fade === undefined) {
741
+ item.fade = built.conf.fade;
742
+ }
743
+ }
744
+ }
745
+
746
+ this._expandButton(
747
+ built.buttons,
748
+ built.conf.buttons,
749
+ built.conf.split,
750
+ !isSplit,
751
+ isSplit,
752
+ attachPoint,
753
+ built.conf
754
+ );
755
+ }
756
+
757
+ built.conf.parent = parentConf;
758
+
759
+ // init call is made here, rather than buildButton as it needs to
760
+ // be selectable, and for that it needs to be in the buttons array
761
+ if (conf.init) {
762
+ conf.init.call(dt.button(built.node), dt, $(built.node), conf);
763
+ }
764
+ }
765
+ },
766
+
767
+ /**
768
+ * Create an individual button
769
+ * @param {object} config Resolved button configuration
770
+ * @param {boolean} inCollection `true` if a collection button
771
+ * @return {object} Completed button description object
772
+ * @private
773
+ */
774
+ _buildButton: function (config, inCollection, isSplit, inSplit) {
775
+ var that = this;
776
+ var configDom = this.c.dom;
777
+ var textNode;
778
+ var dt = this.s.dt;
779
+ var text = function (opt) {
780
+ return typeof opt === 'function' ? opt(dt, button, config) : opt;
781
+ };
782
+
783
+ // Create an object that describes the button which can be in `dom.button`, or
784
+ // `dom.collection.button` or `dom.split.button` or `dom.collection.split.button`!
785
+ // Each should extend from `dom.button`.
786
+ var dom = $.extend(true, {}, configDom.button);
787
+
788
+ if (inCollection && isSplit && configDom.collection.split) {
789
+ $.extend(true, dom, configDom.collection.split.action);
790
+ }
791
+ else if (inSplit || inCollection) {
792
+ $.extend(true, dom, configDom.collection.button);
793
+ }
794
+ else if (isSplit) {
795
+ $.extend(true, dom, configDom.split.button);
796
+ }
797
+
798
+ // Spacers don't do much other than insert an element into the DOM
799
+ if (config.spacer) {
800
+ var spacer = $('<' + dom.spacer.tag + '/>')
801
+ .addClass(
802
+ 'dt-button-spacer ' +
803
+ config.style +
804
+ ' ' +
805
+ dom.spacer.className
806
+ )
807
+ .html(text(config.text));
808
+
809
+ return {
810
+ conf: config,
811
+ node: spacer,
812
+ inserter: spacer,
813
+ buttons: [],
814
+ inCollection: inCollection,
815
+ isSplit: isSplit,
816
+ collection: null,
817
+ textNode: spacer
818
+ };
819
+ }
820
+
821
+ // Make sure that the button is available based on whatever requirements
822
+ // it has. For example, PDF button require pdfmake
823
+ if (
824
+ config.available &&
825
+ !config.available(dt, config) &&
826
+ !config.html
827
+ ) {
828
+ return false;
829
+ }
830
+
831
+ var button;
832
+
833
+ if (!config.html) {
834
+ var run = function (e, dt, button, config, done) {
835
+ config.action.call(dt.button(button), e, dt, button, config, done);
836
+
837
+ $(dt.table().node()).triggerHandler('buttons-action.dt', [
838
+ dt.button(button),
839
+ dt,
840
+ button,
841
+ config
842
+ ]);
843
+ };
844
+
845
+ var action = function(e, dt, button, config) {
846
+ if (config.async) {
847
+ that.processing(button[0], true);
848
+
849
+ setTimeout(function () {
850
+ run(e, dt, button, config, function () {
851
+ that.processing(button[0], false);
852
+ });
853
+ }, config.async);
854
+ }
855
+ else {
856
+ run(e, dt, button, config, function () {});
857
+ }
858
+ };
859
+
860
+ var tag = config.tag || dom.tag;
861
+ var clickBlurs =
862
+ config.clickBlurs === undefined ? true : config.clickBlurs;
863
+
864
+ button = $('<' + tag + '/>')
865
+ .addClass(dom.className)
866
+ .attr('tabindex', this.s.dt.settings()[0].iTabIndex)
867
+ .attr('aria-controls', this.s.dt.table().node().id)
868
+ .on('click.dtb', function (e) {
869
+ e.preventDefault();
870
+
871
+ if (!button.hasClass(dom.disabled) && config.action) {
872
+ action(e, dt, button, config);
873
+ }
874
+
875
+ if (clickBlurs) {
876
+ button.trigger('blur');
877
+ }
878
+ })
879
+ .on('keypress.dtb', function (e) {
880
+ if (e.keyCode === 13) {
881
+ e.preventDefault();
882
+
883
+ if (!button.hasClass(dom.disabled) && config.action) {
884
+ action(e, dt, button, config);
885
+ }
886
+ }
887
+ });
888
+
889
+ // Make `a` tags act like a link
890
+ if (tag.toLowerCase() === 'a') {
891
+ button.attr('href', '#');
892
+ }
893
+
894
+ // Button tags should have `type=button` so they don't have any default behaviour
895
+ if (tag.toLowerCase() === 'button') {
896
+ button.attr('type', 'button');
897
+ }
898
+
899
+ if (dom.liner.tag) {
900
+ var liner = $('<' + dom.liner.tag + '/>')
901
+ .html(text(config.text))
902
+ .addClass(dom.liner.className);
903
+
904
+ if (dom.liner.tag.toLowerCase() === 'a') {
905
+ liner.attr('href', '#');
906
+ }
907
+
908
+ button.append(liner);
909
+ textNode = liner;
910
+ }
911
+ else {
912
+ button.html(text(config.text));
913
+ textNode = button;
914
+ }
915
+
916
+ if (config.enabled === false) {
917
+ button.addClass(dom.disabled);
918
+ }
919
+
920
+ if (config.className) {
921
+ button.addClass(config.className);
922
+ }
923
+
924
+ if (config.titleAttr) {
925
+ button.attr('title', text(config.titleAttr));
926
+ }
927
+
928
+ if (config.attr) {
929
+ button.attr(config.attr);
930
+ }
931
+
932
+ if (!config.namespace) {
933
+ config.namespace = '.dt-button-' + _buttonCounter++;
934
+ }
935
+
936
+ if (config.config !== undefined && config.config.split) {
937
+ config.split = config.config.split;
938
+ }
939
+ }
940
+ else {
941
+ button = $(config.html);
942
+ }
943
+
944
+ var buttonContainer = this.c.dom.buttonContainer;
945
+ var inserter;
946
+ if (buttonContainer && buttonContainer.tag) {
947
+ inserter = $('<' + buttonContainer.tag + '/>')
948
+ .addClass(buttonContainer.className)
949
+ .append(button);
950
+ }
951
+ else {
952
+ inserter = button;
953
+ }
954
+
955
+ this._addKey(config);
956
+
957
+ // Style integration callback for DOM manipulation
958
+ // Note that this is _not_ documented. It is currently
959
+ // for style integration only
960
+ if (this.c.buttonCreated) {
961
+ inserter = this.c.buttonCreated(config, inserter);
962
+ }
963
+
964
+ var splitDiv;
965
+
966
+ if (isSplit) {
967
+ var dropdownConf = inCollection
968
+ ? $.extend(true, this.c.dom.split, this.c.dom.collection.split)
969
+ : this.c.dom.split;
970
+ var wrapperConf = dropdownConf.wrapper;
971
+
972
+ splitDiv = $('<' + wrapperConf.tag + '/>')
973
+ .addClass(wrapperConf.className)
974
+ .append(button);
975
+
976
+ var dropButtonConfig = $.extend(config, {
977
+ autoClose: true,
978
+ align: dropdownConf.dropdown.align,
979
+ attr: {
980
+ 'aria-haspopup': 'dialog',
981
+ 'aria-expanded': false
982
+ },
983
+ className: dropdownConf.dropdown.className,
984
+ closeButton: false,
985
+ splitAlignClass: dropdownConf.dropdown.splitAlignClass,
986
+ text: dropdownConf.dropdown.text
987
+ });
988
+
989
+ this._addKey(dropButtonConfig);
990
+
991
+ var splitAction = function (e, dt, button, config) {
992
+ _dtButtons.split.action.call(
993
+ dt.button(splitDiv),
994
+ e,
995
+ dt,
996
+ button,
997
+ config
998
+ );
999
+
1000
+ $(dt.table().node()).triggerHandler('buttons-action.dt', [
1001
+ dt.button(button),
1002
+ dt,
1003
+ button,
1004
+ config
1005
+ ]);
1006
+ button.attr('aria-expanded', true);
1007
+ };
1008
+
1009
+ var dropButton = $(
1010
+ '<button class="' +
1011
+ dropdownConf.dropdown.className +
1012
+ ' dt-button"></button>'
1013
+ )
1014
+ .html(this.c.dom.button.dropHtml)
1015
+ .addClass(this.c.dom.button.dropClass)
1016
+ .on('click.dtb', function (e) {
1017
+ e.preventDefault();
1018
+ e.stopPropagation();
1019
+
1020
+ if (!dropButton.hasClass(dom.disabled)) {
1021
+ splitAction(e, dt, dropButton, dropButtonConfig);
1022
+ }
1023
+ if (clickBlurs) {
1024
+ dropButton.trigger('blur');
1025
+ }
1026
+ })
1027
+ .on('keypress.dtb', function (e) {
1028
+ if (e.keyCode === 13) {
1029
+ e.preventDefault();
1030
+
1031
+ if (!dropButton.hasClass(dom.disabled)) {
1032
+ splitAction(e, dt, dropButton, dropButtonConfig);
1033
+ }
1034
+ }
1035
+ });
1036
+
1037
+ if (config.split.length === 0) {
1038
+ dropButton.addClass('dtb-hide-drop');
1039
+ }
1040
+
1041
+ splitDiv.append(dropButton).attr(dropButtonConfig.attr);
1042
+ }
1043
+
1044
+ return {
1045
+ conf: config,
1046
+ node: isSplit ? splitDiv.get(0) : button.get(0),
1047
+ inserter: isSplit ? splitDiv : inserter,
1048
+ buttons: [],
1049
+ inCollection: inCollection,
1050
+ isSplit: isSplit,
1051
+ inSplit: inSplit,
1052
+ collection: null,
1053
+ textNode: textNode
1054
+ };
1055
+ },
1056
+
1057
+ /**
1058
+ * Spin over buttons checking if splits should be enabled or not.
1059
+ * @param {*} buttons Array of buttons to check
1060
+ */
1061
+ _checkSplitEnable: function (buttons) {
1062
+ if (! buttons) {
1063
+ buttons = this.s.buttons;
1064
+ }
1065
+
1066
+ for (var i=0 ; i<buttons.length ; i++) {
1067
+ var button = buttons[i];
1068
+
1069
+ // Check if the button is a split one and if so, determine
1070
+ // its state
1071
+ if (button.isSplit) {
1072
+ var splitBtn = button.node.childNodes[1];
1073
+
1074
+ if (this._checkAnyEnabled(button.buttons)) {
1075
+ // Enable the split
1076
+ $(splitBtn)
1077
+ .removeClass(this.c.dom.button.disabled)
1078
+ .prop('disabled', false);
1079
+ }
1080
+ else {
1081
+ $(splitBtn)
1082
+ .addClass(this.c.dom.button.disabled)
1083
+ .prop('disabled', false);
1084
+ }
1085
+ }
1086
+ else if (button.isCollection) {
1087
+ // Nest down into collections
1088
+ this._checkSplitEnable(button.buttons);
1089
+ }
1090
+ }
1091
+ },
1092
+
1093
+ /**
1094
+ * Check an array of buttons and see if any are enabled in it
1095
+ * @param {*} buttons Button array
1096
+ * @returns true if a button is enabled, false otherwise
1097
+ */
1098
+ _checkAnyEnabled: function (buttons) {
1099
+ for (var i=0 ; i<buttons.length ; i++) {
1100
+ if (! buttons[i].disabled) {
1101
+ return true;
1102
+ }
1103
+ }
1104
+
1105
+ return false;
1106
+ },
1107
+
1108
+ /**
1109
+ * Get the button object from a node (recursive)
1110
+ * @param {node} node Button node
1111
+ * @param {array} [buttons] Button array, uses base if not defined
1112
+ * @return {object} Button object
1113
+ * @private
1114
+ */
1115
+ _nodeToButton: function (node, buttons) {
1116
+ if (!buttons) {
1117
+ buttons = this.s.buttons;
1118
+ }
1119
+
1120
+ for (var i = 0, ien = buttons.length; i < ien; i++) {
1121
+ if (buttons[i].node === node || $(buttons[i].node).children().eq(0).get(0) === node) {
1122
+ return buttons[i];
1123
+ }
1124
+
1125
+ if (buttons[i].buttons.length) {
1126
+ var ret = this._nodeToButton(node, buttons[i].buttons);
1127
+
1128
+ if (ret) {
1129
+ return ret;
1130
+ }
1131
+ }
1132
+ }
1133
+ },
1134
+
1135
+ /**
1136
+ * Get container array for a button from a button node (recursive)
1137
+ * @param {node} node Button node
1138
+ * @param {array} [buttons] Button array, uses base if not defined
1139
+ * @return {array} Button's host array
1140
+ * @private
1141
+ */
1142
+ _nodeToHost: function (node, buttons) {
1143
+ if (!buttons) {
1144
+ buttons = this.s.buttons;
1145
+ }
1146
+
1147
+ for (var i = 0, ien = buttons.length; i < ien; i++) {
1148
+ if (buttons[i].node === node) {
1149
+ return buttons;
1150
+ }
1151
+
1152
+ if (buttons[i].buttons.length) {
1153
+ var ret = this._nodeToHost(node, buttons[i].buttons);
1154
+
1155
+ if (ret) {
1156
+ return ret;
1157
+ }
1158
+ }
1159
+ }
1160
+ },
1161
+
1162
+ /**
1163
+ * Handle a key press - determine if any button's key configured matches
1164
+ * what was typed and trigger the action if so.
1165
+ * @param {string} character The character pressed
1166
+ * @param {object} e Key event that triggered this call
1167
+ * @private
1168
+ */
1169
+ _keypress: function (character, e) {
1170
+ // Check if this button press already activated on another instance of Buttons
1171
+ if (e._buttonsHandled) {
1172
+ return;
1173
+ }
1174
+
1175
+ var run = function (conf, node) {
1176
+ if (!conf.key) {
1177
+ return;
1178
+ }
1179
+
1180
+ if (conf.key === character) {
1181
+ e._buttonsHandled = true;
1182
+ $(node).click();
1183
+ }
1184
+ else if ($.isPlainObject(conf.key)) {
1185
+ if (conf.key.key !== character) {
1186
+ return;
1187
+ }
1188
+
1189
+ if (conf.key.shiftKey && !e.shiftKey) {
1190
+ return;
1191
+ }
1192
+
1193
+ if (conf.key.altKey && !e.altKey) {
1194
+ return;
1195
+ }
1196
+
1197
+ if (conf.key.ctrlKey && !e.ctrlKey) {
1198
+ return;
1199
+ }
1200
+
1201
+ if (conf.key.metaKey && !e.metaKey) {
1202
+ return;
1203
+ }
1204
+
1205
+ // Made it this far - it is good
1206
+ e._buttonsHandled = true;
1207
+ $(node).click();
1208
+ }
1209
+ };
1210
+
1211
+ var recurse = function (a) {
1212
+ for (var i = 0, ien = a.length; i < ien; i++) {
1213
+ run(a[i].conf, a[i].node);
1214
+
1215
+ if (a[i].buttons.length) {
1216
+ recurse(a[i].buttons);
1217
+ }
1218
+ }
1219
+ };
1220
+
1221
+ recurse(this.s.buttons);
1222
+ },
1223
+
1224
+ /**
1225
+ * Remove a key from the key listener for this instance (to be used when a
1226
+ * button is removed)
1227
+ * @param {object} conf Button configuration
1228
+ * @private
1229
+ */
1230
+ _removeKey: function (conf) {
1231
+ if (conf.key) {
1232
+ var character = $.isPlainObject(conf.key) ? conf.key.key : conf.key;
1233
+
1234
+ // Remove only one character, as multiple buttons could have the
1235
+ // same listening key
1236
+ var a = this.s.listenKeys.split('');
1237
+ var idx = $.inArray(character, a);
1238
+ a.splice(idx, 1);
1239
+ this.s.listenKeys = a.join('');
1240
+ }
1241
+ },
1242
+
1243
+ /**
1244
+ * Resolve a button configuration
1245
+ * @param {string|function|object} conf Button config to resolve
1246
+ * @return {object} Button configuration
1247
+ * @private
1248
+ */
1249
+ _resolveExtends: function (conf) {
1250
+ var that = this;
1251
+ var dt = this.s.dt;
1252
+ var i, ien;
1253
+ var toConfObject = function (base) {
1254
+ var loop = 0;
1255
+
1256
+ // Loop until we have resolved to a button configuration, or an
1257
+ // array of button configurations (which will be iterated
1258
+ // separately)
1259
+ while (!$.isPlainObject(base) && !Array.isArray(base)) {
1260
+ if (base === undefined) {
1261
+ return;
1262
+ }
1263
+
1264
+ if (typeof base === 'function') {
1265
+ base = base.call(that, dt, conf);
1266
+
1267
+ if (!base) {
1268
+ return false;
1269
+ }
1270
+ }
1271
+ else if (typeof base === 'string') {
1272
+ if (!_dtButtons[base]) {
1273
+ return { html: base };
1274
+ }
1275
+
1276
+ base = _dtButtons[base];
1277
+ }
1278
+
1279
+ loop++;
1280
+ if (loop > 30) {
1281
+ // Protect against misconfiguration killing the browser
1282
+ throw 'Buttons: Too many iterations';
1283
+ }
1284
+ }
1285
+
1286
+ return Array.isArray(base) ? base : $.extend({}, base);
1287
+ };
1288
+
1289
+ conf = toConfObject(conf);
1290
+
1291
+ while (conf && conf.extend) {
1292
+ // Use `toConfObject` in case the button definition being extended
1293
+ // is itself a string or a function
1294
+ if (!_dtButtons[conf.extend]) {
1295
+ throw 'Cannot extend unknown button type: ' + conf.extend;
1296
+ }
1297
+
1298
+ var objArray = toConfObject(_dtButtons[conf.extend]);
1299
+ if (Array.isArray(objArray)) {
1300
+ return objArray;
1301
+ }
1302
+ else if (!objArray) {
1303
+ // This is a little brutal as it might be possible to have a
1304
+ // valid button without the extend, but if there is no extend
1305
+ // then the host button would be acting in an undefined state
1306
+ return false;
1307
+ }
1308
+
1309
+ // Stash the current class name
1310
+ var originalClassName = objArray.className;
1311
+
1312
+ if (conf.config !== undefined && objArray.config !== undefined) {
1313
+ conf.config = $.extend({}, objArray.config, conf.config);
1314
+ }
1315
+
1316
+ conf = $.extend({}, objArray, conf);
1317
+
1318
+ // The extend will have overwritten the original class name if the
1319
+ // `conf` object also assigned a class, but we want to concatenate
1320
+ // them so they are list that is combined from all extended buttons
1321
+ if (originalClassName && conf.className !== originalClassName) {
1322
+ conf.className = originalClassName + ' ' + conf.className;
1323
+ }
1324
+
1325
+ // Although we want the `conf` object to overwrite almost all of
1326
+ // the properties of the object being extended, the `extend`
1327
+ // property should come from the object being extended
1328
+ conf.extend = objArray.extend;
1329
+ }
1330
+
1331
+ // Buttons to be added to a collection -gives the ability to define
1332
+ // if buttons should be added to the start or end of a collection
1333
+ var postfixButtons = conf.postfixButtons;
1334
+ if (postfixButtons) {
1335
+ if (!conf.buttons) {
1336
+ conf.buttons = [];
1337
+ }
1338
+
1339
+ for (i = 0, ien = postfixButtons.length; i < ien; i++) {
1340
+ conf.buttons.push(postfixButtons[i]);
1341
+ }
1342
+ }
1343
+
1344
+ var prefixButtons = conf.prefixButtons;
1345
+ if (prefixButtons) {
1346
+ if (!conf.buttons) {
1347
+ conf.buttons = [];
1348
+ }
1349
+
1350
+ for (i = 0, ien = prefixButtons.length; i < ien; i++) {
1351
+ conf.buttons.splice(i, 0, prefixButtons[i]);
1352
+ }
1353
+ }
1354
+
1355
+ return conf;
1356
+ },
1357
+
1358
+ /**
1359
+ * Display (and replace if there is an existing one) a popover attached to a button
1360
+ * @param {string|node} content Content to show
1361
+ * @param {DataTable.Api} hostButton DT API instance of the button
1362
+ * @param {object} inOpts Options (see object below for all options)
1363
+ */
1364
+ _popover: function (content, hostButton, inOpts) {
1365
+ var dt = hostButton;
1366
+ var c = this.c;
1367
+ var closed = false;
1368
+ var options = $.extend(
1369
+ {
1370
+ align: 'button-left', // button-right, dt-container, split-left, split-right
1371
+ autoClose: false,
1372
+ background: true,
1373
+ backgroundClassName: 'dt-button-background',
1374
+ closeButton: true,
1375
+ containerClassName: c.dom.collection.container.className,
1376
+ contentClassName: c.dom.collection.container.content.className,
1377
+ collectionLayout: '',
1378
+ collectionTitle: '',
1379
+ dropup: false,
1380
+ fade: 400,
1381
+ popoverTitle: '',
1382
+ rightAlignClassName: 'dt-button-right',
1383
+ tag: c.dom.collection.container.tag
1384
+ },
1385
+ inOpts
1386
+ );
1387
+
1388
+ var containerSelector =
1389
+ options.tag + '.' + options.containerClassName.replace(/ /g, '.');
1390
+ var hostButtonNode = hostButton.node();
1391
+ var hostNode = options.collectionLayout.includes('fixed') ? $('body') : hostButton.node();
1392
+
1393
+ var close = function () {
1394
+ closed = true;
1395
+
1396
+ _fadeOut($(containerSelector), options.fade, function () {
1397
+ $(this).detach();
1398
+ });
1399
+
1400
+ $(
1401
+ dt
1402
+ .buttons('[aria-haspopup="dialog"][aria-expanded="true"]')
1403
+ .nodes()
1404
+ ).attr('aria-expanded', 'false');
1405
+
1406
+ $('div.dt-button-background').off('click.dtb-collection');
1407
+ Buttons.background(
1408
+ false,
1409
+ options.backgroundClassName,
1410
+ options.fade,
1411
+ hostNode
1412
+ );
1413
+
1414
+ $(window).off('resize.resize.dtb-collection');
1415
+ $('body').off('.dtb-collection');
1416
+ dt.off('buttons-action.b-internal');
1417
+ dt.off('destroy');
1418
+
1419
+ $('body').trigger('buttons-popover-hide.dt');
1420
+ };
1421
+
1422
+ if (content === false) {
1423
+ close();
1424
+ return;
1425
+ }
1426
+
1427
+ var existingExpanded = $(
1428
+ dt.buttons('[aria-haspopup="dialog"][aria-expanded="true"]').nodes()
1429
+ );
1430
+ if (existingExpanded.length) {
1431
+ // Reuse the current position if the button that was triggered is inside an existing collection
1432
+ if (hostNode.closest(containerSelector).length) {
1433
+ hostNode = existingExpanded.eq(0);
1434
+ }
1435
+
1436
+ close();
1437
+ }
1438
+
1439
+ // Sort buttons if defined
1440
+ if (options.sort) {
1441
+ var elements = $('button', content)
1442
+ .map(function (idx, el) {
1443
+ return {
1444
+ text: $(el).text(),
1445
+ el: el
1446
+ };
1447
+ })
1448
+ .toArray();
1449
+
1450
+ elements.sort(function (a, b) {
1451
+ return a.text.localeCompare(b.text);
1452
+ });
1453
+
1454
+ $(content).append(elements.map(function (v) {
1455
+ return v.el;
1456
+ }));
1457
+ }
1458
+
1459
+ // Try to be smart about the layout
1460
+ var cnt = $('.dt-button', content).length;
1461
+ var mod = '';
1462
+
1463
+ if (cnt === 3) {
1464
+ mod = 'dtb-b3';
1465
+ }
1466
+ else if (cnt === 2) {
1467
+ mod = 'dtb-b2';
1468
+ }
1469
+ else if (cnt === 1) {
1470
+ mod = 'dtb-b1';
1471
+ }
1472
+
1473
+ var display = $('<' + options.tag + '/>')
1474
+ .addClass(options.containerClassName)
1475
+ .addClass(options.collectionLayout)
1476
+ .addClass(options.splitAlignClass)
1477
+ .addClass(mod)
1478
+ .css('display', 'none')
1479
+ .attr({
1480
+ 'aria-modal': true,
1481
+ role: 'dialog'
1482
+ });
1483
+
1484
+ content = $(content)
1485
+ .addClass(options.contentClassName)
1486
+ .attr('role', 'menu')
1487
+ .appendTo(display);
1488
+
1489
+ hostButtonNode.attr('aria-expanded', 'true');
1490
+
1491
+ if (hostNode.parents('body')[0] !== document.body) {
1492
+ hostNode = $(document.body).children('div, section, p').last();
1493
+ }
1494
+
1495
+ if (options.popoverTitle) {
1496
+ display.prepend(
1497
+ '<div class="dt-button-collection-title">' +
1498
+ options.popoverTitle +
1499
+ '</div>'
1500
+ );
1501
+ }
1502
+ else if (options.collectionTitle) {
1503
+ display.prepend(
1504
+ '<div class="dt-button-collection-title">' +
1505
+ options.collectionTitle +
1506
+ '</div>'
1507
+ );
1508
+ }
1509
+
1510
+ if (options.closeButton) {
1511
+ display
1512
+ .prepend('<div class="dtb-popover-close">&times;</div>')
1513
+ .addClass('dtb-collection-closeable');
1514
+ }
1515
+
1516
+ _fadeIn(display.insertAfter(hostNode), options.fade);
1517
+
1518
+ var tableContainer = $(hostButton.table().container());
1519
+ var position = display.css('position');
1520
+
1521
+ if (options.span === 'container' || options.align === 'dt-container') {
1522
+ hostNode = hostNode.parent();
1523
+ display.css('width', tableContainer.width());
1524
+ }
1525
+
1526
+ // Align the popover relative to the DataTables container
1527
+ // Useful for wide popovers such as SearchPanes
1528
+ if (position === 'absolute') {
1529
+ // Align relative to the host button
1530
+ var offsetParent = $(hostNode[0].offsetParent);
1531
+ var buttonPosition = hostNode.position();
1532
+ var buttonOffset = hostNode.offset();
1533
+ var tableSizes = offsetParent.offset();
1534
+ var containerPosition = offsetParent.position();
1535
+ var computed = window.getComputedStyle(offsetParent[0]);
1536
+
1537
+ tableSizes.height = offsetParent.outerHeight();
1538
+ tableSizes.width =
1539
+ offsetParent.width() + parseFloat(computed.paddingLeft);
1540
+ tableSizes.right = tableSizes.left + tableSizes.width;
1541
+ tableSizes.bottom = tableSizes.top + tableSizes.height;
1542
+
1543
+ // Set the initial position so we can read height / width
1544
+ var top = buttonPosition.top + hostNode.outerHeight();
1545
+ var left = buttonPosition.left;
1546
+
1547
+ display.css({
1548
+ top: top,
1549
+ left: left
1550
+ });
1551
+
1552
+ // Get the popover position
1553
+ computed = window.getComputedStyle(display[0]);
1554
+ var popoverSizes = display.offset();
1555
+
1556
+ popoverSizes.height = display.outerHeight();
1557
+ popoverSizes.width = display.outerWidth();
1558
+ popoverSizes.right = popoverSizes.left + popoverSizes.width;
1559
+ popoverSizes.bottom = popoverSizes.top + popoverSizes.height;
1560
+ popoverSizes.marginTop = parseFloat(computed.marginTop);
1561
+ popoverSizes.marginBottom = parseFloat(computed.marginBottom);
1562
+
1563
+ // First position per the class requirements - pop up and right align
1564
+ if (options.dropup) {
1565
+ top =
1566
+ buttonPosition.top -
1567
+ popoverSizes.height -
1568
+ popoverSizes.marginTop -
1569
+ popoverSizes.marginBottom;
1570
+ }
1571
+
1572
+ if (
1573
+ options.align === 'button-right' ||
1574
+ display.hasClass(options.rightAlignClassName)
1575
+ ) {
1576
+ left =
1577
+ buttonPosition.left -
1578
+ popoverSizes.width +
1579
+ hostNode.outerWidth();
1580
+ }
1581
+
1582
+ // Container alignment - make sure it doesn't overflow the table container
1583
+ if (
1584
+ options.align === 'dt-container' ||
1585
+ options.align === 'container'
1586
+ ) {
1587
+ if (left < buttonPosition.left) {
1588
+ left = -buttonPosition.left;
1589
+ }
1590
+ }
1591
+
1592
+ // Window adjustment
1593
+ if (
1594
+ containerPosition.left + left + popoverSizes.width >
1595
+ $(window).width()
1596
+ ) {
1597
+ // Overflowing the document to the right
1598
+ left =
1599
+ $(window).width() -
1600
+ popoverSizes.width -
1601
+ containerPosition.left;
1602
+ }
1603
+
1604
+ if (buttonOffset.left + left < 0) {
1605
+ // Off to the left of the document
1606
+ left = -buttonOffset.left;
1607
+ }
1608
+
1609
+ if (
1610
+ containerPosition.top + top + popoverSizes.height >
1611
+ $(window).height() + $(window).scrollTop()
1612
+ ) {
1613
+ // Pop up if otherwise we'd need the user to scroll down
1614
+ top =
1615
+ buttonPosition.top -
1616
+ popoverSizes.height -
1617
+ popoverSizes.marginTop -
1618
+ popoverSizes.marginBottom;
1619
+ }
1620
+
1621
+ if (offsetParent.offset().top + top < $(window).scrollTop()) {
1622
+ // Correction for when the top is beyond the top of the page
1623
+ top = buttonPosition.top + hostNode.outerHeight();
1624
+ }
1625
+
1626
+ // Calculations all done - now set it
1627
+ display.css({
1628
+ top: top,
1629
+ left: left
1630
+ });
1631
+ }
1632
+ else {
1633
+ // Fix position - centre on screen
1634
+ var place = function () {
1635
+ var half = $(window).height() / 2;
1636
+
1637
+ var top = display.height() / 2;
1638
+ if (top > half) {
1639
+ top = half;
1640
+ }
1641
+
1642
+ display.css('marginTop', top * -1);
1643
+ };
1644
+
1645
+ place();
1646
+
1647
+ $(window).on('resize.dtb-collection', function () {
1648
+ place();
1649
+ });
1650
+ }
1651
+
1652
+ if (options.background) {
1653
+ Buttons.background(
1654
+ true,
1655
+ options.backgroundClassName,
1656
+ options.fade,
1657
+ options.backgroundHost || hostNode
1658
+ );
1659
+ }
1660
+
1661
+ // This is bonkers, but if we don't have a click listener on the
1662
+ // background element, iOS Safari will ignore the body click
1663
+ // listener below. An empty function here is all that is
1664
+ // required to make it work...
1665
+ $('div.dt-button-background').on(
1666
+ 'click.dtb-collection',
1667
+ function () {}
1668
+ );
1669
+
1670
+ if (options.autoClose) {
1671
+ setTimeout(function () {
1672
+ dt.on('buttons-action.b-internal', function (e, btn, dt, node) {
1673
+ if (node[0] === hostNode[0]) {
1674
+ return;
1675
+ }
1676
+ close();
1677
+ });
1678
+ }, 0);
1679
+ }
1680
+
1681
+ $(display).trigger('buttons-popover.dt');
1682
+
1683
+ dt.on('destroy', close);
1684
+
1685
+ setTimeout(function () {
1686
+ closed = false;
1687
+ $('body')
1688
+ .on('click.dtb-collection', function (e) {
1689
+ if (closed) {
1690
+ return;
1691
+ }
1692
+
1693
+ // andSelf is deprecated in jQ1.8, but we want 1.7 compat
1694
+ var back = $.fn.addBack ? 'addBack' : 'andSelf';
1695
+ var parent = $(e.target).parent()[0];
1696
+
1697
+ if (
1698
+ (!$(e.target).parents()[back]().filter(content)
1699
+ .length &&
1700
+ !$(parent).hasClass('dt-buttons')) ||
1701
+ $(e.target).hasClass('dt-button-background')
1702
+ ) {
1703
+ close();
1704
+ }
1705
+ })
1706
+ .on('keyup.dtb-collection', function (e) {
1707
+ if (e.keyCode === 27) {
1708
+ close();
1709
+ }
1710
+ })
1711
+ .on('keydown.dtb-collection', function (e) {
1712
+ // Focus trap for tab key
1713
+ var elements = $('a, button', content);
1714
+ var active = document.activeElement;
1715
+
1716
+ if (e.keyCode !== 9) {
1717
+ // tab
1718
+ return;
1719
+ }
1720
+
1721
+ if (elements.index(active) === -1) {
1722
+ // If current focus is not inside the popover
1723
+ elements.first().focus();
1724
+ e.preventDefault();
1725
+ }
1726
+ else if (e.shiftKey) {
1727
+ // Reverse tabbing order when shift key is pressed
1728
+ if (active === elements[0]) {
1729
+ elements.last().focus();
1730
+ e.preventDefault();
1731
+ }
1732
+ }
1733
+ else {
1734
+ if (active === elements.last()[0]) {
1735
+ elements.first().focus();
1736
+ e.preventDefault();
1737
+ }
1738
+ }
1739
+ });
1740
+ }, 0);
1741
+ }
1742
+ });
1743
+
1744
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1745
+ * Statics
1746
+ */
1747
+
1748
+ /**
1749
+ * Show / hide a background layer behind a collection
1750
+ * @param {boolean} Flag to indicate if the background should be shown or
1751
+ * hidden
1752
+ * @param {string} Class to assign to the background
1753
+ * @static
1754
+ */
1755
+ Buttons.background = function (show, className, fade, insertPoint) {
1756
+ if (fade === undefined) {
1757
+ fade = 400;
1758
+ }
1759
+ if (!insertPoint) {
1760
+ insertPoint = document.body;
1761
+ }
1762
+
1763
+ if (show) {
1764
+ _fadeIn(
1765
+ $('<div/>')
1766
+ .addClass(className)
1767
+ .css('display', 'none')
1768
+ .insertAfter(insertPoint),
1769
+ fade
1770
+ );
1771
+ }
1772
+ else {
1773
+ _fadeOut($('div.' + className), fade, function () {
1774
+ $(this).removeClass(className).remove();
1775
+ });
1776
+ }
1777
+ };
1778
+
1779
+ /**
1780
+ * Instance selector - select Buttons instances based on an instance selector
1781
+ * value from the buttons assigned to a DataTable. This is only useful if
1782
+ * multiple instances are attached to a DataTable.
1783
+ * @param {string|int|array} Instance selector - see `instance-selector`
1784
+ * documentation on the DataTables site
1785
+ * @param {array} Button instance array that was attached to the DataTables
1786
+ * settings object
1787
+ * @return {array} Buttons instances
1788
+ * @static
1789
+ */
1790
+ Buttons.instanceSelector = function (group, buttons) {
1791
+ if (group === undefined || group === null) {
1792
+ return $.map(buttons, function (v) {
1793
+ return v.inst;
1794
+ });
1795
+ }
1796
+
1797
+ var ret = [];
1798
+ var names = $.map(buttons, function (v) {
1799
+ return v.name;
1800
+ });
1801
+
1802
+ // Flatten the group selector into an array of single options
1803
+ var process = function (input) {
1804
+ if (Array.isArray(input)) {
1805
+ for (var i = 0, ien = input.length; i < ien; i++) {
1806
+ process(input[i]);
1807
+ }
1808
+ return;
1809
+ }
1810
+
1811
+ if (typeof input === 'string') {
1812
+ if (input.indexOf(',') !== -1) {
1813
+ // String selector, list of names
1814
+ process(input.split(','));
1815
+ }
1816
+ else {
1817
+ // String selector individual name
1818
+ var idx = $.inArray(input.trim(), names);
1819
+
1820
+ if (idx !== -1) {
1821
+ ret.push(buttons[idx].inst);
1822
+ }
1823
+ }
1824
+ }
1825
+ else if (typeof input === 'number') {
1826
+ // Index selector
1827
+ ret.push(buttons[input].inst);
1828
+ }
1829
+ else if (typeof input === 'object' && input.nodeName) {
1830
+ // Element selector
1831
+ for (var j = 0; j < buttons.length; j++) {
1832
+ if (buttons[j].inst.dom.container[0] === input) {
1833
+ ret.push(buttons[j].inst);
1834
+ }
1835
+ }
1836
+ }
1837
+ else if (typeof input === 'object') {
1838
+ // Actual instance selector
1839
+ ret.push(input);
1840
+ }
1841
+ };
1842
+
1843
+ process(group);
1844
+
1845
+ return ret;
1846
+ };
1847
+
1848
+ /**
1849
+ * Button selector - select one or more buttons from a selector input so some
1850
+ * operation can be performed on them.
1851
+ * @param {array} Button instances array that the selector should operate on
1852
+ * @param {string|int|node|jQuery|array} Button selector - see
1853
+ * `button-selector` documentation on the DataTables site
1854
+ * @return {array} Array of objects containing `inst` and `idx` properties of
1855
+ * the selected buttons so you know which instance each button belongs to.
1856
+ * @static
1857
+ */
1858
+ Buttons.buttonSelector = function (insts, selector) {
1859
+ var ret = [];
1860
+ var nodeBuilder = function (a, buttons, baseIdx) {
1861
+ var button;
1862
+ var idx;
1863
+
1864
+ for (var i = 0, ien = buttons.length; i < ien; i++) {
1865
+ button = buttons[i];
1866
+
1867
+ if (button) {
1868
+ idx = baseIdx !== undefined ? baseIdx + i : i + '';
1869
+
1870
+ a.push({
1871
+ node: button.node,
1872
+ name: button.conf.name,
1873
+ idx: idx
1874
+ });
1875
+
1876
+ if (button.buttons) {
1877
+ nodeBuilder(a, button.buttons, idx + '-');
1878
+ }
1879
+ }
1880
+ }
1881
+ };
1882
+
1883
+ var run = function (selector, inst) {
1884
+ var i, ien;
1885
+ var buttons = [];
1886
+ nodeBuilder(buttons, inst.s.buttons);
1887
+
1888
+ var nodes = $.map(buttons, function (v) {
1889
+ return v.node;
1890
+ });
1891
+
1892
+ if (Array.isArray(selector) || selector instanceof $) {
1893
+ for (i = 0, ien = selector.length; i < ien; i++) {
1894
+ run(selector[i], inst);
1895
+ }
1896
+ return;
1897
+ }
1898
+
1899
+ if (selector === null || selector === undefined || selector === '*') {
1900
+ // Select all
1901
+ for (i = 0, ien = buttons.length; i < ien; i++) {
1902
+ ret.push({
1903
+ inst: inst,
1904
+ node: buttons[i].node
1905
+ });
1906
+ }
1907
+ }
1908
+ else if (typeof selector === 'number') {
1909
+ // Main button index selector
1910
+ if (inst.s.buttons[selector]) {
1911
+ ret.push({
1912
+ inst: inst,
1913
+ node: inst.s.buttons[selector].node
1914
+ });
1915
+ }
1916
+ }
1917
+ else if (typeof selector === 'string') {
1918
+ if (selector.indexOf(',') !== -1) {
1919
+ // Split
1920
+ var a = selector.split(',');
1921
+
1922
+ for (i = 0, ien = a.length; i < ien; i++) {
1923
+ run(a[i].trim(), inst);
1924
+ }
1925
+ }
1926
+ else if (selector.match(/^\d+(\-\d+)*$/)) {
1927
+ // Sub-button index selector
1928
+ var indexes = $.map(buttons, function (v) {
1929
+ return v.idx;
1930
+ });
1931
+
1932
+ ret.push({
1933
+ inst: inst,
1934
+ node: buttons[$.inArray(selector, indexes)].node
1935
+ });
1936
+ }
1937
+ else if (selector.indexOf(':name') !== -1) {
1938
+ // Button name selector
1939
+ var name = selector.replace(':name', '');
1940
+
1941
+ for (i = 0, ien = buttons.length; i < ien; i++) {
1942
+ if (buttons[i].name === name) {
1943
+ ret.push({
1944
+ inst: inst,
1945
+ node: buttons[i].node
1946
+ });
1947
+ }
1948
+ }
1949
+ }
1950
+ else {
1951
+ // jQuery selector on the nodes
1952
+ $(nodes)
1953
+ .filter(selector)
1954
+ .each(function () {
1955
+ ret.push({
1956
+ inst: inst,
1957
+ node: this
1958
+ });
1959
+ });
1960
+ }
1961
+ }
1962
+ else if (typeof selector === 'object' && selector.nodeName) {
1963
+ // Node selector
1964
+ var idx = $.inArray(selector, nodes);
1965
+
1966
+ if (idx !== -1) {
1967
+ ret.push({
1968
+ inst: inst,
1969
+ node: nodes[idx]
1970
+ });
1971
+ }
1972
+ }
1973
+ };
1974
+
1975
+ for (var i = 0, ien = insts.length; i < ien; i++) {
1976
+ var inst = insts[i];
1977
+
1978
+ run(selector, inst);
1979
+ }
1980
+
1981
+ return ret;
1982
+ };
1983
+
1984
+ /**
1985
+ * Default function used for formatting output data.
1986
+ * @param {*} str Data to strip
1987
+ */
1988
+ Buttons.stripData = function (str, config) {
1989
+ // If the input is an HTML element, we can use the HTML from it (HTML might be stripped below).
1990
+ if (str !== null && typeof str === 'object' && str.nodeName && str.nodeType) {
1991
+ str = str.innerHTML;
1992
+ }
1993
+
1994
+ if (typeof str !== 'string') {
1995
+ return str;
1996
+ }
1997
+
1998
+ // Always remove script tags
1999
+ str = Buttons.stripHtmlScript(str);
2000
+
2001
+ // Always remove comments
2002
+ str = Buttons.stripHtmlComments(str);
2003
+
2004
+ if (!config || config.stripHtml) {
2005
+ str = DataTable.util.stripHtml(str);
2006
+ }
2007
+
2008
+ if (!config || config.trim) {
2009
+ str = str.trim();
2010
+ }
2011
+
2012
+ if (!config || config.stripNewlines) {
2013
+ str = str.replace(/\n/g, ' ');
2014
+ }
2015
+
2016
+ if (!config || config.decodeEntities) {
2017
+ if (_entityDecoder) {
2018
+ str = _entityDecoder(str);
2019
+ }
2020
+ else {
2021
+ _exportTextarea.innerHTML = str;
2022
+ str = _exportTextarea.value;
2023
+ }
2024
+ }
2025
+
2026
+ // Prevent Excel from running a formula
2027
+ if (!config || config.escapeExcelFormula) {
2028
+ if (str.match(/^[=+\-@\t\r]/)) {
2029
+ console.log('matching and updateing');
2030
+ str = "'" + str;
2031
+ }
2032
+ }
2033
+
2034
+ return str;
2035
+ };
2036
+
2037
+ /**
2038
+ * Provide a custom entity decoding function - e.g. a regex one, which can be
2039
+ * much faster than the built in DOM option, but also larger code size.
2040
+ * @param {function} fn
2041
+ */
2042
+ Buttons.entityDecoder = function (fn) {
2043
+ _entityDecoder = fn;
2044
+ };
2045
+
2046
+ /**
2047
+ * Common function for stripping HTML comments
2048
+ *
2049
+ * @param {*} input
2050
+ * @returns
2051
+ */
2052
+ Buttons.stripHtmlComments = function (input) {
2053
+ var previous;
2054
+
2055
+ do {
2056
+ previous = input;
2057
+ input = input.replace(/(<!--.*?--!?>)|(<!--[\S\s]+?--!?>)|(<!--[\S\s]*?$)/g, '');
2058
+ } while (input !== previous);
2059
+
2060
+ return input;
2061
+ };
2062
+
2063
+ /**
2064
+ * Common function for stripping HTML script tags
2065
+ *
2066
+ * @param {*} input
2067
+ * @returns
2068
+ */
2069
+ Buttons.stripHtmlScript = function (input) {
2070
+ var previous;
2071
+
2072
+ do {
2073
+ previous = input;
2074
+ input = input.replace(/<script\b[^<]*(?:(?!<\/script[^>]*>)<[^<]*)*<\/script[^>]*>/gi, '');
2075
+ } while (input !== previous);
2076
+
2077
+ return input;
2078
+ };
2079
+
2080
+ /**
2081
+ * Buttons defaults. For full documentation, please refer to the docs/option
2082
+ * directory or the DataTables site.
2083
+ * @type {Object}
2084
+ * @static
2085
+ */
2086
+ Buttons.defaults = {
2087
+ buttons: ['copy', 'excel', 'csv', 'pdf', 'print'],
2088
+ name: 'main',
2089
+ tabIndex: 0,
2090
+ dom: {
2091
+ container: {
2092
+ tag: 'div',
2093
+ className: 'dt-buttons'
2094
+ },
2095
+ collection: {
2096
+ container: {
2097
+ // The element used for the dropdown
2098
+ className: 'dt-button-collection',
2099
+ content: {
2100
+ className: '',
2101
+ tag: 'div'
2102
+ },
2103
+ tag: 'div'
2104
+ }
2105
+ // optionally
2106
+ // , button: IButton - buttons inside the collection container
2107
+ // , split: ISplit - splits inside the collection container
2108
+ },
2109
+ button: {
2110
+ tag: 'button',
2111
+ className: 'dt-button',
2112
+ active: 'dt-button-active', // class name
2113
+ disabled: 'disabled', // class name
2114
+ spacer: {
2115
+ className: 'dt-button-spacer',
2116
+ tag: 'span'
2117
+ },
2118
+ liner: {
2119
+ tag: 'span',
2120
+ className: ''
2121
+ },
2122
+ dropClass: '',
2123
+ dropHtml: '<span class="dt-button-down-arrow">&#x25BC;</span>'
2124
+ },
2125
+ split: {
2126
+ action: {
2127
+ // action button
2128
+ className: 'dt-button-split-drop-button dt-button',
2129
+ tag: 'button'
2130
+ },
2131
+ dropdown: {
2132
+ // button to trigger the dropdown
2133
+ align: 'split-right',
2134
+ className: 'dt-button-split-drop',
2135
+ splitAlignClass: 'dt-button-split-left',
2136
+ tag: 'button'
2137
+ },
2138
+ wrapper: {
2139
+ // wrap around both
2140
+ className: 'dt-button-split',
2141
+ tag: 'div'
2142
+ }
2143
+ }
2144
+ }
2145
+ };
2146
+
2147
+ /**
2148
+ * Version information
2149
+ * @type {string}
2150
+ * @static
2151
+ */
2152
+ Buttons.version = '3.2.2';
2153
+
2154
+ $.extend(_dtButtons, {
2155
+ collection: {
2156
+ text: function (dt) {
2157
+ return dt.i18n('buttons.collection', 'Collection');
2158
+ },
2159
+ className: 'buttons-collection',
2160
+ closeButton: false,
2161
+ dropIcon: true,
2162
+ init: function (dt, button) {
2163
+ button.attr('aria-expanded', false);
2164
+ },
2165
+ action: function (e, dt, button, config) {
2166
+ if (config._collection.parents('body').length) {
2167
+ this.popover(false, config);
2168
+ }
2169
+ else {
2170
+ this.popover(config._collection, config);
2171
+ }
2172
+
2173
+ // When activated using a key - auto focus on the
2174
+ // first item in the popover
2175
+ if (e.type === 'keypress') {
2176
+ $('a, button', config._collection).eq(0).focus();
2177
+ }
2178
+ },
2179
+ attr: {
2180
+ 'aria-haspopup': 'dialog'
2181
+ }
2182
+ // Also the popover options, defined in Buttons.popover
2183
+ },
2184
+ split: {
2185
+ text: function (dt) {
2186
+ return dt.i18n('buttons.split', 'Split');
2187
+ },
2188
+ className: 'buttons-split',
2189
+ closeButton: false,
2190
+ init: function (dt, button) {
2191
+ return button.attr('aria-expanded', false);
2192
+ },
2193
+ action: function (e, dt, button, config) {
2194
+ this.popover(config._collection, config);
2195
+ },
2196
+ attr: {
2197
+ 'aria-haspopup': 'dialog'
2198
+ }
2199
+ // Also the popover options, defined in Buttons.popover
2200
+ },
2201
+ copy: function () {
2202
+ if (_dtButtons.copyHtml5) {
2203
+ return 'copyHtml5';
2204
+ }
2205
+ },
2206
+ csv: function (dt, conf) {
2207
+ if (_dtButtons.csvHtml5 && _dtButtons.csvHtml5.available(dt, conf)) {
2208
+ return 'csvHtml5';
2209
+ }
2210
+ },
2211
+ excel: function (dt, conf) {
2212
+ if (
2213
+ _dtButtons.excelHtml5 &&
2214
+ _dtButtons.excelHtml5.available(dt, conf)
2215
+ ) {
2216
+ return 'excelHtml5';
2217
+ }
2218
+ },
2219
+ pdf: function (dt, conf) {
2220
+ if (_dtButtons.pdfHtml5 && _dtButtons.pdfHtml5.available(dt, conf)) {
2221
+ return 'pdfHtml5';
2222
+ }
2223
+ },
2224
+ pageLength: function (dt) {
2225
+ var lengthMenu = dt.settings()[0].aLengthMenu;
2226
+ var vals = [];
2227
+ var lang = [];
2228
+ var text = function (dt) {
2229
+ return dt.i18n(
2230
+ 'buttons.pageLength',
2231
+ {
2232
+ '-1': 'Show all rows',
2233
+ _: 'Show %d rows'
2234
+ },
2235
+ dt.page.len()
2236
+ );
2237
+ };
2238
+
2239
+ // Support for DataTables 1.x 2D array
2240
+ if (Array.isArray(lengthMenu[0])) {
2241
+ vals = lengthMenu[0];
2242
+ lang = lengthMenu[1];
2243
+ }
2244
+ else {
2245
+ for (var i = 0; i < lengthMenu.length; i++) {
2246
+ var option = lengthMenu[i];
2247
+
2248
+ // Support for DataTables 2 object in the array
2249
+ if ($.isPlainObject(option)) {
2250
+ vals.push(option.value);
2251
+ lang.push(option.label);
2252
+ }
2253
+ else {
2254
+ vals.push(option);
2255
+ lang.push(option);
2256
+ }
2257
+ }
2258
+ }
2259
+
2260
+ return {
2261
+ extend: 'collection',
2262
+ text: text,
2263
+ className: 'buttons-page-length',
2264
+ autoClose: true,
2265
+ buttons: $.map(vals, function (val, i) {
2266
+ return {
2267
+ text: lang[i],
2268
+ className: 'button-page-length',
2269
+ action: function (e, dt) {
2270
+ dt.page.len(val).draw();
2271
+ },
2272
+ init: function (dt, node, conf) {
2273
+ var that = this;
2274
+ var fn = function () {
2275
+ that.active(dt.page.len() === val);
2276
+ };
2277
+
2278
+ dt.on('length.dt' + conf.namespace, fn);
2279
+ fn();
2280
+ },
2281
+ destroy: function (dt, node, conf) {
2282
+ dt.off('length.dt' + conf.namespace);
2283
+ }
2284
+ };
2285
+ }),
2286
+ init: function (dt, node, conf) {
2287
+ var that = this;
2288
+ dt.on('length.dt' + conf.namespace, function () {
2289
+ that.text(conf.text);
2290
+ });
2291
+ },
2292
+ destroy: function (dt, node, conf) {
2293
+ dt.off('length.dt' + conf.namespace);
2294
+ }
2295
+ };
2296
+ },
2297
+ spacer: {
2298
+ style: 'empty',
2299
+ spacer: true,
2300
+ text: function (dt) {
2301
+ return dt.i18n('buttons.spacer', '');
2302
+ }
2303
+ }
2304
+ });
2305
+
2306
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2307
+ * DataTables API
2308
+ *
2309
+ * For complete documentation, please refer to the docs/api directory or the
2310
+ * DataTables site
2311
+ */
2312
+
2313
+ // Buttons group and individual button selector
2314
+ DataTable.Api.register('buttons()', function (group, selector) {
2315
+ // Argument shifting
2316
+ if (selector === undefined) {
2317
+ selector = group;
2318
+ group = undefined;
2319
+ }
2320
+
2321
+ this.selector.buttonGroup = group;
2322
+
2323
+ var res = this.iterator(
2324
+ true,
2325
+ 'table',
2326
+ function (ctx) {
2327
+ if (ctx._buttons) {
2328
+ return Buttons.buttonSelector(
2329
+ Buttons.instanceSelector(group, ctx._buttons),
2330
+ selector
2331
+ );
2332
+ }
2333
+ },
2334
+ true
2335
+ );
2336
+
2337
+ res._groupSelector = group;
2338
+ return res;
2339
+ });
2340
+
2341
+ // Individual button selector
2342
+ DataTable.Api.register('button()', function (group, selector) {
2343
+ // just run buttons() and truncate
2344
+ var buttons = this.buttons(group, selector);
2345
+
2346
+ if (buttons.length > 1) {
2347
+ buttons.splice(1, buttons.length);
2348
+ }
2349
+
2350
+ return buttons;
2351
+ });
2352
+
2353
+ // Active buttons
2354
+ DataTable.Api.registerPlural(
2355
+ 'buttons().active()',
2356
+ 'button().active()',
2357
+ function (flag) {
2358
+ if (flag === undefined) {
2359
+ return this.map(function (set) {
2360
+ return set.inst.active(set.node);
2361
+ });
2362
+ }
2363
+
2364
+ return this.each(function (set) {
2365
+ set.inst.active(set.node, flag);
2366
+ });
2367
+ }
2368
+ );
2369
+
2370
+ // Get / set button action
2371
+ DataTable.Api.registerPlural(
2372
+ 'buttons().action()',
2373
+ 'button().action()',
2374
+ function (action) {
2375
+ if (action === undefined) {
2376
+ return this.map(function (set) {
2377
+ return set.inst.action(set.node);
2378
+ });
2379
+ }
2380
+
2381
+ return this.each(function (set) {
2382
+ set.inst.action(set.node, action);
2383
+ });
2384
+ }
2385
+ );
2386
+
2387
+ // Collection control
2388
+ DataTable.Api.registerPlural(
2389
+ 'buttons().collectionRebuild()',
2390
+ 'button().collectionRebuild()',
2391
+ function (buttons) {
2392
+ return this.each(function (set) {
2393
+ for (var i = 0; i < buttons.length; i++) {
2394
+ if (typeof buttons[i] === 'object') {
2395
+ buttons[i].parentConf = set;
2396
+ }
2397
+ }
2398
+ set.inst.collectionRebuild(set.node, buttons);
2399
+ });
2400
+ }
2401
+ );
2402
+
2403
+ // Enable / disable buttons
2404
+ DataTable.Api.register(
2405
+ ['buttons().enable()', 'button().enable()'],
2406
+ function (flag) {
2407
+ return this.each(function (set) {
2408
+ set.inst.enable(set.node, flag);
2409
+ });
2410
+ }
2411
+ );
2412
+
2413
+ // Disable buttons
2414
+ DataTable.Api.register(
2415
+ ['buttons().disable()', 'button().disable()'],
2416
+ function () {
2417
+ return this.each(function (set) {
2418
+ set.inst.disable(set.node);
2419
+ });
2420
+ }
2421
+ );
2422
+
2423
+ // Button index
2424
+ DataTable.Api.register('button().index()', function () {
2425
+ var idx = null;
2426
+
2427
+ this.each(function (set) {
2428
+ var res = set.inst.index(set.node);
2429
+
2430
+ if (res !== null) {
2431
+ idx = res;
2432
+ }
2433
+ });
2434
+
2435
+ return idx;
2436
+ });
2437
+
2438
+ // Get button nodes
2439
+ DataTable.Api.registerPlural(
2440
+ 'buttons().nodes()',
2441
+ 'button().node()',
2442
+ function () {
2443
+ var jq = $();
2444
+
2445
+ // jQuery will automatically reduce duplicates to a single entry
2446
+ $(
2447
+ this.each(function (set) {
2448
+ jq = jq.add(set.inst.node(set.node));
2449
+ })
2450
+ );
2451
+
2452
+ return jq;
2453
+ }
2454
+ );
2455
+
2456
+ // Get / set button processing state
2457
+ DataTable.Api.registerPlural(
2458
+ 'buttons().processing()',
2459
+ 'button().processing()',
2460
+ function (flag) {
2461
+ if (flag === undefined) {
2462
+ return this.map(function (set) {
2463
+ return set.inst.processing(set.node);
2464
+ });
2465
+ }
2466
+
2467
+ return this.each(function (set) {
2468
+ set.inst.processing(set.node, flag);
2469
+ });
2470
+ }
2471
+ );
2472
+
2473
+ // Get / set button text (i.e. the button labels)
2474
+ DataTable.Api.registerPlural(
2475
+ 'buttons().text()',
2476
+ 'button().text()',
2477
+ function (label) {
2478
+ if (label === undefined) {
2479
+ return this.map(function (set) {
2480
+ return set.inst.text(set.node);
2481
+ });
2482
+ }
2483
+
2484
+ return this.each(function (set) {
2485
+ set.inst.text(set.node, label);
2486
+ });
2487
+ }
2488
+ );
2489
+
2490
+ // Trigger a button's action
2491
+ DataTable.Api.registerPlural(
2492
+ 'buttons().trigger()',
2493
+ 'button().trigger()',
2494
+ function () {
2495
+ return this.each(function (set) {
2496
+ set.inst.node(set.node).trigger('click');
2497
+ });
2498
+ }
2499
+ );
2500
+
2501
+ // Button resolver to the popover
2502
+ DataTable.Api.register('button().popover()', function (content, options) {
2503
+ return this.map(function (set) {
2504
+ return set.inst._popover(content, this.button(this[0].node), options);
2505
+ });
2506
+ });
2507
+
2508
+ // Get the container elements
2509
+ DataTable.Api.register('buttons().containers()', function () {
2510
+ var jq = $();
2511
+ var groupSelector = this._groupSelector;
2512
+
2513
+ // We need to use the group selector directly, since if there are no buttons
2514
+ // the result set will be empty
2515
+ this.iterator(true, 'table', function (ctx) {
2516
+ if (ctx._buttons) {
2517
+ var insts = Buttons.instanceSelector(groupSelector, ctx._buttons);
2518
+
2519
+ for (var i = 0, ien = insts.length; i < ien; i++) {
2520
+ jq = jq.add(insts[i].container());
2521
+ }
2522
+ }
2523
+ });
2524
+
2525
+ return jq;
2526
+ });
2527
+
2528
+ DataTable.Api.register('buttons().container()', function () {
2529
+ // API level of nesting is `buttons()` so we can zip into the containers method
2530
+ return this.containers().eq(0);
2531
+ });
2532
+
2533
+ // Add a new button
2534
+ DataTable.Api.register('button().add()', function (idx, conf, draw) {
2535
+ var ctx = this.context;
2536
+
2537
+ // Don't use `this` as it could be empty - select the instances directly
2538
+ if (ctx.length) {
2539
+ var inst = Buttons.instanceSelector(
2540
+ this._groupSelector,
2541
+ ctx[0]._buttons
2542
+ );
2543
+
2544
+ if (inst.length) {
2545
+ inst[0].add(conf, idx, draw);
2546
+ }
2547
+ }
2548
+
2549
+ return this.button(this._groupSelector, idx);
2550
+ });
2551
+
2552
+ // Destroy the button sets selected
2553
+ DataTable.Api.register('buttons().destroy()', function () {
2554
+ this.pluck('inst')
2555
+ .unique()
2556
+ .each(function (inst) {
2557
+ inst.destroy();
2558
+ });
2559
+
2560
+ return this;
2561
+ });
2562
+
2563
+ // Remove a button
2564
+ DataTable.Api.registerPlural(
2565
+ 'buttons().remove()',
2566
+ 'buttons().remove()',
2567
+ function () {
2568
+ this.each(function (set) {
2569
+ set.inst.remove(set.node);
2570
+ });
2571
+
2572
+ return this;
2573
+ }
2574
+ );
2575
+
2576
+ // Information box that can be used by buttons
2577
+ var _infoTimer;
2578
+ DataTable.Api.register('buttons.info()', function (title, message, time) {
2579
+ var that = this;
2580
+
2581
+ if (title === false) {
2582
+ this.off('destroy.btn-info');
2583
+ _fadeOut($('#datatables_buttons_info'), 400, function () {
2584
+ $(this).remove();
2585
+ });
2586
+ clearTimeout(_infoTimer);
2587
+ _infoTimer = null;
2588
+
2589
+ return this;
2590
+ }
2591
+
2592
+ if (_infoTimer) {
2593
+ clearTimeout(_infoTimer);
2594
+ }
2595
+
2596
+ if ($('#datatables_buttons_info').length) {
2597
+ $('#datatables_buttons_info').remove();
2598
+ }
2599
+
2600
+ title = title ? '<h2>' + title + '</h2>' : '';
2601
+
2602
+ _fadeIn(
2603
+ $('<div id="datatables_buttons_info" class="dt-button-info"/>')
2604
+ .html(title)
2605
+ .append(
2606
+ $('<div/>')[typeof message === 'string' ? 'html' : 'append'](
2607
+ message
2608
+ )
2609
+ )
2610
+ .css('display', 'none')
2611
+ .appendTo('body')
2612
+ );
2613
+
2614
+ if (time !== undefined && time !== 0) {
2615
+ _infoTimer = setTimeout(function () {
2616
+ that.buttons.info(false);
2617
+ }, time);
2618
+ }
2619
+
2620
+ this.on('destroy.btn-info', function () {
2621
+ that.buttons.info(false);
2622
+ });
2623
+
2624
+ return this;
2625
+ });
2626
+
2627
+ // Get data from the table for export - this is common to a number of plug-in
2628
+ // buttons so it is included in the Buttons core library
2629
+ DataTable.Api.register('buttons.exportData()', function (options) {
2630
+ if (this.context.length) {
2631
+ return _exportData(new DataTable.Api(this.context[0]), options);
2632
+ }
2633
+ });
2634
+
2635
+ // Get information about the export that is common to many of the export data
2636
+ // types (DRY)
2637
+ DataTable.Api.register('buttons.exportInfo()', function (conf) {
2638
+ if (!conf) {
2639
+ conf = {};
2640
+ }
2641
+
2642
+ return {
2643
+ filename: _filename(conf, this),
2644
+ title: _title(conf, this),
2645
+ messageTop: _message(this, conf, conf.message || conf.messageTop, 'top'),
2646
+ messageBottom: _message(this, conf, conf.messageBottom, 'bottom')
2647
+ };
2648
+ });
2649
+
2650
+ /**
2651
+ * Get the file name for an exported file.
2652
+ *
2653
+ * @param {object} config Button configuration
2654
+ * @param {object} dt DataTable instance
2655
+ */
2656
+ var _filename = function (config, dt) {
2657
+ // Backwards compatibility
2658
+ var filename =
2659
+ config.filename === '*' &&
2660
+ config.title !== '*' &&
2661
+ config.title !== undefined &&
2662
+ config.title !== null &&
2663
+ config.title !== ''
2664
+ ? config.title
2665
+ : config.filename;
2666
+
2667
+ if (typeof filename === 'function') {
2668
+ filename = filename(config, dt);
2669
+ }
2670
+
2671
+ if (filename === undefined || filename === null) {
2672
+ return null;
2673
+ }
2674
+
2675
+ if (filename.indexOf('*') !== -1) {
2676
+ filename = filename.replace(/\*/g, $('head > title').text()).trim();
2677
+ }
2678
+
2679
+ // Strip characters which the OS will object to
2680
+ filename = filename.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, '');
2681
+
2682
+ var extension = _stringOrFunction(config.extension, config, dt);
2683
+ if (!extension) {
2684
+ extension = '';
2685
+ }
2686
+
2687
+ return filename + extension;
2688
+ };
2689
+
2690
+ /**
2691
+ * Simply utility method to allow parameters to be given as a function
2692
+ *
2693
+ * @param {undefined|string|function} option Option
2694
+ * @return {null|string} Resolved value
2695
+ */
2696
+ var _stringOrFunction = function (option, config, dt) {
2697
+ if (option === null || option === undefined) {
2698
+ return null;
2699
+ }
2700
+ else if (typeof option === 'function') {
2701
+ return option(config, dt);
2702
+ }
2703
+ return option;
2704
+ };
2705
+
2706
+ /**
2707
+ * Get the title for an exported file.
2708
+ *
2709
+ * @param {object} config Button configuration
2710
+ */
2711
+ var _title = function (config, dt) {
2712
+ var title = _stringOrFunction(config.title, config, dt);
2713
+
2714
+ return title === null
2715
+ ? null
2716
+ : title.indexOf('*') !== -1
2717
+ ? title.replace(/\*/g, $('head > title').text() || 'Exported data')
2718
+ : title;
2719
+ };
2720
+
2721
+ var _message = function (dt, config, option, position) {
2722
+ var message = _stringOrFunction(option, config, dt);
2723
+ if (message === null) {
2724
+ return null;
2725
+ }
2726
+
2727
+ var caption = $('caption', dt.table().container()).eq(0);
2728
+ if (message === '*') {
2729
+ var side = caption.css('caption-side');
2730
+ if (side !== position) {
2731
+ return null;
2732
+ }
2733
+
2734
+ return caption.length ? caption.text() : '';
2735
+ }
2736
+
2737
+ return message;
2738
+ };
2739
+
2740
+ var _exportTextarea = $('<textarea/>')[0];
2741
+ var _exportData = function (dt, inOpts) {
2742
+ var config = $.extend(
2743
+ true,
2744
+ {},
2745
+ {
2746
+ rows: null,
2747
+ columns: '',
2748
+ modifier: {
2749
+ search: 'applied',
2750
+ order: 'applied'
2751
+ },
2752
+ orthogonal: 'display',
2753
+ stripHtml: true,
2754
+ stripNewlines: true,
2755
+ decodeEntities: true,
2756
+ escapeExcelFormula: false,
2757
+ trim: true,
2758
+ format: {
2759
+ header: function (d) {
2760
+ return Buttons.stripData(d, config);
2761
+ },
2762
+ footer: function (d) {
2763
+ return Buttons.stripData(d, config);
2764
+ },
2765
+ body: function (d) {
2766
+ return Buttons.stripData(d, config);
2767
+ }
2768
+ },
2769
+ customizeData: null,
2770
+ customizeZip: null
2771
+ },
2772
+ inOpts
2773
+ );
2774
+
2775
+ var header = dt
2776
+ .columns(config.columns)
2777
+ .indexes()
2778
+ .map(function (idx) {
2779
+ var col = dt.column(idx);
2780
+ return config.format.header(col.title(), idx, col.header());
2781
+ })
2782
+ .toArray();
2783
+
2784
+ var footer = dt.table().footer()
2785
+ ? dt
2786
+ .columns(config.columns)
2787
+ .indexes()
2788
+ .map(function (idx) {
2789
+ var el = dt.column(idx).footer();
2790
+ var val = '';
2791
+
2792
+ if (el) {
2793
+ var inner = $('.dt-column-title', el);
2794
+
2795
+ val = inner.length
2796
+ ? inner.html()
2797
+ : $(el).html();
2798
+ }
2799
+
2800
+ return config.format.footer(val, idx, el);
2801
+ })
2802
+ .toArray()
2803
+ : null;
2804
+
2805
+ // If Select is available on this table, and any rows are selected, limit the export
2806
+ // to the selected rows. If no rows are selected, all rows will be exported. Specify
2807
+ // a `selected` modifier to control directly.
2808
+ var modifier = $.extend({}, config.modifier);
2809
+ if (
2810
+ dt.select &&
2811
+ typeof dt.select.info === 'function' &&
2812
+ modifier.selected === undefined
2813
+ ) {
2814
+ if (
2815
+ dt.rows(config.rows, $.extend({ selected: true }, modifier)).any()
2816
+ ) {
2817
+ $.extend(modifier, { selected: true });
2818
+ }
2819
+ }
2820
+
2821
+ var rowIndexes = dt.rows(config.rows, modifier).indexes().toArray();
2822
+ var selectedCells = dt.cells(rowIndexes, config.columns, {
2823
+ order: modifier.order
2824
+ });
2825
+ var cells = selectedCells.render(config.orthogonal).toArray();
2826
+ var cellNodes = selectedCells.nodes().toArray();
2827
+ var cellIndexes = selectedCells.indexes().toArray();
2828
+
2829
+ var columns = dt.columns(config.columns).count();
2830
+ var rows = columns > 0 ? cells.length / columns : 0;
2831
+ var body = [];
2832
+ var cellCounter = 0;
2833
+
2834
+ for (var i = 0, ien = rows; i < ien; i++) {
2835
+ var row = [columns];
2836
+
2837
+ for (var j = 0; j < columns; j++) {
2838
+ row[j] = config.format.body(
2839
+ cells[cellCounter],
2840
+ cellIndexes[cellCounter].row,
2841
+ cellIndexes[cellCounter].column,
2842
+ cellNodes[cellCounter]
2843
+ );
2844
+ cellCounter++;
2845
+ }
2846
+
2847
+ body[i] = row;
2848
+ }
2849
+
2850
+ var data = {
2851
+ header: header,
2852
+ headerStructure: _headerFormatter(
2853
+ config.format.header,
2854
+ dt.table().header.structure(config.columns)
2855
+ ),
2856
+ footer: footer,
2857
+ footerStructure: _headerFormatter(
2858
+ config.format.footer,
2859
+ dt.table().footer.structure(config.columns)
2860
+ ),
2861
+ body: body
2862
+ };
2863
+
2864
+ if (config.customizeData) {
2865
+ config.customizeData(data);
2866
+ }
2867
+
2868
+ return data;
2869
+ };
2870
+
2871
+ function _headerFormatter(formatter, struct) {
2872
+ for (var i=0 ; i<struct.length ; i++) {
2873
+ for (var j=0 ; j<struct[i].length ; j++) {
2874
+ var item = struct[i][j];
2875
+
2876
+ if (item) {
2877
+ item.title = formatter(
2878
+ item.title,
2879
+ j,
2880
+ item.cell
2881
+ );
2882
+ }
2883
+ }
2884
+ }
2885
+
2886
+ return struct;
2887
+ }
2888
+
2889
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2890
+ * DataTables interface
2891
+ */
2892
+
2893
+ // Attach to DataTables objects for global access
2894
+ $.fn.dataTable.Buttons = Buttons;
2895
+ $.fn.DataTable.Buttons = Buttons;
2896
+
2897
+ // DataTables creation - check if the buttons have been defined for this table,
2898
+ // they will have been if the `B` option was used in `dom`, otherwise we should
2899
+ // create the buttons instance here so they can be inserted into the document
2900
+ // using the API. Listen for `init` for compatibility with pre 1.10.10, but to
2901
+ // be removed in future.
2902
+ $(document).on('init.dt plugin-init.dt', function (e, settings) {
2903
+ if (e.namespace !== 'dt') {
2904
+ return;
2905
+ }
2906
+
2907
+ var opts = settings.oInit.buttons || DataTable.defaults.buttons;
2908
+
2909
+ if (opts && !settings._buttons) {
2910
+ new Buttons(settings, opts).container();
2911
+ }
2912
+ });
2913
+
2914
+ function _init(settings, options) {
2915
+ var api = new DataTable.Api(settings);
2916
+ var opts = options
2917
+ ? options
2918
+ : api.init().buttons || DataTable.defaults.buttons;
2919
+
2920
+ return new Buttons(api, opts).container();
2921
+ }
2922
+
2923
+ // DataTables 1 `dom` feature option
2924
+ DataTable.ext.feature.push({
2925
+ fnInit: _init,
2926
+ cFeature: 'B'
2927
+ });
2928
+
2929
+ // DataTables 2 layout feature
2930
+ if (DataTable.feature) {
2931
+ DataTable.feature.register('buttons', _init);
2932
+ }
2933
+
2934
+
2935
+ return DataTable;
2936
+ }));