source: public/vendors/dataTable/Buttons-1.6.1/js/dataTables.buttons.js@ 1c25bcf

develop
Last change on this file since 1c25bcf was 7304c7f, checked in by beratkjufliju <kufliju@…>, 3 years ago

added user authentication, create & forgot password methods and blades

  • Property mode set to 100644
File size: 48.2 KB
Line 
1/*! Buttons for DataTables 1.6.1
2 * ©2016-2019 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 // If not created with a `new` keyword then we return a wrapper function that
52 // will take the settings object for a DT. This allows easy use of new instances
53 // with the `layout` option - e.g. `topLeft: $.fn.dataTable.Buttons( ... )`.
54 if ( !(this instanceof Buttons) ) {
55 return function (settings) {
56 return new Buttons( settings, dt ).container();
57 };
58 }
59
60 // If there is no config set it to an empty object
61 if ( typeof( config ) === 'undefined' ) {
62 config = {};
63 }
64
65 // Allow a boolean true for defaults
66 if ( config === true ) {
67 config = {};
68 }
69
70 // For easy configuration of buttons an array can be given
71 if ( $.isArray( config ) ) {
72 config = { buttons: config };
73 }
74
75 this.c = $.extend( true, {}, Buttons.defaults, config );
76
77 // Don't want a deep copy for the buttons
78 if ( config.buttons ) {
79 this.c.buttons = config.buttons;
80 }
81
82 this.s = {
83 dt: new DataTable.Api( dt ),
84 buttons: [],
85 listenKeys: '',
86 namespace: 'dtb'+(_instCounter++)
87 };
88
89 this.dom = {
90 container: $('<'+this.c.dom.container.tag+'/>')
91 .addClass( this.c.dom.container.className )
92 };
93
94 this._constructor();
95};
96
97
98$.extend( Buttons.prototype, {
99 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
100 * Public methods
101 */
102
103 /**
104 * Get the action of a button
105 * @param {int|string} Button index
106 * @return {function}
107 *//**
108 * Set the action of a button
109 * @param {node} node Button element
110 * @param {function} action Function to set
111 * @return {Buttons} Self for chaining
112 */
113 action: function ( node, action )
114 {
115 var button = this._nodeToButton( node );
116
117 if ( action === undefined ) {
118 return button.conf.action;
119 }
120
121 button.conf.action = action;
122
123 return this;
124 },
125
126 /**
127 * Add an active class to the button to make to look active or get current
128 * active state.
129 * @param {node} node Button element
130 * @param {boolean} [flag] Enable / disable flag
131 * @return {Buttons} Self for chaining or boolean for getter
132 */
133 active: function ( node, flag ) {
134 var button = this._nodeToButton( node );
135 var klass = this.c.dom.button.active;
136 var jqNode = $(button.node);
137
138 if ( flag === undefined ) {
139 return jqNode.hasClass( klass );
140 }
141
142 jqNode.toggleClass( klass, flag === undefined ? true : flag );
143
144 return this;
145 },
146
147 /**
148 * Add a new button
149 * @param {object} config Button configuration object, base string name or function
150 * @param {int|string} [idx] Button index for where to insert the button
151 * @return {Buttons} Self for chaining
152 */
153 add: function ( config, idx )
154 {
155 var buttons = this.s.buttons;
156
157 if ( typeof idx === 'string' ) {
158 var split = idx.split('-');
159 var base = this.s;
160
161 for ( var i=0, ien=split.length-1 ; i<ien ; i++ ) {
162 base = base.buttons[ split[i]*1 ];
163 }
164
165 buttons = base.buttons;
166 idx = split[ split.length-1 ]*1;
167 }
168
169 this._expandButton( buttons, config, base !== undefined, idx );
170 this._draw();
171
172 return this;
173 },
174
175 /**
176 * Get the container node for the buttons
177 * @return {jQuery} Buttons node
178 */
179 container: function ()
180 {
181 return this.dom.container;
182 },
183
184 /**
185 * Disable a button
186 * @param {node} node Button node
187 * @return {Buttons} Self for chaining
188 */
189 disable: function ( node ) {
190 var button = this._nodeToButton( node );
191
192 $(button.node).addClass( this.c.dom.button.disabled );
193
194 return this;
195 },
196
197 /**
198 * Destroy the instance, cleaning up event handlers and removing DOM
199 * elements
200 * @return {Buttons} Self for chaining
201 */
202 destroy: function ()
203 {
204 // Key event listener
205 $('body').off( 'keyup.'+this.s.namespace );
206
207 // Individual button destroy (so they can remove their own events if
208 // needed). Take a copy as the array is modified by `remove`
209 var buttons = this.s.buttons.slice();
210 var i, ien;
211
212 for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
213 this.remove( buttons[i].node );
214 }
215
216 // Container
217 this.dom.container.remove();
218
219 // Remove from the settings object collection
220 var buttonInsts = this.s.dt.settings()[0];
221
222 for ( i=0, ien=buttonInsts.length ; i<ien ; i++ ) {
223 if ( buttonInsts.inst === this ) {
224 buttonInsts.splice( i, 1 );
225 break;
226 }
227 }
228
229 return this;
230 },
231
232 /**
233 * Enable / disable a button
234 * @param {node} node Button node
235 * @param {boolean} [flag=true] Enable / disable flag
236 * @return {Buttons} Self for chaining
237 */
238 enable: function ( node, flag )
239 {
240 if ( flag === false ) {
241 return this.disable( node );
242 }
243
244 var button = this._nodeToButton( node );
245 $(button.node).removeClass( this.c.dom.button.disabled );
246
247 return this;
248 },
249
250 /**
251 * Get the instance name for the button set selector
252 * @return {string} Instance name
253 */
254 name: function ()
255 {
256 return this.c.name;
257 },
258
259 /**
260 * Get a button's node of the buttons container if no button is given
261 * @param {node} [node] Button node
262 * @return {jQuery} Button element, or container
263 */
264 node: function ( node )
265 {
266 if ( ! node ) {
267 return this.dom.container;
268 }
269
270 var button = this._nodeToButton( node );
271 return $(button.node);
272 },
273
274 /**
275 * Set / get a processing class on the selected button
276 * @param {element} node Triggering button node
277 * @param {boolean} flag true to add, false to remove, undefined to get
278 * @return {boolean|Buttons} Getter value or this if a setter.
279 */
280 processing: function ( node, flag )
281 {
282 var dt = this.s.dt;
283 var button = this._nodeToButton( node );
284
285 if ( flag === undefined ) {
286 return $(button.node).hasClass( 'processing' );
287 }
288
289 $(button.node).toggleClass( 'processing', flag );
290
291 $(dt.table().node()).triggerHandler( 'buttons-processing.dt', [
292 flag, dt.button( node ), dt, $(node), button.conf
293 ] );
294
295 return this;
296 },
297
298 /**
299 * Remove a button.
300 * @param {node} node Button node
301 * @return {Buttons} Self for chaining
302 */
303 remove: function ( node )
304 {
305 var button = this._nodeToButton( node );
306 var host = this._nodeToHost( node );
307 var dt = this.s.dt;
308
309 // Remove any child buttons first
310 if ( button.buttons.length ) {
311 for ( var i=button.buttons.length-1 ; i>=0 ; i-- ) {
312 this.remove( button.buttons[i].node );
313 }
314 }
315
316 // Allow the button to remove event handlers, etc
317 if ( button.conf.destroy ) {
318 button.conf.destroy.call( dt.button(node), dt, $(node), button.conf );
319 }
320
321 this._removeKey( button.conf );
322
323 $(button.node).remove();
324
325 var idx = $.inArray( button, host );
326 host.splice( idx, 1 );
327
328 return this;
329 },
330
331 /**
332 * Get the text for a button
333 * @param {int|string} node Button index
334 * @return {string} Button text
335 *//**
336 * Set the text for a button
337 * @param {int|string|function} node Button index
338 * @param {string} label Text
339 * @return {Buttons} Self for chaining
340 */
341 text: function ( node, label )
342 {
343 var button = this._nodeToButton( node );
344 var buttonLiner = this.c.dom.collection.buttonLiner;
345 var linerTag = button.inCollection && buttonLiner && buttonLiner.tag ?
346 buttonLiner.tag :
347 this.c.dom.buttonLiner.tag;
348 var dt = this.s.dt;
349 var jqNode = $(button.node);
350 var text = function ( opt ) {
351 return typeof opt === 'function' ?
352 opt( dt, jqNode, button.conf ) :
353 opt;
354 };
355
356 if ( label === undefined ) {
357 return text( button.conf.text );
358 }
359
360 button.conf.text = label;
361
362 if ( linerTag ) {
363 jqNode.children( linerTag ).html( text(label) );
364 }
365 else {
366 jqNode.html( text(label) );
367 }
368
369 return this;
370 },
371
372
373 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
374 * Constructor
375 */
376
377 /**
378 * Buttons constructor
379 * @private
380 */
381 _constructor: function ()
382 {
383 var that = this;
384 var dt = this.s.dt;
385 var dtSettings = dt.settings()[0];
386 var buttons = this.c.buttons;
387
388 if ( ! dtSettings._buttons ) {
389 dtSettings._buttons = [];
390 }
391
392 dtSettings._buttons.push( {
393 inst: this,
394 name: this.c.name
395 } );
396
397 for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
398 this.add( buttons[i] );
399 }
400
401 dt.on( 'destroy', function ( e, settings ) {
402 if ( settings === dtSettings ) {
403 that.destroy();
404 }
405 } );
406
407 // Global key event binding to listen for button keys
408 $('body').on( 'keyup.'+this.s.namespace, function ( e ) {
409 if ( ! document.activeElement || document.activeElement === document.body ) {
410 // SUse a string of characters for fast lookup of if we need to
411 // handle this
412 var character = String.fromCharCode(e.keyCode).toLowerCase();
413
414 if ( that.s.listenKeys.toLowerCase().indexOf( character ) !== -1 ) {
415 that._keypress( character, e );
416 }
417 }
418 } );
419 },
420
421
422 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
423 * Private methods
424 */
425
426 /**
427 * Add a new button to the key press listener
428 * @param {object} conf Resolved button configuration object
429 * @private
430 */
431 _addKey: function ( conf )
432 {
433 if ( conf.key ) {
434 this.s.listenKeys += $.isPlainObject( conf.key ) ?
435 conf.key.key :
436 conf.key;
437 }
438 },
439
440 /**
441 * Insert the buttons into the container. Call without parameters!
442 * @param {node} [container] Recursive only - Insert point
443 * @param {array} [buttons] Recursive only - Buttons array
444 * @private
445 */
446 _draw: function ( container, buttons )
447 {
448 if ( ! container ) {
449 container = this.dom.container;
450 buttons = this.s.buttons;
451 }
452
453 container.children().detach();
454
455 for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
456 container.append( buttons[i].inserter );
457 container.append( ' ' );
458
459 if ( buttons[i].buttons && buttons[i].buttons.length ) {
460 this._draw( buttons[i].collection, buttons[i].buttons );
461 }
462 }
463 },
464
465 /**
466 * Create buttons from an array of buttons
467 * @param {array} attachTo Buttons array to attach to
468 * @param {object} button Button definition
469 * @param {boolean} inCollection true if the button is in a collection
470 * @private
471 */
472 _expandButton: function ( attachTo, button, inCollection, attachPoint )
473 {
474 var dt = this.s.dt;
475 var buttonCounter = 0;
476 var buttons = ! $.isArray( button ) ?
477 [ button ] :
478 button;
479
480 for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
481 var conf = this._resolveExtends( buttons[i] );
482
483 if ( ! conf ) {
484 continue;
485 }
486
487 // If the configuration is an array, then expand the buttons at this
488 // point
489 if ( $.isArray( conf ) ) {
490 this._expandButton( attachTo, conf, inCollection, attachPoint );
491 continue;
492 }
493
494 var built = this._buildButton( conf, inCollection );
495 if ( ! built ) {
496 continue;
497 }
498
499 if ( attachPoint !== undefined ) {
500 attachTo.splice( attachPoint, 0, built );
501 attachPoint++;
502 }
503 else {
504 attachTo.push( built );
505 }
506
507 if ( built.conf.buttons ) {
508 built.collection = $('<'+this.c.dom.collection.tag+'/>');
509
510 built.conf._collection = built.collection;
511
512 this._expandButton( built.buttons, built.conf.buttons, true, attachPoint );
513 }
514
515 // init call is made here, rather than buildButton as it needs to
516 // be selectable, and for that it needs to be in the buttons array
517 if ( conf.init ) {
518 conf.init.call( dt.button( built.node ), dt, $(built.node), conf );
519 }
520
521 buttonCounter++;
522 }
523 },
524
525 /**
526 * Create an individual button
527 * @param {object} config Resolved button configuration
528 * @param {boolean} inCollection `true` if a collection button
529 * @return {jQuery} Created button node (jQuery)
530 * @private
531 */
532 _buildButton: function ( config, inCollection )
533 {
534 var buttonDom = this.c.dom.button;
535 var linerDom = this.c.dom.buttonLiner;
536 var collectionDom = this.c.dom.collection;
537 var dt = this.s.dt;
538 var text = function ( opt ) {
539 return typeof opt === 'function' ?
540 opt( dt, button, config ) :
541 opt;
542 };
543
544 if ( inCollection && collectionDom.button ) {
545 buttonDom = collectionDom.button;
546 }
547
548 if ( inCollection && collectionDom.buttonLiner ) {
549 linerDom = collectionDom.buttonLiner;
550 }
551
552 // Make sure that the button is available based on whatever requirements
553 // it has. For example, Flash buttons require Flash
554 if ( config.available && ! config.available( dt, config ) ) {
555 return false;
556 }
557
558 var action = function ( e, dt, button, config ) {
559 config.action.call( dt.button( button ), e, dt, button, config );
560
561 $(dt.table().node()).triggerHandler( 'buttons-action.dt', [
562 dt.button( button ), dt, button, config
563 ] );
564 };
565
566 var tag = config.tag || buttonDom.tag;
567 var clickBlurs = config.clickBlurs === undefined ? true : config.clickBlurs
568 var button = $('<'+tag+'/>')
569 .addClass( buttonDom.className )
570 .attr( 'tabindex', this.s.dt.settings()[0].iTabIndex )
571 .attr( 'aria-controls', this.s.dt.table().node().id )
572 .on( 'click.dtb', function (e) {
573 e.preventDefault();
574
575 if ( ! button.hasClass( buttonDom.disabled ) && config.action ) {
576 action( e, dt, button, config );
577 }
578 if( clickBlurs ) {
579 button.blur();
580 }
581 } )
582 .on( 'keyup.dtb', function (e) {
583 if ( e.keyCode === 13 ) {
584 if ( ! button.hasClass( buttonDom.disabled ) && config.action ) {
585 action( e, dt, button, config );
586 }
587 }
588 } );
589
590 // Make `a` tags act like a link
591 if ( tag.toLowerCase() === 'a' ) {
592 button.attr( 'href', '#' );
593 }
594
595 // Button tags should have `type=button` so they don't have any default behaviour
596 if ( tag.toLowerCase() === 'button' ) {
597 button.attr( 'type', 'button' );
598 }
599
600 if ( linerDom.tag ) {
601 var liner = $('<'+linerDom.tag+'/>')
602 .html( text( config.text ) )
603 .addClass( linerDom.className );
604
605 if ( linerDom.tag.toLowerCase() === 'a' ) {
606 liner.attr( 'href', '#' );
607 }
608
609 button.append( liner );
610 }
611 else {
612 button.html( text( config.text ) );
613 }
614
615 if ( config.enabled === false ) {
616 button.addClass( buttonDom.disabled );
617 }
618
619 if ( config.className ) {
620 button.addClass( config.className );
621 }
622
623 if ( config.titleAttr ) {
624 button.attr( 'title', text( config.titleAttr ) );
625 }
626
627 if ( config.attr ) {
628 button.attr( config.attr );
629 }
630
631 if ( ! config.namespace ) {
632 config.namespace = '.dt-button-'+(_buttonCounter++);
633 }
634
635 var buttonContainer = this.c.dom.buttonContainer;
636 var inserter;
637 if ( buttonContainer && buttonContainer.tag ) {
638 inserter = $('<'+buttonContainer.tag+'/>')
639 .addClass( buttonContainer.className )
640 .append( button );
641 }
642 else {
643 inserter = button;
644 }
645
646 this._addKey( config );
647
648 // Style integration callback for DOM manipulation
649 // Note that this is _not_ documented. It is currently
650 // for style integration only
651 if( this.c.buttonCreated ) {
652 inserter = this.c.buttonCreated( config, inserter );
653 }
654
655 return {
656 conf: config,
657 node: button.get(0),
658 inserter: inserter,
659 buttons: [],
660 inCollection: inCollection,
661 collection: null
662 };
663 },
664
665 /**
666 * Get the button object from a node (recursive)
667 * @param {node} node Button node
668 * @param {array} [buttons] Button array, uses base if not defined
669 * @return {object} Button object
670 * @private
671 */
672 _nodeToButton: function ( node, buttons )
673 {
674 if ( ! buttons ) {
675 buttons = this.s.buttons;
676 }
677
678 for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
679 if ( buttons[i].node === node ) {
680 return buttons[i];
681 }
682
683 if ( buttons[i].buttons.length ) {
684 var ret = this._nodeToButton( node, buttons[i].buttons );
685
686 if ( ret ) {
687 return ret;
688 }
689 }
690 }
691 },
692
693 /**
694 * Get container array for a button from a button node (recursive)
695 * @param {node} node Button node
696 * @param {array} [buttons] Button array, uses base if not defined
697 * @return {array} Button's host array
698 * @private
699 */
700 _nodeToHost: function ( node, buttons )
701 {
702 if ( ! buttons ) {
703 buttons = this.s.buttons;
704 }
705
706 for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
707 if ( buttons[i].node === node ) {
708 return buttons;
709 }
710
711 if ( buttons[i].buttons.length ) {
712 var ret = this._nodeToHost( node, buttons[i].buttons );
713
714 if ( ret ) {
715 return ret;
716 }
717 }
718 }
719 },
720
721 /**
722 * Handle a key press - determine if any button's key configured matches
723 * what was typed and trigger the action if so.
724 * @param {string} character The character pressed
725 * @param {object} e Key event that triggered this call
726 * @private
727 */
728 _keypress: function ( character, e )
729 {
730 // Check if this button press already activated on another instance of Buttons
731 if ( e._buttonsHandled ) {
732 return;
733 }
734
735 var run = function ( conf, node ) {
736 if ( ! conf.key ) {
737 return;
738 }
739
740 if ( conf.key === character ) {
741 e._buttonsHandled = true;
742 $(node).click();
743 }
744 else if ( $.isPlainObject( conf.key ) ) {
745 if ( conf.key.key !== character ) {
746 return;
747 }
748
749 if ( conf.key.shiftKey && ! e.shiftKey ) {
750 return;
751 }
752
753 if ( conf.key.altKey && ! e.altKey ) {
754 return;
755 }
756
757 if ( conf.key.ctrlKey && ! e.ctrlKey ) {
758 return;
759 }
760
761 if ( conf.key.metaKey && ! e.metaKey ) {
762 return;
763 }
764
765 // Made it this far - it is good
766 e._buttonsHandled = true;
767 $(node).click();
768 }
769 };
770
771 var recurse = function ( a ) {
772 for ( var i=0, ien=a.length ; i<ien ; i++ ) {
773 run( a[i].conf, a[i].node );
774
775 if ( a[i].buttons.length ) {
776 recurse( a[i].buttons );
777 }
778 }
779 };
780
781 recurse( this.s.buttons );
782 },
783
784 /**
785 * Remove a key from the key listener for this instance (to be used when a
786 * button is removed)
787 * @param {object} conf Button configuration
788 * @private
789 */
790 _removeKey: function ( conf )
791 {
792 if ( conf.key ) {
793 var character = $.isPlainObject( conf.key ) ?
794 conf.key.key :
795 conf.key;
796
797 // Remove only one character, as multiple buttons could have the
798 // same listening key
799 var a = this.s.listenKeys.split('');
800 var idx = $.inArray( character, a );
801 a.splice( idx, 1 );
802 this.s.listenKeys = a.join('');
803 }
804 },
805
806 /**
807 * Resolve a button configuration
808 * @param {string|function|object} conf Button config to resolve
809 * @return {object} Button configuration
810 * @private
811 */
812 _resolveExtends: function ( conf )
813 {
814 var dt = this.s.dt;
815 var i, ien;
816 var toConfObject = function ( base ) {
817 var loop = 0;
818
819 // Loop until we have resolved to a button configuration, or an
820 // array of button configurations (which will be iterated
821 // separately)
822 while ( ! $.isPlainObject(base) && ! $.isArray(base) ) {
823 if ( base === undefined ) {
824 return;
825 }
826
827 if ( typeof base === 'function' ) {
828 base = base( dt, conf );
829
830 if ( ! base ) {
831 return false;
832 }
833 }
834 else if ( typeof base === 'string' ) {
835 if ( ! _dtButtons[ base ] ) {
836 throw 'Unknown button type: '+base;
837 }
838
839 base = _dtButtons[ base ];
840 }
841
842 loop++;
843 if ( loop > 30 ) {
844 // Protect against misconfiguration killing the browser
845 throw 'Buttons: Too many iterations';
846 }
847 }
848
849 return $.isArray( base ) ?
850 base :
851 $.extend( {}, base );
852 };
853
854 conf = toConfObject( conf );
855
856 while ( conf && conf.extend ) {
857 // Use `toConfObject` in case the button definition being extended
858 // is itself a string or a function
859 if ( ! _dtButtons[ conf.extend ] ) {
860 throw 'Cannot extend unknown button type: '+conf.extend;
861 }
862
863 var objArray = toConfObject( _dtButtons[ conf.extend ] );
864 if ( $.isArray( objArray ) ) {
865 return objArray;
866 }
867 else if ( ! objArray ) {
868 // This is a little brutal as it might be possible to have a
869 // valid button without the extend, but if there is no extend
870 // then the host button would be acting in an undefined state
871 return false;
872 }
873
874 // Stash the current class name
875 var originalClassName = objArray.className;
876
877 conf = $.extend( {}, objArray, conf );
878
879 // The extend will have overwritten the original class name if the
880 // `conf` object also assigned a class, but we want to concatenate
881 // them so they are list that is combined from all extended buttons
882 if ( originalClassName && conf.className !== originalClassName ) {
883 conf.className = originalClassName+' '+conf.className;
884 }
885
886 // Buttons to be added to a collection -gives the ability to define
887 // if buttons should be added to the start or end of a collection
888 var postfixButtons = conf.postfixButtons;
889 if ( postfixButtons ) {
890 if ( ! conf.buttons ) {
891 conf.buttons = [];
892 }
893
894 for ( i=0, ien=postfixButtons.length ; i<ien ; i++ ) {
895 conf.buttons.push( postfixButtons[i] );
896 }
897
898 conf.postfixButtons = null;
899 }
900
901 var prefixButtons = conf.prefixButtons;
902 if ( prefixButtons ) {
903 if ( ! conf.buttons ) {
904 conf.buttons = [];
905 }
906
907 for ( i=0, ien=prefixButtons.length ; i<ien ; i++ ) {
908 conf.buttons.splice( i, 0, prefixButtons[i] );
909 }
910
911 conf.prefixButtons = null;
912 }
913
914 // Although we want the `conf` object to overwrite almost all of
915 // the properties of the object being extended, the `extend`
916 // property should come from the object being extended
917 conf.extend = objArray.extend;
918 }
919
920 return conf;
921 },
922
923 /**
924 * Display (and replace if there is an existing one) a popover attached to a button
925 * @param {string|node} content Content to show
926 * @param {DataTable.Api} hostButton DT API instance of the button
927 * @param {object} inOpts Options (see object below for all options)
928 */
929 _popover: function ( content, hostButton, inOpts ) {
930 var dt = hostButton;
931 var buttonsSettings = this.c;
932 var options = $.extend( {
933 align: 'button-left', // button-right, dt-container
934 autoClose: false,
935 background: true,
936 backgroundClassName: 'dt-button-background',
937 contentClassName: buttonsSettings.dom.collection.className,
938 collectionLayout: '',
939 collectionTitle: '',
940 dropup: false,
941 fade: 400,
942 rightAlignClassName: 'dt-button-right',
943 tag: buttonsSettings.dom.collection.tag
944 }, inOpts );
945 var hostNode = hostButton.node();
946
947 var close = function () {
948 $('.dt-button-collection').stop().fadeOut( options.fade, function () {
949 $(this).detach();
950 } );
951
952 $(dt.buttons( '[aria-haspopup="true"][aria-expanded="true"]' ).nodes())
953 .attr('aria-expanded', 'false');
954
955 $('div.dt-button-background').off( 'click.dtb-collection' );
956 Buttons.background( false, options.backgroundClassName, options.fade, hostNode );
957
958 $('body').off( '.dtb-collection' );
959 dt.off( 'buttons-action.b-internal' );
960 };
961
962 if (content === false) {
963 close();
964 }
965
966 var existingExpanded = $(dt.buttons( '[aria-haspopup="true"][aria-expanded="true"]' ).nodes());
967 if ( existingExpanded.length ) {
968 hostNode = existingExpanded.eq(0);
969
970 close();
971 }
972
973 var display = $('<div/>')
974 .addClass('dt-button-collection')
975 .addClass(options.collectionLayout)
976 .css('display', 'none');
977
978 content = $(content)
979 .addClass(options.contentClassName)
980 .attr('role', 'menu')
981 .appendTo(display);
982
983 hostNode.attr( 'aria-expanded', 'true' );
984
985 if ( hostNode.parents('body')[0] !== document.body ) {
986 hostNode = document.body.lastChild;
987 }
988
989 if ( options.collectionTitle ) {
990 display.prepend('<div class="dt-button-collection-title">'+options.collectionTitle+'</div>');
991 }
992
993 display
994 .insertAfter( hostNode )
995 .fadeIn( options.fade );
996
997 var tableContainer = $( hostButton.table().container() );
998 var position = display.css( 'position' );
999
1000 if ( options.align === 'dt-container' ) {
1001 hostNode = hostNode.parent();
1002 display.css('width', tableContainer.width());
1003 }
1004
1005 if ( position === 'absolute' ) {
1006 var hostPosition = hostNode.position();
1007
1008 display.css( {
1009 top: hostPosition.top + hostNode.outerHeight(),
1010 left: hostPosition.left
1011 } );
1012
1013 // calculate overflow when positioned beneath
1014 var collectionHeight = display.outerHeight();
1015 var collectionWidth = display.outerWidth();
1016 var tableBottom = tableContainer.offset().top + tableContainer.height();
1017 var listBottom = hostPosition.top + hostNode.outerHeight() + collectionHeight;
1018 var bottomOverflow = listBottom - tableBottom;
1019
1020 // calculate overflow when positioned above
1021 var listTop = hostPosition.top - collectionHeight;
1022 var tableTop = tableContainer.offset().top;
1023 var topOverflow = tableTop - listTop;
1024
1025 // if bottom overflow is larger, move to the top because it fits better, or if dropup is requested
1026 var moveTop = hostPosition.top - collectionHeight - 5;
1027 if ( (bottomOverflow > topOverflow || options.dropup) && -moveTop < tableTop ) {
1028 display.css( 'top', moveTop);
1029 }
1030
1031 // Right alignment is enabled on a class, e.g. bootstrap:
1032 // $.fn.dataTable.Buttons.defaults.dom.collection.className += " dropdown-menu-right";
1033 if ( display.hasClass( options.rightAlignClassName ) || options.align === 'button-right' ) {
1034 display.css( 'left', hostPosition.left + hostNode.outerWidth() - collectionWidth );
1035 }
1036
1037 // Right alignment in table container
1038 var listRight = hostPosition.left + collectionWidth;
1039 var tableRight = tableContainer.offset().left + tableContainer.width();
1040 if ( listRight > tableRight ) {
1041 display.css( 'left', hostPosition.left - ( listRight - tableRight ) );
1042 }
1043
1044 // Right alignment to window
1045 var listOffsetRight = hostNode.offset().left + collectionWidth;
1046 if ( listOffsetRight > $(window).width() ) {
1047 display.css( 'left', hostPosition.left - (listOffsetRight-$(window).width()) );
1048 }
1049 }
1050 else {
1051 // Fix position - centre on screen
1052 var top = display.height() / 2;
1053 if ( top > $(window).height() / 2 ) {
1054 top = $(window).height() / 2;
1055 }
1056
1057 display.css( 'marginTop', top*-1 );
1058 }
1059
1060 if ( options.background ) {
1061 Buttons.background( true, options.backgroundClassName, options.fade, hostNode );
1062 }
1063
1064 // This is bonkers, but if we don't have a click listener on the
1065 // background element, iOS Safari will ignore the body click
1066 // listener below. An empty function here is all that is
1067 // required to make it work...
1068 $('div.dt-button-background').on( 'click.dtb-collection', function () {} );
1069
1070 $('body')
1071 .on( 'click.dtb-collection', function (e) {
1072 // andSelf is deprecated in jQ1.8, but we want 1.7 compat
1073 var back = $.fn.addBack ? 'addBack' : 'andSelf';
1074
1075 if ( ! $(e.target).parents()[back]().filter( content ).length ) {
1076 close();
1077 }
1078 } )
1079 .on( 'keyup.dtb-collection', function (e) {
1080 if ( e.keyCode === 27 ) {
1081 close();
1082 }
1083 } );
1084
1085 if ( options.autoClose ) {
1086 setTimeout( function () {
1087 dt.on( 'buttons-action.b-internal', function (e, btn, dt, node) {
1088 if ( node[0] === hostNode[0] ) {
1089 return;
1090 }
1091 close();
1092 } );
1093 }, 0);
1094 }
1095 }
1096} );
1097
1098
1099
1100/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1101 * Statics
1102 */
1103
1104/**
1105 * Show / hide a background layer behind a collection
1106 * @param {boolean} Flag to indicate if the background should be shown or
1107 * hidden
1108 * @param {string} Class to assign to the background
1109 * @static
1110 */
1111Buttons.background = function ( show, className, fade, insertPoint ) {
1112 if ( fade === undefined ) {
1113 fade = 400;
1114 }
1115 if ( ! insertPoint ) {
1116 insertPoint = document.body;
1117 }
1118
1119 if ( show ) {
1120 $('<div/>')
1121 .addClass( className )
1122 .css( 'display', 'none' )
1123 .insertAfter( insertPoint )
1124 .stop()
1125 .fadeIn( fade );
1126 }
1127 else {
1128 $('div.'+className)
1129 .stop()
1130 .fadeOut( fade, function () {
1131 $(this)
1132 .removeClass( className )
1133 .remove();
1134 } );
1135 }
1136};
1137
1138/**
1139 * Instance selector - select Buttons instances based on an instance selector
1140 * value from the buttons assigned to a DataTable. This is only useful if
1141 * multiple instances are attached to a DataTable.
1142 * @param {string|int|array} Instance selector - see `instance-selector`
1143 * documentation on the DataTables site
1144 * @param {array} Button instance array that was attached to the DataTables
1145 * settings object
1146 * @return {array} Buttons instances
1147 * @static
1148 */
1149Buttons.instanceSelector = function ( group, buttons )
1150{
1151 if ( group === undefined || group === null ) {
1152 return $.map( buttons, function ( v ) {
1153 return v.inst;
1154 } );
1155 }
1156
1157 var ret = [];
1158 var names = $.map( buttons, function ( v ) {
1159 return v.name;
1160 } );
1161
1162 // Flatten the group selector into an array of single options
1163 var process = function ( input ) {
1164 if ( $.isArray( input ) ) {
1165 for ( var i=0, ien=input.length ; i<ien ; i++ ) {
1166 process( input[i] );
1167 }
1168 return;
1169 }
1170
1171 if ( typeof input === 'string' ) {
1172 if ( input.indexOf( ',' ) !== -1 ) {
1173 // String selector, list of names
1174 process( input.split(',') );
1175 }
1176 else {
1177 // String selector individual name
1178 var idx = $.inArray( $.trim(input), names );
1179
1180 if ( idx !== -1 ) {
1181 ret.push( buttons[ idx ].inst );
1182 }
1183 }
1184 }
1185 else if ( typeof input === 'number' ) {
1186 // Index selector
1187 ret.push( buttons[ input ].inst );
1188 }
1189 };
1190
1191 process( group );
1192
1193 return ret;
1194};
1195
1196/**
1197 * Button selector - select one or more buttons from a selector input so some
1198 * operation can be performed on them.
1199 * @param {array} Button instances array that the selector should operate on
1200 * @param {string|int|node|jQuery|array} Button selector - see
1201 * `button-selector` documentation on the DataTables site
1202 * @return {array} Array of objects containing `inst` and `idx` properties of
1203 * the selected buttons so you know which instance each button belongs to.
1204 * @static
1205 */
1206Buttons.buttonSelector = function ( insts, selector )
1207{
1208 var ret = [];
1209 var nodeBuilder = function ( a, buttons, baseIdx ) {
1210 var button;
1211 var idx;
1212
1213 for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
1214 button = buttons[i];
1215
1216 if ( button ) {
1217 idx = baseIdx !== undefined ?
1218 baseIdx+i :
1219 i+'';
1220
1221 a.push( {
1222 node: button.node,
1223 name: button.conf.name,
1224 idx: idx
1225 } );
1226
1227 if ( button.buttons ) {
1228 nodeBuilder( a, button.buttons, idx+'-' );
1229 }
1230 }
1231 }
1232 };
1233
1234 var run = function ( selector, inst ) {
1235 var i, ien;
1236 var buttons = [];
1237 nodeBuilder( buttons, inst.s.buttons );
1238
1239 var nodes = $.map( buttons, function (v) {
1240 return v.node;
1241 } );
1242
1243 if ( $.isArray( selector ) || selector instanceof $ ) {
1244 for ( i=0, ien=selector.length ; i<ien ; i++ ) {
1245 run( selector[i], inst );
1246 }
1247 return;
1248 }
1249
1250 if ( selector === null || selector === undefined || selector === '*' ) {
1251 // Select all
1252 for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
1253 ret.push( {
1254 inst: inst,
1255 node: buttons[i].node
1256 } );
1257 }
1258 }
1259 else if ( typeof selector === 'number' ) {
1260 // Main button index selector
1261 ret.push( {
1262 inst: inst,
1263 node: inst.s.buttons[ selector ].node
1264 } );
1265 }
1266 else if ( typeof selector === 'string' ) {
1267 if ( selector.indexOf( ',' ) !== -1 ) {
1268 // Split
1269 var a = selector.split(',');
1270
1271 for ( i=0, ien=a.length ; i<ien ; i++ ) {
1272 run( $.trim(a[i]), inst );
1273 }
1274 }
1275 else if ( selector.match( /^\d+(\-\d+)*$/ ) ) {
1276 // Sub-button index selector
1277 var indexes = $.map( buttons, function (v) {
1278 return v.idx;
1279 } );
1280
1281 ret.push( {
1282 inst: inst,
1283 node: buttons[ $.inArray( selector, indexes ) ].node
1284 } );
1285 }
1286 else if ( selector.indexOf( ':name' ) !== -1 ) {
1287 // Button name selector
1288 var name = selector.replace( ':name', '' );
1289
1290 for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
1291 if ( buttons[i].name === name ) {
1292 ret.push( {
1293 inst: inst,
1294 node: buttons[i].node
1295 } );
1296 }
1297 }
1298 }
1299 else {
1300 // jQuery selector on the nodes
1301 $( nodes ).filter( selector ).each( function () {
1302 ret.push( {
1303 inst: inst,
1304 node: this
1305 } );
1306 } );
1307 }
1308 }
1309 else if ( typeof selector === 'object' && selector.nodeName ) {
1310 // Node selector
1311 var idx = $.inArray( selector, nodes );
1312
1313 if ( idx !== -1 ) {
1314 ret.push( {
1315 inst: inst,
1316 node: nodes[ idx ]
1317 } );
1318 }
1319 }
1320 };
1321
1322
1323 for ( var i=0, ien=insts.length ; i<ien ; i++ ) {
1324 var inst = insts[i];
1325
1326 run( selector, inst );
1327 }
1328
1329 return ret;
1330};
1331
1332
1333/**
1334 * Buttons defaults. For full documentation, please refer to the docs/option
1335 * directory or the DataTables site.
1336 * @type {Object}
1337 * @static
1338 */
1339Buttons.defaults = {
1340 buttons: [ 'copy', 'excel', 'csv', 'pdf', 'print' ],
1341 name: 'main',
1342 tabIndex: 0,
1343 dom: {
1344 container: {
1345 tag: 'div',
1346 className: 'dt-buttons'
1347 },
1348 collection: {
1349 tag: 'div',
1350 className: ''
1351 },
1352 button: {
1353 // Flash buttons will not work with `<button>` in IE - it has to be `<a>`
1354 tag: 'ActiveXObject' in window ?
1355 'a' :
1356 'button',
1357 className: 'dt-button',
1358 active: 'active',
1359 disabled: 'disabled'
1360 },
1361 buttonLiner: {
1362 tag: 'span',
1363 className: ''
1364 }
1365 }
1366};
1367
1368/**
1369 * Version information
1370 * @type {string}
1371 * @static
1372 */
1373Buttons.version = '1.6.1';
1374
1375
1376$.extend( _dtButtons, {
1377 collection: {
1378 text: function ( dt ) {
1379 return dt.i18n( 'buttons.collection', 'Collection' );
1380 },
1381 className: 'buttons-collection',
1382 init: function ( dt, button, config ) {
1383 button.attr( 'aria-expanded', false );
1384 },
1385 action: function ( e, dt, button, config ) {
1386 e.stopPropagation();
1387
1388 if ( config._collection.parents('body').length ) {
1389 this.popover(false, config);
1390 }
1391 else {
1392 this.popover(config._collection, config);
1393 }
1394 },
1395 attr: {
1396 'aria-haspopup': true
1397 }
1398 // Also the popover options, defined in Buttons.popover
1399 },
1400 copy: function ( dt, conf ) {
1401 if ( _dtButtons.copyHtml5 ) {
1402 return 'copyHtml5';
1403 }
1404 if ( _dtButtons.copyFlash && _dtButtons.copyFlash.available( dt, conf ) ) {
1405 return 'copyFlash';
1406 }
1407 },
1408 csv: function ( dt, conf ) {
1409 // Common option that will use the HTML5 or Flash export buttons
1410 if ( _dtButtons.csvHtml5 && _dtButtons.csvHtml5.available( dt, conf ) ) {
1411 return 'csvHtml5';
1412 }
1413 if ( _dtButtons.csvFlash && _dtButtons.csvFlash.available( dt, conf ) ) {
1414 return 'csvFlash';
1415 }
1416 },
1417 excel: function ( dt, conf ) {
1418 // Common option that will use the HTML5 or Flash export buttons
1419 if ( _dtButtons.excelHtml5 && _dtButtons.excelHtml5.available( dt, conf ) ) {
1420 return 'excelHtml5';
1421 }
1422 if ( _dtButtons.excelFlash && _dtButtons.excelFlash.available( dt, conf ) ) {
1423 return 'excelFlash';
1424 }
1425 },
1426 pdf: function ( dt, conf ) {
1427 // Common option that will use the HTML5 or Flash export buttons
1428 if ( _dtButtons.pdfHtml5 && _dtButtons.pdfHtml5.available( dt, conf ) ) {
1429 return 'pdfHtml5';
1430 }
1431 if ( _dtButtons.pdfFlash && _dtButtons.pdfFlash.available( dt, conf ) ) {
1432 return 'pdfFlash';
1433 }
1434 },
1435 pageLength: function ( dt ) {
1436 var lengthMenu = dt.settings()[0].aLengthMenu;
1437 var vals = $.isArray( lengthMenu[0] ) ? lengthMenu[0] : lengthMenu;
1438 var lang = $.isArray( lengthMenu[0] ) ? lengthMenu[1] : lengthMenu;
1439 var text = function ( dt ) {
1440 return dt.i18n( 'buttons.pageLength', {
1441 "-1": 'Show all rows',
1442 _: 'Show %d rows'
1443 }, dt.page.len() );
1444 };
1445
1446 return {
1447 extend: 'collection',
1448 text: text,
1449 className: 'buttons-page-length',
1450 autoClose: true,
1451 buttons: $.map( vals, function ( val, i ) {
1452 return {
1453 text: lang[i],
1454 className: 'button-page-length',
1455 action: function ( e, dt ) {
1456 dt.page.len( val ).draw();
1457 },
1458 init: function ( dt, node, conf ) {
1459 var that = this;
1460 var fn = function () {
1461 that.active( dt.page.len() === val );
1462 };
1463
1464 dt.on( 'length.dt'+conf.namespace, fn );
1465 fn();
1466 },
1467 destroy: function ( dt, node, conf ) {
1468 dt.off( 'length.dt'+conf.namespace );
1469 }
1470 };
1471 } ),
1472 init: function ( dt, node, conf ) {
1473 var that = this;
1474 dt.on( 'length.dt'+conf.namespace, function () {
1475 that.text( conf.text );
1476 } );
1477 },
1478 destroy: function ( dt, node, conf ) {
1479 dt.off( 'length.dt'+conf.namespace );
1480 }
1481 };
1482 }
1483} );
1484
1485
1486/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1487 * DataTables API
1488 *
1489 * For complete documentation, please refer to the docs/api directory or the
1490 * DataTables site
1491 */
1492
1493// Buttons group and individual button selector
1494DataTable.Api.register( 'buttons()', function ( group, selector ) {
1495 // Argument shifting
1496 if ( selector === undefined ) {
1497 selector = group;
1498 group = undefined;
1499 }
1500
1501 this.selector.buttonGroup = group;
1502
1503 var res = this.iterator( true, 'table', function ( ctx ) {
1504 if ( ctx._buttons ) {
1505 return Buttons.buttonSelector(
1506 Buttons.instanceSelector( group, ctx._buttons ),
1507 selector
1508 );
1509 }
1510 }, true );
1511
1512 res._groupSelector = group;
1513 return res;
1514} );
1515
1516// Individual button selector
1517DataTable.Api.register( 'button()', function ( group, selector ) {
1518 // just run buttons() and truncate
1519 var buttons = this.buttons( group, selector );
1520
1521 if ( buttons.length > 1 ) {
1522 buttons.splice( 1, buttons.length );
1523 }
1524
1525 return buttons;
1526} );
1527
1528// Active buttons
1529DataTable.Api.registerPlural( 'buttons().active()', 'button().active()', function ( flag ) {
1530 if ( flag === undefined ) {
1531 return this.map( function ( set ) {
1532 return set.inst.active( set.node );
1533 } );
1534 }
1535
1536 return this.each( function ( set ) {
1537 set.inst.active( set.node, flag );
1538 } );
1539} );
1540
1541// Get / set button action
1542DataTable.Api.registerPlural( 'buttons().action()', 'button().action()', function ( action ) {
1543 if ( action === undefined ) {
1544 return this.map( function ( set ) {
1545 return set.inst.action( set.node );
1546 } );
1547 }
1548
1549 return this.each( function ( set ) {
1550 set.inst.action( set.node, action );
1551 } );
1552} );
1553
1554// Enable / disable buttons
1555DataTable.Api.register( ['buttons().enable()', 'button().enable()'], function ( flag ) {
1556 return this.each( function ( set ) {
1557 set.inst.enable( set.node, flag );
1558 } );
1559} );
1560
1561// Disable buttons
1562DataTable.Api.register( ['buttons().disable()', 'button().disable()'], function () {
1563 return this.each( function ( set ) {
1564 set.inst.disable( set.node );
1565 } );
1566} );
1567
1568// Get button nodes
1569DataTable.Api.registerPlural( 'buttons().nodes()', 'button().node()', function () {
1570 var jq = $();
1571
1572 // jQuery will automatically reduce duplicates to a single entry
1573 $( this.each( function ( set ) {
1574 jq = jq.add( set.inst.node( set.node ) );
1575 } ) );
1576
1577 return jq;
1578} );
1579
1580// Get / set button processing state
1581DataTable.Api.registerPlural( 'buttons().processing()', 'button().processing()', function ( flag ) {
1582 if ( flag === undefined ) {
1583 return this.map( function ( set ) {
1584 return set.inst.processing( set.node );
1585 } );
1586 }
1587
1588 return this.each( function ( set ) {
1589 set.inst.processing( set.node, flag );
1590 } );
1591} );
1592
1593// Get / set button text (i.e. the button labels)
1594DataTable.Api.registerPlural( 'buttons().text()', 'button().text()', function ( label ) {
1595 if ( label === undefined ) {
1596 return this.map( function ( set ) {
1597 return set.inst.text( set.node );
1598 } );
1599 }
1600
1601 return this.each( function ( set ) {
1602 set.inst.text( set.node, label );
1603 } );
1604} );
1605
1606// Trigger a button's action
1607DataTable.Api.registerPlural( 'buttons().trigger()', 'button().trigger()', function () {
1608 return this.each( function ( set ) {
1609 set.inst.node( set.node ).trigger( 'click' );
1610 } );
1611} );
1612
1613// Button resolver to the popover
1614DataTable.Api.register( 'button().popover()', function (content, options) {
1615 return this.map( function ( set ) {
1616 return set.inst._popover( content, this.button(this[0].node), options );
1617 } );
1618} );
1619
1620// Get the container elements
1621DataTable.Api.register( 'buttons().containers()', function () {
1622 var jq = $();
1623 var groupSelector = this._groupSelector;
1624
1625 // We need to use the group selector directly, since if there are no buttons
1626 // the result set will be empty
1627 this.iterator( true, 'table', function ( ctx ) {
1628 if ( ctx._buttons ) {
1629 var insts = Buttons.instanceSelector( groupSelector, ctx._buttons );
1630
1631 for ( var i=0, ien=insts.length ; i<ien ; i++ ) {
1632 jq = jq.add( insts[i].container() );
1633 }
1634 }
1635 } );
1636
1637 return jq;
1638} );
1639
1640DataTable.Api.register( 'buttons().container()', function () {
1641 // API level of nesting is `buttons()` so we can zip into the containers method
1642 return this.containers().eq(0);
1643} );
1644
1645// Add a new button
1646DataTable.Api.register( 'button().add()', function ( idx, conf ) {
1647 var ctx = this.context;
1648
1649 // Don't use `this` as it could be empty - select the instances directly
1650 if ( ctx.length ) {
1651 var inst = Buttons.instanceSelector( this._groupSelector, ctx[0]._buttons );
1652
1653 if ( inst.length ) {
1654 inst[0].add( conf, idx );
1655 }
1656 }
1657
1658 return this.button( this._groupSelector, idx );
1659} );
1660
1661// Destroy the button sets selected
1662DataTable.Api.register( 'buttons().destroy()', function () {
1663 this.pluck( 'inst' ).unique().each( function ( inst ) {
1664 inst.destroy();
1665 } );
1666
1667 return this;
1668} );
1669
1670// Remove a button
1671DataTable.Api.registerPlural( 'buttons().remove()', 'buttons().remove()', function () {
1672 this.each( function ( set ) {
1673 set.inst.remove( set.node );
1674 } );
1675
1676 return this;
1677} );
1678
1679// Information box that can be used by buttons
1680var _infoTimer;
1681DataTable.Api.register( 'buttons.info()', function ( title, message, time ) {
1682 var that = this;
1683
1684 if ( title === false ) {
1685 this.off('destroy.btn-info');
1686 $('#datatables_buttons_info').fadeOut( function () {
1687 $(this).remove();
1688 } );
1689 clearTimeout( _infoTimer );
1690 _infoTimer = null;
1691
1692 return this;
1693 }
1694
1695 if ( _infoTimer ) {
1696 clearTimeout( _infoTimer );
1697 }
1698
1699 if ( $('#datatables_buttons_info').length ) {
1700 $('#datatables_buttons_info').remove();
1701 }
1702
1703 title = title ? '<h2>'+title+'</h2>' : '';
1704
1705 $('<div id="datatables_buttons_info" class="dt-button-info"/>')
1706 .html( title )
1707 .append( $('<div/>')[ typeof message === 'string' ? 'html' : 'append' ]( message ) )
1708 .css( 'display', 'none' )
1709 .appendTo( 'body' )
1710 .fadeIn();
1711
1712 if ( time !== undefined && time !== 0 ) {
1713 _infoTimer = setTimeout( function () {
1714 that.buttons.info( false );
1715 }, time );
1716 }
1717
1718 this.on('destroy.btn-info', function () {
1719 that.buttons.info(false);
1720 });
1721
1722 return this;
1723} );
1724
1725// Get data from the table for export - this is common to a number of plug-in
1726// buttons so it is included in the Buttons core library
1727DataTable.Api.register( 'buttons.exportData()', function ( options ) {
1728 if ( this.context.length ) {
1729 return _exportData( new DataTable.Api( this.context[0] ), options );
1730 }
1731} );
1732
1733// Get information about the export that is common to many of the export data
1734// types (DRY)
1735DataTable.Api.register( 'buttons.exportInfo()', function ( conf ) {
1736 if ( ! conf ) {
1737 conf = {};
1738 }
1739
1740 return {
1741 filename: _filename( conf ),
1742 title: _title( conf ),
1743 messageTop: _message(this, conf.message || conf.messageTop, 'top'),
1744 messageBottom: _message(this, conf.messageBottom, 'bottom')
1745 };
1746} );
1747
1748
1749
1750/**
1751 * Get the file name for an exported file.
1752 *
1753 * @param {object} config Button configuration
1754 * @param {boolean} incExtension Include the file name extension
1755 */
1756var _filename = function ( config )
1757{
1758 // Backwards compatibility
1759 var filename = config.filename === '*' && config.title !== '*' && config.title !== undefined && config.title !== null && config.title !== '' ?
1760 config.title :
1761 config.filename;
1762
1763 if ( typeof filename === 'function' ) {
1764 filename = filename();
1765 }
1766
1767 if ( filename === undefined || filename === null ) {
1768 return null;
1769 }
1770
1771 if ( filename.indexOf( '*' ) !== -1 ) {
1772 filename = $.trim( filename.replace( '*', $('head > title').text() ) );
1773 }
1774
1775 // Strip characters which the OS will object to
1776 filename = filename.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, "");
1777
1778 var extension = _stringOrFunction( config.extension );
1779 if ( ! extension ) {
1780 extension = '';
1781 }
1782
1783 return filename + extension;
1784};
1785
1786/**
1787 * Simply utility method to allow parameters to be given as a function
1788 *
1789 * @param {undefined|string|function} option Option
1790 * @return {null|string} Resolved value
1791 */
1792var _stringOrFunction = function ( option )
1793{
1794 if ( option === null || option === undefined ) {
1795 return null;
1796 }
1797 else if ( typeof option === 'function' ) {
1798 return option();
1799 }
1800 return option;
1801};
1802
1803/**
1804 * Get the title for an exported file.
1805 *
1806 * @param {object} config Button configuration
1807 */
1808var _title = function ( config )
1809{
1810 var title = _stringOrFunction( config.title );
1811
1812 return title === null ?
1813 null : title.indexOf( '*' ) !== -1 ?
1814 title.replace( '*', $('head > title').text() || 'Exported data' ) :
1815 title;
1816};
1817
1818var _message = function ( dt, option, position )
1819{
1820 var message = _stringOrFunction( option );
1821 if ( message === null ) {
1822 return null;
1823 }
1824
1825 var caption = $('caption', dt.table().container()).eq(0);
1826 if ( message === '*' ) {
1827 var side = caption.css( 'caption-side' );
1828 if ( side !== position ) {
1829 return null;
1830 }
1831
1832 return caption.length ?
1833 caption.text() :
1834 '';
1835 }
1836
1837 return message;
1838};
1839
1840
1841
1842
1843
1844
1845
1846var _exportTextarea = $('<textarea/>')[0];
1847var _exportData = function ( dt, inOpts )
1848{
1849 var config = $.extend( true, {}, {
1850 rows: null,
1851 columns: '',
1852 modifier: {
1853 search: 'applied',
1854 order: 'applied'
1855 },
1856 orthogonal: 'display',
1857 stripHtml: true,
1858 stripNewlines: true,
1859 decodeEntities: true,
1860 trim: true,
1861 format: {
1862 header: function ( d ) {
1863 return strip( d );
1864 },
1865 footer: function ( d ) {
1866 return strip( d );
1867 },
1868 body: function ( d ) {
1869 return strip( d );
1870 }
1871 },
1872 customizeData: null
1873 }, inOpts );
1874
1875 var strip = function ( str ) {
1876 if ( typeof str !== 'string' ) {
1877 return str;
1878 }
1879
1880 // Always remove script tags
1881 str = str.replace( /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '' );
1882
1883 // Always remove comments
1884 str = str.replace( /<!\-\-.*?\-\->/g, '' );
1885
1886 if ( config.stripHtml ) {
1887 str = str.replace( /<[^>]*>/g, '' );
1888 }
1889
1890 if ( config.trim ) {
1891 str = str.replace( /^\s+|\s+$/g, '' );
1892 }
1893
1894 if ( config.stripNewlines ) {
1895 str = str.replace( /\n/g, ' ' );
1896 }
1897
1898 if ( config.decodeEntities ) {
1899 _exportTextarea.innerHTML = str;
1900 str = _exportTextarea.value;
1901 }
1902
1903 return str;
1904 };
1905
1906
1907 var header = dt.columns( config.columns ).indexes().map( function (idx) {
1908 var el = dt.column( idx ).header();
1909 return config.format.header( el.innerHTML, idx, el );
1910 } ).toArray();
1911
1912 var footer = dt.table().footer() ?
1913 dt.columns( config.columns ).indexes().map( function (idx) {
1914 var el = dt.column( idx ).footer();
1915 return config.format.footer( el ? el.innerHTML : '', idx, el );
1916 } ).toArray() :
1917 null;
1918
1919 // If Select is available on this table, and any rows are selected, limit the export
1920 // to the selected rows. If no rows are selected, all rows will be exported. Specify
1921 // a `selected` modifier to control directly.
1922 var modifier = $.extend( {}, config.modifier );
1923 if ( dt.select && typeof dt.select.info === 'function' && modifier.selected === undefined ) {
1924 if ( dt.rows( config.rows, $.extend( { selected: true }, modifier ) ).any() ) {
1925 $.extend( modifier, { selected: true } )
1926 }
1927 }
1928
1929 var rowIndexes = dt.rows( config.rows, modifier ).indexes().toArray();
1930 var selectedCells = dt.cells( rowIndexes, config.columns );
1931 var cells = selectedCells
1932 .render( config.orthogonal )
1933 .toArray();
1934 var cellNodes = selectedCells
1935 .nodes()
1936 .toArray();
1937
1938 var columns = header.length;
1939 var rows = columns > 0 ? cells.length / columns : 0;
1940 var body = [];
1941 var cellCounter = 0;
1942
1943 for ( var i=0, ien=rows ; i<ien ; i++ ) {
1944 var row = [ columns ];
1945
1946 for ( var j=0 ; j<columns ; j++ ) {
1947 row[j] = config.format.body( cells[ cellCounter ], i, j, cellNodes[ cellCounter ] );
1948 cellCounter++;
1949 }
1950
1951 body[i] = row;
1952 }
1953
1954 var data = {
1955 header: header,
1956 footer: footer,
1957 body: body
1958 };
1959
1960 if ( config.customizeData ) {
1961 config.customizeData( data );
1962 }
1963
1964 return data;
1965};
1966
1967
1968/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1969 * DataTables interface
1970 */
1971
1972// Attach to DataTables objects for global access
1973$.fn.dataTable.Buttons = Buttons;
1974$.fn.DataTable.Buttons = Buttons;
1975
1976
1977
1978// DataTables creation - check if the buttons have been defined for this table,
1979// they will have been if the `B` option was used in `dom`, otherwise we should
1980// create the buttons instance here so they can be inserted into the document
1981// using the API. Listen for `init` for compatibility with pre 1.10.10, but to
1982// be removed in future.
1983$(document).on( 'init.dt plugin-init.dt', function (e, settings) {
1984 if ( e.namespace !== 'dt' ) {
1985 return;
1986 }
1987
1988 var opts = settings.oInit.buttons || DataTable.defaults.buttons;
1989
1990 if ( opts && ! settings._buttons ) {
1991 new Buttons( settings, opts ).container();
1992 }
1993} );
1994
1995function _init ( settings ) {
1996 var api = new DataTable.Api( settings );
1997 var opts = api.init().buttons || DataTable.defaults.buttons;
1998
1999 return new Buttons( api, opts ).container();
2000}
2001
2002// DataTables `dom` feature option
2003DataTable.ext.feature.push( {
2004 fnInit: _init,
2005 cFeature: "B"
2006} );
2007
2008// DataTables 2 layout feature
2009if ( DataTable.ext.features ) {
2010 DataTable.ext.features.register( 'buttons', _init );
2011}
2012
2013
2014return Buttons;
2015}));
Note: See TracBrowser for help on using the repository browser.