LaravelTest

Форк
0
2478 строк · 59.3 Кб
1
/*! Buttons for DataTables 2.2.2
2
 * ©2016-2022 SpryMedia Ltd - datatables.net/license
3
 */
4

5
(function( factory ){
6
	if ( typeof define === 'function' && define.amd ) {
7
		// AMD
8
		define( ['jquery', 'datatables.net'], function ( $ ) {
9
			return factory( $, window, document );
10
		} );
11
	}
12
	else if ( typeof exports === 'object' ) {
13
		// CommonJS
14
		module.exports = function (root, $) {
15
			if ( ! root ) {
16
				root = window;
17
			}
18

19
			if ( ! $ || ! $.fn.dataTable ) {
20
				$ = require('datatables.net')(root, $).$;
21
			}
22

23
			return factory( $, root, root.document );
24
		};
25
	}
26
	else {
27
		// Browser
28
		factory( jQuery, window, document );
29
	}
30
}(function( $, window, document, undefined ) {
31
'use strict';
32
var DataTable = $.fn.dataTable;
33

34

35
// Used for namespacing events added to the document by each instance, so they
36
// can be removed on destroy
37
var _instCounter = 0;
38

39
// Button namespacing counter for namespacing events on individual buttons
40
var _buttonCounter = 0;
41

42
var _dtButtons = DataTable.ext.buttons;
43

44
// Allow for jQuery slim
45
function _fadeIn(el, duration, fn) {
46
	if ($.fn.animate) {
47
		el
48
			.stop()
49
			.fadeIn( duration, fn );
50

51
	}
52
	else {
53
		el.css('display', 'block');
54

55
		if (fn) {
56
			fn.call(el);
57
		}
58
	}
59
}
60

61
function _fadeOut(el, duration, fn) {
62
	if ($.fn.animate) {
63
		el
64
			.stop()
65
			.fadeOut( duration, fn );
66
	}
67
	else {
68
		el.css('display', 'none');
69
		
70
		if (fn) {
71
			fn.call(el);
72
		}
73
	}
74
}
75

76
/**
77
 * [Buttons description]
78
 * @param {[type]}
79
 * @param {[type]}
80
 */
81
var Buttons = function( dt, config )
82
{
83
	// If not created with a `new` keyword then we return a wrapper function that
84
	// will take the settings object for a DT. This allows easy use of new instances
85
	// with the `layout` option - e.g. `topLeft: $.fn.dataTable.Buttons( ... )`.
86
	if ( !(this instanceof Buttons) ) {
87
		return function (settings) {
88
			return new Buttons( settings, dt ).container();
89
		};
90
	}
91

92
	// If there is no config set it to an empty object
93
	if ( typeof( config ) === 'undefined' ) {
94
		config = {};	
95
	}
96
	
97
	// Allow a boolean true for defaults
98
	if ( config === true ) {
99
		config = {};
100
	}
101

102
	// For easy configuration of buttons an array can be given
103
	if ( Array.isArray( config ) ) {
104
		config = { buttons: config };
105
	}
106

107
	this.c = $.extend( true, {}, Buttons.defaults, config );
108

109
	// Don't want a deep copy for the buttons
110
	if ( config.buttons ) {
111
		this.c.buttons = config.buttons;
112
	}
113

114
	this.s = {
115
		dt: new DataTable.Api( dt ),
116
		buttons: [],
117
		listenKeys: '',
118
		namespace: 'dtb'+(_instCounter++)
119
	};
120

121
	this.dom = {
122
		container: $('<'+this.c.dom.container.tag+'/>')
123
			.addClass( this.c.dom.container.className )
124
	};
125

126
	this._constructor();
127
};
128

129

130
$.extend( Buttons.prototype, {
131
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
132
	 * Public methods
133
	 */
134

135
	/**
136
	 * Get the action of a button
137
	 * @param  {int|string} Button index
138
	 * @return {function}
139
	 *//**
140
	 * Set the action of a button
141
	 * @param  {node} node Button element
142
	 * @param  {function} action Function to set
143
	 * @return {Buttons} Self for chaining
144
	 */
145
	action: function ( node, action )
146
	{
147
		var button = this._nodeToButton( node );
148

149
		if ( action === undefined ) {
150
			return button.conf.action;
151
		}
152

153
		button.conf.action = action;
154

155
		return this;
156
	},
157

158
	/**
159
	 * Add an active class to the button to make to look active or get current
160
	 * active state.
161
	 * @param  {node} node Button element
162
	 * @param  {boolean} [flag] Enable / disable flag
163
	 * @return {Buttons} Self for chaining or boolean for getter
164
	 */
165
	active: function ( node, flag ) {
166
		var button = this._nodeToButton( node );
167
		var klass = this.c.dom.button.active;
168
		var jqNode = $(button.node);
169

170
		if ( flag === undefined ) {
171
			return jqNode.hasClass( klass );
172
		}
173

174
		jqNode.toggleClass( klass, flag === undefined ? true : flag );
175

176
		return this;
177
	},
178

179
	/**
180
	 * Add a new button
181
	 * @param {object} config Button configuration object, base string name or function
182
	 * @param {int|string} [idx] Button index for where to insert the button
183
	 * @param {boolean} [draw=true] Trigger a draw. Set a false when adding
184
	 *   lots of buttons, until the last button.
185
	 * @return {Buttons} Self for chaining
186
	 */
187
	add: function ( config, idx, draw )
188
	{
189
		var buttons = this.s.buttons;
190

191
		if ( typeof idx === 'string' ) {
192
			var split = idx.split('-');
193
			var base = this.s;
194

195
			for ( var i=0, ien=split.length-1 ; i<ien ; i++ ) {
196
				base = base.buttons[ split[i]*1 ];
197
			}
198

199
			buttons = base.buttons;
200
			idx = split[ split.length-1 ]*1;
201
		}
202

203
		this._expandButton(
204
			buttons,
205
			config,
206
			config !== undefined ? config.split : undefined,
207
			(config === undefined || config.split === undefined || config.split.length === 0) && base !== undefined,
208
			false,
209
			idx
210
		);
211

212
		if (draw === undefined || draw === true) {
213
			this._draw();
214
		}
215
	
216
		return this;
217
	},
218

219
	/**
220
	 * Clear buttons from a collection and then insert new buttons
221
	 */
222
	collectionRebuild: function ( node, newButtons )
223
	{
224
		var button = this._nodeToButton( node );
225
		
226
		if(newButtons !== undefined) {
227
			var i;
228
			// Need to reverse the array
229
			for (i=button.buttons.length-1; i>=0; i--) {
230
				this.remove(button.buttons[i].node);
231
			}
232
	
233
			for (i=0; i<newButtons.length; i++) {
234
				var newBtn = newButtons[i];
235

236
				this._expandButton(
237
					button.buttons,
238
					newBtn,
239
					newBtn !== undefined && newBtn.config !== undefined && newBtn.config.split !== undefined,
240
					true,
241
					newBtn.parentConf !== undefined && newBtn.parentConf.split !== undefined,
242
					i,
243
					newBtn.parentConf
244
				);
245
			}
246
		}
247

248
		this._draw(button.collection, button.buttons);
249
	},
250

251
	/**
252
	 * Get the container node for the buttons
253
	 * @return {jQuery} Buttons node
254
	 */
255
	container: function ()
256
	{
257
		return this.dom.container;
258
	},
259

260
	/**
261
	 * Disable a button
262
	 * @param  {node} node Button node
263
	 * @return {Buttons} Self for chaining
264
	 */
265
	disable: function ( node ) {
266
		var button = this._nodeToButton( node );
267

268
		$(button.node)
269
			.addClass( this.c.dom.button.disabled )
270
			.attr('disabled', true);
271

272
		return this;
273
	},
274

275
	/**
276
	 * Destroy the instance, cleaning up event handlers and removing DOM
277
	 * elements
278
	 * @return {Buttons} Self for chaining
279
	 */
280
	destroy: function ()
281
	{
282
		// Key event listener
283
		$('body').off( 'keyup.'+this.s.namespace );
284

285
		// Individual button destroy (so they can remove their own events if
286
		// needed). Take a copy as the array is modified by `remove`
287
		var buttons = this.s.buttons.slice();
288
		var i, ien;
289
		
290
		for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
291
			this.remove( buttons[i].node );
292
		}
293

294
		// Container
295
		this.dom.container.remove();
296

297
		// Remove from the settings object collection
298
		var buttonInsts = this.s.dt.settings()[0];
299

300
		for ( i=0, ien=buttonInsts.length ; i<ien ; i++ ) {
301
			if ( buttonInsts.inst === this ) {
302
				buttonInsts.splice( i, 1 );
303
				break;
304
			}
305
		}
306

307
		return this;
308
	},
309

310
	/**
311
	 * Enable / disable a button
312
	 * @param  {node} node Button node
313
	 * @param  {boolean} [flag=true] Enable / disable flag
314
	 * @return {Buttons} Self for chaining
315
	 */
316
	enable: function ( node, flag )
317
	{
318
		if ( flag === false ) {
319
			return this.disable( node );
320
		}
321

322
		var button = this._nodeToButton( node );
323
		$(button.node)
324
			.removeClass( this.c.dom.button.disabled )
325
			.removeAttr('disabled');
326

327
		return this;
328
	},
329

330
	/**
331
	 * Get a button's index
332
	 * 
333
	 * This is internally recursive
334
	 * @param {element} node Button to get the index of
335
	 * @return {string} Button index
336
	 */
337
	index: function ( node, nested, buttons )
338
	{
339
		if ( ! nested ) {
340
			nested = '';
341
			buttons = this.s.buttons;
342
		}
343

344
		for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
345
			var inner = buttons[i].buttons;
346

347
			if (buttons[i].node === node) {
348
				return nested + i;
349
			}
350

351
			if ( inner && inner.length ) {
352
				var match = this.index(node, i + '-', inner);
353

354
				if (match !== null) {
355
					return match;
356
				}
357
			}
358
		}
359

360
		return null;
361
	},
362

363

364
	/**
365
	 * Get the instance name for the button set selector
366
	 * @return {string} Instance name
367
	 */
368
	name: function ()
369
	{
370
		return this.c.name;
371
	},
372

373
	/**
374
	 * Get a button's node of the buttons container if no button is given
375
	 * @param  {node} [node] Button node
376
	 * @return {jQuery} Button element, or container
377
	 */
378
	node: function ( node )
379
	{
380
		if ( ! node ) {
381
			return this.dom.container;
382
		}
383

384
		var button = this._nodeToButton( node );
385
		return $(button.node);
386
	},
387

388
	/**
389
	 * Set / get a processing class on the selected button
390
	 * @param {element} node Triggering button node
391
	 * @param  {boolean} flag true to add, false to remove, undefined to get
392
	 * @return {boolean|Buttons} Getter value or this if a setter.
393
	 */
394
	processing: function ( node, flag )
395
	{
396
		var dt = this.s.dt;
397
		var button = this._nodeToButton( node );
398

399
		if ( flag === undefined ) {
400
			return $(button.node).hasClass( 'processing' );
401
		}
402

403
		$(button.node).toggleClass( 'processing', flag );
404

405
		$(dt.table().node()).triggerHandler( 'buttons-processing.dt', [
406
			flag, dt.button( node ), dt, $(node), button.conf
407
		] );
408

409
		return this;
410
	},
411

412
	/**
413
	 * Remove a button.
414
	 * @param  {node} node Button node
415
	 * @return {Buttons} Self for chaining
416
	 */
417
	remove: function ( node )
418
	{
419
		var button = this._nodeToButton( node );
420
		var host = this._nodeToHost( node );
421
		var dt = this.s.dt;
422

423
		// Remove any child buttons first
424
		if ( button.buttons.length ) {
425
			for ( var i=button.buttons.length-1 ; i>=0 ; i-- ) {
426
				this.remove( button.buttons[i].node );
427
			}
428
		}
429

430
		button.conf.destroying = true;
431

432
		// Allow the button to remove event handlers, etc
433
		if ( button.conf.destroy ) {
434
			button.conf.destroy.call( dt.button(node), dt, $(node), button.conf );
435
		}
436

437
		this._removeKey( button.conf );
438

439
		$(button.node).remove();
440

441
		var idx = $.inArray( button, host );
442
		host.splice( idx, 1 );
443

444
		return this;
445
	},
446

447
	/**
448
	 * Get the text for a button
449
	 * @param  {int|string} node Button index
450
	 * @return {string} Button text
451
	 *//**
452
	 * Set the text for a button
453
	 * @param  {int|string|function} node Button index
454
	 * @param  {string} label Text
455
	 * @return {Buttons} Self for chaining
456
	 */
457
	text: function ( node, label )
458
	{
459
		var button = this._nodeToButton( node );
460
		var buttonLiner = this.c.dom.collection.buttonLiner;
461
		var linerTag = button.inCollection && buttonLiner && buttonLiner.tag ?
462
			buttonLiner.tag :
463
			this.c.dom.buttonLiner.tag;
464
		var dt = this.s.dt;
465
		var jqNode = $(button.node);
466
		var text = function ( opt ) {
467
			return typeof opt === 'function' ?
468
				opt( dt, jqNode, button.conf ) :
469
				opt;
470
		};
471

472
		if ( label === undefined ) {
473
			return text( button.conf.text );
474
		}
475

476
		button.conf.text = label;
477

478
		if ( linerTag ) {
479
			jqNode
480
				.children( linerTag )
481
				.eq(0)
482
				.filter(':not(.dt-down-arrow)')
483
				.html( text(label) );
484
		}
485
		else {
486
			jqNode.html( text(label) );
487
		}
488

489
		return this;
490
	},
491

492

493
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
494
	 * Constructor
495
	 */
496

497
	/**
498
	 * Buttons constructor
499
	 * @private
500
	 */
501
	_constructor: function ()
502
	{
503
		var that = this;
504
		var dt = this.s.dt;
505
		var dtSettings = dt.settings()[0];
506
		var buttons =  this.c.buttons;
507

508
		if ( ! dtSettings._buttons ) {
509
			dtSettings._buttons = [];
510
		}
511

512
		dtSettings._buttons.push( {
513
			inst: this,
514
			name: this.c.name
515
		} );
516

517
		for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
518
			this.add( buttons[i] );
519
		}
520

521
		dt.on( 'destroy', function ( e, settings ) {
522
			if ( settings === dtSettings ) {
523
				that.destroy();
524
			}
525
		} );
526

527
		// Global key event binding to listen for button keys
528
		$('body').on( 'keyup.'+this.s.namespace, function ( e ) {
529
			if ( ! document.activeElement || document.activeElement === document.body ) {
530
				// SUse a string of characters for fast lookup of if we need to
531
				// handle this
532
				var character = String.fromCharCode(e.keyCode).toLowerCase();
533

534
				if ( that.s.listenKeys.toLowerCase().indexOf( character ) !== -1 ) {
535
					that._keypress( character, e );
536
				}
537
			}
538
		} );
539
	},
540

541

542
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
543
	 * Private methods
544
	 */
545

546
	/**
547
	 * Add a new button to the key press listener
548
	 * @param {object} conf Resolved button configuration object
549
	 * @private
550
	 */
551
	_addKey: function ( conf )
552
	{
553
		if ( conf.key ) {
554
			this.s.listenKeys += $.isPlainObject( conf.key ) ?
555
				conf.key.key :
556
				conf.key;
557
		}
558
	},
559

560
	/**
561
	 * Insert the buttons into the container. Call without parameters!
562
	 * @param  {node} [container] Recursive only - Insert point
563
	 * @param  {array} [buttons] Recursive only - Buttons array
564
	 * @private
565
	 */
566
	_draw: function ( container, buttons )
567
	{
568
		if ( ! container ) {
569
			container = this.dom.container;
570
			buttons = this.s.buttons;
571
		}
572

573
		container.children().detach();
574

575
		for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
576
			container.append( buttons[i].inserter );
577
			container.append( ' ' );
578

579
			if ( buttons[i].buttons && buttons[i].buttons.length ) {
580
				this._draw( buttons[i].collection, buttons[i].buttons );
581
			}
582
		}
583
	},
584

585
	/**
586
	 * Create buttons from an array of buttons
587
	 * @param  {array} attachTo Buttons array to attach to
588
	 * @param  {object} button Button definition
589
	 * @param  {boolean} inCollection true if the button is in a collection
590
	 * @private
591
	 */
592
	_expandButton: function ( attachTo, button, split, inCollection, inSplit, attachPoint, parentConf )
593
	{
594
		var dt = this.s.dt;
595
		var buttonCounter = 0;
596
		var isSplit = false;
597
		var buttons = ! Array.isArray( button ) ?
598
			[ button ] :
599
			button;
600
		
601
		if(button === undefined ) {
602
			buttons = !Array.isArray(split) ?
603
				[ split ] :
604
				split;
605
		}
606

607
		if (button !== undefined && button.split !== undefined) {
608
			isSplit = true;
609
		}
610
			
611
		for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
612
			var conf = this._resolveExtends( buttons[i] );
613

614
			if ( ! conf ) {
615
				continue;
616
			}
617

618
			if( conf.config !== undefined && conf.config.split) {
619
				isSplit = true;
620
			}
621
			else {
622
				isSplit = false;
623
			}
624
			
625
			// If the configuration is an array, then expand the buttons at this
626
			// point
627
			if ( Array.isArray( conf ) ) {
628
				this._expandButton( attachTo, conf, built !== undefined && built.conf !== undefined ? built.conf.split : undefined, inCollection, parentConf !== undefined && parentConf.split !== undefined, attachPoint, parentConf );
629
				continue;
630
			}
631

632
			var built = this._buildButton( conf, inCollection, conf.split !== undefined || (conf.config !== undefined && conf.config.split !== undefined), inSplit );
633
			if ( ! built ) {
634
				continue;
635
			}
636

637
			if ( attachPoint !== undefined && attachPoint !== null ) {
638
				attachTo.splice( attachPoint, 0, built );
639
				attachPoint++;
640
			}
641
			else {
642
				attachTo.push( built );
643
			}
644

645
			
646
			if ( built.conf.buttons || built.conf.split ) {
647
				built.collection = $('<'+(isSplit ? this.c.dom.splitCollection.tag : this.c.dom.collection.tag)+'/>');
648

649
				built.conf._collection = built.collection;
650

651
				if(built.conf.split) {
652
					for(var j = 0; j < built.conf.split.length; j++) {
653
						if(typeof built.conf.split[j] === "object") {
654
							built.conf.split[j].parent = parentConf;
655
							if(built.conf.split[j].collectionLayout === undefined) {
656
								built.conf.split[j].collectionLayout = built.conf.collectionLayout;
657
							}
658
							if(built.conf.split[j].dropup === undefined) {
659
								built.conf.split[j].dropup = built.conf.dropup;
660
							}
661
							if(built.conf.split[j].fade === undefined) {
662
								built.conf.split[j].fade = built.conf.fade;
663
							}
664
						}
665
					}
666
				}
667
				else {
668
					$(built.node).append($('<span class="dt-down-arrow">'+this.c.dom.splitDropdown.text+'</span>'))
669
				}
670

671
				this._expandButton( built.buttons, built.conf.buttons, built.conf.split, !isSplit, isSplit, attachPoint, built.conf );
672
			}
673
			built.conf.parent = parentConf;
674

675
			// init call is made here, rather than buildButton as it needs to
676
			// be selectable, and for that it needs to be in the buttons array
677
			if ( conf.init ) {
678
				conf.init.call( dt.button( built.node ), dt, $(built.node), conf );
679
			}
680

681
			buttonCounter++;
682
		}
683
	},
