PageSourceSearch

https://theartssociety.org/sites/all/modules/custom/civi_societyad…/js/dataTables.buttons.js?tlvm4t

js theartssociety.org collected 2026-09-24 20:56:42 UTC 39,573 bytes, 1,638 lines download raw bytes

1/*! Buttons for DataTables 1.1.2
2 * ©2015 SpryMedia Ltd - datatables.net/license
3 */
4
5(function( factory ){
6	if ( typeof define === 'function' && define.amd ) {
7		// AMD
8		define( ['jquery', 'datatables.net'], function ( $ ) {
9			return factory( $, window, document );
10		} );
11	}
12	else if ( typeof exports === 'object' ) {
13		// CommonJS
14		module.exports = function (root, $) {
15			if ( ! root ) {
16				root = window;
17			}
18
19			if ( ! $ || ! $.fn.dataTable ) {
20				$ = require('datatables.net')(root, $).$;
21			}
22
23			return factory( $, root, root.document );
24		};
25	}
26	else {
27		// Browser
28		factory( jQuery, window, document );
29	}
30}(function( $, window, document, undefined ) {
31'use strict';
32var DataTable = $.fn.dataTable;
33
34
35// Used for namespacing events added to the document by each instance, so they
36// can be removed on destroy
37var _instCounter = 0;
38
39// Button namespacing counter for namespacing events on individual buttons
40var _buttonCounter = 0;
41
42var _dtButtons = DataTable.ext.buttons;
43
44/**
45 * [Buttons description]
46 * @param {[type]}
47 * @param {[type]}
48 */
49var Buttons = function( dt, config )
50{
51	// Allow a boolean true for defaults
52	if ( config === true ) {
53		config = {};
54	}
55
56	// For easy configuration of buttons an array can be given
57	if ( $.isArray( config ) ) {
58		config = { buttons: config };
59	}
60
61	this.c = $.extend( true, {}, Buttons.defaults, config );
62
63	// Don't want a deep copy for the buttons
64	if ( config.buttons ) {
65		this.c.buttons = config.buttons;
66	}
67
68	this.s = {
69		dt: new DataTable.Api( dt ),
70		buttons: [],
71		subButtons: [],
72		listenKeys: '',
73		namespace: 'dtb'+(_instCounter++)
74	};
75
76	this.dom = {
77		container: $('<'+this.c.dom.container.tag+'/>')
78			.addClass( this.c.dom.container.className )
79	};
80
81	this._constructor();
82};
83
84
85$.extend( Buttons.prototype, {
86	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
87	 * Public methods
88	 */
89
90	/**
91	 * Get the action of a button
92	 * @param  {int|string} Button index
93	 * @return {function}
94	 *//**
95	 * Set the action of a button
96	 * @param  {int|string} Button index
97	 * @param  {function} Function to set
98	 * @return {Buttons} Self for chaining
99	 */
100	action: function ( idx, action )
101	{
102		var button = this._indexToButton( idx ).conf;
103		var dt = this.s.dt;
104
105		if ( action === undefined ) {
106			return button.action;
107		}
108
109		button.action = action;
110
111		return this;
112	},
113
114	/**
115	 * Add an active class to the button to make to look active or get current
116	 * active state.
117	 * @param  {int|string} Button index
118	 * @param  {boolean} [flag] Enable / disable flag
119	 * @return {Buttons} Self for chaining or boolean for getter
120	 */
121	active: function ( idx, flag ) {
122		var button = this._indexToButton( idx );
123		var klass = this.c.dom.button.active;
124
125		if ( flag === undefined ) {
126			return button.node.hasClass( klass );
127		}
128
129		button.node.toggleClass( klass, flag === undefined ? true : flag );
130
131		return this;
132	},
133
134	/**
135	 * Add a new button
136	 * @param {int|string} Button index for where to insert the button
137	 * @param {object} Button configuration object, base string name or function
138	 * @return {Buttons} Self for chaining
139	 */
140	add: function ( idx, config )
141	{
142		if ( typeof idx === 'string' && idx.indexOf('-') !== -1 ) {
143			var idxs = idx.split('-');
144			this.c.buttons[idxs[0]*1].buttons.splice( idxs[1]*1, 0, config );
145		}
146		else {
147			this.c.buttons.splice( idx*1, 0, config );
148		}
149
150		this.dom.container.empty();
151		this._buildButtons( this.c.buttons );
152
153		return this;
154	},
155
156	/**
157	 * Get the container node for the buttons
158	 * @return {jQuery} Buttons node
159	 */
160	container: function ()
161	{
162		return this.dom.container;
163	},
164
165	/**
166	 * Disable a button
167	 * @param  {int|string} Button index
168	 * @return {Buttons} Self for chaining
169	 */
170	disable: function ( idx ) {
171		var button = this._indexToButton( idx );
172		button.node.addClass( this.c.dom.button.disabled );
173
174		return this;
175	},
176
177	/**
178	 * Destroy the instance, cleaning up event handlers and removing DOM
179	 * elements
180	 * @return {Buttons} Self for chaining
181	 */
182	destroy: function ()
183	{
184		// Key event listener
185		$('body').off( 'keyup.'+this.s.namespace );
186
187		// Individual button destroy (so they can remove their own events if
188		// needed
189		var buttons = this.s.buttons;
190		var subButtons = this.s.subButtons;
191		var i, ien, j, jen;
192		
193		for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
194			this.removePrep( i );
195
196			for ( j=0, jen=subButtons[i].length ; j<jen ; j++ ) {
197				this.removePrep( i+'-'+j );
198			}
199		}
200
201		this.removeCommit();
202
203		// Container
204		this.dom.container.remove();
205
206		// Remove from the settings object collection
207		var buttonInsts = this.s.dt.settings()[0];
208
209		for ( i=0, ien=buttonInsts.length ; i<ien ; i++ ) {
210			if ( buttonInsts.inst === this ) {
211				buttonInsts.splice( i, 1 );
212				break;
213			}
214		}
215
216		return this;
217	},
218
219	/**
220	 * Enable / disable a button
221	 * @param  {int|string} Button index
222	 * @param  {boolean} [flag=true] Enable / disable flag
223	 * @return {Buttons} Self for chaining
224	 */
225	enable: function ( idx, flag )
226	{
227		if ( flag === false ) {
228			return this.disable( idx );
229		}
230
231		var button = this._indexToButton( idx );
232		button.node.removeClass( this.c.dom.button.disabled );
233
234		return this;
235	},
236
237	/**
238	 * Get the instance name for the button set selector
239	 * @return {string} Instance name
240	 */
241	name: function ()
242	{
243		return this.c.name;
244	},
245
246	/**
247	 * Get a button's node
248	 * @param  {int|string} Button index
249	 * @return {jQuery} Button element
250	 */
251	node: function ( idx )
252	{
253		var button = this._indexToButton( idx );
254		return button.node;
255	},
256
257	/**
258	 * Tidy up any buttons that have been scheduled for removal. This is
259	 * required so multiple buttons can be removed without upsetting the button
260	 * indexes while removing them.
261	 * @param  {int|string} Button index
262	 * @return {Buttons} Self for chaining
263	 */
264	removeCommit: function ()
265	{
266		var buttons = this.s.buttons;
267		var subButtons = this.s.subButtons;
268		var i, ien, j;
269
270		for ( i=buttons.length-1 ; i>=0 ; i-- ) {
271			if ( buttons[i] === null ) {
272				buttons.splice( i, 1 );
273				subButtons.splice( i, 1 );
274				this.c.buttons.splice( i, 1 );
275			}
276		}
277
278		for ( i=0, ien=subButtons.length ; i<ien ; i++ ) {
279			for ( j=subButtons[i].length-1 ; j>=0 ; j-- ) {
280				if ( subButtons[i][j] === null ) {
281					subButtons[i].splice( j, 1 );
282					this.c.buttons[i].buttons.splice( j, 1 );
283				}
284			}
285		}
286
287		return this;
288	},
289
290	/**
291	 * Scheduled a button for removal. This is required so multiple buttons can
292	 * be removed without upsetting the button indexes while removing them.
293	 * @return {Buttons} Self for chaining
294	 */
295	removePrep: function ( idx )
296	{
297		var button;
298		var dt = this.s.dt;
299
300		if ( typeof idx === 'number' || idx.indexOf('-') === -1 ) {
301			// Top level button
302			button = this.s.buttons[ idx*1 ];
303
304			if ( button.conf.destroy ) {
305				button.conf.destroy.call( dt.button(idx), dt, button, button.conf );
306			}
307
308			button.node.remove();
309			this._removeKey( button.conf );
310			this.s.buttons[ idx*1 ] = null;
311		}
312		else {
313			// Collection button
314			var idxs = idx.split('-');
315			button = this.s.subButtons[ idxs[0]*1 ][ idxs[1]*1 ];
316
317			if ( button.conf.destroy ) {
318				button.conf.destroy.call( dt.button(idx), dt, button, button.conf );
319			}
320
321			button.node.remove();
322			this._removeKey( button.conf );
323			this.s.subButtons[ idxs[0]*1 ][ idxs[1]*1 ] = null;
324		}
325
326		return this;
327	},
328
329	/**
330	 * Get the text for a button
331	 * @param  {int|string} Button index
332	 * @return {string} Button text
333	 *//**
334	 * Set the text for a button
335	 * @param  {int|string|function} Button index
336	 * @param  {string} Text
337	 * @return {Buttons} Self for chaining
338	 */
339	text: function ( idx, label )
340	{
341		var button = this._indexToButton( idx );
342		var buttonLiner = this.c.dom.collection.buttonLiner;
343		var linerTag = typeof idx === 'string' && idx.indexOf( '-' ) !== -1 && buttonLiner && buttonLiner.tag ?
344			buttonLiner.tag :
345			this.c.dom.buttonLiner.tag;
346		var dt = this.s.dt;
347		var text = function ( opt ) {
348			return typeof opt === 'function' ?
349				opt( dt, button.node, button.conf ) :
350				opt;
351		};
352
353		if ( label === undefined ) {
354			return text( button.conf.text );
355		}
356
357		button.conf.text = label;
358
359		if ( linerTag ) {
360			button.node.children( linerTag ).html( text(label) );
361		}
362		else {
363			button.node.html( text(label) );
364		}
365
366		return this;
367	},
368
369	/**
370	 * Calculate button index from a node
371	 * @param  {node} Button node (_not_ a jQuery object)
372	 * @return {string} Index. Undefined if not found
373	 */
374	toIndex: function ( node )
375	{
376		var i, ien, j, jen;
377		var buttons = this.s.buttons;
378		var subButtons = this.s.subButtons;
379
380		// Loop the main buttons first
381		for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
382			if ( buttons[i].node[0] === node ) {
383				return i+'';
384			}
385		}
386
387		// Then the sub-buttons
388		for ( i=0, ien=subButtons.length ; i<ien ; i++ ) {
389			for ( j=0, jen=subButtons[i].length ; j<jen ; j++ ) {
390				if ( subButtons[i][j].node[0] === node ) {
391					return i+'-'+j;
392				}
393			}
394		}
395	},
396
397
398	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
399	 * Constructor
400	 */
401
402	/**
403	 * Buttons constructor
404	 * @private
405	 */
406	_constructor: function ()
407	{
408		var that = this;
409		var dt = this.s.dt;
410		var dtSettings = dt.settings()[0];
411
412		if ( ! dtSettings._buttons ) {
413			dtSettings._buttons = [];
414		}
415
416		dtSettings._buttons.push( {
417			inst: this,
418			name: this.c.name
419		} );
420
421		this._buildButtons( this.c.buttons );
422
423		dt.on( 'destroy', function () {
424			that.destroy();
425		} );
426
427		// Global key event binding to listen for button keys
428		$('body').on( 'keyup.'+this.s.namespace, function ( e ) {
429			if ( ! document.activeElement || document.activeElement === document.body ) {
430				// SUse a string of characters for fast lookup of if we need to
431				// handle this
432				var character = String.fromCharCode(e.keyCode).toLowerCase();
433
434				if ( that.s.listenKeys.toLowerCase().indexOf( character ) !== -1 ) {
435					that._keypress( character, e );
436				}
437			}
438		} );
439	},
440
441
442	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
443	 * Private methods
444	 */
445
446	/**
447	 * Add a new button to the key press listener
448	 * @param {object} Resolved button configuration object
449	 * @private
450	 */
451	_addKey: function ( conf )
452	{
453		if ( conf.key ) {
454			this.s.listenKeys += $.isPlainObject( conf.key ) ?
455				conf.key.key :
456				conf.key;
457		}
458	},
459
460	/**
461	 * Create buttons from an array of buttons
462	 * @param  {array} Buttons to create
463	 * @param  {jQuery} Container node into which the created button should be
464	 *   inserted.
465	 * @param  {int} Counter for sub-buttons to be stored in a collection
466	 * @private
467	 */
468	_buildButtons: function ( buttons, container, collectionCounter )
469	{
470		var dt = this.s.dt;
471		var buttonCounter = 0;
472
473		if ( ! container ) {
474			container = this.dom.container;
475			this.s.buttons = [];
476			this.s.subButtons = [];
477		}
478
479		for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
480			var conf = this._resolveExtends( buttons[i] );
481
482			if ( ! conf ) {
483				continue;
484			}
485
486			// If the configuration is an array, then expand the buttons at this
487			// point
488			if ( $.isArray( conf ) ) {
489				this._buildButtons( conf, container, collectionCounter );
490				continue;
491			}
492
493			var button = this._buildButton(
494				conf,
495				collectionCounter!==undefined ? true : false
496			);
497
498			if ( ! button ) {
499				continue;
500			}
501
502			var buttonNode = button.node;
503			container.append( button.inserter );
504
505			if ( collectionCounter === undefined ) {
506				this.s.buttons.push( {
507					node:     buttonNode,
508					conf:     conf,
509					inserter: button.inserter
510				} );
511				this.s.subButtons.push( [] );
512			}
513			else {
514				this.s.subButtons[ collectionCounter ].push( {
515					node:     buttonNode,
516					conf:     conf,
517					inserter: button.inserter
518				} );
519			}
520
521			if ( conf.buttons ) {
522				var collectionDom = this.c.dom.collection;
523				conf._collection = $('<'+collectionDom.tag+'/>')
524					.addClass( collectionDom.className );
525
526				this._buildButtons( conf.buttons, conf._collection, buttonCounter );
527			}
528
529			// init call is made here, rather than buildButton as it needs to
530			// have been added to the buttons / subButtons array first
531			if ( conf.init ) {
532				conf.init.call( dt.button( buttonNode ), dt, buttonNode, conf );
533			}
534
535			buttonCounter++;
536		}
537	},
538
539	/**
540	 * Create an individual button
541	 * @param  {object} config            Resolved button configuration
542	 * @param  {boolean} collectionButton `true` if a collection button
543	 * @return {jQuery} Created button node (jQuery)
544	 * @private
545	 */
546	_buildButton: function ( config, collectionButton )
547	{
548		var that = this;
549		var buttonDom = this.c.dom.button;
550		var linerDom = this.c.dom.buttonLiner;
551		var collectionDom = this.c.dom.collection;
552		var dt = this.s.dt;
553		var text = function ( opt ) {
554			return typeof opt === 'function' ?
555				opt( dt, button, config ) :
556				opt;
557		};
558
559		if ( collectionButton && collectionDom.button ) {
560			buttonDom = collectionDom.button;
561		}
562
563		if ( collectionButton && collectionDom.buttonLiner ) {
564			linerDom = collectionDom.buttonLiner;
565		}
566
567		// Make sure that the button is available based on whatever requirements
568		// it has. For example, Flash buttons require Flash
569		if ( config.available && ! config.available( dt, config ) ) {
570			return false;
571		}
572
573		var action = function ( e, dt, button, config ) {
574			config.action.call( dt.button( button ), e, dt, button, config );
575
576			$(dt.table().node()).triggerHandler( 'buttons-action.dt', [
577				dt.button( button ), dt, button, config 
578			] );
579		};
580
581		var button = $('<'+buttonDom.tag+'/>')
582			.addClass( buttonDom.className )
583			.attr( 'tabindex', this.s.dt.settings()[0].iTabIndex )
584			.attr( 'aria-controls', this.s.dt.table().node().id )
585			.on( 'click.dtb', function (e) {
586				e.preventDefault();
587
588				if ( ! button.hasClass( buttonDom.disabled ) && config.action ) {
589					action( e, dt, button, config );
590				}
591
592				button.blur();
593			} )
594			.on( 'keyup.dtb', function (e) {
595				if ( e.keyCode === 13 ) {
596					if ( ! button.hasClass( buttonDom.disabled ) && config.action ) {
597						action( e, dt, button, config );
598					}
599				}
600			} );
601
602		if ( linerDom.tag ) {
603			button.append(
604				$('<'+linerDom.tag+'/>')
605					.html( text( config.text ) )
606					.addClass( linerDom.className )
607			);
608		}
609		else {
610			button.html( text( config.text ) );
611		}
612
613		if ( config.enabled === false ) {
614			button.addClass( buttonDom.disabled );
615		}
616
617		if ( config.className ) {
618			button.addClass( config.className );
619		}
620
621		if ( config.titleAttr ) {
622			button.attr( 'title', config.titleAttr );
623		}
624
625		if ( ! config.namespace ) {
626			config.namespace = '.dt-button-'+(_buttonCounter++);
627		}
628
629		var buttonContainer = this.c.dom.buttonContainer;
630		var inserter;
631		if ( buttonContainer && buttonContainer.tag ) {
632			inserter = $('<'+buttonContainer.tag+'/>')
633				.addClass( buttonContainer.className )
634				.append( button );
635		}
636		else {
637			inserter = button;
638		}
639
640		this._addKey( config );
641
642		return {
643			node: button,
644			inserter: inserter
645		};
646	},
647
648	/**
649	 * Get a button's host information from a button index
650	 * @param  {int|string} Button index
651	 * @return {object} Button information - object contains `node` and `conf`
652	 *   properties
653	 * @private
654	 */
655	_indexToButton: function ( idx )
656	{
657		if ( typeof idx === 'number' || idx.indexOf('-') === -1 ) {
658			return this.s.buttons[ idx*1 ];
659		}
660
661		var idxs = idx.split('-');
662		return this.s.subButtons[ idxs[0]*1 ][ idxs[1]*1 ];
663	},
664
665	/**
666	 * Handle a key press - determine if any button's key configured matches
667	 * what was typed and trigger the action if so.
668	 * @param  {string} The character pressed
669	 * @param  {object} Key event that triggered this call
670	 * @private
671	 */
672	_keypress: function ( character, e )
673	{
674		var i, ien, j, jen;
675		var buttons = this.s.buttons;
676		var subButtons = this.s.subButtons;
677		var run = function ( conf, node ) {
678			if ( ! conf.key ) {
679				return;
680			}
681
682			if ( conf.key === character ) {
683				node.click();
684			}
685			else if ( $.isPlainObject( conf.key ) ) {
686				if ( conf.key.key !== character ) {
687					return;
688				}
689
690				if ( conf.key.shiftKey && ! e.shiftKey ) {
691					return;
692				}
693
694				if ( conf.key.altKey && ! e.altKey ) {
695					return;
696				}
697
698				if ( conf.key.ctrlKey && ! e.ctrlKey ) {
699					return;
700				}
701
702				if ( conf.key.metaKey && ! e.metaKey ) {
703					return;
704				}
705
706				// Made it this far - it is good
707				node.click();
708			}
709		};
710
711		// Loop the main buttons first
712		for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
713			run( buttons[i].conf, buttons[i].node );
714		}
715
716		// Then the sub-buttons
717		for ( i=0, ien=subButtons.length ; i<ien ; i++ ) {
718			for ( j=0, jen=subButtons[i].length ; j<jen ; j++ ) {
719				run( subButtons[i][j].conf, subButtons[i][j].node );
720			}
721		}
722	},
723
724	/**
725	 * Remove a key from the key listener for this instance (to be used when a
726	 * button is removed)
727	 * @param  {object} Button configuration
728	 */
729	_removeKey: function ( conf )
730	{
731		if ( conf.key ) {
732			var character = $.isPlainObject( conf.key ) ?
733				conf.key.key :
734				conf.key;
735
736			// Remove only one character, as multiple buttons could have the
737			// same listening key
738			var a = this.s.listenKeys.split('');
739			var idx = $.inArray( character, a );
740			a.splice( idx, 1 );
741			this.s.listenKeys = a.join('');
742		}
743	},
744
745	/**
746	 * Resolve a button configuration
747	 * @param  {string|function|object} Button config to resolve
748	 * @return {object} Button configuration
749	 */
750	_resolveExtends: function ( conf )
751	{
752		var dt = this.s.dt;
753		var i, ien;
754		var toConfObject = function ( base ) {
755			var loop = 0;
756
757			// Loop until we have resolved to a button configuration, or an
758			// array of button configurations (which will be iterated
759			// separately)
760			while ( ! $.isPlainObject(base) && ! $.isArray(base) ) {
761				if ( base === undefined ) {
762					return;
763				}
764
765				if ( typeof base === 'function' ) {
766					base = base( dt, conf );
767
768					if ( ! base ) {
769						return false;
770					}
771				}
772				else if ( typeof base === 'string' ) {
773					if ( ! _dtButtons[ base ] ) {
774						throw 'Unknown button type: '+base;
775					}
776
777					base = _dtButtons[ base ];
778				}
779
780				loop++;
781				if ( loop > 30 ) {
782					// Protect against misconfiguration killing the browser
783					throw 'Buttons: Too many iterations';
784				}
785			}
786
787			return $.isArray( base ) ?
788				base :
789				$.extend( {}, base );
790		};
791
792		conf = toConfObject( conf );
793
794		while ( conf && conf.extend ) {
795			// Use `toConfObject` in case the button definition being extended
796			// is itself a string or a function
797			if ( ! _dtButtons[ conf.extend ] ) {
798				throw 'Cannot extend unknown button type: '+conf.extend;
799			}
800
801			var objArray = toConfObject( _dtButtons[ conf.extend ] );
802			if ( $.isArray( objArray ) ) {
803				return objArray;
804			}
805			else if ( ! objArray ) {
806				// This is a little brutal as it might be possible to have a
807				// valid button without the extend, but if there is no extend
808				// then the host button would be acting in an undefined state
809				return false;
810			}
811
812			// Stash the current class name
813			var originalClassName = objArray.className;
814
815			conf = $.extend( {}, objArray, conf );
816
817			// The extend will have overwritten the original class name if the
818			// `conf` object also assigned a class, but we want to concatenate
819			// them so they are list that is combined from all extended buttons
820			if ( originalClassName && conf.className !== originalClassName ) {
821				conf.className = originalClassName+' '+conf.className;
822			}
823
824			// Buttons to be added to a collection  -gives the ability to define
825			// if buttons should be added to the start or end of a collection
826			var postfixButtons = conf.postfixButtons;
827			if ( postfixButtons ) {
828				if ( ! conf.buttons ) {
829					conf.buttons = [];
830				}
831
832				for ( i=0, ien=postfixButtons.length ; i<ien ; i++ ) {
833					conf.buttons.push( postfixButtons[i] );
834				}
835
836				conf.postfixButtons = null;
837			}
838
839			var prefixButtons = conf.prefixButtons;
840			if ( prefixButtons ) {
841				if ( ! conf.buttons ) {
842					conf.buttons = [];
843				}
844
845				for ( i=0, ien=prefixButtons.length ; i<ien ; i++ ) {
846					conf.buttons.splice( i, 0, prefixButtons[i] );
847				}
848
849				conf.prefixButtons = null;
850			}
851
852			// Although we want the `conf` object to overwrite almost all of
853			// the properties of the object being extended, the `extend`
854			// property should come from the object being extended
855			conf.extend = objArray.extend;
856		}
857
858		return conf;
859	}
860} );
861
862
863
864/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
865 * Statics
866 */
867
868/**
869 * Show / hide a background layer behind a collection
870 * @param  {boolean} Flag to indicate if the background should be shown or
871 *   hidden 
872 * @param  {string} Class to assign to the background
873 * @static
874 */
875Buttons.background = function ( show, className, fade ) {
876	if ( fade === undefined ) {
877		fade = 400;
878	}
879
880	if ( show ) {
881		$('<div/>')
882			.addClass( className )
883			.css( 'display', 'none' )
884			.appendTo( 'body' )
885			.fadeIn( fade );
886	}
887	else {
888		$('body > div.'+className)
889			.fadeOut( fade, function () {
890				$(this).remove();
891			} );
892	}
893};
894
895/**
896 * Instance selector - select Buttons instances based on an instance selector
897 * value from the buttons assigned to a DataTable. This is only useful if
898 * multiple instances are attached to a DataTable.
899 * @param  {string|int|array} Instance selector - see `instance-selector`
900 *   documentation on the DataTables site
901 * @param  {array} Button instance array that was attached to the DataTables
902 *   settings object
903 * @return {array} Buttons instances
904 * @static
905 */
906Buttons.instanceSelector = function ( group, buttons )
907{
908	if ( ! group ) {
909		return $.map( buttons, function ( v ) {
910			return v.inst;
911		} );
912	}
913
914	var ret = [];
915	var names = $.map( buttons, function ( v ) {
916		return v.name;
917	} );
918
919	// Flatten the group selector into an array of single options
920	var process = function ( input ) {
921		if ( $.isArray( input ) ) {
922			for ( var i=0, ien=input.length ; i<ien ; i++ ) {
923				process( input[i] );
924			}
925			return;
926		}
927
928		if ( typeof input === 'string' ) {
929			if ( input.indexOf( ',' ) !== -1 ) {
930				// String selector, list of names
931				process( input.split(',') );
932			}
933			else {
934				// String selector individual name
935				var idx = $.inArray( $.trim(input), names );
936
937				if ( idx !== -1 ) {
938					ret.push( buttons[ idx ].inst );
939				}
940			}
941		}
942		else if ( typeof input === 'number' ) {
943			// Index selector
944			ret.push( buttons[ input ].inst );
945		}
946	};
947	
948	process( group );
949
950	return ret;
951};
952
953/**
954 * Button selector - select one or more buttons from a selector input so some
955 * operation can be performed on them.
956 * @param  {array} Button instances array that the selector should operate on
957 * @param  {string|int|node|jQuery|array} Button selector - see
958 *   `button-selector` documentation on the DataTables site
959 * @return {array} Array of objects containing `inst` and `idx` properties of
960 *   the selected buttons so you know which instance each button belongs to.
961 * @static
962 */
963Buttons.buttonSelector = function ( insts, selector )
964{
965	var ret = [];
966	var run = function ( selector, inst ) {
967		var i, ien, j, jen;
968		var buttons = [];
969
970		$.each( inst.s.buttons, function (i, v) {
971			if ( v !== null ) {
972				buttons.push( {
973					node: v.node[0],
974					name: v.conf.name
975				} );
976			}
977		} );
978
979		$.each( inst.s.subButtons, function (i, v) {
980			$.each( v, function (j, w) {
981				if ( w !== null ) {
982					buttons.push( {
983						node: w.node[0],
984						name: w.conf.name
985					} );
986				}
987			} );
988		} );
989
990		var nodes = $.map( buttons, function (v) {
991			return v.node;
992		} );
993
994		if ( $.isArray( selector ) || selector instanceof $ ) {
995			for ( i=0, ien=selector.length ; i<ien ; i++ ) {
996				run( selector[i], inst );
997			}
998			return;
999		}
1000
1001		if ( selector === null || selector === undefined || selector === '*' ) {
1002			// Select all
1003			for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
1004				ret.push( {
1005					inst: inst,
1006					idx: inst.toIndex( buttons[i].node )
1007				} );
1008			}
1009		}
1010		else if ( typeof selector === 'number' ) {
1011			// Main button index selector
1012			ret.push( {
1013				inst: inst,
1014				idx: selector
1015			} );
1016		}
1017		else if ( typeof selector === 'string' ) {
1018			if ( selector.indexOf( ',' ) !== -1 ) {
1019				// Split
1020				var a = selector.split(',');
1021
1022				for ( i=0, ien=a.length ; i<ien ; i++ ) {
1023					run( $.trim(a[i]), inst );
1024				}
1025			}
1026			else if ( selector.match( /^\d+(\-\d+)?$/ ) ) {
1027				// Sub-button index selector
1028				ret.push( {
1029					inst: inst,
1030					idx: selector
1031				} );
1032			}
1033			else if ( selector.indexOf( ':name' ) !== -1 ) {
1034				// Button name selector
1035				var name = selector.replace( ':name', '' );
1036
1037				for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
1038					if ( buttons[i].name === name ) {
1039						ret.push( {
1040							inst: inst,
1041							idx: inst.toIndex( buttons[i].node )
1042						} );
1043					}
1044				}
1045			}
1046			else {
1047				// jQuery selector on the nodes
1048				$( nodes ).filter( selector ).each( function () {
1049					ret.push( {
1050						inst: inst,
1051						idx: inst.toIndex( this )
1052					} );
1053				} );
1054			}
1055		}
1056		else if ( typeof selector === 'object' && selector.nodeName ) {
1057			// Node selector
1058			var idx = $.inArray( selector, nodes );
1059
1060			if ( idx !== -1 ) {
1061				ret.push( {
1062					inst: inst,
1063					idx: inst.toIndex( nodes[ idx ] )
1064				} );
1065			}
1066		}
1067	};
1068
1069
1070	for ( var i=0, ien=insts.length ; i<ien ; i++ ) {
1071		var inst = insts[i];
1072
1073		run( selector, inst );
1074	}
1075
1076	return ret;
1077};
1078
1079
1080/**
1081 * Buttons defaults. For full documentation, please refer to the docs/option
1082 * directory or the DataTables site.
1083 * @type {Object}
1084 * @static
1085 */
1086Buttons.defaults = {
1087	buttons: [ 'copy', 'excel', 'csv', 'pdf', 'print' ],
1088	name: 'main',
1089	tabIndex: 0,
1090	dom: {
1091		container: {
1092			tag: 'div',
1093			className: 'dt-buttons'
1094		},
1095		collection: {
1096			tag: 'div',
1097			className: 'dt-button-collection'
1098		},
1099		button: {
1100			tag: 'a',
1101			className: 'dt-button',
1102			active: 'active',
1103			disabled: 'disabled'
1104		},
1105		buttonLiner: {
1106			tag: 'span',
1107			className: ''
1108		}
1109	}
1110};
1111
1112/**
1113 * Version information
1114 * @type {string}
1115 * @static
1116 */
1117Buttons.version = '1.1.2';
1118
1119
1120$.extend( _dtButtons, {
1121	collection: {
1122		text: function ( dt, button, config ) {
1123			return dt.i18n( 'buttons.collection', 'Collection' );
1124		},
1125		className: 'buttons-collection',
1126		action: function ( e, dt, button, config ) {
1127			var background;
1128			var host = button;
1129			var hostOffset = host.offset();
1130			var tableContainer = $( dt.table().container() );
1131			var multiLevel = false;
1132
1133			// Remove any old collection
1134			if ( $('div.dt-button-background').length ) {
1135				multiLevel = $('div.dt-button-collection').offset();
1136				$(document).trigger( 'click.dtb-collection' );
1137			}
1138
1139			config._collection
1140				.addClass( config.collectionLayout )
1141				.css( 'display', 'none' )
1142				.appendTo( 'body' )
1143				.fadeIn( config.fade );
1144
1145			var position = config._collection.css( 'position' );
1146
1147			if ( multiLevel && position === 'absolute' ) {
1148				config._collection.css( {
1149					top: multiLevel.top + 5, // magic numbers for a little offset
1150					left: multiLevel.left + 5
1151				} );
1152			}
1153			else if ( position === 'absolute' ) {
1154				config._collection.css( {
1155					top: hostOffset.top + host.outerHeight(),
1156					left: hostOffset.left
1157				} );
1158
1159				var listRight = hostOffset.left + config._collection.outerWidth();
1160				var tableRight = tableContainer.offset().left + tableContainer.width();
1161				if ( listRight > tableRight ) {
1162					config._collection.css( 'left', hostOffset.left - ( listRight - tableRight ) );
1163				}
1164			}
1165			else {
1166				// Fix position - centre on screen
1167				var top = config._collection.height() / 2;
1168				if ( top > $(window).height() / 2 ) {
1169					top = $(window).height() / 2;
1170				}
1171
1172				config._collection.css( 'marginTop', top*-1 );
1173			}
1174
1175			if ( config.background ) {
1176				Buttons.background( true, config.backgroundClassName, config.fade );
1177			}
1178
1179			// Need to break the 'thread' for the collection button being
1180			// activated by a click - it would also trigger this event
1181			setTimeout( function () {
1182				// This is bonkers, but if we don't have a click listener on the
1183				// background element, iOS Safari will ignore the body click
1184				// listener below. An empty function here is all that is
1185				// required to make it work...
1186				$('div.dt-button-background').on( 'click.dtb-collection', function () {} );
1187
1188				$('body').on( 'click.dtb-collection', function (e) {
1189					if ( ! $(e.target).parents().andSelf().filter( config._collection ).length ) {
1190						config._collection
1191							.fadeOut( config.fade, function () {
1192								config._collection.detach();
1193							} );
1194
1195						$('div.dt-button-background').off( 'click.dtb-collection' );
1196						Buttons.background( false, config.backgroundClassName, config.fade );
1197
1198						$('body').off( 'click.dtb-collection' );
1199						dt.off( 'buttons-action.b-internal' );
1200					}
1201				} );
1202			}, 10 );
1203
1204			if ( config.autoClose ) {
1205				dt.on( 'buttons-action.b-internal', function () {
1206					$('div.dt-button-background').click();
1207				} );
1208			}
1209		},
1210		background: true,
1211		collectionLayout: '',
1212		backgroundClassName: 'dt-button-background',
1213		autoClose: false,
1214		fade: 400
1215	},
1216	copy: function ( dt, conf ) {
1217		if ( _dtButtons.copyHtml5 ) {
1218			return 'copyHtml5';
1219		}
1220		if ( _dtButtons.copyFlash && _dtButtons.copyFlash.available( dt, conf ) ) {
1221			return 'copyFlash';
1222		}
1223	},
1224	csv: function ( dt, conf ) {
1225		// Common option that will use the HTML5 or Flash export buttons
1226		if ( _dtButtons.csvHtml5 && _dtButtons.csvHtml5.available( dt, conf ) ) {
1227			return 'csvHtml5';
1228		}
1229		if ( _dtButtons.csvFlash && _dtButtons.csvFlash.available( dt, conf ) ) {
1230			return 'csvFlash';
1231		}
1232	},
1233	excel: function ( dt, conf ) {
1234		// Common option that will use the HTML5 or Flash export buttons
1235		if ( _dtButtons.excelHtml5 && _dtButtons.excelHtml5.available( dt, conf ) ) {
1236			return 'excelHtml5';
1237		}
1238		if ( _dtButtons.excelFlash && _dtButtons.excelFlash.available( dt, conf ) ) {
1239			return 'excelFlash';
1240		}
1241	},
1242	pdf: function ( dt, conf ) {
1243		// Common option that will use the HTML5 or Flash export buttons
1244		if ( _dtButtons.pdfHtml5 && _dtButtons.pdfHtml5.available( dt, conf ) ) {
1245			return 'pdfHtml5';
1246		}
1247		if ( _dtButtons.pdfFlash && _dtButtons.pdfFlash.available( dt, conf ) ) {
1248			return 'pdfFlash';
1249		}
1250	},
1251	pageLength: function ( dt, conf ) {
1252		var lengthMenu = dt.settings()[0].aLengthMenu;
1253		var vals = $.isArray( lengthMenu[0] ) ? lengthMenu[0] : lengthMenu;
1254		var lang = $.isArray( lengthMenu[0] ) ? lengthMenu[1] : lengthMenu;
1255		var text = function ( dt ) {
1256			return dt.i18n( 'buttons.pageLength', {
1257				"-1": 'Show all rows',
1258				_:    'Show %d rows'
1259			}, dt.page.len() );
1260		};
1261
1262		return {
1263			extend: 'collection',
1264			text: text,
1265			className: 'buttons-page-length',
1266			autoClose: true,
1267			buttons: $.map( vals, function ( val, i ) {
1268				return {
1269					text: lang[i],
1270					action: function ( e, dt, button, conf ) {
1271						dt.page.len( val ).draw();
1272					},
1273					init: function ( dt, node, conf ) {
1274						var that = this;
1275						var fn = function () {
1276							that.active( dt.page.len() === val );
1277						};
1278
1279						dt.on( 'length.dt'+conf.namespace, fn );
1280						fn();
1281					},
1282					destroy: function ( dt, node, conf ) {
1283						dt.off( 'length.dt'+conf.namespace );
1284					}
1285				};
1286			} ),
1287			init: function ( dt, node, conf ) {
1288				var that = this;
1289				dt.on( 'length.dt'+conf.namespace, function () {
1290					that.text( text( dt ) );
1291				} );
1292			},
1293			destroy: function ( dt, node, conf ) {
1294				dt.off( 'length.dt'+conf.namespace );
1295			}
1296		};
1297	}
1298} );
1299
1300
1301/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1302 * DataTables API
1303 *
1304 * For complete documentation, please refer to the docs/api directory or the
1305 * DataTables site
1306 */
1307
1308// Buttons group and individual button selector
1309DataTable.Api.register( 'buttons()', function ( group, selector ) {
1310	// Argument shifting
1311	if ( selector === undefined ) {
1312		selector = group;
1313		group = undefined;
1314	}
1315
1316	return this.iterator( true, 'table', function ( ctx ) {
1317		if ( ctx._buttons ) {
1318			return Buttons.buttonSelector(
1319				Buttons.instanceSelector( group, ctx._buttons ),
1320				selector
1321			);
1322		}
1323	}, true );
1324} );
1325
1326// Individual button selector
1327DataTable.Api.register( 'button()', function ( group, selector ) {
1328	// just run buttons() and truncate
1329	var buttons = this.buttons( group, selector );
1330
1331	if ( buttons.length > 1 ) {
1332		buttons.splice( 1, buttons.length );
1333	}
1334
1335	return buttons;
1336} );
1337
1338// Active buttons
1339DataTable.Api.registerPlural( 'buttons().active()', 'button().active()', function ( flag ) {
1340	if ( flag === undefined ) {
1341		return this.map( function ( set ) {
1342			 return set.inst.active( set.idx );
1343		} );
1344	}
1345
1346	return this.each( function ( set ) {
1347		set.inst.active( set.idx, flag );
1348	} );
1349} );
1350
1351// Get / set button action
1352DataTable.Api.registerPlural( 'buttons().action()', 'button().action()', function ( action ) {
1353	if ( action === undefined ) {
1354		return this.map( function ( set ) {
1355			 return set.inst.action( set.idx );
1356		} );
1357	}
1358
1359	return this.each( function ( set ) {
1360		set.inst.action( set.idx, action );
1361	} );
1362} );
1363
1364// Enable / disable buttons
1365DataTable.Api.register( ['buttons().enable()', 'button().enable()'], function ( flag ) {
1366	return this.each( function ( set ) {
1367		set.inst.enable( set.idx, flag );
1368	} );
1369} );
1370
1371// Disable buttons
1372DataTable.Api.register( ['buttons().disable()', 'button().disable()'], function () {
1373	return this.each( function ( set ) {
1374		set.inst.disable( set.idx );
1375	} );
1376} );
1377
1378// Get button nodes
1379DataTable.Api.registerPlural( 'buttons().nodes()', 'button().node()', function () {
1380	var jq = $();
1381
1382	// jQuery will automatically reduce duplicates to a single entry
1383	$( this.each( function ( set ) {
1384		jq = jq.add( set.inst.node( set.idx ) );
1385	} ) );
1386
1387	return jq;
1388} );
1389
1390// Get / set button text (i.e. the button labels)
1391DataTable.Api.registerPlural( 'buttons().text()', 'button().text()', function ( label ) {
1392	if ( label === undefined ) {
1393		return this.map( function ( set ) {
1394			 return set.inst.text( set.idx );
1395		} );
1396	}
1397
1398	return this.each( function ( set ) {
1399		set.inst.text( set.idx, label );
1400	} );
1401} );
1402
1403// Trigger a button's action
1404DataTable.Api.registerPlural( 'buttons().trigger()', 'button().trigger()', function () {
1405	return this.each( function ( set ) {
1406		set.inst.node( set.idx ).trigger( 'click' );
1407	} );
1408} );
1409
1410// Get the container elements for the button sets selected
1411DataTable.Api.registerPlural( 'buttons().containers()', 'buttons().container()', function () {
1412	var jq = $();
1413
1414	// jQuery will automatically reduce duplicates to a single entry
1415	$( this.each( function ( set ) {
1416		jq = jq.add( set.inst.container() );
1417	} ) );
1418
1419	return jq;
1420} );
1421
1422// Add a new button
1423DataTable.Api.register( 'button().add()', function ( idx, conf ) {
1424	if ( this.length === 1 ) {
1425		this[0].inst.add( idx, conf );
1426	}
1427
1428	return this.button( idx );
1429} );
1430
1431// Destroy the button sets selected
1432DataTable.Api.register( 'buttons().destroy()', function ( idx ) {
1433	this.pluck( 'inst' ).unique().each( function ( inst ) {
1434		inst.destroy();
1435	} );
1436
1437	return this;
1438} );
1439
1440// Remove a button
1441DataTable.Api.registerPlural( 'buttons().remove()', 'buttons().remove()', function () {
1442	// Need to split into prep and commit so the indexes remain constant during the remove
1443	this.each( function ( set ) {
1444		set.inst.removePrep( set.idx );
1445	} );
1446
1447	this.pluck( 'inst' ).unique().each( function ( inst ) {
1448		inst.removeCommit();
1449	} );
1450
1451	return this;
1452} );
1453
1454// Information box that can be used by buttons
1455var _infoTimer;
1456DataTable.Api.register( 'buttons.info()', function ( title, message, time ) {
1457	var that = this;
1458
1459	if ( title === false ) {
1460		$('#datatables_buttons_info').fadeOut( function () {
1461			$(this).remove();
1462		} );
1463		clearTimeout( _infoTimer );
1464		_infoTimer = null;
1465
1466		return this;
1467	}
1468
1469	if ( _infoTimer ) {
1470		clearTimeout( _infoTimer );
1471	}
1472
1473	if ( $('#datatables_buttons_info').length ) {
1474		$('#datatables_buttons_info').remove();
1475	}
1476
1477	title = title ? '<h2>'+title+'</h2>' : '';
1478
1479	$('<div id="datatables_buttons_info" class="dt-button-info"/>')
1480		.html( title )
1481		.append( $('<div/>')[ typeof message === 'string' ? 'html' : 'append' ]( message ) )
1482		.css( 'display', 'none' )
1483		.appendTo( 'body' )
1484		.fadeIn();
1485
1486	if ( time !== undefined && time !== 0 ) {
1487		_infoTimer = setTimeout( function () {
1488			that.buttons.info( false );
1489		}, time );
1490	}
1491
1492	return this;
1493} );
1494
1495// Get data from the table for export - this is common to a number of plug-in
1496// buttons so it is included in the Buttons core library
1497DataTable.Api.register( 'buttons.exportData()', function ( options ) {
1498	if ( this.context.length ) {
1499		return _exportData( new DataTable.Api( this.context[0] ), options );
1500	}
1501} );
1502
1503
1504var _exportTextarea = $('<textarea/>')[0];
1505var _exportData = function ( dt, inOpts )
1506{
1507	var config = $.extend( true, {}, {
1508		rows:           null,
1509		columns:        '',
1510		modifier:       {
1511			search: 'applied',
1512			order:  'applied'
1513		},
1514		orthogonal:     'display',
1515		stripHtml:      true,
1516		stripNewlines:  true,
1517		decodeEntities: true,
1518		trim:           true,
1519		format:         {
1520			header: function ( d ) {
1521				return strip( d );
1522			},
1523			footer: function ( d ) {
1524				return strip( d );
1525			},
1526			body: function ( d ) {
1527				return strip( d );
1528			}
1529		}
1530	}, inOpts );
1531
1532	var strip = function ( str ) {
1533		if ( typeof str !== 'string' ) {
1534			return str;
1535		}
1536
1537		if ( config.stripHtml ) {
1538			str = str.replace( /<.*?>/g, '' );
1539		}
1540
1541		if ( config.trim ) {
1542			str = str.replace( /^\s+|\s+$/g, '' );
1543		}
1544
1545		if ( config.stripNewlines ) {
1546			str = str.replace( /\n/g, ' ' );
1547		}
1548
1549		if ( config.decodeEntities ) {
1550			_exportTextarea.innerHTML = str;
1551			str = _exportTextarea.value;
1552		}
1553
1554		return str;
1555	};
1556
1557
1558	var header = dt.columns( config.columns ).indexes().map( function (idx, i) {
1559		return config.format.header( dt.column( idx ).header().innerHTML, idx );
1560	} ).toArray();
1561
1562	var footer = dt.table().footer() ?
1563		dt.columns( config.columns ).indexes().map( function (idx, i) {
1564			var el = dt.column( idx ).footer();
1565			return config.format.footer( el ? el.innerHTML : '', idx );
1566		} ).toArray() :
1567		null;
1568
1569	var rowIndexes = dt.rows( config.rows, config.modifier ).indexes().toArray();
1570	var cells = dt
1571		.cells( rowIndexes, config.columns )
1572		.render( config.orthogonal )
1573		.toArray();
1574	var columns = header.length;
1575	var rows = columns > 0 ? cells.length / columns : 0;
1576	var body = new Array( rows );
1577	var cellCounter = 0;
1578
1579	for ( var i=0, ien=rows ; i<ien ; i++ ) {
1580		var row = new Array( columns );
1581
1582		for ( var j=0 ; j<columns ; j++ ) {
1583			row[j] = config.format.body( cells[ cellCounter ], j, i );
1584			cellCounter++;
1585		}
1586
1587		body[i] = row;
1588	}
1589
1590	return {
1591		header: header,
1592		footer: footer,
1593		body:   body
1594	};
1595};
1596
1597
1598/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1599 * DataTables interface
1600 */
1601
1602// Attach to DataTables objects for global access
1603$.fn.dataTable.Buttons = Buttons;
1604$.fn.DataTable.Buttons = Buttons;
1605
1606
1607
1608// DataTables creation - check if the buttons have been defined for this table,
1609// they will have been if the `B` option was used in `dom`, otherwise we should
1610// create the buttons instance here so they can be inserted into the document
1611// using the API. Listen for `init` for compatibility with pre 1.10.10, but to
1612// be removed in future.
1613$(document).on( 'init.dt plugin-init.dt', function (e, settings, json) {
1614	if ( e.namespace !== 'dt' ) {
1615		return;
1616	}
1617
1618	var opts = settings.oInit.buttons || DataTable.defaults.buttons;
1619
1620	if ( opts && ! settings._buttons ) {
1621		new Buttons( settings, opts ).container();
1622	}
1623} );
1624
1625// DataTables `dom` feature option
1626DataTable.ext.feature.push( {
1627	fnInit: function( settings ) {
1628		var api = new DataTable.Api( settings );
1629		var opts = api.init().buttons || DataTable.defaults.buttons;
1630
1631		return new Buttons( api, opts ).container();
1632	},
1633	cFeature: "B"
1634} );
1635
1636
1637return Buttons;
1638}));

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.