| /*! Buttons for DataTables 1.3.1 |
| * ©2016 SpryMedia Ltd - datatables.net/license |
| */ |
| |
| (function( factory ){ |
| if ( typeof define === 'function' && define.amd ) { |
| // AMD |
| define( ['jquery', 'datatables.net'], function ( $ ) { |
| return factory( $, window, document ); |
| } ); |
| } |
| else if ( typeof exports === 'object' ) { |
| // CommonJS |
| module.exports = function (root, $) { |
| if ( ! root ) { |
| root = window; |
| } |
| |
| if ( ! $ || ! $.fn.dataTable ) { |
| $ = require('datatables.net')(root, $).$; |
| } |
| |
| return factory( $, root, root.document ); |
| }; |
| } |
| else { |
| // Browser |
| factory( jQuery, window, document ); |
| } |
| }(function( $, window, document, undefined ) { |
| 'use strict'; |
| var DataTable = $.fn.dataTable; |
| |
| |
| // Used for namespacing events added to the document by each instance, so they |
| // can be removed on destroy |
| var _instCounter = 0; |
| |
| // Button namespacing counter for namespacing events on individual buttons |
| var _buttonCounter = 0; |
| |
| var _dtButtons = DataTable.ext.buttons; |
| |
| /** |
| * [Buttons description] |
| * @param {[type]} |
| * @param {[type]} |
| */ |
| var Buttons = function( dt, config ) |
| { |
| // If there is no config set it to an empty object |
| if ( typeof( config ) === 'undefined' ) { |
| config = {}; |
| } |
| |
| // Allow a boolean true for defaults |
| if ( config === true ) { |
| config = {}; |
| } |
| |
| // For easy configuration of buttons an array can be given |
| if ( $.isArray( config ) ) { |
| config = { buttons: config }; |
| } |
| |
| this.c = $.extend( true, {}, Buttons.defaults, config ); |
| |
| // Don't want a deep copy for the buttons |
| if ( config.buttons ) { |
| this.c.buttons = config.buttons; |
| } |
| |
| this.s = { |
| dt: new DataTable.Api( dt ), |
| buttons: [], |
| listenKeys: '', |
| namespace: 'dtb'+(_instCounter++) |
| }; |
| |
| this.dom = { |
| container: $('<'+this.c.dom.container.tag+'/>') |
| .addClass( this.c.dom.container.className ) |
| }; |
| |
| this._constructor(); |
| }; |
| |
| |
| $.extend( Buttons.prototype, { |
| /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
| * Public methods |
| */ |
| |
| /** |
| * Get the action of a button |
| * @param {int|string} Button index |
| * @return {function} |
| *//** |
| * Set the action of a button |
| * @param {node} node Button element |
| * @param {function} action Function to set |
| * @return {Buttons} Self for chaining |
| */ |
| action: function ( node, action ) |
| { |
| var button = this._nodeToButton( node ); |
| |
| if ( action === undefined ) { |
| return button.conf.action; |
| } |
| |
| button.conf.action = action; |
| |
| return this; |
| }, |
| |
| /** |
| * Add an active class to the button to make to look active or get current |
| * active state. |
| * @param {node} node Button element |
| * @param {boolean} [flag] Enable / disable flag |
| * @return {Buttons} Self for chaining or boolean for getter |
| */ |
| active: function ( node, flag ) { |
| var button = this._nodeToButton( node ); |
| var klass = this.c.dom.button.active; |
| var jqNode = $(button.node); |
| |
| if ( flag === undefined ) { |
| return jqNode.hasClass( klass ); |
| } |
| |
| jqNode.toggleClass( klass, flag === undefined ? true : flag ); |
| |
| return this; |
| }, |
| |
| /** |
| * Add a new button |
| * @param {object} config Button configuration object, base string name or function |
| * @param {int|string} [idx] Button index for where to insert the button |
| * @return {Buttons} Self for chaining |
| */ |
| add: function ( config, idx ) |
| { |
| var buttons = this.s.buttons; |
| |
| if ( typeof idx === 'string' ) { |
| var split = idx.split('-'); |
| var base = this.s; |
| |
| for ( var i=0, ien=split.length-1 ; i<ien ; i++ ) { |
| base = base.buttons[ split[i]*1 ]; |
| } |
| |
| buttons = base.buttons; |
| idx = split[ split.length-1 ]*1; |
| } |
| |
| this._expandButton( buttons, config, false, idx ); |
| this._draw(); |
| |
| return this; |
| }, |
| |
| /** |
| * Get the container node for the buttons |
| * @return {jQuery} Buttons node |
| */ |
| container: function () |
| { |
| return this.dom.container; |
| }, |
| |
| /** |
| * Disable a button |
| * @param {node} node Button node |
| * @return {Buttons} Self for chaining |
| */ |
| disable: function ( node ) { |
| var button = this._nodeToButton( node ); |
| |
| $(button.node).addClass( this.c.dom.button.disabled ); |
| |
| return this; |
| }, |
| |
| /** |
| * Destroy the instance, cleaning up event handlers and removing DOM |
| * elements |
| * @return {Buttons} Self for chaining |
| */ |
| destroy: function () |
| { |
| // Key event listener |
| $('body').off( 'keyup.'+this.s.namespace ); |
| |
| // Individual button destroy (so they can remove their own events if |
| // needed). Take a copy as the array is modified by `remove` |
| var buttons = this.s.buttons.slice(); |
| var i, ien; |
| |
| for ( i=0, ien=buttons.length ; i<ien ; i++ ) { |
| this.remove( buttons[i].node ); |
| } |
| |
| // Container |
| this.dom.container.remove(); |
| |
| // Remove from the settings object collection |
| var buttonInsts = this.s.dt.settings()[0]; |
| |
| for ( i=0, ien=buttonInsts.length ; i<ien ; i++ ) { |
| if ( buttonInsts.inst === this ) { |
| buttonInsts.splice( i, 1 ); |
| break; |
| } |
| } |
| |
| return this; |
| }, |
| |
| /** |
| * Enable / disable a button |
| * @param {node} node Button node |
| * @param {boolean} [flag=true] Enable / disable flag |
| * @return {Buttons} Self for chaining |
| */ |
| enable: function ( node, flag ) |
| { |
| if ( flag === false ) { |
| return this.disable( node ); |
| } |
| |
| var button = this._nodeToButton( node ); |
| $(button.node).removeClass( this.c.dom.button.disabled ); |
| |
| return this; |
| }, |
| |
| /** |
| * Get the instance name for the button set selector |
| * @return {string} Instance name |
| */ |
| name: function () |
| { |
| return this.c.name; |
| }, |
| |
| /** |
| * Get a button's node |
| * @param {node} node Button node |
| * @return {jQuery} Button element |
| */ |
| node: function ( node ) |
| { |
| var button = this._nodeToButton( node ); |
| return $(button.node); |
| }, |
| |
| /** |
| * Set / get a processing class on the selected button |
| * @param {boolean} flag true to add, false to remove, undefined to get |
| * @return {boolean|Buttons} Getter value or this if a setter. |
| */ |
| processing: function ( node, flag ) |
| { |
| var button = this._nodeToButton( node ); |
| |
| if ( flag === undefined ) { |
| return $(button.node).hasClass( 'processing' ); |
| } |
| |
| $(button.node).toggleClass( 'processing', flag ); |
| |
| return this; |
| }, |
| |
| /** |
| * Remove a button. |
| * @param {node} node Button node |
| * @return {Buttons} Self for chaining |
| */ |
| remove: function ( node ) |
| { |
| var button = this._nodeToButton( node ); |
| var host = this._nodeToHost( node ); |
| var dt = this.s.dt; |
| |
| // Remove any child buttons first |
| if ( button.buttons.length ) { |
| for ( var i=button.buttons.length-1 ; i>=0 ; i-- ) { |
| this.remove( button.buttons[i].node ); |
| } |
| } |
| |
| // Allow the button to remove event handlers, etc |
| if ( button.conf.destroy ) { |
| button.conf.destroy.call( dt.button(node), dt, $(node), button.conf ); |
| } |
| |
| this._removeKey( button.conf ); |
| |
| $(button.node).remove(); |
| |
| var idx = $.inArray( button, host ); |
| host.splice( idx, 1 ); |
| |
| return this; |
| }, |
| |
| /** |
| * Get the text for a button |
| * @param {int|string} node Button index |
| * @return {string} Button text |
| *//** |
| * Set the text for a button |
| * @param {int|string|function} node Button index |
| * @param {string} label Text |
| * @return {Buttons} Self for chaining |
| */ |
| text: function ( node, label ) |
| { |
| var button = this._nodeToButton( node ); |
| var buttonLiner = this.c.dom.collection.buttonLiner; |
| var linerTag = button.inCollection && buttonLiner && buttonLiner.tag ? |
| buttonLiner.tag : |
| this.c.dom.buttonLiner.tag; |
| var dt = this.s.dt; |
| var jqNode = $(button.node); |
| var text = function ( opt ) { |
| return typeof opt === 'function' ? |
| opt( dt, jqNode, button.conf ) : |
| opt; |
| }; |
| |
| if ( label === undefined ) { |
| return text( button.conf.text ); |
| } |
| |
| button.conf.text = label; |
| |
| if ( linerTag ) { |
| jqNode.children( linerTag ).html( text(label) ); |
| } |
| else { |
| jqNode.html( text(label) ); |
| } |
| |
| return this; |
| }, |
| |
| |
| /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
| * Constructor |
| */ |
| |
| /** |
| * Buttons constructor |
| * @private |
| */ |
| _constructor: function () |
| { |
| var that = this; |
| var dt = this.s.dt; |
| var dtSettings = dt.settings()[0]; |
| var buttons = this.c.buttons; |
| |
| if ( ! dtSettings._buttons ) { |
| dtSettings._buttons = []; |
| } |
| |
| dtSettings._buttons.push( { |
| inst: this, |
| name: this.c.name |
| } ); |
| |
| for ( var i=0, ien=buttons.length ; i<ien ; i++ ) { |
| this.add( buttons[i] ); |
| } |
| |
| dt.on( 'destroy', function () { |
| that.destroy(); |
| } ); |
| |
| // Global key event binding to listen for button keys |
| $('body').on( 'keyup.'+this.s.namespace, function ( e ) { |
| if ( ! document.activeElement || document.activeElement === document.body ) { |
| // SUse a string of characters for fast lookup of if we need to |
| // handle this |
| var character = String.fromCharCode(e.keyCode).toLowerCase(); |
| |
| if ( that.s.listenKeys.toLowerCase().indexOf( character ) !== -1 ) { |
| that._keypress( character, e ); |
| } |
| } |
| } ); |
| }, |
| |
| |
| /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
| * Private methods |
| */ |
| |
| /** |
| * Add a new button to the key press listener |
| * @param {object} conf Resolved button configuration object |
| * @private |
| */ |
| _addKey: function ( conf ) |
| { |
| if ( conf.key ) { |
| this.s.listenKeys += $.isPlainObject( conf.key ) ? |
| conf.key.key : |
| conf.key; |
| } |
| }, |
| |
| /** |
| * Insert the buttons into the container. Call without parameters! |
| * @param {node} [container] Recursive only - Insert point |
| * @param {array} [buttons] Recursive only - Buttons array |
| * @private |
| */ |
| _draw: function ( container, buttons ) |
| { |
| if ( ! container ) { |
| container = this.dom.container; |
| buttons = this.s.buttons; |
| } |
| |
| container.children().detach(); |
| |
| for ( var i=0, ien=buttons.length ; i<ien ; i++ ) { |
| container.append( buttons[i].inserter ); |
| |
| if ( buttons[i].buttons && buttons[i].buttons.length ) { |
| this._draw( buttons[i].collection, buttons[i].buttons ); |
| } |
| } |
| }, |
| |
| /** |
| * Create buttons from an array of buttons |
| * @param {array} attachTo Buttons array to attach to |
| * @param {object} button Button definition |
| * @param {boolean} inCollection true if the button is in a collection |
| * @private |
| */ |
| _expandButton: function ( attachTo, button, inCollection, attachPoint ) |
| { |
| var dt = this.s.dt; |
| var buttonCounter = 0; |
| var buttons = ! $.isArray( button ) ? |
| [ button ] : |
| button; |
| |
| for ( var i=0, ien=buttons.length ; i<ien ; i++ ) { |
| var conf = this._resolveExtends( buttons[i] ); |
| |
| if ( ! conf ) { |
| continue; |
| } |
| |
| // If the configuration is an array, then expand the buttons at this |
| // point |
| if ( $.isArray( conf ) ) { |
| this._expandButton( attachTo, conf, inCollection, attachPoint ); |
| continue; |
| } |
| |
| var built = this._buildButton( conf, inCollection ); |
| if ( ! built ) { |
| continue; |
| } |
| |
| if ( attachPoint !== undefined ) { |
| attachTo.splice( attachPoint, 0, built ); |
| attachPoint++; |
| } |
| else { |
| attachTo.push( built ); |
| } |
| |
| if ( built.conf.buttons ) { |
| var collectionDom = this.c.dom.collection; |
| built.collection = $('<'+collectionDom.tag+'/>') |
| .addClass( collectionDom.className ) |
| .attr( 'role', 'menu') ; |
| built.conf._collection = built.collection; |
| |
| this._expandButton( built.buttons, built.conf.buttons, true, attachPoint ); |
| } |
| |
| // init call is made here, rather than buildButton as it needs to |
| // be selectable, and for that it needs to be in the buttons array |
| if ( conf.init ) { |
| conf.init.call( dt.button( built.node ), dt, $(built.node), conf ); |
| } |
| |
| buttonCounter++; |
| } |
| }, |
| |
| /** |
| * Create an individual button |
| * @param {object} config Resolved button configuration |
| * @param {boolean} inCollection `true` if a collection button |
| * @return {jQuery} Created button node (jQuery) |
| * @private |
| */ |
| _buildButton: function ( config, inCollection ) |
| { |
| var buttonDom = this.c.dom.button; |
| var linerDom = this.c.dom.buttonLiner; |
| var collectionDom = this.c.dom.collection; |
| var dt = this.s.dt; |
| var text = function ( opt ) { |
| return typeof opt === 'function' ? |
| opt( dt, button, config ) : |
| opt; |
| }; |
| |
| if ( inCollection && collectionDom.button ) { |
| buttonDom = collectionDom.button; |
| } |
| |
| if ( inCollection && collectionDom.buttonLiner ) { |
| linerDom = collectionDom.buttonLiner; |
| } |
| |
| // Make sure that the button is available based on whatever requirements |
| // it has. For example, Flash buttons require Flash |
| if ( config.available && ! config.available( dt, config ) ) { |
| return false; |
| } |
| |
| var action = function ( e, dt, button, config ) { |
| config.action.call( dt.button( button ), e, dt, button, config ); |
| |
| $(dt.table().node()).triggerHandler( 'buttons-action.dt', [ |
| dt.button( button ), dt, button, config |
| ] ); |
| }; |
| |
| var button = $('<'+buttonDom.tag+'/>') |
| .addClass( buttonDom.className ) |
| .attr( 'tabindex', this.s.dt.settings()[0].iTabIndex ) |
| .attr( 'aria-controls', this.s.dt.table().node().id ) |
| .on( 'click.dtb', function (e) { |
| e.preventDefault(); |
| |
| if ( ! button.hasClass( buttonDom.disabled ) && config.action ) { |
| action( e, dt, button, config ); |
| } |
| |
| button.blur(); |
| } ) |
| .on( 'keyup.dtb', function (e) { |
| if ( e.keyCode === 13 ) { |
| if ( ! button.hasClass( buttonDom.disabled ) && config.action ) { |
| action( e, dt, button, config ); |
| } |
| } |
| } ); |
| |
| // Make `a` tags act like a link |
| if ( buttonDom.tag.toLowerCase() === 'a' ) { |
| button.attr( 'href', '#' ); |
| } |
| |
| if ( linerDom.tag ) { |
| var liner = $('<'+linerDom.tag+'/>') |
| .html( text( config.text ) ) |
| .addClass( linerDom.className ); |
| |
| if ( linerDom.tag.toLowerCase() === 'a' ) { |
| liner.attr( 'href', '#' ); |
| } |
| |
| button.append( liner ); |
| } |
| else { |
| button.html( text( config.text ) ); |
| } |
| |
| if ( config.enabled === false ) { |
| button.addClass( buttonDom.disabled ); |
| } |
| |
| if ( config.className ) { |
| button.addClass( config.className ); |
| } |
| |
| if ( config.titleAttr ) { |
| button.attr( 'title', text( config.titleAttr ) ); |
| } |
| |
| if ( ! config.namespace ) { |
| config.namespace = '.dt-button-'+(_buttonCounter++); |
| } |
| |
| var buttonContainer = this.c.dom.buttonContainer; |
| var inserter; |
| if ( buttonContainer && buttonContainer.tag ) { |
| inserter = $('<'+buttonContainer.tag+'/>') |
| .addClass( buttonContainer.className ) |
| .append( button ); |
| } |
| else { |
| inserter = button; |
| } |
| |
| this._addKey( config ); |
| |
| return { |
| conf: config, |
| node: button.get(0), |
| inserter: inserter, |
| buttons: [], |
| inCollection: inCollection, |
| collection: null |
| }; |
| }, |
| |
| /** |
| * Get the button object from a node (recursive) |
| * @param {node} node Button node |
| * @param {array} [buttons] Button array, uses base if not defined |
| * @return {object} Button object |
| * @private |
| */ |
| _nodeToButton: function ( node, buttons ) |
| { |
| if ( ! buttons ) { |
| buttons = this.s.buttons; |
| } |
| |
| for ( var i=0, ien=buttons.length ; i<ien ; i++ ) { |
| if ( buttons[i].node === node ) { |
| return buttons[i]; |
| } |
| |
| if ( buttons[i].buttons.length ) { |
| var ret = this._nodeToButton( node, buttons[i].buttons ); |
| |
| if ( ret ) { |
| return ret; |
| } |
| } |
| } |
| }, |
| |
| /** |
| * Get container array for a button from a button node (recursive) |
| * @param {node} node Button node |
| * @param {array} [buttons] Button array, uses base if not defined |
| * @return {array} Button's host array |
| * @private |
| */ |
| _nodeToHost: function ( node, buttons ) |
| { |
| if ( ! buttons ) { |
| buttons = this.s.buttons; |
| } |
| |
| for ( var i=0, ien=buttons.length ; i<ien ; i++ ) { |
| if ( buttons[i].node === node ) { |
| return buttons; |
| } |
| |
| if ( buttons[i].buttons.length ) { |
| var ret = this._nodeToHost( node, buttons[i].buttons ); |
| |
| if ( ret ) { |
| return ret; |
| } |
| } |
| } |
| }, |
| |
| /** |
| * Handle a key press - determine if any button's key configured matches |
| * what was typed and trigger the action if so. |
| * @param {string} character The character pressed |
| * @param {object} e Key event that triggered this call |
| * @private |
| */ |
| _keypress: function ( character, e ) |
| { |
| var run = function ( conf, node ) { |
| if ( ! conf.key ) { |
| return; |
| } |
| |
| if ( conf.key === character ) { |
| $(node).click(); |
| } |
| else if ( $.isPlainObject( conf.key ) ) { |
| if ( conf.key.key !== character ) { |
| return; |
| } |
| |
| if ( conf.key.shiftKey && ! e.shiftKey ) { |
| return; |
| } |
| |
| if ( conf.key.altKey && ! e.altKey ) { |
| return; |
| } |
| |
| if ( conf.key.ctrlKey && ! e.ctrlKey ) { |
| return; |
| } |
| |
| if ( conf.key.metaKey && ! e.metaKey ) { |
| return; |
| } |
| |
| // Made it this far - it is good |
| $(node).click(); |
| } |
| }; |
| |
| var recurse = function ( a ) { |
| for ( var i=0, ien=a.length ; i<ien ; i++ ) { |
| run( a[i].conf, a[i].node ); |
| |
| if ( a[i].buttons.length ) { |
| recurse( a[i].buttons ); |
| } |
| } |
| }; |
| |
| recurse( this.s.buttons ); |
| }, |
| |
| /** |
| * Remove a key from the key listener for this instance (to be used when a |
| * button is removed) |
| * @param {object} conf Button configuration |
| * @private |
| */ |
| _removeKey: function ( conf ) |
| { |
| if ( conf.key ) { |
| var character = $.isPlainObject( conf.key ) ? |
| conf.key.key : |
| conf.key; |
| |
| // Remove only one character, as multiple buttons could have the |
| // same listening key |
| var a = this.s.listenKeys.split(''); |
| var idx = $.inArray( character, a ); |
| a.splice( idx, 1 ); |
| this.s.listenKeys = a.join(''); |
| } |
| }, |
| |
| /** |
| * Resolve a button configuration |
| * @param {string|function|object} conf Button config to resolve |
| * @return {object} Button configuration |
| * @private |
| */ |
| _resolveExtends: function ( conf ) |
| { |
| var dt = this.s.dt; |
| var i, ien; |
| var toConfObject = function ( base ) { |
| var loop = 0; |
| |
| // Loop until we have resolved to a button configuration, or an |
| // array of button configurations (which will be iterated |
| // separately) |
| while ( ! $.isPlainObject(base) && ! $.isArray(base) ) { |
| if ( base === undefined ) { |
| return; |
| } |
| |
| if ( typeof base === 'function' ) { |
| base = base( dt, conf ); |
| |
| if ( ! base ) { |
| return false; |
| } |
| } |
| else if ( typeof base === 'string' ) { |
| if ( ! _dtButtons[ base ] ) { |
| throw 'Unknown button type: '+base; |
| } |
| |
| base = _dtButtons[ base ]; |
| } |
| |
| loop++; |
| if ( loop > 30 ) { |
| // Protect against misconfiguration killing the browser |
| throw 'Buttons: Too many iterations'; |
| } |
| } |
| |
| return $.isArray( base ) ? |
| base : |
| $.extend( {}, base ); |
| }; |
| |
| conf = toConfObject( conf ); |
| |
| while ( conf && conf.extend ) { |
| // Use `toConfObject` in case the button definition being extended |
| // is itself a string or a function |
| if ( ! _dtButtons[ conf.extend ] ) { |
| throw 'Cannot extend unknown button type: '+conf.extend; |
| } |
| |
| var objArray = toConfObject( _dtButtons[ conf.extend ] ); |
| if ( $.isArray( objArray ) ) { |
| return objArray; |
| } |
| else if ( ! objArray ) { |
| // This is a little brutal as it might be possible to have a |
| // valid button without the extend, but if there is no extend |
| // then the host button would be acting in an undefined state |
| return false; |
| } |
| |
| // Stash the current class name |
| var originalClassName = objArray.className; |
| |
| conf = $.extend( {}, objArray, conf ); |
| |
| // The extend will have overwritten the original class name if the |
| // `conf` object also assigned a class, but we want to concatenate |
| // them so they are list that is combined from all extended buttons |
| if ( originalClassName && conf.className !== originalClassName ) { |
| conf.className = originalClassName+' '+conf.className; |
| } |
| |
| // Buttons to be added to a collection -gives the ability to define |
| // if buttons should be added to the start or end of a collection |
| var postfixButtons = conf.postfixButtons; |
| if ( postfixButtons ) { |
| if ( ! conf.buttons ) { |
| conf.buttons = []; |
| } |
| |
| for ( i=0, ien=postfixButtons.length ; i<ien ; i++ ) { |
| conf.buttons.push( postfixButtons[i] ); |
| } |
| |
| conf.postfixButtons = null; |
| } |
| |
| var prefixButtons = conf.prefixButtons; |
| if ( prefixButtons ) { |
| if ( ! conf.buttons ) { |
| conf.buttons = []; |
| } |
| |
| for ( i=0, ien=prefixButtons.length ; i<ien ; i++ ) { |
| conf.buttons.splice( i, 0, prefixButtons[i] ); |
| } |
| |
| conf.prefixButtons = null; |
| } |
| |
| // Although we want the `conf` object to overwrite almost all of |
| // the properties of the object being extended, the `extend` |
| // property should come from the object being extended |
| conf.extend = objArray.extend; |
| } |
| |
| return conf; |
| } |
| } ); |
| |
| |
| |
| /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
| * Statics |
| */ |
| |
| /** |
| * Show / hide a background layer behind a collection |
| * @param {boolean} Flag to indicate if the background should be shown or |
| * hidden |
| * @param {string} Class to assign to the background |
| * @static |
| */ |
| Buttons.background = function ( show, className, fade ) { |
| if ( fade === undefined ) { |
| fade = 400; |
| } |
| |
| if ( show ) { |
| $('<div/>') |
| .addClass( className ) |
| .css( 'display', 'none' ) |
| .appendTo( 'body' ) |
| .fadeIn( fade ); |
| } |
| else { |
| $('body > div.'+className) |
| .fadeOut( fade, function () { |
| $(this) |
| .removeClass( className ) |
| .remove(); |
| } ); |
| } |
| }; |
| |
| /** |
| * Instance selector - select Buttons instances based on an instance selector |
| * value from the buttons assigned to a DataTable. This is only useful if |
| * multiple instances are attached to a DataTable. |
| * @param {string|int|array} Instance selector - see `instance-selector` |
| * documentation on the DataTables site |
| * @param {array} Button instance array that was attached to the DataTables |
| * settings object |
| * @return {array} Buttons instances |
| * @static |
| */ |
| Buttons.instanceSelector = function ( group, buttons ) |
| { |
| if ( ! group ) { |
| return $.map( buttons, function ( v ) { |
| return v.inst; |
| } ); |
| } |
| |
| var ret = []; |
| var names = $.map( buttons, function ( v ) { |
| return v.name; |
| } ); |
| |
| // Flatten the group selector into an array of single options |
| var process = function ( input ) { |
| if ( $.isArray( input ) ) { |
| for ( var i=0, ien=input.length ; i<ien ; i++ ) { |
| process( input[i] ); |
| } |
| return; |
| } |
| |
| if ( typeof input === 'string' ) { |
| if ( input.indexOf( ',' ) !== -1 ) { |
| // String selector, list of names |
| process( input.split(',') ); |
| } |
| else { |
| // String selector individual name |
| var idx = $.inArray( $.trim(input), names ); |
| |
| if ( idx !== -1 ) { |
| ret.push( buttons[ idx ].inst ); |
| } |
| } |
| } |
| else if ( typeof input === 'number' ) { |
| // Index selector |
| ret.push( buttons[ input ].inst ); |
| } |
| }; |
| |
| process( group ); |
| |
| return ret; |
| }; |
| |
| /** |
| * Button selector - select one or more buttons from a selector input so some |
| * operation can be performed on them. |
| * @param {array} Button instances array that the selector should operate on |
| * @param {string|int|node|jQuery|array} Button selector - see |
| * `button-selector` documentation on the DataTables site |
| * @return {array} Array of objects containing `inst` and `idx` properties of |
| * the selected buttons so you know which instance each button belongs to. |
| * @static |
| */ |
| Buttons.buttonSelector = function ( insts, selector ) |
| { |
| var ret = []; |
| var nodeBuilder = function ( a, buttons, baseIdx ) { |
| var button; |
| var idx; |
| |
| for ( var i=0, ien=buttons.length ; i<ien ; i++ ) { |
| button = buttons[i]; |
| |
| if ( button ) { |
| idx = baseIdx !== undefined ? |
| baseIdx+i : |
| i+''; |
| |
| a.push( { |
| node: button.node, |
| name: button.conf.name, |
| idx: idx |
| } ); |
| |
| if ( button.buttons ) { |
| nodeBuilder( a, button.buttons, idx+'-' ); |
| } |
| } |
| } |
| }; |
| |
| var run = function ( selector, inst ) { |
| var i, ien; |
| var buttons = []; |
| nodeBuilder( buttons, inst.s.buttons ); |
| |
| var nodes = $.map( buttons, function (v) { |
| return v.node; |
| } ); |
| |
| if ( $.isArray( selector ) || selector instanceof $ ) { |
| for ( i=0, ien=selector.length ; i<ien ; i++ ) { |
| run( selector[i], inst ); |
| } |
| return; |
| } |
| |
| if ( selector === null || selector === undefined || selector === '*' ) { |
| // Select all |
| for ( i=0, ien=buttons.length ; i<ien ; i++ ) { |
| ret.push( { |
| inst: inst, |
| node: buttons[i].node |
| } ); |
| } |
| } |
| else if ( typeof selector === 'number' ) { |
| // Main button index selector |
| ret.push( { |
| inst: inst, |
| node: inst.s.buttons[ selector ].node |
| } ); |
| } |
| else if ( typeof selector === 'string' ) { |
| if ( selector.indexOf( ',' ) !== -1 ) { |
| // Split |
| var a = selector.split(','); |
| |
| for ( i=0, ien=a.length ; i<ien ; i++ ) { |
| run( $.trim(a[i]), inst ); |
| } |
| } |
| else if ( selector.match( /^\d+(\-\d+)*$/ ) ) { |
| // Sub-button index selector |
| var indexes = $.map( buttons, function (v) { |
| return v.idx; |
| } ); |
| |
| ret.push( { |
| inst: inst, |
| node: buttons[ $.inArray( selector, indexes ) ].node |
| } ); |
| } |
| else if ( selector.indexOf( ':name' ) !== -1 ) { |
| // Button name selector |
| var name = selector.replace( ':name', '' ); |
| |
| for ( i=0, ien=buttons.length ; i<ien ; i++ ) { |
| if ( buttons[i].name === name ) { |
| ret.push( { |
| inst: inst, |
| node: buttons[i].node |
| } ); |
| } |
| } |
| } |
| else { |
| // jQuery selector on the nodes |
| $( nodes ).filter( selector ).each( function () { |
| ret.push( { |
| inst: inst, |
| node: this |
| } ); |
| } ); |
| } |
| } |
| else if ( typeof selector === 'object' && selector.nodeName ) { |
| // Node selector |
| var idx = $.inArray( selector, nodes ); |
| |
| if ( idx !== -1 ) { |
| ret.push( { |
| inst: inst, |
| node: nodes[ idx ] |
| } ); |
| } |
| } |
| }; |
| |
| |
| for ( var i=0, ien=insts.length ; i<ien ; i++ ) { |
| var inst = insts[i]; |
| |
| run( selector, inst ); |
| } |
| |
| return ret; |
| }; |
| |
| |
| /** |
| * Buttons defaults. For full documentation, please refer to the docs/option |
| * directory or the DataTables site. |
| * @type {Object} |
| * @static |
| */ |
| Buttons.defaults = { |
| buttons: [ 'copy', 'excel', 'csv', 'pdf', 'print' ], |
| name: 'main', |
| tabIndex: 0, |
| dom: { |
| container: { |
| tag: 'div', |
| className: 'dt-buttons' |
| }, |
| collection: { |
| tag: 'div', |
| className: 'dt-button-collection' |
| }, |
| button: { |
| tag: 'a', |
| className: 'dt-button', |
| active: 'active', |
| disabled: 'disabled' |
| }, |
| buttonLiner: { |
| tag: 'span', |
| className: '' |
| } |
| } |
| }; |
| |
| /** |
| * Version information |
| * @type {string} |
| * @static |
| */ |
| Buttons.version = '1.3.1'; |
| |
| |
| $.extend( _dtButtons, { |
| collection: { |
| text: function ( dt ) { |
| return dt.i18n( 'buttons.collection', 'Collection' ); |
| }, |
| className: 'buttons-collection', |
| action: function ( e, dt, button, config ) { |
| var host = button; |
| var hostOffset = host.offset(); |
| var tableContainer = $( dt.table().container() ); |
| var multiLevel = false; |
| |
| // Remove any old collection |
| if ( $('div.dt-button-background').length ) { |
| multiLevel = $('.dt-button-collection').offset(); |
| $('body').trigger( 'click.dtb-collection' ); |
| } |
| |
| config._collection |
| .addClass( config.collectionLayout ) |
| .css( 'display', 'none' ) |
| .appendTo( 'body' ) |
| .fadeIn( config.fade ); |
| |
| var position = config._collection.css( 'position' ); |
| |
| if ( multiLevel && position === 'absolute' ) { |
| config._collection.css( { |
| top: multiLevel.top, |
| left: multiLevel.left |
| } ); |
| } |
| else if ( position === 'absolute' ) { |
| config._collection.css( { |
| top: hostOffset.top + host.outerHeight(), |
| left: hostOffset.left |
| } ); |
| |
| var listRight = hostOffset.left + config._collection.outerWidth(); |
| var tableRight = tableContainer.offset().left + tableContainer.width(); |
| if ( listRight > tableRight ) { |
| config._collection.css( 'left', hostOffset.left - ( listRight - tableRight ) ); |
| } |
| } |
| else { |
| // Fix position - centre on screen |
| var top = config._collection.height() / 2; |
| if ( top > $(window).height() / 2 ) { |
| top = $(window).height() / 2; |
| } |
| |
| config._collection.css( 'marginTop', top*-1 ); |
| } |
| |
| if ( config.background ) { |
| Buttons.background( true, config.backgroundClassName, config.fade ); |
| } |
| |
| // Need to break the 'thread' for the collection button being |
| // activated by a click - it would also trigger this event |
| setTimeout( function () { |
| // This is bonkers, but if we don't have a click listener on the |
| // background element, iOS Safari will ignore the body click |
| // listener below. An empty function here is all that is |
| // required to make it work... |
| $('div.dt-button-background').on( 'click.dtb-collection', function () {} ); |
| |
| $('body').on( 'click.dtb-collection', function (e) { |
| // andSelf is deprecated in jQ1.8, but we want 1.7 compat |
| var back = $.fn.addBack ? 'addBack' : 'andSelf'; |
| |
| if ( ! $(e.target).parents()[back]().filter( config._collection ).length ) { |
| config._collection |
| .fadeOut( config.fade, function () { |
| config._collection.detach(); |
| } ); |
| |
| $('div.dt-button-background').off( 'click.dtb-collection' ); |
| Buttons.background( false, config.backgroundClassName, config.fade ); |
| |
| $('body').off( 'click.dtb-collection' ); |
| dt.off( 'buttons-action.b-internal' ); |
| } |
| } ); |
| }, 10 ); |
| |
| if ( config.autoClose ) { |
| dt.on( 'buttons-action.b-internal', function () { |
| $('div.dt-button-background').click(); |
| } ); |
| } |
| }, |
| background: true, |
| collectionLayout: '', |
| backgroundClassName: 'dt-button-background', |
| autoClose: false, |
| fade: 400 |
| }, |
| copy: function ( dt, conf ) { |
| if ( _dtButtons.copyHtml5 ) { |
| return 'copyHtml5'; |
| } |
| if ( _dtButtons.copyFlash && _dtButtons.copyFlash.available( dt, conf ) ) { |
| return 'copyFlash'; |
| } |
| }, |
| csv: function ( dt, conf ) { |
| // Common option that will use the HTML5 or Flash export buttons |
| if ( _dtButtons.csvHtml5 && _dtButtons.csvHtml5.available( dt, conf ) ) { |
| return 'csvHtml5'; |
| } |
| if ( _dtButtons.csvFlash && _dtButtons.csvFlash.available( dt, conf ) ) { |
| return 'csvFlash'; |
| } |
| }, |
| excel: function ( dt, conf ) { |
| // Common option that will use the HTML5 or Flash export buttons |
| if ( _dtButtons.excelHtml5 && _dtButtons.excelHtml5.available( dt, conf ) ) { |
| return 'excelHtml5'; |
| } |
| if ( _dtButtons.excelFlash && _dtButtons.excelFlash.available( dt, conf ) ) { |
| return 'excelFlash'; |
| } |
| }, |
| pdf: function ( dt, conf ) { |
| // Common option that will use the HTML5 or Flash export buttons |
| if ( _dtButtons.pdfHtml5 && _dtButtons.pdfHtml5.available( dt, conf ) ) { |
| return 'pdfHtml5'; |
| } |
| if ( _dtButtons.pdfFlash && _dtButtons.pdfFlash.available( dt, conf ) ) { |
| return 'pdfFlash'; |
| } |
| }, |
| pageLength: function ( dt ) { |
| var lengthMenu = dt.settings()[0].aLengthMenu; |
| var vals = $.isArray( lengthMenu[0] ) ? lengthMenu[0] : lengthMenu; |
| var lang = $.isArray( lengthMenu[0] ) ? lengthMenu[1] : lengthMenu; |
| var text = function ( dt ) { |
| return dt.i18n( 'buttons.pageLength', { |
| "-1": 'Show all rows', |
| _: 'Show %d rows' |
| }, dt.page.len() ); |
| }; |
| |
| return { |
| extend: 'collection', |
| text: text, |
| className: 'buttons-page-length', |
| autoClose: true, |
| buttons: $.map( vals, function ( val, i ) { |
| return { |
| text: lang[i], |
| className: 'button-page-length', |
| action: function ( e, dt ) { |
| dt.page.len( val ).draw(); |
| }, |
| init: function ( dt, node, conf ) { |
| var that = this; |
| var fn = function () { |
| that.active( dt.page.len() === val ); |
| }; |
| |
| dt.on( 'length.dt'+conf.namespace, fn ); |
| fn(); |
| }, |
| destroy: function ( dt, node, conf ) { |
| dt.off( 'length.dt'+conf.namespace ); |
| } |
| }; |
| } ), |
| init: function ( dt, node, conf ) { |
| var that = this; |
| dt.on( 'length.dt'+conf.namespace, function () { |
| that.text( text( dt ) ); |
| } ); |
| }, |
| destroy: function ( dt, node, conf ) { |
| dt.off( 'length.dt'+conf.namespace ); |
| } |
| }; |
| } |
| } ); |
| |
| |
| /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
| * DataTables API |
| * |
| * For complete documentation, please refer to the docs/api directory or the |
| * DataTables site |
| */ |
| |
| // Buttons group and individual button selector |
| DataTable.Api.register( 'buttons()', function ( group, selector ) { |
| // Argument shifting |
| if ( selector === undefined ) { |
| selector = group; |
| group = undefined; |
| } |
| |
| this.selector.buttonGroup = group; |
| |
| var res = this.iterator( true, 'table', function ( ctx ) { |
| if ( ctx._buttons ) { |
| return Buttons.buttonSelector( |
| Buttons.instanceSelector( group, ctx._buttons ), |
| selector |
| ); |
| } |
| }, true ); |
| |
| res._groupSelector = group; |
| return res; |
| } ); |
| |
| // Individual button selector |
| DataTable.Api.register( 'button()', function ( group, selector ) { |
| // just run buttons() and truncate |
| var buttons = this.buttons( group, selector ); |
| |
| if ( buttons.length > 1 ) { |
| buttons.splice( 1, buttons.length ); |
| } |
| |
| return buttons; |
| } ); |
| |
| // Active buttons |
| DataTable.Api.registerPlural( 'buttons().active()', 'button().active()', function ( flag ) { |
| if ( flag === undefined ) { |
| return this.map( function ( set ) { |
| return set.inst.active( set.node ); |
| } ); |
| } |
| |
| return this.each( function ( set ) { |
| set.inst.active( set.node, flag ); |
| } ); |
| } ); |
| |
| // Get / set button action |
| DataTable.Api.registerPlural( 'buttons().action()', 'button().action()', function ( action ) { |
| if ( action === undefined ) { |
| return this.map( function ( set ) { |
| return set.inst.action( set.node ); |
| } ); |
| } |
| |
| return this.each( function ( set ) { |
| set.inst.action( set.node, action ); |
| } ); |
| } ); |
| |
| // Enable / disable buttons |
| DataTable.Api.register( ['buttons().enable()', 'button().enable()'], function ( flag ) { |
| return this.each( function ( set ) { |
| set.inst.enable( set.node, flag ); |
| } ); |
| } ); |
| |
| // Disable buttons |
| DataTable.Api.register( ['buttons().disable()', 'button().disable()'], function () { |
| return this.each( function ( set ) { |
| set.inst.disable( set.node ); |
| } ); |
| } ); |
| |
| // Get button nodes |
| DataTable.Api.registerPlural( 'buttons().nodes()', 'button().node()', function () { |
| var jq = $(); |
| |
| // jQuery will automatically reduce duplicates to a single entry |
| $( this.each( function ( set ) { |
| jq = jq.add( set.inst.node( set.node ) ); |
| } ) ); |
| |
| return jq; |
| } ); |
| |
| // Get / set button processing state |
| DataTable.Api.registerPlural( 'buttons().processing()', 'button().processing()', function ( flag ) { |
| if ( flag === undefined ) { |
| return this.map( function ( set ) { |
| return set.inst.processing( set.node ); |
| } ); |
| } |
| |
| return this.each( function ( set ) { |
| set.inst.processing( set.node, flag ); |
| } ); |
| } ); |
| |
| // Get / set button text (i.e. the button labels) |
| DataTable.Api.registerPlural( 'buttons().text()', 'button().text()', function ( label ) { |
| if ( label === undefined ) { |
| return this.map( function ( set ) { |
| return set.inst.text( set.node ); |
| } ); |
| } |
| |
| return this.each( function ( set ) { |
| set.inst.text( set.node, label ); |
| } ); |
| } ); |
| |
| // Trigger a button's action |
| DataTable.Api.registerPlural( 'buttons().trigger()', 'button().trigger()', function () { |
| return this.each( function ( set ) { |
| set.inst.node( set.node ).trigger( 'click' ); |
| } ); |
| } ); |
| |
| // Get the container elements |
| DataTable.Api.registerPlural( 'buttons().containers()', 'buttons().container()', function () { |
| var jq = $(); |
| var groupSelector = this._groupSelector; |
| |
| // We need to use the group selector directly, since if there are no buttons |
| // the result set will be empty |
| this.iterator( true, 'table', function ( ctx ) { |
| if ( ctx._buttons ) { |
| var insts = Buttons.instanceSelector( groupSelector, ctx._buttons ); |
| |
| for ( var i=0, ien=insts.length ; i<ien ; i++ ) { |
| jq = jq.add( insts[i].container() ); |
| } |
| } |
| } ); |
| |
| return jq; |
| } ); |
| |
| // Add a new button |
| DataTable.Api.register( 'button().add()', function ( idx, conf ) { |
| var ctx = this.context; |
| |
| // Don't use `this` as it could be empty - select the instances directly |
| if ( ctx.length ) { |
| var inst = Buttons.instanceSelector( this._groupSelector, ctx[0]._buttons ); |
| |
| if ( inst.length ) { |
| inst[0].add( conf, idx ); |
| } |
| } |
| |
| return this.button( this._groupSelector, idx ); |
| } ); |
| |
| // Destroy the button sets selected |
| DataTable.Api.register( 'buttons().destroy()', function () { |
| this.pluck( 'inst' ).unique().each( function ( inst ) { |
| inst.destroy(); |
| } ); |
| |
| return this; |
| } ); |
| |
| // Remove a button |
| DataTable.Api.registerPlural( 'buttons().remove()', 'buttons().remove()', function () { |
| this.each( function ( set ) { |
| set.inst.remove( set.node ); |
| } ); |
| |
| return this; |
| } ); |
| |
| // Information box that can be used by buttons |
| var _infoTimer; |
| DataTable.Api.register( 'buttons.info()', function ( title, message, time ) { |
| var that = this; |
| |
| if ( title === false ) { |
| $('#datatables_buttons_info').fadeOut( function () { |
| $(this).remove(); |
| } ); |
| clearTimeout( _infoTimer ); |
| _infoTimer = null; |
| |
| return this; |
| } |
| |
| if ( _infoTimer ) { |
| clearTimeout( _infoTimer ); |
| } |
| |
| if ( $('#datatables_buttons_info').length ) { |
| $('#datatables_buttons_info').remove(); |
| } |
| |
| title = title ? '<h2>'+title+'</h2>' : ''; |
| |
| $('<div id="datatables_buttons_info" class="dt-button-info"/>') |
| .html( title ) |
| .append( $('<div/>')[ typeof message === 'string' ? 'html' : 'append' ]( message ) ) |
| .css( 'display', 'none' ) |
| .appendTo( 'body' ) |
| .fadeIn(); |
| |
| if ( time !== undefined && time !== 0 ) { |
| _infoTimer = setTimeout( function () { |
| that.buttons.info( false ); |
| }, time ); |
| } |
| |
| return this; |
| } ); |
| |
| // Get data from the table for export - this is common to a number of plug-in |
| // buttons so it is included in the Buttons core library |
| DataTable.Api.register( 'buttons.exportData()', function ( options ) { |
| if ( this.context.length ) { |
| return _exportData( new DataTable.Api( this.context[0] ), options ); |
| } |
| } ); |
| |
| |
| var _exportTextarea = $('<textarea/>')[0]; |
| var _exportData = function ( dt, inOpts ) |
| { |
| var config = $.extend( true, {}, { |
| rows: null, |
| columns: '', |
| modifier: { |
| search: 'applied', |
| order: 'applied' |
| }, |
| orthogonal: 'display', |
| stripHtml: true, |
| stripNewlines: true, |
| decodeEntities: true, |
| trim: true, |
| format: { |
| header: function ( d ) { |
| return strip( d ); |
| }, |
| footer: function ( d ) { |
| return strip( d ); |
| }, |
| body: function ( d ) { |
| return strip( d ); |
| } |
| } |
| }, inOpts ); |
| |
| var strip = function ( str ) { |
| if ( typeof str !== 'string' ) { |
| return str; |
| } |
| |
| // Always remove script tags |
| str = str.replace( /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '' ); |
| |
| if ( config.stripHtml ) { |
| str = str.replace( /<[^>]*>/g, '' ); |
| } |
| |
| if ( config.trim ) { |
| str = str.replace( /^\s+|\s+$/g, '' ); |
| } |
| |
| if ( config.stripNewlines ) { |
| str = str.replace( /\n/g, ' ' ); |
| } |
| |
| if ( config.decodeEntities ) { |
| _exportTextarea.innerHTML = str; |
| str = _exportTextarea.value; |
| } |
| |
| return str; |
| }; |
| |
| |
| var header = dt.columns( config.columns ).indexes().map( function (idx) { |
| var el = dt.column( idx ).header(); |
| return config.format.header( el.innerHTML, idx, el ); |
| } ).toArray(); |
| |
| var footer = dt.table().footer() ? |
| dt.columns( config.columns ).indexes().map( function (idx) { |
| var el = dt.column( idx ).footer(); |
| return config.format.footer( el ? el.innerHTML : '', idx, el ); |
| } ).toArray() : |
| null; |
| |
| var rowIndexes = dt.rows( config.rows, config.modifier ).indexes().toArray(); |
| var selectedCells = dt.cells( rowIndexes, config.columns ); |
| var cells = selectedCells |
| .render( config.orthogonal ) |
| .toArray(); |
| var cellNodes = selectedCells |
| .nodes() |
| .toArray(); |
| |
| var columns = header.length; |
| var rows = columns > 0 ? cells.length / columns : 0; |
| var body = new Array( rows ); |
| var cellCounter = 0; |
| |
| for ( var i=0, ien=rows ; i<ien ; i++ ) { |
| var row = new Array( columns ); |
| |
| for ( var j=0 ; j<columns ; j++ ) { |
| row[j] = config.format.body( cells[ cellCounter ], i, j, cellNodes[ cellCounter ] ); |
| cellCounter++; |
| } |
| |
| body[i] = row; |
| } |
| |
| return { |
| header: header, |
| footer: footer, |
| body: body |
| }; |
| }; |
| |
| |
| /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * |
| * DataTables interface |
| */ |
| |
| // Attach to DataTables objects for global access |
| $.fn.dataTable.Buttons = Buttons; |
| $.fn.DataTable.Buttons = Buttons; |
| |
| |
| |
| // DataTables creation - check if the buttons have been defined for this table, |
| // they will have been if the `B` option was used in `dom`, otherwise we should |
| // create the buttons instance here so they can be inserted into the document |
| // using the API. Listen for `init` for compatibility with pre 1.10.10, but to |
| // be removed in future. |
| $(document).on( 'init.dt plugin-init.dt', function (e, settings) { |
| if ( e.namespace !== 'dt' ) { |
| return; |
| } |
| |
| var opts = settings.oInit.buttons || DataTable.defaults.buttons; |
| |
| if ( opts && ! settings._buttons ) { |
| new Buttons( settings, opts ).container(); |
| } |
| } ); |
| |
| // DataTables `dom` feature option |
| DataTable.ext.feature.push( { |
| fnInit: function( settings ) { |
| var api = new DataTable.Api( settings ); |
| var opts = api.init().buttons || DataTable.defaults.buttons; |
| |
| return new Buttons( api, opts ).container(); |
| }, |
| cFeature: "B" |
| } ); |
| |
| |
| return Buttons; |
| })); |