684

685
	/**
686
	 * Create an individual button
687
	 * @param  {object} config            Resolved button configuration
688
	 * @param  {boolean} inCollection `true` if a collection button
689
	 * @return {jQuery} Created button node (jQuery)
690
	 * @private
691
	 */
692
	_buildButton: function ( config, inCollection, isSplit, inSplit )
693
	{
694
		var buttonDom = this.c.dom.button;
695
		var linerDom = this.c.dom.buttonLiner;
696
		var collectionDom = this.c.dom.collection;
697
		var splitDom = this.c.dom.split;
698
		var splitCollectionDom = this.c.dom.splitCollection;
699
		var splitDropdownButton = this.c.dom.splitDropdownButton;
700
		var dt = this.s.dt;
701
		var text = function ( opt ) {
702
			return typeof opt === 'function' ?
703
				opt( dt, button, config ) :
704
				opt;
705
		};
706

707
		// Spacers don't do much other than insert an element into the DOM
708
		if (config.spacer) {
709
			var spacer = $('<span></span>')
710
				.addClass('dt-button-spacer ' + config.style + ' ' + buttonDom.spacerClass)
711
				.html(text(config.text));
712

713
			return {
714
				conf:         config,
715
				node:         spacer,
716
				inserter:     spacer,
717
				buttons:      [],
718
				inCollection: inCollection,
719
				isSplit:	  isSplit,
720
				inSplit:	  inSplit,
721
				collection:   null
722
			};
723
		}
724

725
		if ( !isSplit && inSplit && splitCollectionDom ) {
726
			buttonDom = splitDropdownButton;
727
		}
728
		else if ( !isSplit && inCollection && collectionDom.button ) {
729
			buttonDom = collectionDom.button;
730
		} 
731

732
		if ( !isSplit && inSplit && splitCollectionDom.buttonLiner ) {
733
			linerDom = splitCollectionDom.buttonLiner
734
		}
735
		else if ( !isSplit && inCollection && collectionDom.buttonLiner ) {
736
			linerDom = collectionDom.buttonLiner;
737
		}
738

739
		// Make sure that the button is available based on whatever requirements
740
		// it has. For example, PDF button require pdfmake
741
		if ( config.available && ! config.available( dt, config ) && !config.hasOwnProperty('html') ) {
742
			return false;
743
		}
744

745
		var button;
746
		if(!config.hasOwnProperty('html')) {
747
			var action = function ( e, dt, button, config ) {
748
				config.action.call( dt.button( button ), e, dt, button, config );
749
	
750
				$(dt.table().node()).triggerHandler( 'buttons-action.dt', [
751
					dt.button( button ), dt, button, config 
752
				] );
753
			};
754

755
			var tag = config.tag || buttonDom.tag;
756
			var clickBlurs = config.clickBlurs === undefined
757
				? true :
758
				config.clickBlurs;
759

760
			button = $('<'+tag+'/>')
761
				.addClass( buttonDom.className )
762
				.addClass( inSplit ? this.c.dom.splitDropdownButton.className : '')
763
				.attr( 'tabindex', this.s.dt.settings()[0].iTabIndex )
764
				.attr( 'aria-controls', this.s.dt.table().node().id )
765
				.on( 'click.dtb', function (e) {
766
					e.preventDefault();
767
	
768
					if ( ! button.hasClass( buttonDom.disabled ) && config.action ) {
769
						action( e, dt, button, config );
770
					}
771
					if( clickBlurs ) {
772
						button.trigger('blur');
773
					}
774
				} )
775
				.on( 'keypress.dtb', function (e) {
776
					if ( e.keyCode === 13 ) {
777
						e.preventDefault();
778

779
						if ( ! button.hasClass( buttonDom.disabled ) && config.action ) {
780
							action( e, dt, button, config );
781
						}
782
					}
783
				} );
784
	
785
			// Make `a` tags act like a link
786
			if ( tag.toLowerCase() === 'a' ) {
787
				button.attr( 'href', '#' );
788
			}
789
	
790
			// Button tags should have `type=button` so they don't have any default behaviour
791
			if ( tag.toLowerCase() === 'button' ) {
792
				button.attr( 'type', 'button' );
793
			}
794
	
795
			if ( linerDom.tag ) {
796
				var liner = $('<'+linerDom.tag+'/>')
797
					.html( text( config.text ) )
798
					.addClass( linerDom.className );
799
	
800
				if ( linerDom.tag.toLowerCase() === 'a' ) {
801
					liner.attr( 'href', '#' );
802
				}
803
	
804
				button.append( liner );
805
			}
806
			else {
807
				button.html( text( config.text ) );
808
			}
809
	
810
			if ( config.enabled === false ) {
811
				button.addClass( buttonDom.disabled );
812
			}
813
	
814
			if ( config.className ) {
815
				button.addClass( config.className );
816
			}
817
	
818
			if ( config.titleAttr ) {
819
				button.attr( 'title', text( config.titleAttr ) );
820
			}
821
	
822
			if ( config.attr ) {
823
				button.attr( config.attr );
824
			}
825
	
826
			if ( ! config.namespace ) {
827
				config.namespace = '.dt-button-'+(_buttonCounter++);
828
			}
829

830
			if  ( config.config !== undefined && config.config.split ) {
831
				config.split = config.config.split;
832
			}
833
		}
834
		else {
835
			button = $(config.html)
836
		}
837
	
838
		var buttonContainer = this.c.dom.buttonContainer;
839
		var inserter;
840
		if ( buttonContainer && buttonContainer.tag ) {
841
			inserter = $('<'+buttonContainer.tag+'/>')
842
				.addClass( buttonContainer.className )
843
				.append( button );
844
		}
845
		else {
846
			inserter = button;
847
		}
848

849
		this._addKey( config );
850

851
		// Style integration callback for DOM manipulation
852
		// Note that this is _not_ documented. It is currently
853
		// for style integration only
854
		if( this.c.buttonCreated ) {
855
			inserter = this.c.buttonCreated( config, inserter );
856
		}
857

858
		var splitDiv;
859
		if(isSplit) {
860
			splitDiv = $('<div/>').addClass(this.c.dom.splitWrapper.className)
861
			splitDiv.append(button);
862
			var dropButtonConfig = $.extend(config, {
863
				text: this.c.dom.splitDropdown.text,
864
				className: this.c.dom.splitDropdown.className,
865
				closeButton: false,
866
				attr: {
867
					'aria-haspopup': true,
868
					'aria-expanded': false
869
				},
870
				align: this.c.dom.splitDropdown.align,
871
				splitAlignClass: this.c.dom.splitDropdown.splitAlignClass
872
				
873
			})
874

875
			this._addKey(dropButtonConfig);
876

877
			var splitAction = function ( e, dt, button, config ) {
878
				_dtButtons.split.action.call( dt.button($('div.dt-btn-split-wrapper')[0] ), e, dt, button, config );
879
	
880
				$(dt.table().node()).triggerHandler( 'buttons-action.dt', [
881
					dt.button( button ), dt, button, config 
882
				] );
883
				button.attr('aria-expanded', true)
884
			};
885
			
886
			var dropButton = $('<button class="' + this.c.dom.splitDropdown.className + ' dt-button"><span class="dt-btn-split-drop-arrow">'+this.c.dom.splitDropdown.text+'</span></button>')
887
				.on( 'click.dtb', function (e) {
888
					e.preventDefault();
889
					e.stopPropagation();
890

891
					if ( ! dropButton.hasClass( buttonDom.disabled )) {
892
						splitAction( e, dt, dropButton, dropButtonConfig );
893
					}
894
					if ( clickBlurs ) {
895
						dropButton.trigger('blur');
896
					}
897
				} )
898
				.on( 'keypress.dtb', function (e) {
899
					if ( e.keyCode === 13 ) {
900
						e.preventDefault();
901

902
						if ( ! dropButton.hasClass( buttonDom.disabled ) ) {
903
							splitAction( e, dt, dropButton, dropButtonConfig );
904
						}
905
					}
906
				} );
907

908
			if(config.split.length === 0) {
909
				dropButton.addClass('dtb-hide-drop');
910
			}
911

912
			splitDiv.append(dropButton).attr(dropButtonConfig.attr);
913
		}
914

915
		return {
916
			conf:         config,
917
			node:         isSplit ? splitDiv.get(0) : button.get(0),
918
			inserter:     isSplit ? splitDiv : inserter,
919
			buttons:      [],
920
			inCollection: inCollection,
921
			isSplit:	  isSplit,
922
			inSplit:	  inSplit,
923
			collection:   null
924
		};
925
	},
