All Downloads are FREE. Search and download functionalities are using the official Maven repository.

META-INF.resources.bower_components.datatables.net-buttons.js.dataTables.buttons.js Maven / Gradle / Ivy

There is a newer version: 0.66.0.1
Show newest version
/*! Buttons for DataTables 1.5.2
 * ©2016-2018 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 (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, 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 tag = config.tag || buttonDom.tag;
            var button = $('<' + 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 (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++);
            }

            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) {
            // 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 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) {
            $('
') .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: 'button', className: 'dt-button', active: 'active', disabled: 'disabled' }, buttonLiner: { tag: 'span', className: '' } } }; /** * Version information * @type {string} * @static */ Buttons.version = '1.5.2'; $.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 collectionParent = $(button).parents('div.dt-button-collection'); var hostPosition = host.position(); var tableContainer = $(dt.table().container()); var multiLevel = false; var insertPoint = host; // Remove any old collection if (collectionParent.length) { multiLevel = $('.dt-button-collection').position(); insertPoint = collectionParent; $('body').trigger('click.dtb-collection'); } if (insertPoint.parents('body')[0] !== document.body) { insertPoint = document.body.lastChild; } config._collection .addClass(config.collectionLayout) .css('display', 'none') .insertAfter(insertPoint) .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: hostPosition.top + host.outerHeight(), left: hostPosition.left }); // calculate overflow when positioned beneath var tableBottom = tableContainer.offset().top + tableContainer.height(); var listBottom = hostPosition.top + host.outerHeight() + config._collection.outerHeight(); var bottomOverflow = listBottom - tableBottom; // calculate overflow when positioned above var listTop = hostPosition.top - config._collection.outerHeight(); var tableTop = tableContainer.offset().top; var topOverflow = tableTop - listTop; // if bottom overflow is larger, move to the top because it fits better, or if dropup is requested if (bottomOverflow > topOverflow || config.dropup) { config._collection.css('top', hostPosition.top - config._collection.outerHeight() - 5); } // Right alignment in table container var listRight = hostPosition.left + config._collection.outerWidth(); var tableRight = tableContainer.offset().left + tableContainer.width(); if (listRight > tableRight) { config._collection.css('left', hostPosition.left - (listRight - tableRight)); } // Right alignment to window var listOffsetRight = host.offset().left + config._collection.outerWidth(); if (listOffsetRight > $(window).width()) { config._collection.css('left', hostPosition.left - (listOffsetRight - $(window).width())); } } 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); } var close = function () { 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('.dtb-collection'); dt.off('buttons-action.b-internal'); }; // 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) { close(); } }) .on('keyup.dtb-collection', function (e) { if (e.keyCode === 27) { close(); } }); if (config.autoClose) { dt.on('buttons-action.b-internal', function () { close(); }); } }, 10); }, background: true, collectionLayout: '', backgroundClassName: 'dt-button-background', autoClose: false, fade: 400, attr: { 'aria-haspopup': true } }, 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 ? '

' + title + '

' : ''; $('
') .html(title) .append($('
')[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); } }); // 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 = $.trim(filename.replace('*', $('head > title').text())); } // 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 = $('