diff options
Diffstat (limited to 'dbdocs/bower/datatables.net-buttons/dataTables.buttons.js')
-rw-r--r-- | dbdocs/bower/datatables.net-buttons/dataTables.buttons.js | 1705 |
1 files changed, 1705 insertions, 0 deletions
diff --git a/dbdocs/bower/datatables.net-buttons/dataTables.buttons.js b/dbdocs/bower/datatables.net-buttons/dataTables.buttons.js new file mode 100644 index 0000000..02ceacb --- /dev/null +++ b/dbdocs/bower/datatables.net-buttons/dataTables.buttons.js @@ -0,0 +1,1705 @@ +/*! 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; +})); |