926

927
	/**
928
	 * Get the button object from a node (recursive)
929
	 * @param  {node} node Button node
930
	 * @param  {array} [buttons] Button array, uses base if not defined
931
	 * @return {object} Button object
932
	 * @private
933
	 */
934
	_nodeToButton: function ( node, buttons )
935
	{
936
		if ( ! buttons ) {
937
			buttons = this.s.buttons;
938
		}
939

940
		for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
941
			if ( buttons[i].node === node ) {
942
				return buttons[i];
943
			}
944

945
			if ( buttons[i].buttons.length ) {
946
				var ret = this._nodeToButton( node, buttons[i].buttons );
947

948
				if ( ret ) {
949
					return ret;
950
				}
951
			}
952
		}
953
	},
954

955
	/**
956
	 * Get container array for a button from a button node (recursive)
957
	 * @param  {node} node Button node
958
	 * @param  {array} [buttons] Button array, uses base if not defined
959
	 * @return {array} Button's host array
960
	 * @private
961
	 */
962
	_nodeToHost: function ( node, buttons )
963
	{
964
		if ( ! buttons ) {
965
			buttons = this.s.buttons;
966
		}
967

968
		for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
969
			if ( buttons[i].node === node ) {
970
				return buttons;
971
			}
972

973
			if ( buttons[i].buttons.length ) {
974
				var ret = this._nodeToHost( node, buttons[i].buttons );
975

976
				if ( ret ) {
977
					return ret;
978
				}
979
			}
980
		}
