PageSourceSearch

https://www.ciencia.gob.es/.resources/site-web/webresources/js/dat…es.buttons~2026-09-16-05-38-56-950~cache.js

js ciencia.gob.es collected 2026-09-24 08:47:13 UTC 67,566 bytes, 2,820 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.