effective_datatables 4.17.4 → 4.19.0

Sign up to get free protection for your applications and to get access to all the features.
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
  }));