981
	},
982

983
	/**
984
	 * Handle a key press - determine if any button's key configured matches
985
	 * what was typed and trigger the action if so.
986
	 * @param  {string} character The character pressed
987
	 * @param  {object} e Key event that triggered this call
988
	 * @private
989
	 */
990
	_keypress: function ( character, e )
991
	{
992
		// Check if this button press already activated on another instance of Buttons
993
		if ( e._buttonsHandled ) {
994
			return;
995
		}
996

997
		var run = function ( conf, node ) {
998
			if ( ! conf.key ) {
999
				return;
1000
			}
1001

1002
			if ( conf.key === character ) {
1003
				e._buttonsHandled = true;
1004
				$(node).click();
1005
			}
1006
			else if ( $.isPlainObject( conf.key ) ) {
1007
				if ( conf.key.key !== character ) {
1008
					return;
1009
				}
1010

1011
				if ( conf.key.shiftKey && ! e.shiftKey ) {
1012
					return;
1013
				}
1014

1015
				if ( conf.key.altKey && ! e.altKey ) {
1016
					return;
1017
				}
1018

1019
				if ( conf.key.ctrlKey && ! e.ctrlKey ) {
1020
					return;
1021
				}
1022

1023
				if ( conf.key.metaKey && ! e.metaKey ) {
1024
					return;
1025
				}
1026

1027
				// Made it this far - it is good
1028
				e._buttonsHandled = true;
1029
				$(node).click();
1030
			}
1031
		};
1032

1033
		var recurse = function ( a ) {
1034
			for ( var i=0, ien=a.length ; i<ien ; i++ ) {
1035
				run( a[i].conf, a[i].node );
1036

1037
				if ( a[i].buttons.length ) {
1038
					recurse( a[i].buttons );
1039
				}
1040
			}
1041
		};
1042

1043
		recurse( this.s.buttons );
1044
	},
1045

1046
	/**
1047
	 * Remove a key from the key listener for this instance (to be used when a
1048
	 * button is removed)
1049
	 * @param  {object} conf Button configuration
1050
	 * @private
1051
	 */
1052
	_removeKey: function ( conf )
1053
	{
1054
		if ( conf.key ) {
1055
			var character = $.isPlainObject( conf.key ) ?
1056
				conf.key.key :
1057
				conf.key;
1058

1059
			// Remove only one character, as multiple buttons could have the
1060
			// same listening key
1061
			var a = this.s.listenKeys.split('');
1062
			var idx = $.inArray( character, a );
1063
			a.splice( idx, 1 );
1064
			this.s.listenKeys = a.join('');
1065
		}
1066
	},
1067

1068
	/**
1069
	 * Resolve a button configuration
1070
	 * @param  {string|function|object} conf Button config to resolve
1071
	 * @return {object} Button configuration
1072
	 * @private
1073
	 */
1074
	_resolveExtends: function ( conf )
1075
	{
1076
		var that = this;
1077
		var dt = this.s.dt;
1078
		var i, ien;
1079
		var toConfObject = function ( base ) {
1080
			var loop = 0;
1081

1082
			// Loop until we have resolved to a button configuration, or an
1083
			// array of button configurations (which will be iterated
1084
			// separately)
1085
			while ( ! $.isPlainObject(base) && ! Array.isArray(base) ) {
1086
				if ( base === undefined ) {
1087
					return;
1088
				}
1089

1090
				if ( typeof base === 'function' ) {
1091
					base = base.call( that, dt, conf );
1092

1093
					if ( ! base ) {
1094
						return false;
1095
					}
1096
				}
1097
				else if ( typeof base === 'string' ) {
1098
					if ( ! _dtButtons[ base ] ) {
1099
						return {html: base}
1100
					}
1101

1102
					base = _dtButtons[ base ];
1103
				}
1104

1105
				loop++;
1106
				if ( loop > 30 ) {
1107
					// Protect against misconfiguration killing the browser
1108
					throw 'Buttons: Too many iterations';
1109
				}
1110
			}
1111

1112
			return Array.isArray( base ) ?
1113
				base :
1114
				$.extend( {}, base );
1115
		};
1116

1117
		conf = toConfObject( conf );
1118

1119
		while ( conf && conf.extend ) {
1120
			// Use `toConfObject` in case the button definition being extended
1121
			// is itself a string or a function
1122
			if ( ! _dtButtons[ conf.extend ] ) {
1123
				throw 'Cannot extend unknown button type: '+conf.extend;
1124
			}
1125

1126
			var objArray = toConfObject( _dtButtons[ conf.extend ] );
1127
			if ( Array.isArray( objArray ) ) {
1128
				return objArray;
1129
			}
1130
			else if ( ! objArray ) {
1131
				// This is a little brutal as it might be possible to have a
1132
				// valid button without the extend, but if there is no extend
1133
				// then the host button would be acting in an undefined state
1134
				return false;
1135
			}
1136

1137
			// Stash the current class name
1138
			var originalClassName = objArray.className;
1139

1140
			if (conf.config !== undefined && objArray.config !== undefined) {
1141
				conf.config = $.extend({}, objArray.config, conf.config)
1142
			}
1143

1144
			conf = $.extend( {}, objArray, conf );
1145

1146
			// The extend will have overwritten the original class name if the
1147
			// `conf` object also assigned a class, but we want to concatenate
1148
			// them so they are list that is combined from all extended buttons
1149
			if ( originalClassName && conf.className !== originalClassName ) {
1150
				conf.className = originalClassName+' '+conf.className;
1151
			}
1152

1153
			// Buttons to be added to a collection  -gives the ability to define
1154
			// if buttons should be added to the start or end of a collection
1155
			var postfixButtons = conf.postfixButtons;
1156
			if ( postfixButtons ) {
1157
				if ( ! conf.buttons ) {
1158
					conf.buttons = [];
1159
				}
1160

1161
				for ( i=0, ien=postfixButtons.length ; i<ien ; i++ ) {
1162
					conf.buttons.push( postfixButtons[i] );
1163
				}
1164

1165
				conf.postfixButtons = null;
1166
			}
1167

1168
			var prefixButtons = conf.prefixButtons;
1169
			if ( prefixButtons ) {
1170
				if ( ! conf.buttons ) {
1171
					conf.buttons = [];
1172
				}
1173

1174
				for ( i=0, ien=prefixButtons.length ; i<ien ; i++ ) {
1175
					conf.buttons.splice( i, 0, prefixButtons[i] );
1176
				}
1177

1178
				conf.prefixButtons = null;
1179
			}
1180

1181
			// Although we want the `conf` object to overwrite almost all of
1182
			// the properties of the object being extended, the `extend`
1183
			// property should come from the object being extended
1184
			conf.extend = objArray.extend;
1185
		}
1186

1187
		return conf;
1188
	},
1189

1190
	/**
1191
	 * Display (and replace if there is an existing one) a popover attached to a button
1192
	 * @param {string|node} content Content to show
1193
	 * @param {DataTable.Api} hostButton DT API instance of the button
1194
	 * @param {object} inOpts Options (see object below for all options)
1195
	 */
