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