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