1196
	_popover: function ( content, hostButton, inOpts, e ) {
1197
		var dt = hostButton;
1198
		var buttonsSettings = this.c;
1199
		var closed = false;
1200
		var options = $.extend( {
1201
			align: 'button-left', // button-right, dt-container, split-left, split-right
1202
			autoClose: false,
1203
			background: true,
1204
			backgroundClassName: 'dt-button-background',
1205
			closeButton: true,
1206
			contentClassName: buttonsSettings.dom.collection.className,
1207
			collectionLayout: '',
1208
			collectionTitle: '',
1209
			dropup: false,
1210
			fade: 400,
1211
			popoverTitle: '',
1212
			rightAlignClassName: 'dt-button-right',
1213
			tag: buttonsSettings.dom.collection.tag
1214
		}, inOpts );
1215

1216
		var hostNode = hostButton.node();
1217

1218
		var close = function () {
1219
			closed = true;
1220

1221
			_fadeOut(
1222
				$('.dt-button-collection'),
1223
				options.fade,
1224
				function () {
1225
					$(this).detach();
1226
				}
1227
			);
1228

1229
			$(dt.buttons( '[aria-haspopup="true"][aria-expanded="true"]' ).nodes())
1230
				.attr('aria-expanded', 'false');
1231

1232
			$('div.dt-button-background').off( 'click.dtb-collection' );
1233
			Buttons.background( false, options.backgroundClassName, options.fade, hostNode );
1234

1235
			$(window).off('resize.resize.dtb-collection');
1236
			$('body').off( '.dtb-collection' );
1237
			dt.off( 'buttons-action.b-internal' );
1238
			dt.off( 'destroy' );
1239
		};
1240

1241
		if (content === false) {
1242
			close();
1243
			return;
1244
		}
1245

1246
		var existingExpanded = $(dt.buttons( '[aria-haspopup="true"][aria-expanded="true"]' ).nodes());
1247
		if ( existingExpanded.length ) {
1248
			// Reuse the current position if the button that was triggered is inside an existing collection
1249
			if (hostNode.closest('div.dt-button-collection').length) {
1250
				hostNode = existingExpanded.eq(0);
1251
			}
1252

1253
			close();
1254
		}
1255

1256
		// Try to be smart about the layout
1257
		var cnt = $('.dt-button', content).length;
1258
		var mod = '';
1259

1260
		if (cnt === 3) {
1261
			mod = 'dtb-b3';
1262
		}
1263
		else if (cnt === 2) {
1264
			mod = 'dtb-b2';
1265
		}
1266
		else if (cnt === 1) {
1267
			mod = 'dtb-b1';
1268
		}
1269

1270
		var display = $('<div/>')
1271
			.addClass('dt-button-collection')
1272
			.addClass(options.collectionLayout)
1273
			.addClass(options.splitAlignClass)
1274
			.addClass(mod)
1275
			.css('display', 'none');
1276

1277
		content = $(content)
1278
			.addClass(options.contentClassName)
1279
			.attr('role', 'menu')
1280
			.appendTo(display);
1281

1282
		hostNode.attr( 'aria-expanded', 'true' );
1283

1284
		if ( hostNode.parents('body')[0] !== document.body ) {
1285
			hostNode = document.body.lastChild;
1286
		}
1287

1288
		if ( options.popoverTitle ) {
1289
			display.prepend('<div class="dt-button-collection-title">'+options.popoverTitle+'</div>');
1290
		}
1291
		else if ( options.collectionTitle ) {
1292
			display.prepend('<div class="dt-button-collection-title">'+options.collectionTitle+'</div>');
1293
		}
1294

1295
		if (options.closeButton) {
1296
			display.prepend('<div class="dtb-popover-close">x</div>').addClass('dtb-collection-closeable')
1297
		}
1298

1299
		_fadeIn( display.insertAfter( hostNode ), options.fade );
1300

1301
		var tableContainer = $( hostButton.table().container() );
1302
		var position = display.css( 'position' );
1303

1304
		if ( options.span === 'container' || options.align === 'dt-container' ) {
1305
			hostNode = hostNode.parent();
1306
			display.css('width', tableContainer.width());
1307
		}
1308

1309
		// Align the popover relative to the DataTables container
1310
		// Useful for wide popovers such as SearchPanes
1311
		if (position === 'absolute') {
1312
			// Align relative to the host button
1313
			var offsetParent = $(hostNode[0].offsetParent);
1314
			var buttonPosition = hostNode.position();
1315
			var buttonOffset = hostNode.offset();
1316
			var tableSizes = offsetParent.offset();
1317
			var containerPosition = offsetParent.position();
1318
			var computed = window.getComputedStyle(offsetParent[0]);
1319

1320
			tableSizes.height = offsetParent.outerHeight();
1321
			tableSizes.width = offsetParent.width() + parseFloat(computed.paddingLeft);
1322
			tableSizes.right = tableSizes.left + tableSizes.width;
1323
			tableSizes.bottom = tableSizes.top + tableSizes.height;
1324

1325
			// Set the initial position so we can read height / width
1326
			var top = buttonPosition.top + hostNode.outerHeight();
1327
			var left = buttonPosition.left;
1328

1329
			display.css( {
1330
				top: top,
1331
				left: left
1332
			} );
1333

1334
			// Get the popover position
1335
			computed = window.getComputedStyle(display[0]);
1336
			var popoverSizes = display.offset();
1337

1338
			popoverSizes.height = display.outerHeight();
1339
			popoverSizes.width = display.outerWidth();
1340
			popoverSizes.right = popoverSizes.left + popoverSizes.width;
1341
			popoverSizes.bottom = popoverSizes.top + popoverSizes.height;
1342
			popoverSizes.marginTop = parseFloat(computed.marginTop);
1343
			popoverSizes.marginBottom = parseFloat(computed.marginBottom);
1344

1345
			// First position per the class requirements - pop up and right align
1346
			if (options.dropup) {
1347
				top = buttonPosition.top - popoverSizes.height - popoverSizes.marginTop - popoverSizes.marginBottom;
1348
			}
1349

1350
			if (options.align === 'button-right' || display.hasClass( options.rightAlignClassName )) {
1351
				left = buttonPosition.left - popoverSizes.width + hostNode.outerWidth(); 
1352
			}
1353

1354
			// Container alignment - make sure it doesn't overflow the table container
1355
			if (options.align === 'dt-container' || options.align === 'container') {
1356
				if (left < buttonPosition.left) {
1357
					left = -buttonPosition.left;
1358
				}
1359

1360
				if (left + popoverSizes.width > tableSizes.width) {
1361
					left = tableSizes.width - popoverSizes.width;
1362
				}
1363
			}
1364

1365
			// Window adjustment
1366
			if (containerPosition.left + left + popoverSizes.width > $(window).width()) {
1367
				// Overflowing the document to the right
1368
				left = $(window).width() - popoverSizes.width - containerPosition.left;
1369
			}
1370

1371
			if (buttonOffset.left + left < 0) {
1372
				// Off to the left of the document
1373
				left = -buttonOffset.left;
1374
			}
1375

1376
			if (containerPosition.top + top + popoverSizes.height > $(window).height() + $(window).scrollTop()) {
1377
				// Pop up if otherwise we'd need the user to scroll down
1378
				top = buttonPosition.top - popoverSizes.height - popoverSizes.marginTop - popoverSizes.marginBottom;
1379
			}
1380

1381
			if (containerPosition.top + top < $(window).scrollTop()) {
1382
				// Correction for when the top is beyond the top of the page
1383
				top = buttonPosition.top + hostNode.outerHeight();
1384
			}
1385

1386
			// Calculations all done - now set it
1387
			display.css( {
1388
				top: top,
1389
				left: left
1390
			} );
1391
		}
1392
		else {
1393
			// Fix position - centre on screen
1394
			var position = function () {
1395
				var half = $(window).height() / 2;
1396

1397
				var top = display.height() / 2;
1398
				if ( top > half ) {
1399
					top = half;
1400
				}
1401

1402
				display.css( 'marginTop', top*-1 );
1403
			};
1404

1405
			position();
1406

1407
			$(window).on('resize.dtb-collection', function () {
1408
				position();
1409
			});
1410
		}
1411

1412
		if ( options.background ) {
1413
			Buttons.background(
1414
				true,
1415
				options.backgroundClassName,
1416
				options.fade,
1417
				options.backgroundHost || hostNode
1418
			);
1419
		}
1420

1421
		// This is bonkers, but if we don't have a click listener on the
1422
		// background element, iOS Safari will ignore the body click
1423
		// listener below. An empty function here is all that is
1424
		// required to make it work...
1425
		$('div.dt-button-background').on( 'click.dtb-collection', function () {} );
1426

1427
		if ( options.autoClose ) {
1428
			setTimeout( function () {
1429
				dt.on( 'buttons-action.b-internal', function (e, btn, dt, node) {
1430
					if ( node[0] === hostNode[0] ) {
1431
						return;
1432
					}
1433
					close();
1434
				} );
1435
			}, 0);
1436
		}
1437
		
1438
		$(display).trigger('buttons-popover.dt');
1439

1440

1441
		dt.on('destroy', close);
1442

1443
		setTimeout(function() {
1444
			closed = false;
1445
			$('body')
1446
				.on( 'click.dtb-collection', function (e) {
1447
					if (closed) {
1448
						return;
1449
					}
1450

1451
					// andSelf is deprecated in jQ1.8, but we want 1.7 compat
1452
					var back = $.fn.addBack ? 'addBack' : 'andSelf';
1453
					var parent = $(e.target).parent()[0];
1454
	
1455
					if (( ! $(e.target).parents()[back]().filter( content ).length  && !$(parent).hasClass('dt-buttons')) || $(e.target).hasClass('dt-button-background')) {
1456
						close();
1457
					}
1458
				} )
1459
				.on( 'keyup.dtb-collection', function (e) {
1460
					if ( e.keyCode === 27 ) {
1461
						close();
1462
					}
1463
				} );
1464
		}, 0);
1465
	}
1466
} );
1467

1468

1469

1470
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1471
 * Statics
1472
 */
1473

1474
/**
1475
 * Show / hide a background layer behind a collection
1476
 * @param  {boolean} Flag to indicate if the background should be shown or
1477
 *   hidden 
1478
 * @param  {string} Class to assign to the background
1479
 * @static
1480
 */
1481
Buttons.background = function ( show, className, fade, insertPoint ) {
1482
	if ( fade === undefined ) {
1483
		fade = 400;
1484
	}
1485
	if ( ! insertPoint ) {
1486
		insertPoint = document.body;
1487
	}
1488

1489
	if ( show ) {
1490
		_fadeIn(
1491
			$('<div/>')
1492
				.addClass( className )
1493
				.css( 'display', 'none' )
1494
				.insertAfter( insertPoint ),
1495
			fade
1496
		);
1497
	}
1498
	else {
1499
		_fadeOut(
1500
			$('div.'+className),
1501
			fade,
1502
			function () {
1503
				$(this)
1504
					.removeClass( className )
1505
					.remove();
1506
			}
1507
		);
1508
	}
1509
};
1510

1511
/**
1512
 * Instance selector - select Buttons instances based on an instance selector
1513
 * value from the buttons assigned to a DataTable. This is only useful if
1514
 * multiple instances are attached to a DataTable.
1515
 * @param  {string|int|array} Instance selector - see `instance-selector`
1516
 *   documentation on the DataTables site
1517
 * @param  {array} Button instance array that was attached to the DataTables
1518
 *   settings object
1519
 * @return {array} Buttons instances
1520
 * @static
1521
 */
1522
Buttons.instanceSelector = function ( group, buttons )
1523
{
1524
	if ( group === undefined || group === null ) {
1525
		return $.map( buttons, function ( v ) {
1526
			return v.inst;
1527
		} );
1528
	}
1529

1530
	var ret = [];
1531
	var names = $.map( buttons, function ( v ) {
1532
		return v.name;
1533
	} );
1534

1535
	// Flatten the group selector into an array of single options
1536
	var process = function ( input ) {
1537
		if ( Array.isArray( input ) ) {
1538
			for ( var i=0, ien=input.length ; i<ien ; i++ ) {
1539
				process( input[i] );
1540
			}
1541
			return;
1542
		}
1543

1544
		if ( typeof input === 'string' ) {
1545
			if ( input.indexOf( ',' ) !== -1 ) {
1546
				// String selector, list of names
1547
				process( input.split(',') );
1548
			}
1549
			else {
1550
				// String selector individual name
1551
				var idx = $.inArray( input.trim(), names );
1552

1553
				if ( idx !== -1 ) {
1554
					ret.push( buttons[ idx ].inst );
1555
				}
1556
			}
1557
		}
1558
		else if ( typeof input === 'number' ) {
1559
			// Index selector
1560
			ret.push( buttons[ input ].inst );
1561
		}
1562
		else if ( typeof input === 'object' ) {
1563
			// Actual instance selector
1564
			ret.push( input );
1565
		}
1566
	};
1567
	
1568
	process( group );
1569

1570
	return ret;
1571
};
1572

