diff options
Diffstat (limited to 'build/resources/main/static/plugins/datatables-buttons/js/dataTables.buttons.js')
-rw-r--r-- | build/resources/main/static/plugins/datatables-buttons/js/dataTables.buttons.js | 2478 |
1 files changed, 2478 insertions, 0 deletions
diff --git a/build/resources/main/static/plugins/datatables-buttons/js/dataTables.buttons.js b/build/resources/main/static/plugins/datatables-buttons/js/dataTables.buttons.js new file mode 100644 index 0000000..f35a9a8 --- /dev/null +++ b/build/resources/main/static/plugins/datatables-buttons/js/dataTables.buttons.js @@ -0,0 +1,2478 @@ +/*! Buttons for DataTables 2.2.2 + * ©2016-2022 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; + +// Allow for jQuery slim +function _fadeIn(el, duration, fn) { + if ($.fn.animate) { + el + .stop() + .fadeIn( duration, fn ); + + } + else { + el.css('display', 'block'); + + if (fn) { + fn.call(el); + } + } +} + +function _fadeOut(el, duration, fn) { + if ($.fn.animate) { + el + .stop() + .fadeOut( duration, fn ); + } + else { + el.css('display', 'none'); + + if (fn) { + fn.call(el); + } + } +} + +/** + * [Buttons description] + * @param {[type]} + * @param {[type]} + */ +var Buttons = function( dt, config ) +{ + // If not created with a `new` keyword then we return a wrapper function that + // will take the settings object for a DT. This allows easy use of new instances + // with the `layout` option - e.g. `topLeft: $.fn.dataTable.Buttons( ... )`. + if ( !(this instanceof Buttons) ) { + return function (settings) { + return new Buttons( settings, dt ).container(); + }; + } + + // 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 ( Array.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 + * @param {boolean} [draw=true] Trigger a draw. Set a false when adding + * lots of buttons, until the last button. + * @return {Buttons} Self for chaining + */ + add: function ( config, idx, draw ) + { + 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, + config !== undefined ? config.split : undefined, + (config === undefined || config.split === undefined || config.split.length === 0) && base !== undefined, + false, + idx + ); + + if (draw === undefined || draw === true) { + this._draw(); + } + + return this; + }, + + /** + * Clear buttons from a collection and then insert new buttons + */ + collectionRebuild: function ( node, newButtons ) + { + var button = this._nodeToButton( node ); + + if(newButtons !== undefined) { + var i; + // Need to reverse the array + for (i=button.buttons.length-1; i>=0; i--) { + this.remove(button.buttons[i].node); + } + + for (i=0; i<newButtons.length; i++) { + var newBtn = newButtons[i]; + + this._expandButton( + button.buttons, + newBtn, + newBtn !== undefined && newBtn.config !== undefined && newBtn.config.split !== undefined, + true, + newBtn.parentConf !== undefined && newBtn.parentConf.split !== undefined, + i, + newBtn.parentConf + ); + } + } + + this._draw(button.collection, button.buttons); + }, + + /** + * 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 ) + .attr('disabled', true); + + 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 ) + .removeAttr('disabled'); + + return this; + }, + + /** + * Get a button's index + * + * This is internally recursive + * @param {element} node Button to get the index of + * @return {string} Button index + */ + index: function ( node, nested, buttons ) + { + if ( ! nested ) { + nested = ''; + buttons = this.s.buttons; + } + + for ( var i=0, ien=buttons.length ; i<ien ; i++ ) { + var inner = buttons[i].buttons; + + if (buttons[i].node === node) { + return nested + i; + } + + if ( inner && inner.length ) { + var match = this.index(node, i + '-', inner); + + if (match !== null) { + return match; + } + } + } + + return null; + }, + + + /** + * Get the instance name for the button set selector + * @return {string} Instance name + */ + name: function () + { + return this.c.name; + }, + + /** + * Get a button's node of the buttons container if no button is given + * @param {node} [node] Button node + * @return {jQuery} Button element, or container + */ + node: function ( node ) + { + if ( ! node ) { + return this.dom.container; + } + + var button = this._nodeToButton( node ); + return $(button.node); + }, + + /** + * Set / get a processing class on the selected button + * @param {element} node Triggering button node + * @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 dt = this.s.dt; + var button = this._nodeToButton( node ); + + if ( flag === undefined ) { + return $(button.node).hasClass( 'processing' ); + } + + $(button.node).toggleClass( 'processing', flag ); + + $(dt.table().node()).triggerHandler( 'buttons-processing.dt', [ + flag, dt.button( node ), dt, $(node), button.conf + ] ); + + 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 ); + } + } + + button.conf.destroying = true; + + // 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 ) + .eq(0) + .filter(':not(.dt-down-arrow)') + .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 ( e, settings ) { + if ( settings === dtSettings ) { + 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 ); + container.append( ' ' ); + + 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, split, inCollection, inSplit, attachPoint, parentConf ) + { + var dt = this.s.dt; + var buttonCounter = 0; + var isSplit = false; + var buttons = ! Array.isArray( button ) ? + [ button ] : + button; + + if(button === undefined ) { + buttons = !Array.isArray(split) ? + [ split ] : + split; + } + + if (button !== undefined && button.split !== undefined) { + isSplit = true; + } + + for ( var i=0, ien=buttons.length ; i<ien ; i++ ) { + var conf = this._resolveExtends( buttons[i] ); + + if ( ! conf ) { + continue; + } + + if( conf.config !== undefined && conf.config.split) { + isSplit = true; + } + else { + isSplit = false; + } + + // If the configuration is an array, then expand the buttons at this + // point + if ( Array.isArray( conf ) ) { + this._expandButton( attachTo, conf, built !== undefined && built.conf !== undefined ? built.conf.split : undefined, inCollection, parentConf !== undefined && parentConf.split !== undefined, attachPoint, parentConf ); + continue; + } + + var built = this._buildButton( conf, inCollection, conf.split !== undefined || (conf.config !== undefined && conf.config.split !== undefined), inSplit ); + if ( ! built ) { + continue; + } + + if ( attachPoint !== undefined && attachPoint !== null ) { + attachTo.splice( attachPoint, 0, built ); + attachPoint++; + } + else { + attachTo.push( built ); + } + + + if ( built.conf.buttons || built.conf.split ) { + built.collection = $('<'+(isSplit ? this.c.dom.splitCollection.tag : this.c.dom.collection.tag)+'/>'); + + built.conf._collection = built.collection; + + if(built.conf.split) { + for(var j = 0; j < built.conf.split.length; j++) { + if(typeof built.conf.split[j] === "object") { + built.conf.split[j].parent = parentConf; + if(built.conf.split[j].collectionLayout === undefined) { + built.conf.split[j].collectionLayout = built.conf.collectionLayout; + } + if(built.conf.split[j].dropup === undefined) { + built.conf.split[j].dropup = built.conf.dropup; + } + if(built.conf.split[j].fade === undefined) { + built.conf.split[j].fade = built.conf.fade; + } + } + } + } + else { + $(built.node).append($('<span class="dt-down-arrow">'+this.c.dom.splitDropdown.text+'</span>')) + } + + this._expandButton( built.buttons, built.conf.buttons, built.conf.split, !isSplit, isSplit, attachPoint, built.conf ); + } + built.conf.parent = parentConf; + + // 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, isSplit, inSplit ) + { + var buttonDom = this.c.dom.button; + var linerDom = this.c.dom.buttonLiner; + var collectionDom = this.c.dom.collection; + var splitDom = this.c.dom.split; + var splitCollectionDom = this.c.dom.splitCollection; + var splitDropdownButton = this.c.dom.splitDropdownButton; + var dt = this.s.dt; + var text = function ( opt ) { + return typeof opt === 'function' ? + opt( dt, button, config ) : + opt; + }; + + // Spacers don't do much other than insert an element into the DOM + if (config.spacer) { + var spacer = $('<span></span>') + .addClass('dt-button-spacer ' + config.style + ' ' + buttonDom.spacerClass) + .html(text(config.text)); + + return { + conf: config, + node: spacer, + inserter: spacer, + buttons: [], + inCollection: inCollection, + isSplit: isSplit, + inSplit: inSplit, + collection: null + }; + } + + if ( !isSplit && inSplit && splitCollectionDom ) { + buttonDom = splitDropdownButton; + } + else if ( !isSplit && inCollection && collectionDom.button ) { + buttonDom = collectionDom.button; + } + + if ( !isSplit && inSplit && splitCollectionDom.buttonLiner ) { + linerDom = splitCollectionDom.buttonLiner + } + else if ( !isSplit && inCollection && collectionDom.buttonLiner ) { + linerDom = collectionDom.buttonLiner; + } + + // Make sure that the button is available based on whatever requirements + // it has. For example, PDF button require pdfmake + if ( config.available && ! config.available( dt, config ) && !config.hasOwnProperty('html') ) { + return false; + } + + var button; + if(!config.hasOwnProperty('html')) { + 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 tag = config.tag || buttonDom.tag; + var clickBlurs = config.clickBlurs === undefined + ? true : + config.clickBlurs; + + button = $('<'+tag+'/>') + .addClass( buttonDom.className ) + .addClass( inSplit ? this.c.dom.splitDropdownButton.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 ); + } + if( clickBlurs ) { + button.trigger('blur'); + } + } ) + .on( 'keypress.dtb', function (e) { + if ( e.keyCode === 13 ) { + e.preventDefault(); + + if ( ! button.hasClass( buttonDom.disabled ) && config.action ) { + action( e, dt, button, config ); + } + } + } ); + + // Make `a` tags act like a link + if ( tag.toLowerCase() === 'a' ) { + button.attr( 'href', '#' ); + } + + // Button tags should have `type=button` so they don't have any default behaviour + if ( tag.toLowerCase() === 'button' ) { + button.attr( 'type', 'button' ); + } + + 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.attr ) { + button.attr( config.attr ); + } + + if ( ! config.namespace ) { + config.namespace = '.dt-button-'+(_buttonCounter++); + } + + if ( config.config !== undefined && config.config.split ) { + config.split = config.config.split; + } + } + else { + button = $(config.html) + } + + 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 ); + + // Style integration callback for DOM manipulation + // Note that this is _not_ documented. It is currently + // for style integration only + if( this.c.buttonCreated ) { + inserter = this.c.buttonCreated( config, inserter ); + } + + var splitDiv; + if(isSplit) { + splitDiv = $('<div/>').addClass(this.c.dom.splitWrapper.className) + splitDiv.append(button); + var dropButtonConfig = $.extend(config, { + text: this.c.dom.splitDropdown.text, + className: this.c.dom.splitDropdown.className, + closeButton: false, + attr: { + 'aria-haspopup': true, + 'aria-expanded': false + }, + align: this.c.dom.splitDropdown.align, + splitAlignClass: this.c.dom.splitDropdown.splitAlignClass + + }) + + this._addKey(dropButtonConfig); + + var splitAction = function ( e, dt, button, config ) { + _dtButtons.split.action.call( dt.button($('div.dt-btn-split-wrapper')[0] ), e, dt, button, config ); + + $(dt.table().node()).triggerHandler( 'buttons-action.dt', [ + dt.button( button ), dt, button, config + ] ); + button.attr('aria-expanded', true) + }; + + var dropButton = $('<button class="' + this.c.dom.splitDropdown.className + ' dt-button"><span class="dt-btn-split-drop-arrow">'+this.c.dom.splitDropdown.text+'</span></button>') + .on( 'click.dtb', function (e) { + e.preventDefault(); + e.stopPropagation(); + + if ( ! dropButton.hasClass( buttonDom.disabled )) { + splitAction( e, dt, dropButton, dropButtonConfig ); + } + if ( clickBlurs ) { + dropButton.trigger('blur'); + } + } ) + .on( 'keypress.dtb', function (e) { + if ( e.keyCode === 13 ) { + e.preventDefault(); + + if ( ! dropButton.hasClass( buttonDom.disabled ) ) { + splitAction( e, dt, dropButton, dropButtonConfig ); + } + } + } ); + + if(config.split.length === 0) { + dropButton.addClass('dtb-hide-drop'); + } + + splitDiv.append(dropButton).attr(dropButtonConfig.attr); + } + + return { + conf: config, + node: isSplit ? splitDiv.get(0) : button.get(0), + inserter: isSplit ? splitDiv : inserter, + buttons: [], + inCollection: inCollection, + isSplit: isSplit, + inSplit: inSplit, + 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 ) + { + // Check if this button press already activated on another instance of Buttons + if ( e._buttonsHandled ) { + return; + } + + var run = function ( conf, node ) { + if ( ! conf.key ) { + return; + } + + if ( conf.key === character ) { + e._buttonsHandled = true; + $(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 + e._buttonsHandled = true; + $(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 that = this; + 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) && ! Array.isArray(base) ) { + if ( base === undefined ) { + return; + } + + if ( typeof base === 'function' ) { + base = base.call( that, dt, conf ); + + if ( ! base ) { + return false; + } + } + else if ( typeof base === 'string' ) { + if ( ! _dtButtons[ base ] ) { + return {html: base} + } + + base = _dtButtons[ base ]; + } + + loop++; + if ( loop > 30 ) { + // Protect against misconfiguration killing the browser + throw 'Buttons: Too many iterations'; + } + } + + return Array.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 ( Array.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; + + if (conf.config !== undefined && objArray.config !== undefined) { + conf.config = $.extend({}, objArray.config, conf.config) + } + + 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; + }, + + /** + * Display (and replace if there is an existing one) a popover attached to a button + * @param {string|node} content Content to show + * @param {DataTable.Api} hostButton DT API instance of the button + * @param {object} inOpts Options (see object below for all options) + */ + _popover: function ( content, hostButton, inOpts, e ) { + var dt = hostButton; + var buttonsSettings = this.c; + var closed = false; + var options = $.extend( { + align: 'button-left', // button-right, dt-container, split-left, split-right + autoClose: false, + background: true, + backgroundClassName: 'dt-button-background', + closeButton: true, + contentClassName: buttonsSettings.dom.collection.className, + collectionLayout: '', + collectionTitle: '', + dropup: false, + fade: 400, + popoverTitle: '', + rightAlignClassName: 'dt-button-right', + tag: buttonsSettings.dom.collection.tag + }, inOpts ); + + var hostNode = hostButton.node(); + + var close = function () { + closed = true; + + _fadeOut( + $('.dt-button-collection'), + options.fade, + function () { + $(this).detach(); + } + ); + + $(dt.buttons( '[aria-haspopup="true"][aria-expanded="true"]' ).nodes()) + .attr('aria-expanded', 'false'); + + $('div.dt-button-background').off( 'click.dtb-collection' ); + Buttons.background( false, options.backgroundClassName, options.fade, hostNode ); + + $(window).off('resize.resize.dtb-collection'); + $('body').off( '.dtb-collection' ); + dt.off( 'buttons-action.b-internal' ); + dt.off( 'destroy' ); + }; + + if (content === false) { + close(); + return; + } + + var existingExpanded = $(dt.buttons( '[aria-haspopup="true"][aria-expanded="true"]' ).nodes()); + if ( existingExpanded.length ) { + // Reuse the current position if the button that was triggered is inside an existing collection + if (hostNode.closest('div.dt-button-collection').length) { + hostNode = existingExpanded.eq(0); + } + + close(); + } + + // Try to be smart about the layout + var cnt = $('.dt-button', content).length; + var mod = ''; + + if (cnt === 3) { + mod = 'dtb-b3'; + } + else if (cnt === 2) { + mod = 'dtb-b2'; + } + else if (cnt === 1) { + mod = 'dtb-b1'; + } + + var display = $('<div/>') + .addClass('dt-button-collection') + .addClass(options.collectionLayout) + .addClass(options.splitAlignClass) + .addClass(mod) + .css('display', 'none'); + + content = $(content) + .addClass(options.contentClassName) + .attr('role', 'menu') + .appendTo(display); + + hostNode.attr( 'aria-expanded', 'true' ); + + if ( hostNode.parents('body')[0] !== document.body ) { + hostNode = document.body.lastChild; + } + + if ( options.popoverTitle ) { + display.prepend('<div class="dt-button-collection-title">'+options.popoverTitle+'</div>'); + } + else if ( options.collectionTitle ) { + display.prepend('<div class="dt-button-collection-title">'+options.collectionTitle+'</div>'); + } + + if (options.closeButton) { + display.prepend('<div class="dtb-popover-close">x</div>').addClass('dtb-collection-closeable') + } + + _fadeIn( display.insertAfter( hostNode ), options.fade ); + + var tableContainer = $( hostButton.table().container() ); + var position = display.css( 'position' ); + + if ( options.span === 'container' || options.align === 'dt-container' ) { + hostNode = hostNode.parent(); + display.css('width', tableContainer.width()); + } + + // Align the popover relative to the DataTables container + // Useful for wide popovers such as SearchPanes + if (position === 'absolute') { + // Align relative to the host button + var offsetParent = $(hostNode[0].offsetParent); + var buttonPosition = hostNode.position(); + var buttonOffset = hostNode.offset(); + var tableSizes = offsetParent.offset(); + var containerPosition = offsetParent.position(); + var computed = window.getComputedStyle(offsetParent[0]); + + tableSizes.height = offsetParent.outerHeight(); + tableSizes.width = offsetParent.width() + parseFloat(computed.paddingLeft); + tableSizes.right = tableSizes.left + tableSizes.width; + tableSizes.bottom = tableSizes.top + tableSizes.height; + + // Set the initial position so we can read height / width + var top = buttonPosition.top + hostNode.outerHeight(); + var left = buttonPosition.left; + + display.css( { + top: top, + left: left + } ); + + // Get the popover position + computed = window.getComputedStyle(display[0]); + var popoverSizes = display.offset(); + + popoverSizes.height = display.outerHeight(); + popoverSizes.width = display.outerWidth(); + popoverSizes.right = popoverSizes.left + popoverSizes.width; + popoverSizes.bottom = popoverSizes.top + popoverSizes.height; + popoverSizes.marginTop = parseFloat(computed.marginTop); + popoverSizes.marginBottom = parseFloat(computed.marginBottom); + + // First position per the class requirements - pop up and right align + if (options.dropup) { + top = buttonPosition.top - popoverSizes.height - popoverSizes.marginTop - popoverSizes.marginBottom; + } + + if (options.align === 'button-right' || display.hasClass( options.rightAlignClassName )) { + left = buttonPosition.left - popoverSizes.width + hostNode.outerWidth(); + } + + // Container alignment - make sure it doesn't overflow the table container + if (options.align === 'dt-container' || options.align === 'container') { + if (left < buttonPosition.left) { + left = -buttonPosition.left; + } + + if (left + popoverSizes.width > tableSizes.width) { + left = tableSizes.width - popoverSizes.width; + } + } + + // Window adjustment + if (containerPosition.left + left + popoverSizes.width > $(window).width()) { + // Overflowing the document to the right + left = $(window).width() - popoverSizes.width - containerPosition.left; + } + + if (buttonOffset.left + left < 0) { + // Off to the left of the document + left = -buttonOffset.left; + } + + if (containerPosition.top + top + popoverSizes.height > $(window).height() + $(window).scrollTop()) { + // Pop up if otherwise we'd need the user to scroll down + top = buttonPosition.top - popoverSizes.height - popoverSizes.marginTop - popoverSizes.marginBottom; + } + + if (containerPosition.top + top < $(window).scrollTop()) { + // Correction for when the top is beyond the top of the page + top = buttonPosition.top + hostNode.outerHeight(); + } + + // Calculations all done - now set it + display.css( { + top: top, + left: left + } ); + } + else { + // Fix position - centre on screen + var position = function () { + var half = $(window).height() / 2; + + var top = display.height() / 2; + if ( top > half ) { + top = half; + } + + display.css( 'marginTop', top*-1 ); + }; + + position(); + + $(window).on('resize.dtb-collection', function () { + position(); + }); + } + + if ( options.background ) { + Buttons.background( + true, + options.backgroundClassName, + options.fade, + options.backgroundHost || hostNode + ); + } + + // 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 () {} ); + + if ( options.autoClose ) { + setTimeout( function () { + dt.on( 'buttons-action.b-internal', function (e, btn, dt, node) { + if ( node[0] === hostNode[0] ) { + return; + } + close(); + } ); + }, 0); + } + + $(display).trigger('buttons-popover.dt'); + + + dt.on('destroy', close); + + setTimeout(function() { + closed = false; + $('body') + .on( 'click.dtb-collection', function (e) { + if (closed) { + return; + } + + // andSelf is deprecated in jQ1.8, but we want 1.7 compat + var back = $.fn.addBack ? 'addBack' : 'andSelf'; + var parent = $(e.target).parent()[0]; + + if (( ! $(e.target).parents()[back]().filter( content ).length && !$(parent).hasClass('dt-buttons')) || $(e.target).hasClass('dt-button-background')) { + close(); + } + } ) + .on( 'keyup.dtb-collection', function (e) { + if ( e.keyCode === 27 ) { + close(); + } + } ); + }, 0); + } +} ); + + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * 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, insertPoint ) { + if ( fade === undefined ) { + fade = 400; + } + if ( ! insertPoint ) { + insertPoint = document.body; + } + + if ( show ) { + _fadeIn( + $('<div/>') + .addClass( className ) + .css( 'display', 'none' ) + .insertAfter( insertPoint ), + fade + ); + } + else { + _fadeOut( + $('div.'+className), + 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 === undefined || group === null ) { + 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 ( Array.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( input.trim(), names ); + + if ( idx !== -1 ) { + ret.push( buttons[ idx ].inst ); + } + } + } + else if ( typeof input === 'number' ) { + // Index selector + ret.push( buttons[ input ].inst ); + } + else if ( typeof input === 'object' ) { + // Actual instance selector + ret.push( input ); + } + }; + + 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 ( Array.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 + if (inst.s.buttons[ 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( a[i].trim(), 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; +}; + +/** + * Default function used for formatting output data. + * @param {*} str Data to strip + */ +Buttons.stripData = function ( str, config ) { + if ( typeof str !== 'string' ) { + return str; + } + + // Always remove script tags + str = str.replace( /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '' ); + + // Always remove comments + str = str.replace( /<!\-\-.*?\-\->/g, '' ); + + if ( ! config || config.stripHtml ) { + str = str.replace( /<[^>]*>/g, '' ); + } + + if ( ! config || config.trim ) { + str = str.replace( /^\s+|\s+$/g, '' ); + } + + if ( ! config || config.stripNewlines ) { + str = str.replace( /\n/g, ' ' ); + } + + if ( ! config || config.decodeEntities ) { + _exportTextarea.innerHTML = str; + str = _exportTextarea.value; + } + + return str; +}; + + +/** + * 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: '' + }, + button: { + tag: 'button', + className: 'dt-button', + active: 'active', + disabled: 'disabled', + spacerClass: '' + }, + buttonLiner: { + tag: 'span', + className: '' + }, + split: { + tag: 'div', + className: 'dt-button-split', + }, + splitWrapper: { + tag: 'div', + className: 'dt-btn-split-wrapper', + }, + splitDropdown: { + tag: 'button', + text: '▼', + className: 'dt-btn-split-drop', + align: 'split-right', + splitAlignClass: 'dt-button-split-left' + }, + splitDropdownButton: { + tag: 'button', + className: 'dt-btn-split-drop-button dt-button', + }, + splitCollection: { + tag: 'div', + className: 'dt-button-split-collection', + } + } +}; + +/** + * Version information + * @type {string} + * @static + */ +Buttons.version = '2.2.2'; + + +$.extend( _dtButtons, { + collection: { + text: function ( dt ) { + return dt.i18n( 'buttons.collection', 'Collection' ); + }, + className: 'buttons-collection', + closeButton: false, + init: function ( dt, button, config ) { + button.attr( 'aria-expanded', false ); + }, + action: function ( e, dt, button, config ) { + if ( config._collection.parents('body').length ) { + this.popover(false, config); + } + else { + this.popover(config._collection, config); + } + }, + attr: { + 'aria-haspopup': true + } + // Also the popover options, defined in Buttons.popover + }, + split: { + text: function ( dt ) { + return dt.i18n( 'buttons.split', 'Split' ); + }, + className: 'buttons-split', + closeButton: false, + init: function ( dt, button, config ) { + return button.attr( 'aria-expanded', false ); + }, + action: function ( e, dt, button, config ) { + this.popover(config._collection, config); + }, + attr: { + 'aria-haspopup': true + } + // Also the popover options, defined in Buttons.popover + }, + copy: function ( dt, conf ) { + if ( _dtButtons.copyHtml5 ) { + return 'copyHtml5'; + } + }, + csv: function ( dt, conf ) { + if ( _dtButtons.csvHtml5 && _dtButtons.csvHtml5.available( dt, conf ) ) { + return 'csvHtml5'; + } + }, + excel: function ( dt, conf ) { + if ( _dtButtons.excelHtml5 && _dtButtons.excelHtml5.available( dt, conf ) ) { + return 'excelHtml5'; + } + }, + pdf: function ( dt, conf ) { + if ( _dtButtons.pdfHtml5 && _dtButtons.pdfHtml5.available( dt, conf ) ) { + return 'pdfHtml5'; + } + }, + pageLength: function ( dt ) { + var lengthMenu = dt.settings()[0].aLengthMenu; + var vals = []; + var lang = []; + var text = function ( dt ) { + return dt.i18n( 'buttons.pageLength', { + "-1": 'Show all rows', + _: 'Show %d rows' + }, dt.page.len() ); + }; + + // Support for DataTables 1.x 2D array + if (Array.isArray( lengthMenu[0] )) { + vals = lengthMenu[0]; + lang = lengthMenu[1]; + } + else { + for (var i=0 ; i<lengthMenu.length ; i++) { + var option = lengthMenu[i]; + + // Support for DataTables 2 object in the array + if ($.isPlainObject(option)) { + vals.push(option.value); + lang.push(option.label); + } + else { + vals.push(option); + lang.push(option); + } + } + } + + 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( conf.text ); + } ); + }, + destroy: function ( dt, node, conf ) { + dt.off( 'length.dt'+conf.namespace ); + } + }; + }, + spacer: { + style: 'empty', + spacer: true, + text: function ( dt ) { + return dt.i18n( 'buttons.spacer', '' ); + } + } +} ); + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * 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 ); + } ); +} ); + +// Collection control +DataTable.Api.registerPlural( 'buttons().collectionRebuild()', 'button().collectionRebuild()', function ( buttons ) { + return this.each( function ( set ) { + for(var i = 0; i < buttons.length; i++) { + if(typeof buttons[i] === 'object') { + buttons[i].parentConf = set; + } + } + set.inst.collectionRebuild( set.node, buttons ); + } ); +} ); + +// 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 ); + } ); +} ); + +// Button index +DataTable.Api.register( 'button().index()', function () { + var idx = null; + + this.each( function ( set ) { + var res = set.inst.index( set.node ); + + if (res !== null) { + idx = res; + } + } ); + + return idx; +} ); + +// 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' ); + } ); +} ); + +// Button resolver to the popover +DataTable.Api.register( 'button().popover()', function (content, options) { + return this.map( function ( set ) { + return set.inst._popover( content, this.button(this[0].node), options ); + } ); +} ); + +// Get the container elements +DataTable.Api.register( 'buttons().containers()', 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; +} ); + +DataTable.Api.register( 'buttons().container()', function () { + // API level of nesting is `buttons()` so we can zip into the containers method + return this.containers().eq(0); +} ); + +// Add a new button +DataTable.Api.register( 'button().add()', function ( idx, conf, draw ) { + 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 , draw); + } + } + + 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 ) { + this.off('destroy.btn-info'); + _fadeOut( + $('#datatables_buttons_info'), + 400, + 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>' : ''; + + _fadeIn( + $('<div id="datatables_buttons_info" class="dt-button-info"/>') + .html( title ) + .append( $('<div/>')[ typeof message === 'string' ? 'html' : 'append' ]( message ) ) + .css( 'display', 'none' ) + .appendTo( 'body' ) + ); + + if ( time !== undefined && time !== 0 ) { + _infoTimer = setTimeout( function () { + that.buttons.info( false ); + }, time ); + } + + this.on('destroy.btn-info', function () { + that.buttons.info(false); + }); + + 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 ); + } +} ); + +// Get information about the export that is common to many of the export data +// types (DRY) +DataTable.Api.register( 'buttons.exportInfo()', function ( conf ) { + if ( ! conf ) { + conf = {}; + } + + return { + filename: _filename( conf ), + title: _title( conf ), + messageTop: _message(this, conf.message || conf.messageTop, 'top'), + messageBottom: _message(this, conf.messageBottom, 'bottom') + }; +} ); + + + +/** + * Get the file name for an exported file. + * + * @param {object} config Button configuration + * @param {boolean} incExtension Include the file name extension + */ +var _filename = function ( config ) +{ + // Backwards compatibility + var filename = config.filename === '*' && config.title !== '*' && config.title !== undefined && config.title !== null && config.title !== '' ? + config.title : + config.filename; + + if ( typeof filename === 'function' ) { + filename = filename(); + } + + if ( filename === undefined || filename === null ) { + return null; + } + + if ( filename.indexOf( '*' ) !== -1 ) { + filename = filename.replace( '*', $('head > title').text() ).trim(); + } + + // Strip characters which the OS will object to + filename = filename.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, ""); + + var extension = _stringOrFunction( config.extension ); + if ( ! extension ) { + extension = ''; + } + + return filename + extension; +}; + +/** + * Simply utility method to allow parameters to be given as a function + * + * @param {undefined|string|function} option Option + * @return {null|string} Resolved value + */ +var _stringOrFunction = function ( option ) +{ + if ( option === null || option === undefined ) { + return null; + } + else if ( typeof option === 'function' ) { + return option(); + } + return option; +}; + +/** + * Get the title for an exported file. + * + * @param {object} config Button configuration + */ +var _title = function ( config ) +{ + var title = _stringOrFunction( config.title ); + + return title === null ? + null : title.indexOf( '*' ) !== -1 ? + title.replace( '*', $('head > title').text() || 'Exported data' ) : + title; +}; + +var _message = function ( dt, option, position ) +{ + var message = _stringOrFunction( option ); + if ( message === null ) { + return null; + } + + var caption = $('caption', dt.table().container()).eq(0); + if ( message === '*' ) { + var side = caption.css( 'caption-side' ); + if ( side !== position ) { + return null; + } + + return caption.length ? + caption.text() : + ''; + } + + return message; +}; + + + + +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 Buttons.stripData( d, config ); + }, + footer: function ( d ) { + return Buttons.stripData( d, config ); + }, + body: function ( d ) { + return Buttons.stripData( d, config ); + } + }, + customizeData: null + }, inOpts ); + + 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; + + // If Select is available on this table, and any rows are selected, limit the export + // to the selected rows. If no rows are selected, all rows will be exported. Specify + // a `selected` modifier to control directly. + var modifier = $.extend( {}, config.modifier ); + if ( dt.select && typeof dt.select.info === 'function' && modifier.selected === undefined ) { + if ( dt.rows( config.rows, $.extend( { selected: true }, modifier ) ).any() ) { + $.extend( modifier, { selected: true } ) + } + } + + var rowIndexes = dt.rows( config.rows, 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 = []; + var cellCounter = 0; + + for ( var i=0, ien=rows ; i<ien ; i++ ) { + var row = [ columns ]; + + for ( var j=0 ; j<columns ; j++ ) { + row[j] = config.format.body( cells[ cellCounter ], i, j, cellNodes[ cellCounter ] ); + cellCounter++; + } + + body[i] = row; + } + + var data = { + header: header, + footer: footer, + body: body + }; + + if ( config.customizeData ) { + config.customizeData( data ); + } + + return data; +}; + + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * 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(); + } +} ); + +function _init ( settings, options ) { + var api = new DataTable.Api( settings ); + var opts = options + ? options + : api.init().buttons || DataTable.defaults.buttons; + + return new Buttons( api, opts ).container(); +} + +// DataTables `dom` feature option +DataTable.ext.feature.push( { + fnInit: _init, + cFeature: "B" +} ); + +// DataTables 2 layout feature +if ( DataTable.ext.features ) { + DataTable.ext.features.register( 'buttons', _init ); +} + + +return Buttons; +})); |