cherrypy-foundation 1.0.0__py3-none-any.whl

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