1573
/**
1574
 * Button selector - select one or more buttons from a selector input so some
1575
 * operation can be performed on them.
1576
 * @param  {array} Button instances array that the selector should operate on
1577
 * @param  {string|int|node|jQuery|array} Button selector - see
1578
 *   `button-selector` documentation on the DataTables site
1579
 * @return {array} Array of objects containing `inst` and `idx` properties of
1580
 *   the selected buttons so you know which instance each button belongs to.
1581
 * @static
1582
 */
1583
Buttons.buttonSelector = function ( insts, selector )
1584
{
1585
	var ret = [];
1586
	var nodeBuilder = function ( a, buttons, baseIdx ) {
1587
		var button;
1588
		var idx;
1589

1590
		for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
1591
			button = buttons[i];
1592

1593
			if ( button ) {
1594
				idx = baseIdx !== undefined ?
1595
					baseIdx+i :
1596
					i+'';
1597

1598
				a.push( {
1599
					node: button.node,
1600
					name: button.conf.name,
1601
					idx:  idx
1602
				} );
1603

1604
				if ( button.buttons ) {
1605
					nodeBuilder( a, button.buttons, idx+'-' );
1606
				}
1607
			}
1608
		}
1609
	};
1610

1611
	var run = function ( selector, inst ) {
1612
		var i, ien;
1613
		var buttons = [];
1614
		nodeBuilder( buttons, inst.s.buttons );
1615

1616
		var nodes = $.map( buttons, function (v) {
1617
			return v.node;
1618
		} );
1619

1620
		if ( Array.isArray( selector ) || selector instanceof $ ) {
1621
			for ( i=0, ien=selector.length ; i<ien ; i++ ) {
1622
				run( selector[i], inst );
1623
			}
1624
			return;
1625
		}
1626

1627
		if ( selector === null || selector === undefined || selector === '*' ) {
1628
			// Select all
1629
			for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
1630
				ret.push( {
1631
					inst: inst,
1632
					node: buttons[i].node
1633
				} );
1634
			}
1635
		}
1636
		else if ( typeof selector === 'number' ) {
1637
			// Main button index selector
1638
			if (inst.s.buttons[ selector ]) {
1639
				ret.push( {
1640
					inst: inst,
1641
					node: inst.s.buttons[ selector ].node
1642
				} );
1643
			}
1644
		}
1645
		else if ( typeof selector === 'string' ) {
1646
			if ( selector.indexOf( ',' ) !== -1 ) {
1647
				// Split
1648
				var a = selector.split(',');
1649

1650
				for ( i=0, ien=a.length ; i<ien ; i++ ) {
1651
					run( a[i].trim(), inst );
1652
				}
1653
			}
1654
			else if ( selector.match( /^\d+(\-\d+)*$/ ) ) {
1655
				// Sub-button index selector
1656
				var indexes = $.map( buttons, function (v) {
1657
					return v.idx;
1658
				} );
1659

1660
				ret.push( {
1661
					inst: inst,
1662
					node: buttons[ $.inArray( selector, indexes ) ].node
1663
				} );
1664
			}
1665
			else if ( selector.indexOf( ':name' ) !== -1 ) {
1666
				// Button name selector
1667
				var name = selector.replace( ':name', '' );
1668

1669
				for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
1670
					if ( buttons[i].name === name ) {
1671
						ret.push( {
1672
							inst: inst,
1673
							node: buttons[i].node
1674
						} );
1675
					}
1676
				}
1677
			}
1678
			else {
1679
				// jQuery selector on the nodes
1680
				$( nodes ).filter( selector ).each( function () {
1681
					ret.push( {
1682
						inst: inst,
1683
						node: this
1684
					} );
1685
				} );
1686
			}
1687
		}
1688
		else if ( typeof selector === 'object' && selector.nodeName ) {
1689
			// Node selector
1690
			var idx = $.inArray( selector, nodes );
1691

1692
			if ( idx !== -1 ) {
1693
				ret.push( {
1694
					inst: inst,
1695
					node: nodes[ idx ]
1696
				} );
1697
			}
1698
		}
1699
	};
1700

1701

1702
	for ( var i=0, ien=insts.length ; i<ien ; i++ ) {
1703
		var inst = insts[i];
1704

1705
		run( selector, inst );
1706
	}
1707

1708
	return ret;
1709
};
1710

1711
/**
1712
 * Default function used for formatting output data.
1713
 * @param {*} str Data to strip
1714
 */
1715
Buttons.stripData = function ( str, config ) {
1716
	if ( typeof str !== 'string' ) {
1717
		return str;
1718
	}
1719

1720
	// Always remove script tags
1721
	str = str.replace( /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '' );
1722

1723
	// Always remove comments
1724
	str = str.replace( /<!\-\-.*?\-\->/g, '' );
1725

1726
	if ( ! config || config.stripHtml ) {
1727
		str = str.replace( /<[^>]*>/g, '' );
1728
	}
1729

1730
	if ( ! config || config.trim ) {
1731
		str = str.replace( /^\s+|\s+$/g, '' );
1732
	}
1733

1734
	if ( ! config || config.stripNewlines ) {
1735
		str = str.replace( /\n/g, ' ' );
1736
	}
1737

1738
	if ( ! config || config.decodeEntities ) {
1739
		_exportTextarea.innerHTML = str;
1740
		str = _exportTextarea.value;
1741
	}
1742

1743
	return str;
1744
};
1745

1746

1747
/**
1748
 * Buttons defaults. For full documentation, please refer to the docs/option
1749
 * directory or the DataTables site.
1750
 * @type {Object}
1751
 * @static
1752
 */
1753
Buttons.defaults = {
1754
	buttons: [ 'copy', 'excel', 'csv', 'pdf', 'print' ],
1755
	name: 'main',
1756
	tabIndex: 0,
1757
	dom: {
1758
		container: {
1759
			tag: 'div',
1760
			className: 'dt-buttons'
1761
		},
1762
		collection: {
1763
			tag: 'div',
1764
			className: ''
1765
		},
1766
		button: {
1767
			tag: 'button',
1768
			className: 'dt-button',
1769
			active: 'active',
1770
			disabled: 'disabled',
1771
			spacerClass: ''
1772
		},
1773
		buttonLiner: {
1774
			tag: 'span',
1775
			className: ''
1776
		},
1777
		split: {
1778
			tag: 'div',
1779
			className: 'dt-button-split',
1780
		},
1781
		splitWrapper: {
1782
			tag: 'div',
1783
			className: 'dt-btn-split-wrapper',
1784
		},
1785
		splitDropdown: {
1786
			tag: 'button',
1787
			text: '&#x25BC;',
1788
			className: 'dt-btn-split-drop',
1789
			align: 'split-right',
1790
			splitAlignClass: 'dt-button-split-left'
1791
		},
1792
		splitDropdownButton: {
1793
			tag: 'button',
1794
			className: 'dt-btn-split-drop-button dt-button',
1795
		},
1796
		splitCollection: {
1797
			tag: 'div',
1798
			className: 'dt-button-split-collection',
1799
		}
1800
	}
1801
};
1802

1803
/**
1804
 * Version information
1805
 * @type {string}
1806
 * @static
1807
 */
1808
Buttons.version = '2.2.2';
1809

1810

