effective_datatables 4.17.4 → 4.19.0

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