1/*! Buttons for DataTables 2.4.1
2 * © SpryMedia Ltd - datatables.net/license
3 */
4
5(function( factory ){
6	if ( typeof define === 'function' && define.amd ) {
7		// AMD
8		define( ['jquery', 'datatables.net'], function ( $ ) {
9			return factory( $, window, document );
10		} );
11	}
12	else if ( typeof exports === 'object' ) {
13		// CommonJS
14		var jq = require('jquery');
15		var cjsRequires = function (root, $) {
16			if ( ! $.fn.dataTable ) {
17				require('datatables.net')(root, $);
18			}
19		};
20
21		if (typeof window === 'undefined') {
22			module.exports = function (root, $) {
23				if ( ! root ) {
24					// CommonJS environments without a window global must pass a
25					// root. This will give an error otherwise
26					root = window;
27				}
28
29				if ( ! $ ) {
30					$ = jq( root );
31				}
32
33				cjsRequires( root, $ );
34				return factory( $, root, root.document );
35			};
36		}
37		else {
38			cjsRequires( window, jq );
39			module.exports = factory( jq, window, window.document );
40		}
41	}
42	else {
43		// Browser
44		factory( jQuery, window, document );
45	}
46}(function( $, window, document, undefined ) {
47'use strict';
48var DataTable = $.fn.dataTable;
49
50
51
52// Used for namespacing events added to the document by each instance, so they
53// can be removed on destroy
54var _instCounter = 0;
55
56// Button namespacing counter for namespacing events on individual buttons
57var _buttonCounter = 0;
58
59var _dtButtons = DataTable.ext.buttons;
60
61// Allow for jQuery slim
62function _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
75function _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
88/**
89 * [Buttons description]
90 * @param {[type]}
91 * @param {[type]}
92 */
93var 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
103	// If there is no config set it to an empty object
104	if (typeof config === 'undefined') {
105		config = {};
106	}
107
108	// Allow a boolean true for defaults
109	if (config === true) {
110		config = {};
111	}
112
113	// For easy configuration of buttons an array can be given
114	if (Array.isArray(config)) {
115		config = { buttons: config };
116	}
117
118	this.c = $.extend(true, {}, Buttons.defaults, config);
119
120	// Don't want a deep copy for the buttons
121	if (config.buttons) {
122		this.c.buttons = config.buttons;
123	}
124
125	this.s = {
126		dt: new DataTable.Api(dt),
127		buttons: [],
128		listenKeys: '',
129		namespace: 'dtb' + _instCounter++
130	};
131
132	this.dom = {
133		container: $('<' + this.c.dom.container.tag + '/>').addClass(this.c.dom.container.className)
134	};
135
136	this._constructor();
137};
138
139$.extend(Buttons.prototype, {
140	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
141	 * Public methods
142	 */
143
144	/**
145	 * Get the action of a button
146	 * @param  {int|string} Button index
147	 * @return {function}
148	 */ /**
149	 * Set the action of a button
150	 * @param  {node} node Button element
151	 * @param  {function} action Function to set
152	 * @return {Buttons} Self for chaining
153	 */
154	action: function (node, action) {
155		var button = this._nodeToButton(node);
156
157		if (action === undefined) {
158			return button.conf.action;
159		}
160
161		button.conf.action = action;
162
163		return this;
164	},
165
166	/**
167	 * Add an active class to the button to make to look active or get current
168	 * active state.
169	 * @param  {node} node Button element
170	 * @param  {boolean} [flag] Enable / disable flag
171	 * @return {Buttons} Self for chaining or boolean for getter
172	 */
173	active: function (node, flag) {
174		var button = this._nodeToButton(node);
175		var klass = this.c.dom.button.active;
176		var jqNode = $(button.node);
177
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);
188		}
189
190		jqNode.toggleClass(klass, flag === undefined ? true : flag);
191
192		return this;
193	},
194
195	/**
196	 * Add a new button
197	 * @param {object} config Button configuration object, base string name or function
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.
201	 * @return {Buttons} Self for chaining
202	 */
203	add: function (config, idx, draw) {
204		var buttons = this.s.buttons;
205
206		if (typeof idx === 'string') {
207			var split = idx.split('-');
208			var base = this.s;
209
210			for (var i = 0, ien = split.length - 1; i < ien; i++) {
211				base = base.buttons[split[i] * 1];
212			}
213
214			buttons = base.buttons;
215			idx = split[split.length - 1] * 1;
216		}
217
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		}
231
232		return this;
233	},
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
277	/**
278	 * Get the container node for the buttons
279	 * @return {jQuery} Buttons node
280	 */
281	container: function () {
282		return this.dom.container;
283	},
284
285	/**
286	 * Disable a button
287	 * @param  {node} node Button node
288	 * @return {Buttons} Self for chaining
289	 */
290	disable: function (node) {
291		var button = this._nodeToButton(node);
292
293		$(button.node).addClass(this.c.dom.button.disabled).prop('disabled', true);
294
295		return this;
296	},
297
298	/**
299	 * Destroy the instance, cleaning up event handlers and removing DOM
300	 * elements
301	 * @return {Buttons} Self for chaining
302	 */
303	destroy: function () {
304		// Key event listener
305		$('body').off('keyup.' + this.s.namespace);
306
307		// Individual button destroy (so they can remove their own events if
308		// needed). Take a copy as the array is modified by `remove`
309		var buttons = this.s.buttons.slice();
310		var i, ien;
311
312		for (i = 0, ien = buttons.length; i < ien; i++) {
313			this.remove(buttons[i].node);
314		}
315
316		// Container
317		this.dom.container.remove();
318
319		// Remove from the settings object collection
320		var buttonInsts = this.s.dt.settings()[0];
321
322		for (i = 0, ien = buttonInsts.length; i < ien; i++) {
323			if (buttonInsts.inst === this) {
324				buttonInsts.splice(i, 1);
325				break;
326			}
327		}
328
329		return this;
330	},
331
332	/**
333	 * Enable / disable a button
334	 * @param  {node} node Button node
335	 * @param  {boolean} [flag=true] Enable / disable flag
336	 * @return {Buttons} Self for chaining
337	 */
338	enable: function (node, flag) {
339		if (flag === false) {
340			return this.disable(node);
341		}
342
343		var button = this._nodeToButton(node);
344		$(button.node).removeClass(this.c.dom.button.disabled).prop('disabled', false);
345
346		return this;
347	},
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
381	/**
382	 * Get the instance name for the button set selector
383	 * @return {string} Instance name
384	 */
385	name: function () {
386		return this.c.name;
387	},
388
389	/**
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
393	 */
394	node: function (node) {
395		if (!node) {
396			return this.dom.container;
397		}
398
399		var button = this._nodeToButton(node);
400		return $(button.node);
401	},
402
403	/**
404	 * Set / get a processing class on the selected button
405	 * @param {element} node Triggering button node
406	 * @param  {boolean} flag true to add, false to remove, undefined to get
407	 * @return {boolean|Buttons} Getter value or this if a setter.
408	 */
409	processing: function (node, flag) {
410		var dt = this.s.dt;
411		var button = this._nodeToButton(node);
412
413		if (flag === undefined) {
414			return $(button.node).hasClass('processing');
415		}
416
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		]);
426
427		return this;
428	},
429
430	/**
431	 * Remove a button.
432	 * @param  {node} node Button node
433	 * @return {Buttons} Self for chaining
434	 */
435	remove: function (node) {
436		var button = this._nodeToButton(node);
437		var host = this._nodeToHost(node);
438		var dt = this.s.dt;
439
440		// Remove any child buttons first
441		if (button.buttons.length) {
442			for (var i = button.buttons.length - 1; i >= 0; i--) {
443				this.remove(button.buttons[i].node);
444			}
445		}
446
447		button.conf.destroying = true;
448
449		// Allow the button to remove event handlers, etc
450		if (button.conf.destroy) {
451			button.conf.destroy.call(dt.button(node), dt, $(node), button.conf);
452		}
453
454		this._removeKey(button.conf);
455
456		$(button.node).remove();
457
458		var idx = $.inArray(button, host);
459		host.splice(idx, 1);
460
461		return this;
462	},
463
464	/**
465	 * Get the text for a button
466	 * @param  {int|string} node Button index
467	 * @return {string} Button text
468	 */ /**
469	 * Set the text for a button
470	 * @param  {int|string|function} node Button index
471	 * @param  {string} label Text
472	 * @return {Buttons} Self for chaining
473	 */
474	text: function (node, label) {
475		var button = this._nodeToButton(node);
476		var textNode = button.textNode;
477		var dt = this.s.dt;
478		var jqNode = $(button.node);
479		var text = function (opt) {
480			return typeof opt === 'function' ? opt(dt, jqNode, button.conf) : opt;
481		};
482
483		if (label === undefined) {
484			return text(button.conf.text);
485		}
486
487		button.conf.text = label;
488		textNode.html(text(label));
489
490		return this;
491	},
492
493	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
494	 * Constructor
495	 */
496
497	/**
498	 * Buttons constructor
499	 * @private
500	 */
501	_constructor: function () {
502		var that = this;
503		var dt = this.s.dt;
504		var dtSettings = dt.settings()[0];
505		var buttons = this.c.buttons;
506
507		if (!dtSettings._buttons) {
508			dtSettings._buttons = [];
509		}
510
511		dtSettings._buttons.push({
512			inst: this,
513			name: this.c.name
514		});
515
516		for (var i = 0, ien = buttons.length; i < ien; i++) {
517			this.add(buttons[i]);
518		}
519
520		dt.on('destroy', function (e, settings) {
521			if (settings === dtSettings) {
522				that.destroy();
523			}
524		});
525
526		// Global key event binding to listen for button keys
527		$('body').on('keyup.' + this.s.namespace, function (e) {
528			if (!document.activeElement || document.activeElement === document.body) {
529				// SUse a string of characters for fast lookup of if we need to
530				// handle this
531				var character = String.fromCharCode(e.keyCode).toLowerCase();
532
533				if (that.s.listenKeys.toLowerCase().indexOf(character) !== -1) {
534					that._keypress(character, e);
535				}
536			}
537		});
538	},
539
540	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
541	 * Private methods
542	 */
543
544	/**
545	 * Add a new button to the key press listener
546	 * @param {object} conf Resolved button configuration object
547	 * @private
548	 */
549	_addKey: function (conf) {
550		if (conf.key) {
551			this.s.listenKeys += $.isPlainObject(conf.key) ? conf.key.key : conf.key;
552		}
553	},
554
555	/**
556	 * Insert the buttons into the container. Call without parameters!
557	 * @param  {node} [container] Recursive only - Insert point
558	 * @param  {array} [buttons] Recursive only - Buttons array
559	 * @private
560	 */
561	_draw: function (container, buttons) {
562		if (!container) {
563			container = this.dom.container;
564			buttons = this.s.buttons;
565		}
566
567		container.children().detach();
568
569		for (var i = 0, ien = buttons.length; i < ien; i++) {
570			container.append(buttons[i].inserter);
571			container.append(' ');
572
573			if (buttons[i].buttons && buttons[i].buttons.length) {
574				this._draw(buttons[i].collection, buttons[i].buttons);
575			}
576		}
577	},
578
579	/**
580	 * Create buttons from an array of buttons
581	 * @param  {array} attachTo Buttons array to attach to
582	 * @param  {object} button Button definition
583	 * @param  {boolean} inCollection true if the button is in a collection
584	 * @private
585	 */
586	_expandButton: function (
587		attachTo,
588		button,
589		split,
590		inCollection,
591		inSplit,
592		attachPoint,
593		parentConf
594	) {
595		var dt = this.s.dt;
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		}
603
604		for (var i = 0, ien = buttons.length; i < ien; i++) {
605			var conf = this._resolveExtends(buttons[i]);
606
607			if (!conf) {
608				continue;
609			}
610
611			isSplit = conf.config && conf.config.split ? true : false;
612
613			// If the configuration is an array, then expand the buttons at this
614			// point
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				);
625				continue;
626			}
627
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) {
636				continue;
637			}
638
639			if (attachPoint !== undefined && attachPoint !== null) {
640				attachTo.splice(attachPoint, 0, built);
641				attachPoint++;
642			}
643			else {
644				attachTo.push(built);
645			}
646
647			// Create the dropdown for a collection
648			if (built.conf.buttons) {
649				built.collection = $('<' + domCollection.container.content.tag + '/>');
650				built.conf._collection = built.collection;
651
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				);
699			}
700
701			built.conf.parent = parentConf;
702
703			// init call is made here, rather than buildButton as it needs to
704			// be selectable, and for that it needs to be in the buttons array
705			if (conf.init) {
706				conf.init.call(dt.button(built.node), dt, $(built.node), conf);
707			}
708		}
709	},
710
711	/**
712	 * Create an individual button
713	 * @param  {object} config            Resolved button configuration
714	 * @param  {boolean} inCollection `true` if a collection button
715	 * @return {object} Completed button description object
716	 * @private
717	 */
718	_buildButton: function (config, inCollection, isSplit, inSplit) {
719		var configDom = this.c.dom;
720		var textNode;
721		var dt = this.s.dt;
722		var text = function (opt) {
723			return typeof opt === 'function' ? opt(dt, button, config) : opt;
724		};
725
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);
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));
746
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			};
757		}
758
759		// Make sure that the button is available based on whatever requirements
760		// it has. For example, PDF button require pdfmake
761		if (config.available && !config.available(dt, config) && !config.hasOwnProperty('html')) {
762			return false;
763		}
764
765		var button;
766
767		if (!config.hasOwnProperty('html')) {
768			var action = function (e, dt, button, config) {
769				config.action.call(dt.button(button), e, dt, button, config);
770
771				$(dt.table().node()).triggerHandler('buttons-action.dt', [
772					dt.button(button),
773					dt,
774					button,
775					config
776				]);
777			};
778
779			var tag = config.tag || dom.tag;
780			var clickBlurs = config.clickBlurs === undefined ? true : config.clickBlurs;
781
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);
791					}
792
793					if (clickBlurs) {
794						button.trigger('blur');
795					}
796				})
797				.on('keypress.dtb', function (e) {
798					if (e.keyCode === 13) {
799						e.preventDefault();
800
801						if (!button.hasClass(dom.disabled) && config.action) {
802							action(e, dt, button, config);
803						}
804					}
805				});
806
807			// Make `a` tags act like a link
808			if (tag.toLowerCase() === 'a') {
809				button.attr('href', '#');
810			}
811
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');
815			}
816
817			if (dom.liner.tag) {
818				var liner = $('<' + dom.liner.tag + '/>')
819					.html(text(config.text))
820					.addClass(dom.liner.className);
821
822				if (dom.liner.tag.toLowerCase() === 'a') {
823					liner.attr('href', '#');
824				}
825
826				button.append(liner);
827				textNode = liner;
828			}
829			else {
830				button.html(text(config.text));
831				textNode = button;
832			}
833
834			if (config.enabled === false) {
835				button.addClass(dom.disabled);
836			}
837
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			}
853
854			if (config.config !== undefined && config.config.split) {
855				config.split = config.config.split;
856			}
857		}
858		else {
859			button = $(config.html);
860		}
861
862		var buttonContainer = this.c.dom.buttonContainer;
863		var inserter;
864		if (buttonContainer && buttonContainer.tag) {
865			inserter = $('<' + buttonContainer.tag + '/>')
866				.addClass(buttonContainer.className)
867				.append(button);
868		}
869		else {
870			inserter = button;
871		}
872
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		}
951
952		return {
953			conf: config,
954			node: isSplit ? splitDiv.get(0) : button.get(0),
955			inserter: isSplit ? splitDiv : inserter,
956			buttons: [],
957			inCollection: inCollection,
958			isSplit: isSplit,
959			inSplit: inSplit,
960			collection: null,
961			textNode: textNode
962		};
963	},
964
965	/**
966	 * Get the button object from a node (recursive)
967	 * @param  {node} node Button node
968	 * @param  {array} [buttons] Button array, uses base if not defined
969	 * @return {object} Button object
970	 * @private
971	 */
972	_nodeToButton: function (node, buttons) {
973		if (!buttons) {
974			buttons = this.s.buttons;
975		}
976
977		for (var i = 0, ien = buttons.length; i < ien; i++) {
978			if (buttons[i].node === node) {
979				return buttons[i];
980			}
981
982			if (buttons[i].buttons.length) {
983				var ret = this._nodeToButton(node, buttons[i].buttons);
984
985				if (ret) {
986					return ret;
987				}
988			}
989		}
990	},
991
992	/**
993	 * Get container array for a button from a button node (recursive)
994	 * @param  {node} node Button node
995	 * @param  {array} [buttons] Button array, uses base if not defined
996	 * @return {array} Button's host array
997	 * @private
998	 */
999	_nodeToHost: function (node, buttons) {
1000		if (!buttons) {
1001			buttons = this.s.buttons;
1002		}
1003
1004		for (var i = 0, ien = buttons.length; i < ien; i++) {
1005			if (buttons[i].node === node) {
1006				return buttons;
1007			}
1008
1009			if (buttons[i].buttons.length) {
1010				var ret = this._nodeToHost(node, buttons[i].buttons);
1011
1012				if (ret) {
1013					return ret;
1014				}
1015			}
1016		}
1017	},
1018
1019	/**
1020	 * Handle a key press - determine if any button's key configured matches
1021	 * what was typed and trigger the action if so.
1022	 * @param  {string} character The character pressed
1023	 * @param  {object} e Key event that triggered this call
1024	 * @private
1025	 */
1026	_keypress: function (character, e) {
1027		// Check if this button press already activated on another instance of Buttons
1028		if (e._buttonsHandled) {
1029			return;
1030		}
1031
1032		var run = function (conf, node) {
1033			if (!conf.key) {
1034				return;
1035			}
1036
1037			if (conf.key === character) {
1038				e._buttonsHandled = true;
1039				$(node).click();
1040			}
1041			else if ($.isPlainObject(conf.key)) {
1042				if (conf.key.key !== character) {
1043					return;
1044				}
1045
1046				if (conf.key.shiftKey && !e.shiftKey) {
1047					return;
1048				}
1049
1050				if (conf.key.altKey && !e.altKey) {
1051					return;
1052				}
1053
1054				if (conf.key.ctrlKey && !e.ctrlKey) {
1055					return;
1056				}
1057
1058				if (conf.key.metaKey && !e.metaKey) {
1059					return;
1060				}
1061
1062				// Made it this far - it is good
1063				e._buttonsHandled = true;
1064				$(node).click();
1065			}
1066		};
1067
1068		var recurse = function (a) {
1069			for (var i = 0, ien = a.length; i < ien; i++) {
1070				run(a[i].conf, a[i].node);
1071
1072				if (a[i].buttons.length) {
1073					recurse(a[i].buttons);
1074				}
1075			}
1076		};
1077
1078		recurse(this.s.buttons);
1079	},
1080
1081	/**
1082	 * Remove a key from the key listener for this instance (to be used when a
1083	 * button is removed)
1084	 * @param  {object} conf Button configuration
1085	 * @private
1086	 */
1087	_removeKey: function (conf) {
1088		if (conf.key) {
1089			var character = $.isPlainObject(conf.key) ? conf.key.key : conf.key;
1090
1091			// Remove only one character, as multiple buttons could have the
1092			// same listening key
1093			var a = this.s.listenKeys.split('');
1094			var idx = $.inArray(character, a);
1095			a.splice(idx, 1);
1096			this.s.listenKeys = a.join('');
1097		}
1098	},
1099
1100	/**
1101	 * Resolve a button configuration
1102	 * @param  {string|function|object} conf Button config to resolve
1103	 * @return {object} Button configuration
1104	 * @private
1105	 */
1106	_resolveExtends: function (conf) {
1107		var that = this;
1108		var dt = this.s.dt;
1109		var i, ien;
1110		var toConfObject = function (base) {
1111			var loop = 0;
1112
1113			// Loop until we have resolved to a button configuration, or an
1114			// array of button configurations (which will be iterated
1115			// separately)
1116			while (!$.isPlainObject(base) && !Array.isArray(base)) {
1117				if (base === undefined) {
1118					return;
1119				}
1120
1121				if (typeof base === 'function') {
1122					base = base.call(that, dt, conf);
1123
1124					if (!base) {
1125						return false;
1126					}
1127				}
1128				else if (typeof base === 'string') {
1129					if (!_dtButtons[base]) {
1130						return { html: base };
1131					}
1132
1133					base = _dtButtons[base];
1134				}
1135
1136				loop++;
1137				if (loop > 30) {
1138					// Protect against misconfiguration killing the browser
1139					throw 'Buttons: Too many iterations';
1140				}
1141			}
1142
1143			return Array.isArray(base) ? base : $.extend({}, base);
1144		};
1145
1146		conf = toConfObject(conf);
1147
1148		while (conf && conf.extend) {
1149			// Use `toConfObject` in case the button definition being extended
1150			// is itself a string or a function
1151			if (!_dtButtons[conf.extend]) {
1152				throw 'Cannot extend unknown button type: ' + conf.extend;
1153			}
1154
1155			var objArray = toConfObject(_dtButtons[conf.extend]);
1156			if (Array.isArray(objArray)) {
1157				return objArray;
1158			}
1159			else if (!objArray) {
1160				// This is a little brutal as it might be possible to have a
1161				// valid button without the extend, but if there is no extend
1162				// then the host button would be acting in an undefined state
1163				return false;
1164			}
1165
1166			// Stash the current class name
1167			var originalClassName = objArray.className;
1168
1169			if (conf.config !== undefined && objArray.config !== undefined) {
1170				conf.config = $.extend({}, objArray.config, conf.config);
1171			}
1172
1173			conf = $.extend({}, objArray, conf);
1174
1175			// The extend will have overwritten the original class name if the
1176			// `conf` object also assigned a class, but we want to concatenate
1177			// them so they are list that is combined from all extended buttons
1178			if (originalClassName && conf.className !== originalClassName) {
1179				conf.className = originalClassName + ' ' + conf.className;
1180			}
1181
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		}
1187
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();
1247
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);
1281			}
1282
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;
1402				}
1403
1404				if (left + popoverSizes.width > tableSizes.width) {
1405					left = tableSizes.width - popoverSizes.width;
1406				}
1407			}
1408
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;
1413			}
1414
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			});
1442		}
1443		else {
1444			// Fix position - centre on screen
1445			var position = function () {
1446				var half = $(window).height() / 2;
1447
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();
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					}
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});
1550
1551/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1552 * Statics
1553 */
1554
1555/**
1556 * Show / hide a background layer behind a collection
1557 * @param  {boolean} Flag to indicate if the background should be shown or
1558 *   hidden
1559 * @param  {string} Class to assign to the background
1560 * @static
1561 */
1562Buttons.background = function (show, className, fade, insertPoint) {
1563	if (fade === undefined) {
1564		fade = 400;
1565	}
1566	if (!insertPoint) {
1567		insertPoint = document.body;
1568	}
1569
1570	if (show) {
1571		_fadeIn(
1572			$('<div/>').addClass(className).css('display', 'none').insertAfter(insertPoint),
1573			fade
1574		);
1575	}
1576	else {
1577		_fadeOut($('div.' + className), fade, function () {
1578			$(this).removeClass(className).remove();
1579		});
1580	}
1581};
1582
1583/**
1584 * Instance selector - select Buttons instances based on an instance selector
1585 * value from the buttons assigned to a DataTable. This is only useful if
1586 * multiple instances are attached to a DataTable.
1587 * @param  {string|int|array} Instance selector - see `instance-selector`
1588 *   documentation on the DataTables site
1589 * @param  {array} Button instance array that was attached to the DataTables
1590 *   settings object
1591 * @return {array} Buttons instances
1592 * @static
1593 */
1594Buttons.instanceSelector = function (group, buttons) {
1595	if (group === undefined || group === null) {
1596		return $.map(buttons, function (v) {
1597			return v.inst;
1598		});
1599	}
1600
1601	var ret = [];
1602	var names = $.map(buttons, function (v) {
1603		return v.name;
1604	});
1605
1606	// Flatten the group selector into an array of single options
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]);
1611			}
1612			return;
1613		}
1614
1615		if (typeof input === 'string') {
1616			if (input.indexOf(',') !== -1) {
1617				// String selector, list of names
1618				process(input.split(','));
1619			}
1620			else {
1621				// String selector individual name
1622				var idx = $.inArray(input.trim(), names);
1623
1624				if (idx !== -1) {
1625					ret.push(buttons[idx].inst);
1626				}
1627			}
1628		}
1629		else if (typeof input === 'number') {
1630			// Index selector
1631			ret.push(buttons[input].inst);
1632		}
1633		else if (typeof input === 'object') {
1634			// Actual instance selector
1635			ret.push(input);
1636		}
1637	};
1638
1639	process(group);
1640
1641	return ret;
1642};
1643
1644/**
1645 * Button selector - select one or more buttons from a selector input so some
1646 * operation can be performed on them.
1647 * @param  {array} Button instances array that the selector should operate on
1648 * @param  {string|int|node|jQuery|array} Button selector - see
1649 *   `button-selector` documentation on the DataTables site
1650 * @return {array} Array of objects containing `inst` and `idx` properties of
1651 *   the selected buttons so you know which instance each button belongs to.
1652 * @static
1653 */
1654Buttons.buttonSelector = function (insts, selector) {
1655	var ret = [];
1656	var nodeBuilder = function (a, buttons, baseIdx) {
1657		var button;
1658		var idx;
1659
1660		for (var i = 0, ien = buttons.length; i < ien; i++) {
1661			button = buttons[i];
1662
1663			if (button) {
1664				idx = baseIdx !== undefined ? baseIdx + i : i + '';
1665
1666				a.push({
1667					node: button.node,
1668					name: button.conf.name,
1669					idx: idx
1670				});
1671
1672				if (button.buttons) {
1673					nodeBuilder(a, button.buttons, idx + '-');
1674				}
1675			}
1676		}
1677	};
1678
1679	var run = function (selector, inst) {
1680		var i, ien;
1681		var buttons = [];
1682		nodeBuilder(buttons, inst.s.buttons);
1683
1684		var nodes = $.map(buttons, function (v) {
1685			return v.node;
1686		});
1687
1688		if (Array.isArray(selector) || selector instanceof $) {
1689			for (i = 0, ien = selector.length; i < ien; i++) {
1690				run(selector[i], inst);
1691			}
1692			return;
1693		}
1694
1695		if (selector === null || selector === undefined || selector === '*') {
1696			// Select all
1697			for (i = 0, ien = buttons.length; i < ien; i++) {
1698				ret.push({
1699					inst: inst,
1700					node: buttons[i].node
1701				});
1702			}
1703		}
1704		else if (typeof selector === 'number') {
1705			// Main button index selector
1706			if (inst.s.buttons[selector]) {
1707				ret.push({
1708					inst: inst,
1709					node: inst.s.buttons[selector].node
1710				});
1711			}
1712		}
1713		else if (typeof selector === 'string') {
1714			if (selector.indexOf(',') !== -1) {
1715				// Split
1716				var a = selector.split(',');
1717
1718				for (i = 0, ien = a.length; i < ien; i++) {
1719					run(a[i].trim(), inst);
1720				}
1721			}
1722			else if (selector.match(/^\d+(\-\d+)*$/)) {
1723				// Sub-button index selector
1724				var indexes = $.map(buttons, function (v) {
1725					return v.idx;
1726				});
1727
1728				ret.push({
1729					inst: inst,
1730					node: buttons[$.inArray(selector, indexes)].node
1731				});
1732			}
1733			else if (selector.indexOf(':name') !== -1) {
1734				// Button name selector
1735				var name = selector.replace(':name', '');
1736
1737				for (i = 0, ien = buttons.length; i < ien; i++) {
1738					if (buttons[i].name === name) {
1739						ret.push({
1740							inst: inst,
1741							node: buttons[i].node
1742						});
1743					}
1744				}
1745			}
1746			else {
1747				// jQuery selector on the nodes
1748				$(nodes)
1749					.filter(selector)
1750					.each(function () {
1751						ret.push({
1752							inst: inst,
1753							node: this
1754						});
1755					});
1756			}
1757		}
1758		else if (typeof selector === 'object' && selector.nodeName) {
1759			// Node selector
1760			var idx = $.inArray(selector, nodes);
1761
1762			if (idx !== -1) {
1763				ret.push({
1764					inst: inst,
1765					node: nodes[idx]
1766				});
1767			}
1768		}
1769	};
1770
1771	for (var i = 0, ien = insts.length; i < ien; i++) {
1772		var inst = insts[i];
1773
1774		run(selector, inst);
1775	}
1776
1777	return ret;
1778};
1779
1780/**
1781 * Default function used for formatting output data.
1782 * @param {*} str Data to strip
1783 */
1784Buttons.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};
1814
1815/**
1816 * Buttons defaults. For full documentation, please refer to the docs/option
1817 * directory or the DataTables site.
1818 * @type {Object}
1819 * @static
1820 */
1821Buttons.defaults = {
1822	buttons: ['copy', 'excel', 'csv', 'pdf', 'print'],
1823	name: 'main',
1824	tabIndex: 0,
1825	dom: {
1826		container: {
1827			tag: 'div',
1828			className: 'dt-buttons'
1829		},
1830		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
1847		},
1848		button: {
1849			tag: 'button',
1850			className: 'dt-button',
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			}
1861		},
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			}
1881		}
1882	}
1883};
1884
1885/**
1886 * Version information
1887 * @type {string}
1888 * @static
1889 */
1890Buttons.version = '2.4.1';
1891
1892$.extend(_dtButtons, {
1893	collection: {
1894		text: function (dt) {
1895			return dt.i18n('buttons.collection', 'Collection');
1896		},
1897		className: 'buttons-collection',
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);
1905			}
1906			else {
1907				this.popover(config._collection, config);
1908			}
1909
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();
1914			}
1915		},
1916		attr: {
1917			'aria-haspopup': 'dialog'
1918		}
1919		// Also the popover options, defined in Buttons.popover
1920	},
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'
1935		}
1936		// Also the popover options, defined in Buttons.popover
1937	},
1938	copy: function (dt, conf) {
1939		if (_dtButtons.copyHtml5) {
1940			return 'copyHtml5';
1941		}
1942	},
1943	csv: function (dt, conf) {
1944		if (_dtButtons.csvHtml5 && _dtButtons.csvHtml5.available(dt, conf)) {
1945			return 'csvHtml5';
1946		}
1947	},
1948	excel: function (dt, conf) {
1949		if (_dtButtons.excelHtml5 && _dtButtons.excelHtml5.available(dt, conf)) {
1950			return 'excelHtml5';
1951		}
1952	},
1953	pdf: function (dt, conf) {
1954		if (_dtButtons.pdfHtml5 && _dtButtons.pdfHtml5.available(dt, conf)) {
1955			return 'pdfHtml5';
1956		}
1957	},
1958	pageLength: function (dt) {
1959		var lengthMenu = dt.settings()[0].aLengthMenu;
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			);
1971		};
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
1994		return {
1995			extend: 'collection',
1996			text: text,
1997			className: 'buttons-page-length',
1998			autoClose: true,
1999			buttons: $.map(vals, function (val, i) {
2000				return {
2001					text: lang[i],
2002					className: 'button-page-length',
2003					action: function (e, dt) {
2004						dt.page.len(val).draw();
2005					},
2006					init: function (dt, node, conf) {
2007						var that = this;
2008						var fn = function () {
2009							that.active(dt.page.len() === val);
2010						};
2011
2012						dt.on('length.dt' + conf.namespace, fn);
2013						fn();
2014					},
2015					destroy: function (dt, node, conf) {
2016						dt.off('length.dt' + conf.namespace);
2017					}
2018				};
2019			}),
2020			init: function (dt, node, conf) {
2021				var that = this;
2022				dt.on('length.dt' + conf.namespace, function () {
2023					that.text(conf.text);
2024				});
2025			},
2026			destroy: function (dt, node, conf) {
2027				dt.off('length.dt' + conf.namespace);
2028			}
2029		};
2030	},
2031	spacer: {
2032		style: 'empty',
2033		spacer: true,
2034		text: function (dt) {
2035			return dt.i18n('buttons.spacer', '');
2036		}
2037	}
2038});
2039
2040/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2041 * DataTables API
2042 *
2043 * For complete documentation, please refer to the docs/api directory or the
2044 * DataTables site
2045 */
2046
2047// Buttons group and individual button selector
2048DataTable.Api.register('buttons()', function (group, selector) {
2049	// Argument shifting
2050	if (selector === undefined) {
2051		selector = group;
2052		group = undefined;
2053	}
2054
2055	this.selector.buttonGroup = group;
2056
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	);
2070
2071	res._groupSelector = group;
2072	return res;
2073});
2074
2075// Individual button selector
2076DataTable.Api.register('button()', function (group, selector) {
2077	// just run buttons() and truncate
2078	var buttons = this.buttons(group, selector);
2079
2080	if (buttons.length > 1) {
2081		buttons.splice(1, buttons.length);
2082	}
2083
2084	return buttons;
2085});
2086
2087// Active buttons
2088DataTable.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		});
2093	}
2094
2095	return this.each(function (set) {
2096		set.inst.active(set.node, flag);
2097	});
2098});
2099
2100// Get / set button action
2101DataTable.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		});
2106	}
2107
2108	return this.each(function (set) {
2109		set.inst.action(set.node, action);
2110	});
2111});
2112
2113// Collection control
2114DataTable.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);
2128
2129// Enable / disable buttons
2130DataTable.Api.register(['buttons().enable()', 'button().enable()'], function (flag) {
2131	return this.each(function (set) {
2132		set.inst.enable(set.node, flag);
2133	});
2134});
2135
2136// Disable buttons
2137DataTable.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
2144DataTable.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});
2157
2158// Get button nodes
2159DataTable.Api.registerPlural('buttons().nodes()', 'button().node()', function () {
2160	var jq = $();
2161
2162	// jQuery will automatically reduce duplicates to a single entry
2163	$(
2164		this.each(function (set) {
2165			jq = jq.add(set.inst.node(set.node));
2166		})
2167	);
2168
2169	return jq;
2170});
2171
2172// Get / set button processing state
2173DataTable.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		});
2178	}
2179
2180	return this.each(function (set) {
2181		set.inst.processing(set.node, flag);
2182	});
2183});
2184
2185// Get / set button text (i.e. the button labels)
2186DataTable.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		});
2191	}
2192
2193	return this.each(function (set) {
2194		set.inst.text(set.node, label);
2195	});
2196});
2197
2198// Trigger a button's action
2199DataTable.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
2206DataTable.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});
2211
2212// Get the container elements
2213DataTable.Api.register('buttons().containers()', function () {
2214	var jq = $();
2215	var groupSelector = this._groupSelector;
2216
2217	// We need to use the group selector directly, since if there are no buttons
2218	// the result set will be empty
2219	this.iterator(true, 'table', function (ctx) {
2220		if (ctx._buttons) {
2221			var insts = Buttons.instanceSelector(groupSelector, ctx._buttons);
2222
2223			for (var i = 0, ien = insts.length; i < ien; i++) {
2224				jq = jq.add(insts[i].container());
2225			}
2226		}
2227	});
2228
2229	return jq;
2230});
2231
2232DataTable.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});
2236
2237// Add a new button
2238DataTable.Api.register('button().add()', function (idx, conf, draw) {
2239	var ctx = this.context;
2240
2241	// Don't use `this` as it could be empty - select the instances directly
2242	if (ctx.length) {
2243		var inst = Buttons.instanceSelector(this._groupSelector, ctx[0]._buttons);
2244
2245		if (inst.length) {
2246			inst[0].add(conf, idx, draw);
2247		}
2248	}
2249
2250	return this.button(this._groupSelector, idx);
2251});
2252
2253// Destroy the button sets selected
2254DataTable.Api.register('buttons().destroy()', function () {
2255	this.pluck('inst')
2256		.unique()
2257		.each(function (inst) {
2258			inst.destroy();
2259		});
2260
2261	return this;
2262});
2263
2264// Remove a button
2265DataTable.Api.registerPlural('buttons().remove()', 'buttons().remove()', function () {
2266	this.each(function (set) {
2267		set.inst.remove(set.node);
2268	});
2269
2270	return this;
2271});
2272
2273// Information box that can be used by buttons
2274var _infoTimer;
2275DataTable.Api.register('buttons.info()', function (title, message, time) {
2276	var that = this;
2277
2278	if (title === false) {
2279		this.off('destroy.btn-info');
2280		_fadeOut($('#datatables_buttons_info'), 400, function () {
2281			$(this).remove();
2282		});
2283		clearTimeout(_infoTimer);
2284		_infoTimer = null;
2285
2286		return this;
2287	}
2288
2289	if (_infoTimer) {
2290		clearTimeout(_infoTimer);
2291	}
2292
2293	if ($('#datatables_buttons_info').length) {
2294		$('#datatables_buttons_info').remove();
2295	}
2296
2297	title = title ? '<h2>' + title + '</h2>' : '';
2298
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	);
2306
2307	if (time !== undefined && time !== 0) {
2308		_infoTimer = setTimeout(function () {
2309			that.buttons.info(false);
2310		}, time);
2311	}
2312
2313	this.on('destroy.btn-info', function () {
2314		that.buttons.info(false);
2315	});
2316
2317	return this;
2318});
2319
2320// Get data from the table for export - this is common to a number of plug-in
2321// buttons so it is included in the Buttons core library
2322DataTable.Api.register('buttons.exportData()', function (options) {
2323	if (this.context.length) {
2324		return _exportData(new DataTable.Api(this.context[0]), options);
2325	}
2326});
2327
2328// Get information about the export that is common to many of the export data
2329// types (DRY)
2330DataTable.Api.register('buttons.exportInfo()', function (conf) {
2331	if (!conf) {
2332		conf = {};
2333	}
2334
2335	return {
2336		filename: _filename(conf),
2337		title: _title(conf),
2338		messageTop: _message(this, conf.message || conf.messageTop, 'top'),
2339		messageBottom: _message(this, conf.messageBottom, 'bottom')
2340	};
2341});
2342
2343/**
2344 * Get the file name for an exported file.
2345 *
2346 * @param {object}	config Button configuration
2347 * @param {boolean} incExtension Include the file name extension
2348 */
2349var _filename = function (config) {
2350	// Backwards compatibility
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') {
2361		filename = filename();
2362	}
2363
2364	if (filename === undefined || filename === null) {
2365		return null;
2366	}
2367
2368	if (filename.indexOf('*') !== -1) {
2369		filename = filename.replace('*', $('head > title').text()).trim();
2370	}
2371
2372	// Strip characters which the OS will object to
2373	filename = filename.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, '');
2374
2375	var extension = _stringOrFunction(config.extension);
2376	if (!extension) {
2377		extension = '';
2378	}
2379
2380	return filename + extension;
2381};
2382
2383/**
2384 * Simply utility method to allow parameters to be given as a function
2385 *
2386 * @param {undefined|string|function} option Option
2387 * @return {null|string} Resolved value
2388 */
2389var _stringOrFunction = function (option) {
2390	if (option === null || option === undefined) {
2391		return null;
2392	}
2393	else if (typeof option === 'function') {
2394		return option();
2395	}
2396	return option;
2397};
2398
2399/**
2400 * Get the title for an exported file.
2401 *
2402 * @param {object} config	Button configuration
2403 */
2404var _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;
2412};
2413
2414var _message = function (dt, option, position) {
2415	var message = _stringOrFunction(option);
2416	if (message === null) {
2417		return null;
2418	}
2419
2420	var caption = $('caption', dt.table().container()).eq(0);
2421	if (message === '*') {
2422		var side = caption.css('caption-side');
2423		if (side !== position) {
2424			return null;
2425		}
2426
2427		return caption.length ? caption.text() : '';
2428	}
2429
2430	return message;
2431};
2432
2433var _exportTextarea = $('<textarea/>')[0];
2434var _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'
2444			},
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				}
2460			},
2461			customizeData: null
2462		},
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();
2474
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;
2485
2486	// If Select is available on this table, and any rows are selected, limit the export
2487	// to the selected rows. If no rows are selected, all rows will be exported. Specify
2488	// a `selected` modifier to control directly.
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 });
2493		}
2494	}
2495
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();
2500
2501	var columns = header.length;
2502	var rows = columns > 0 ? cells.length / columns : 0;
2503	var body = [];
2504	var cellCounter = 0;
2505
2506	for (var i = 0, ien = rows; i < ien; i++) {
2507		var row = [columns];
2508
2509		for (var j = 0; j < columns; j++) {
2510			row[j] = config.format.body(cells[cellCounter], i, j, cellNodes[cellCounter]);
2511			cellCounter++;
2512		}
2513
2514		body[i] = row;
2515	}
2516
2517	var data = {
2518		header: header,
2519		footer: footer,
2520		body: body
2521	};
2522
2523	if (config.customizeData) {
2524		config.customizeData(data);
2525	}
2526
2527	return data;
2528};
2529
2530/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2531 * DataTables interface
2532 */
2533
2534// Attach to DataTables objects for global access
2535$.fn.dataTable.Buttons = Buttons;
2536$.fn.DataTable.Buttons = Buttons;
2537
2538// DataTables creation - check if the buttons have been defined for this table,
2539// they will have been if the `B` option was used in `dom`, otherwise we should
2540// create the buttons instance here so they can be inserted into the document
2541// using the API. Listen for `init` for compatibility with pre 1.10.10, but to
2542// be removed in future.
2543$(document).on('init.dt plugin-init.dt', function (e, settings) {
2544	if (e.namespace !== 'dt') {
2545		return;
2546	}
2547
2548	var opts = settings.oInit.buttons || DataTable.defaults.buttons;
2549
2550	if (opts && !settings._buttons) {
2551		new Buttons(settings, opts).container();
2552	}
2553});
2554
2555function _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}
2561
2562// DataTables `dom` feature option
2563DataTable.ext.feature.push({
2564	fnInit: _init,
2565	cFeature: 'B'
2566});
2567
2568// DataTables 2 layout feature
2569if (DataTable.ext.features) {
2570	DataTable.ext.features.register('buttons', _init);
2571}
2572
2573
2574return DataTable;
2575}));
2576