1811
$.extend( _dtButtons, {
1812
	collection: {
1813
		text: function ( dt ) {
1814
			return dt.i18n( 'buttons.collection', 'Collection' );
1815
		},
1816
		className: 'buttons-collection',
1817
		closeButton: false,
1818
		init: function ( dt, button, config ) {
1819
			button.attr( 'aria-expanded', false );
1820
		},
1821
		action: function ( e, dt, button, config ) {
1822
			if ( config._collection.parents('body').length ) {
1823
				this.popover(false, config);
1824
			}
1825
			else {
1826
				this.popover(config._collection, config);
1827
			}
1828
		},
1829
		attr: {
1830
			'aria-haspopup': true
1831
		}
1832
		// Also the popover options, defined in Buttons.popover
1833
	},
1834
	split: {
1835
		text: function ( dt ) {
1836
			return dt.i18n( 'buttons.split', 'Split' );
1837
		},
1838
		className: 'buttons-split',
1839
		closeButton: false,
1840
		init: function ( dt, button, config ) {
1841
			return button.attr( 'aria-expanded', false );
1842
		},
1843
		action: function ( e, dt, button, config ) {
1844
			this.popover(config._collection, config);
1845
		},
1846
		attr: {
1847
			'aria-haspopup': true
1848
		}
1849
		// Also the popover options, defined in Buttons.popover
1850
	},
1851
	copy: function ( dt, conf ) {
1852
		if ( _dtButtons.copyHtml5 ) {
1853
			return 'copyHtml5';
1854
		}
1855
	},
1856
	csv: function ( dt, conf ) {
1857
		if ( _dtButtons.csvHtml5 && _dtButtons.csvHtml5.available( dt, conf ) ) {
1858
			return 'csvHtml5';
1859
		}
1860
	},
1861
	excel: function ( dt, conf ) {
1862
		if ( _dtButtons.excelHtml5 && _dtButtons.excelHtml5.available( dt, conf ) ) {
1863
			return 'excelHtml5';
1864
		}
1865
	},
1866
	pdf: function ( dt, conf ) {
1867
		if ( _dtButtons.pdfHtml5 && _dtButtons.pdfHtml5.available( dt, conf ) ) {
1868
			return 'pdfHtml5';
1869
		}
1870
	},
1871
	pageLength: function ( dt ) {
1872
		var lengthMenu = dt.settings()[0].aLengthMenu;
1873
		var vals = [];
1874
		var lang = [];
1875
		var text = function ( dt ) {
1876
			return dt.i18n( 'buttons.pageLength', {
1877
				"-1": 'Show all rows',
1878
				_:    'Show %d rows'
1879
			}, dt.page.len() );
1880
		};
1881

1882
		// Support for DataTables 1.x 2D array
1883
		if (Array.isArray( lengthMenu[0] )) {
1884
			vals = lengthMenu[0];
1885
			lang = lengthMenu[1];
1886
		}
1887
		else {
1888
			for (var i=0 ; i<lengthMenu.length ; i++) {
1889
				var option = lengthMenu[i];
1890

1891
				// Support for DataTables 2 object in the array
1892
				if ($.isPlainObject(option)) {
1893
					vals.push(option.value);
1894
					lang.push(option.label);
1895
				}
1896
				else {
1897
					vals.push(option);
1898
					lang.push(option);
1899
				}
1900
			}
1901
		}
1902

1903
		return {
1904
			extend: 'collection',
1905
			text: text,
1906
			className: 'buttons-page-length',
1907
			autoClose: true,
1908
			buttons: $.map( vals, function ( val, i ) {
1909
				return {
1910
					text: lang[i],
1911
					className: 'button-page-length',
1912
					action: function ( e, dt ) {
1913
						dt.page.len( val ).draw();
1914
					},
1915
					init: function ( dt, node, conf ) {
1916
						var that = this;
1917
						var fn = function () {
1918
							that.active( dt.page.len() === val );
1919
						};
1920

1921
						dt.on( 'length.dt'+conf.namespace, fn );
1922
						fn();
1923
					},
1924
					destroy: function ( dt, node, conf ) {
1925
						dt.off( 'length.dt'+conf.namespace );
1926
					}
1927
				};
1928
			} ),
1929
			init: function ( dt, node, conf ) {
1930
				var that = this;
1931
				dt.on( 'length.dt'+conf.namespace, function () {
1932
					that.text( conf.text );
1933
				} );
1934
			},
1935
			destroy: function ( dt, node, conf ) {
1936
				dt.off( 'length.dt'+conf.namespace );
1937
			}
1938
		};
1939
	},
1940
	spacer: {
1941
		style: 'empty',
1942
		spacer: true,
1943
		text: function ( dt ) {
1944
			return dt.i18n( 'buttons.spacer', '' );
1945
		}
1946
	}
1947
} );
1948

1949

1950
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1951
 * DataTables API
1952
 *
1953
 * For complete documentation, please refer to the docs/api directory or the
1954
 * DataTables site
1955
 */
1956

1957
// Buttons group and individual button selector
1958
DataTable.Api.register( 'buttons()', function ( group, selector ) {
1959
	// Argument shifting
1960
	if ( selector === undefined ) {
1961
		selector = group;
1962
		group = undefined;
1963
	}
1964

1965
	this.selector.buttonGroup = group;
1966

1967
	var res = this.iterator( true, 'table', function ( ctx ) {
1968
		if ( ctx._buttons ) {
1969
			return Buttons.buttonSelector(
1970
				Buttons.instanceSelector( group, ctx._buttons ),
1971
				selector
1972
			);
1973
		}
1974
	}, true );
1975

1976
	res._groupSelector = group;
1977
	return res;
1978
} );
1979

1980
// Individual button selector
1981
DataTable.Api.register( 'button()', function ( group, selector ) {
1982
	// just run buttons() and truncate
1983
	var buttons = this.buttons( group, selector );
1984

1985
	if ( buttons.length > 1 ) {
1986
		buttons.splice( 1, buttons.length );
1987
	}
1988

1989
	return buttons;
1990
} );
1991

1992
// Active buttons
1993
DataTable.Api.registerPlural( 'buttons().active()', 'button().active()', function ( flag ) {
1994
	if ( flag === undefined ) {
1995
		return this.map( function ( set ) {
1996
			return set.inst.active( set.node );
1997
		} );
1998
	}
1999

2000
	return this.each( function ( set ) {
2001
		set.inst.active( set.node, flag );
2002
	} );
2003
} );
2004

2005
// Get / set button action
2006
DataTable.Api.registerPlural( 'buttons().action()', 'button().action()', function ( action ) {
2007
	if ( action === undefined ) {
2008
		return this.map( function ( set ) {
2009
			return set.inst.action( set.node );
2010
		} );
2011
	}
2012

2013
	return this.each( function ( set ) {
2014
		set.inst.action( set.node, action );
2015
	} );
2016
} );
2017

2018
// Collection control
2019
DataTable.Api.registerPlural( 'buttons().collectionRebuild()', 'button().collectionRebuild()', function ( buttons ) {
2020
	return this.each( function ( set ) {
2021
		for(var i = 0; i < buttons.length; i++) {
2022
			if(typeof buttons[i] === 'object') {
2023
				buttons[i].parentConf = set;
2024
			}
2025
		}
2026
		set.inst.collectionRebuild( set.node, buttons );
2027
	} );
2028
} );
2029

2030
// Enable / disable buttons
2031
DataTable.Api.register( ['buttons().enable()', 'button().enable()'], function ( flag ) {
2032
	return this.each( function ( set ) {
2033
		set.inst.enable( set.node, flag );
2034
	} );
2035
} );
2036

2037
// Disable buttons
2038
DataTable.Api.register( ['buttons().disable()', 'button().disable()'], function () {
2039
	return this.each( function ( set ) {
2040
		set.inst.disable( set.node );
2041
	} );
2042
} );
2043

2044
// Button index
2045
DataTable.Api.register( 'button().index()', function () {
2046
	var idx = null;
2047

2048
	this.each( function ( set ) {
2049
		var res = set.inst.index( set.node );
2050

2051
		if (res !== null) {
2052
			idx = res;
2053
		}
2054
	} );
2055

2056
	return idx;
2057
} );
2058

2059
// Get button nodes
2060
DataTable.Api.registerPlural( 'buttons().nodes()', 'button().node()', function () {
2061
	var jq = $();
2062

2063
	// jQuery will automatically reduce duplicates to a single entry
2064
	$( this.each( function ( set ) {
2065
		jq = jq.add( set.inst.node( set.node ) );
2066
	} ) );
2067

2068
	return jq;
2069
} );
2070

2071
// Get / set button processing state
2072
DataTable.Api.registerPlural( 'buttons().processing()', 'button().processing()', function ( flag ) {
2073
	if ( flag === undefined ) {
2074
		return this.map( function ( set ) {
2075
			return set.inst.processing( set.node );
2076
		} );
2077
	}
2078

2079
	return this.each( function ( set ) {
2080
		set.inst.processing( set.node, flag );
2081
	} );
2082
} );
2083

2084
// Get / set button text (i.e. the button labels)
2085
DataTable.Api.registerPlural( 'buttons().text()', 'button().text()', function ( label ) {
2086
	if ( label === undefined ) {
2087
		return this.map( function ( set ) {
2088
			return set.inst.text( set.node );
2089
		} );
2090
	}
2091

2092
	return this.each( function ( set ) {
2093
		set.inst.text( set.node, label );
2094
	} );
2095
} );
2096

2097
// Trigger a button's action
2098
DataTable.Api.registerPlural( 'buttons().trigger()', 'button().trigger()', function () {
2099
	return this.each( function ( set ) {
2100
		set.inst.node( set.node ).trigger( 'click' );
2101
	} );
2102
} );
2103

2104
// Button resolver to the popover
2105
DataTable.Api.register( 'button().popover()', function (content, options) {
2106
	return this.map( function ( set ) {
2107
		return set.inst._popover( content, this.button(this[0].node), options );
2108
	} );
2109
} );
2110

2111
// Get the container elements
2112
DataTable.Api.register( 'buttons().containers()', function () {
2113
	var jq = $();
2114
	var groupSelector = this._groupSelector;
2115

2116
	// We need to use the group selector directly, since if there are no buttons
2117
	// the result set will be empty
2118
	this.iterator( true, 'table', function ( ctx ) {
2119
		if ( ctx._buttons ) {
2120
			var insts = Buttons.instanceSelector( groupSelector, ctx._buttons );
2121

2122
			for ( var i=0, ien=insts.length ; i<ien ; i++ ) {
2123
				jq = jq.add( insts[i].container() );
2124
			}
2125
		}
2126
	} );
2127

2128
	return jq;
2129
} );
2130

2131
DataTable.Api.register( 'buttons().container()', function () {
2132
	// API level of nesting is `buttons()` so we can zip into the containers method
2133
	return this.containers().eq(0);
2134
} );
2135

2136
// Add a new button
2137
DataTable.Api.register( 'button().add()', function ( idx, conf, draw ) {
2138
	var ctx = this.context;
2139

2140
	// Don't use `this` as it could be empty - select the instances directly
2141
	if ( ctx.length ) {
2142
		var inst = Buttons.instanceSelector( this._groupSelector, ctx[0]._buttons );
2143

2144
		if ( inst.length ) {
2145
			inst[0].add( conf, idx , draw);
2146
		}
2147
	}
2148

2149
	return this.button( this._groupSelector, idx );
2150
} );
2151

2152
// Destroy the button sets selected
2153
DataTable.Api.register( 'buttons().destroy()', function () {
2154
	this.pluck( 'inst' ).unique().each( function ( inst ) {
2155
		inst.destroy();
2156
	} );
2157

2158
	return this;
2159
} );
2160

2161
// Remove a button
2162
DataTable.Api.registerPlural( 'buttons().remove()', 'buttons().remove()', function () {
2163
	this.each( function ( set ) {
2164
		set.inst.remove( set.node );
2165
	} );
2166

2167
	return this;
2168
} );
2169

