/*! Buttons for DataTables 1.7.0 * ©2016-2021 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 * @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, base !== undefined, 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) .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 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); } } // 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 = !Array.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 (Array.isArray(conf)) { this._expandButton(attachTo, conf, inCollection, attachPoint); continue; } var built = this._buildButton(conf, inCollection); if (!built) { continue; } if (attachPoint !== undefined && attachPoint !== null) { attachTo.splice(attachPoint, 0, built); attachPoint++; } else { attachTo.push(built); } if (built.conf.buttons) { built.collection = $('<' + this.c.dom.collection.tag + '/>'); 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, PDF button require pdfmake 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 clickBlurs = config.clickBlurs === undefined ? true : config.clickBlurs 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); } if (clickBlurs) { button.trigger('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); // 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); } 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) && !Array.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 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; 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) { var dt = hostButton; var buttonsSettings = this.c; var options = $.extend({ align: 'button-left', // button-right, dt-container autoClose: false, background: true, backgroundClassName: 'dt-button-background', contentClassName: buttonsSettings.dom.collection.className, collectionLayout: '', collectionTitle: '', dropup: false, fade: 400, rightAlignClassName: 'dt-button-right', tag: buttonsSettings.dom.collection.tag }, inOpts); var hostNode = hostButton.node(); var close = function () { _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); $('body').off('.dtb-collection'); dt.off('buttons-action.b-internal'); }; if (content === false) { close(); } var existingExpanded = $(dt.buttons('[aria-haspopup="true"][aria-expanded="true"]').nodes()); if (existingExpanded.length) { hostNode = existingExpanded.eq(0); close(); } var display = $('
') .addClass('dt-button-collection') .addClass(options.collectionLayout) .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.collectionTitle) { display.prepend(''); } _fadeIn(display.insertAfter(hostNode), options.fade); var tableContainer = $(hostButton.table().container()); var position = display.css('position'); if (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' && ( display.hasClass(options.rightAlignClassName) || display.hasClass(options.leftAlignClassName) || options.align === 'dt-container' ) ) { var hostPosition = hostNode.position(); display.css({ top: hostPosition.top + hostNode.outerHeight(), left: hostPosition.left }); // calculate overflow when positioned beneath var collectionHeight = display.outerHeight(); var tableBottom = tableContainer.offset().top + tableContainer.height(); var listBottom = hostPosition.top + hostNode.outerHeight() + collectionHeight; var bottomOverflow = listBottom - tableBottom; // calculate overflow when positioned above var listTop = hostPosition.top - collectionHeight; 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 var moveTop = hostPosition.top - collectionHeight - 5; if ((bottomOverflow > topOverflow || options.dropup) && -moveTop < tableTop) { display.css('top', moveTop); } // Get the size of the container (left and width - and thus also right) var tableLeft = tableContainer.offset().left; var tableWidth = tableContainer.width(); var tableRight = tableLeft + tableWidth; // Get the size of the popover (left and width - and ...) var popoverLeft = display.offset().left; var popoverWidth = display.width(); var popoverRight = popoverLeft + popoverWidth; // Get the size of the host buttons (left and width - and ...) var buttonsLeft = hostNode.offset().left; var buttonsWidth = hostNode.outerWidth() var buttonsRight = buttonsLeft + buttonsWidth; // You've then got all the numbers you need to do some calculations and if statements, // so we can do some quick JS maths and apply it only once // If it has the right align class OR the buttons are right aligned OR the button container is floated right, // then calculate left position for the popover to align the popover to the right hand // side of the button - check to see if the left of the popover is inside the table container. // If not, move the popover so it is, but not more than it means that the popover is to the right of the table container var popoverShuffle = 0; if (display.hasClass(options.rightAlignClassName)) { popoverShuffle = buttonsRight - popoverRight; if (tableLeft > (popoverLeft + popoverShuffle)) { var leftGap = tableLeft - (popoverLeft + popoverShuffle); var rightGap = tableRight - (popoverRight + popoverShuffle); if (leftGap > rightGap) { popoverShuffle += rightGap; } else { popoverShuffle += leftGap; } } } // else attempt to left align the popover to the button. Similar to above, if the popover's right goes past the table container's right, // then move it back, but not so much that it goes past the left of the table container else { popoverShuffle = tableLeft - popoverLeft; if (tableRight < (popoverRight + popoverShuffle)) { var leftGap = tableLeft - (popoverLeft + popoverShuffle); var rightGap = tableRight - (popoverRight + popoverShuffle); if (leftGap > rightGap) { popoverShuffle += rightGap; } else { popoverShuffle += leftGap; } } } display.css('left', display.position().left + popoverShuffle); } else if (position === 'absolute') { // Align relative to the host button var hostPosition = hostNode.position(); display.css({ top: hostPosition.top + hostNode.outerHeight(), left: hostPosition.left }); // calculate overflow when positioned beneath var collectionHeight = display.outerHeight(); var top = hostNode.offset().top var popoverShuffle = 0; // Get the size of the host buttons (left and width - and ...) var buttonsLeft = hostNode.offset().left; var buttonsWidth = hostNode.outerWidth() var buttonsRight = buttonsLeft + buttonsWidth; // Get the size of the popover (left and width - and ...) var popoverLeft = display.offset().left; var popoverWidth = content.width(); var popoverRight = popoverLeft + popoverWidth; var moveTop = hostPosition.top - collectionHeight - 5; var tableBottom = tableContainer.offset().top + tableContainer.height(); var listBottom = hostPosition.top + hostNode.outerHeight() + collectionHeight; var bottomOverflow = listBottom - tableBottom; // calculate overflow when positioned above var listTop = hostPosition.top - collectionHeight; var tableTop = tableContainer.offset().top; var topOverflow = tableTop - listTop; if ((bottomOverflow > topOverflow || options.dropup) && -moveTop < tableTop) { display.css('top', moveTop); } popoverShuffle = options.align === 'button-right' ? buttonsRight - popoverRight : buttonsLeft - popoverLeft; display.css('left', display.position().left + popoverShuffle); } else { // Fix position - centre on screen var top = display.height() / 2; if (top > $(window).height() / 2) { top = $(window).height() / 2; } display.css('marginTop', top * -1); } if (options.background) { Buttons.background(true, options.backgroundClassName, options.fade, 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 () { }); $('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'; 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(); } }); 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'); } }); /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 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( $('') .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); } }; 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 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(/