Subversion-Projekte lars-tiefland.cienc

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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