2170
// Information box that can be used by buttons
2171
var _infoTimer;
2172
DataTable.Api.register( 'buttons.info()', function ( title, message, time ) {
2173
	var that = this;
2174

2175
	if ( title === false ) {
2176
		this.off('destroy.btn-info');
2177
		_fadeOut(
2178
			$('#datatables_buttons_info'),
2179
			400,
2180
			function () {
2181
				$(this).remove();
2182
			}
2183
		);
2184
		clearTimeout( _infoTimer );
2185
		_infoTimer = null;
2186

2187
		return this;
2188
	}
2189

2190
	if ( _infoTimer ) {
2191
		clearTimeout( _infoTimer );
2192
	}
2193

2194
	if ( $('#datatables_buttons_info').length ) {
2195
		$('#datatables_buttons_info').remove();
2196
	}
2197

2198
	title = title ? '<h2>'+title+'</h2>' : '';
2199

2200
	_fadeIn(
2201
		$('<div id="datatables_buttons_info" class="dt-button-info"/>')
2202
			.html( title )
2203
			.append( $('<div/>')[ typeof message === 'string' ? 'html' : 'append' ]( message ) )
2204
			.css( 'display', 'none' )
2205
			.appendTo( 'body' )
2206
	);
2207

2208
	if ( time !== undefined && time !== 0 ) {
2209
		_infoTimer = setTimeout( function () {
2210
			that.buttons.info( false );
2211
		}, time );
2212
	}
2213

2214
	this.on('destroy.btn-info', function () {
2215
		that.buttons.info(false);
2216
	});
2217

2218
	return this;
2219
} );
2220

2221
// Get data from the table for export - this is common to a number of plug-in
2222
// buttons so it is included in the Buttons core library
2223
DataTable.Api.register( 'buttons.exportData()', function ( options ) {
2224
	if ( this.context.length ) {
2225
		return _exportData( new DataTable.Api( this.context[0] ), options );
2226
	}
2227
} );
2228

2229
// Get information about the export that is common to many of the export data
2230
// types (DRY)
2231
DataTable.Api.register( 'buttons.exportInfo()', function ( conf ) {
2232
	if ( ! conf ) {
2233
		conf = {};
2234
	}
2235

2236
	return {
2237
		filename: _filename( conf ),
2238
		title: _title( conf ),
2239
		messageTop: _message(this, conf.message || conf.messageTop, 'top'),
2240
		messageBottom: _message(this, conf.messageBottom, 'bottom')
2241
	};
2242
} );
2243

2244

2245

2246
/**
2247
 * Get the file name for an exported file.
2248
 *
2249
 * @param {object}	config Button configuration
2250
 * @param {boolean} incExtension Include the file name extension
2251
 */
2252
var _filename = function ( config )
2253
{
2254
	// Backwards compatibility
2255
	var filename = config.filename === '*' && config.title !== '*' && config.title !== undefined && config.title !== null && config.title !== '' ?
2256
		config.title :
2257
		config.filename;
2258

2259
	if ( typeof filename === 'function' ) {
2260
		filename = filename();
2261
	}
2262

2263
	if ( filename === undefined || filename === null ) {
2264
		return null;
2265
	}
2266

2267
	if ( filename.indexOf( '*' ) !== -1 ) {
2268
		filename = filename.replace( '*', $('head > title').text() ).trim();
2269
	}
2270

2271
	// Strip characters which the OS will object to
2272
	filename = filename.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, "");
2273

2274
	var extension = _stringOrFunction( config.extension );
2275
	if ( ! extension ) {
2276
		extension = '';
2277
	}
2278

2279
	return filename + extension;
2280
};
2281

2282
/**
2283
 * Simply utility method to allow parameters to be given as a function
2284
 *
2285
 * @param {undefined|string|function} option Option
2286
 * @return {null|string} Resolved value
2287
 */
2288
var _stringOrFunction = function ( option )
2289
{
2290
	if ( option === null || option === undefined ) {
2291
		return null;
2292
	}
2293
	else if ( typeof option === 'function' ) {
2294
		return option();
2295
	}
2296
	return option;
2297
};
2298

2299
/**
2300
 * Get the title for an exported file.
2301
 *
2302
 * @param {object} config	Button configuration
2303
 */
2304
var _title = function ( config )
2305
{
2306
	var title = _stringOrFunction( config.title );
2307

2308
	return title === null ?
2309
		null : title.indexOf( '*' ) !== -1 ?
2310
			title.replace( '*', $('head > title').text() || 'Exported data' ) :
2311
			title;
2312
};
2313

2314
var _message = function ( dt, option, position )
2315
{
2316
	var message = _stringOrFunction( option );
2317
	if ( message === null ) {
2318
		return null;
2319
	}
2320

2321
	var caption = $('caption', dt.table().container()).eq(0);
2322
	if ( message === '*' ) {
2323
		var side = caption.css( 'caption-side' );
2324
		if ( side !== position ) {
2325
			return null;
2326
		}
2327

2328
		return caption.length ?
2329
			caption.text() :
2330
			'';
2331
	}
2332

2333
	return message;
2334
};
2335

2336

2337

2338

2339
var _exportTextarea = $('<textarea/>')[0];
2340
var _exportData = function ( dt, inOpts )
2341
{
2342
	var config = $.extend( true, {}, {
2343
		rows:           null,
2344
		columns:        '',
2345
		modifier:       {
2346
			search: 'applied',
2347
			order:  'applied'
2348
		},
2349
		orthogonal:     'display',
2350
		stripHtml:      true,
2351
		stripNewlines:  true,
2352
		decodeEntities: true,
2353
		trim:           true,
2354
		format:         {
2355
			header: function ( d ) {
2356
				return Buttons.stripData( d, config );
2357
			},
2358
			footer: function ( d ) {
2359
				return Buttons.stripData( d, config );
2360
			},
2361
			body: function ( d ) {
2362
				return Buttons.stripData( d, config );
2363
			}
2364
		},
2365
		customizeData: null
2366
	}, inOpts );
2367

2368
	var header = dt.columns( config.columns ).indexes().map( function (idx) {
2369
		var el = dt.column( idx ).header();
2370
		return config.format.header( el.innerHTML, idx, el );
2371
	} ).toArray();
2372

2373
	var footer = dt.table().footer() ?
2374
		dt.columns( config.columns ).indexes().map( function (idx) {
2375
			var el = dt.column( idx ).footer();
2376
			return config.format.footer( el ? el.innerHTML : '', idx, el );
2377
		} ).toArray() :
2378
		null;
2379
	
2380
	// If Select is available on this table, and any rows are selected, limit the export
2381
	// to the selected rows. If no rows are selected, all rows will be exported. Specify
2382
	// a `selected` modifier to control directly.
2383
	var modifier = $.extend( {}, config.modifier );
2384
	if ( dt.select && typeof dt.select.info === 'function' && modifier.selected === undefined ) {
2385
		if ( dt.rows( config.rows, $.extend( { selected: true }, modifier ) ).any() ) {
2386
			$.extend( modifier, { selected: true } )
2387
		}
2388
	}
2389

2390
	var rowIndexes = dt.rows( config.rows, modifier ).indexes().toArray();
2391
	var selectedCells = dt.cells( rowIndexes, config.columns );
2392
	var cells = selectedCells
2393
		.render( config.orthogonal )
2394
		.toArray();
2395
	var cellNodes = selectedCells
2396
		.nodes()
2397
		.toArray();
2398

2399
	var columns = header.length;
2400
	var rows = columns > 0 ? cells.length / columns : 0;
2401
	var body = [];
2402
	var cellCounter = 0;
2403

2404
	for ( var i=0, ien=rows ; i<ien ; i++ ) {
2405
		var row = [ columns ];
2406

2407
		for ( var j=0 ; j<columns ; j++ ) {
2408
			row[j] = config.format.body( cells[ cellCounter ], i, j, cellNodes[ cellCounter ] );
2409
			cellCounter++;
2410
		}
2411

2412
		body[i] = row;
2413
	}
2414

2415
	var data = {
2416
		header: header,
2417
		footer: footer,
2418
		body:   body
2419
	};
2420

2421
	if ( config.customizeData ) {
2422
		config.customizeData( data );
2423
	}
2424

2425
	return data;
2426
};
2427

2428

2429
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2430
 * DataTables interface
2431
 */
2432

2433
// Attach to DataTables objects for global access
2434
$.fn.dataTable.Buttons = Buttons;
2435
$.fn.DataTable.Buttons = Buttons;
2436

2437

2438

2439
// DataTables creation - check if the buttons have been defined for this table,
2440
// they will have been if the `B` option was used in `dom`, otherwise we should
2441
// create the buttons instance here so they can be inserted into the document
2442
// using the API. Listen for `init` for compatibility with pre 1.10.10, but to
2443
// be removed in future.
2444
$(document).on( 'init.dt plugin-init.dt', function (e, settings) {
2445
	if ( e.namespace !== 'dt' ) {
2446
		return;
2447
	}
2448

2449
	var opts = settings.oInit.buttons || DataTable.defaults.buttons;
2450

2451
	if ( opts && ! settings._buttons ) {
2452
		new Buttons( settings, opts ).container();
2453
	}
2454
} );
2455

2456
function _init ( settings, options ) {
2457
	var api = new DataTable.Api( settings );
2458
	var opts = options
2459
		? options
2460
		: api.init().buttons || DataTable.defaults.buttons;
2461

2462
	return new Buttons( api, opts ).container();
2463
}
2464

2465
// DataTables `dom` feature option
2466
DataTable.ext.feature.push( {
2467
	fnInit: _init,
2468
	cFeature: "B"
2469
} );
2470

2471
// DataTables 2 layout feature
2472
if ( DataTable.ext.features ) {
2473
	DataTable.ext.features.register( 'buttons', _init );
2474
}
2475

2476

2477
return Buttons;
2478
}));
2479

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.