/*! Buttons for DataTables 2.4.1 * © 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 var jq = require('jquery'); var cjsRequires = function (root, $) { if ( ! $.fn.dataTable ) { require('datatables.net')(root, $); } }; if (typeof window === 'undefined') { module.exports = function (root, $) { if ( ! root ) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } if ( ! $ ) { $ = jq( root ); } cjsRequires( root, $ ); return factory( $, root, root.document ); }; } else { cjsRequires( window, jq ); module.exports = factory( jq, window, window.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 ( button.inCollection && this.c.dom.collection.button && this.c.dom.collection.button.active !== undefined ) { klass = this.c.dom.collection.button.active; } 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); } // If the collection has prefix and / or postfix buttons we need to add them in if (button.conf.prefixButtons) { newButtons.unshift.apply(newButtons, button.conf.prefixButtons); } if (button.conf.postfixButtons) { newButtons.push.apply(newButtons, button.conf.postfixButtons); } 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, null, 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).prop('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).prop('disabled', false); 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 textNode = button.textNode; 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; textNode.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 isSplit = false; var domCollection = this.c.dom.collection; var buttons = !Array.isArray(button) ? [button] : button; if (button === undefined) { buttons = !Array.isArray(split) ? [split] : split; } for (var i = 0, ien = buttons.length; i < ien; i++) { var conf = this._resolveExtends(buttons[i]); if (!conf) { continue; } isSplit = conf.config && conf.config.split ? true : 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); } // Create the dropdown for a collection if (built.conf.buttons) { built.collection = $('<' + domCollection.container.content.tag + '/>'); built.conf._collection = built.collection; $(built.node).append(domCollection.action.dropHtml); this._expandButton( built.buttons, built.conf.buttons, built.conf.split, !isSplit, isSplit, attachPoint, built.conf ); } // And the split collection if (built.conf.split) { built.collection = $('<' + domCollection.container.tag + '/>'); built.conf._collection = built.collection; for (var j = 0; j < built.conf.split.length; j++) { var item = built.conf.split[j]; if (typeof item === 'object') { item.parent = parentConf; if (item.collectionLayout === undefined) { item.collectionLayout = built.conf.collectionLayout; } if (item.dropup === undefined) { item.dropup = built.conf.dropup; } if (item.fade === undefined) { item.fade = built.conf.fade; } } } 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); } } }, /** * Create an individual button * @param {object} config Resolved button configuration * @param {boolean} inCollection `true` if a collection button * @return {object} Completed button description object * @private */ _buildButton: function (config, inCollection, isSplit, inSplit) { var configDom = this.c.dom; var textNode; var dt = this.s.dt; var text = function (opt) { return typeof opt === 'function' ? opt(dt, button, config) : opt; }; // Create an object that describes the button which can be in `dom.button`, or // `dom.collection.button` or `dom.split.button` or `dom.collection.split.button`! // Each should extend from `dom.button`. var dom = $.extend(true, {}, configDom.button); if (inCollection && isSplit && configDom.collection.split) { $.extend(true, dom, configDom.collection.split.action); } else if (inSplit || inCollection) { $.extend(true, dom, configDom.collection.button); } else if (isSplit) { $.extend(true, dom, configDom.split.button); } // Spacers don't do much other than insert an element into the DOM if (config.spacer) { var spacer = $('<' + dom.spacer.tag + '/>') .addClass('dt-button-spacer ' + config.style + ' ' + dom.spacer.className) .html(text(config.text)); return { conf: config, node: spacer, inserter: spacer, buttons: [], inCollection: inCollection, isSplit: isSplit, collection: null, textNode: spacer }; } // 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 || dom.tag; var clickBlurs = config.clickBlurs === undefined ? true : config.clickBlurs; button = $('<' + tag + '/>') .addClass(dom.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(dom.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(dom.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 (dom.liner.tag) { var liner = $('<' + dom.liner.tag + '/>') .html(text(config.text)) .addClass(dom.liner.className); if (dom.liner.tag.toLowerCase() === 'a') { liner.attr('href', '#'); } button.append(liner); textNode = liner; } else { button.html(text(config.text)); textNode = button; } if (config.enabled === false) { button.addClass(dom.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) { var dropdownConf = inCollection ? $.extend(true, this.c.dom.split, this.c.dom.collection.split) : this.c.dom.split; var wrapperConf = dropdownConf.wrapper; splitDiv = $('<' + wrapperConf.tag + '/>') .addClass(wrapperConf.className) .append(button); var dropButtonConfig = $.extend(config, { align: dropdownConf.dropdown.align, attr: { 'aria-haspopup': 'dialog', 'aria-expanded': false }, className: dropdownConf.dropdown.className, closeButton: false, splitAlignClass: dropdownConf.dropdown.splitAlignClass, text: dropdownConf.dropdown.text }); this._addKey(dropButtonConfig); var splitAction = function (e, dt, button, config) { _dtButtons.split.action.call(dt.button(splitDiv), e, dt, button, config); $(dt.table().node()).triggerHandler('buttons-action.dt', [ dt.button(button), dt, button, config ]); button.attr('aria-expanded', true); }; var dropButton = $( '' ) .html(dropdownConf.dropdown.dropHtml) .on('click.dtb', function (e) { e.preventDefault(); e.stopPropagation(); if (!dropButton.hasClass(dom.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(dom.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, textNode: textNode }; }, /** * 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; } // 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; } // 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]); } } 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]); } } 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 c = 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, containerClassName: c.dom.collection.container.className, contentClassName: c.dom.collection.container.content.className, collectionLayout: '', collectionTitle: '', dropup: false, fade: 400, popoverTitle: '', rightAlignClassName: 'dt-button-right', tag: c.dom.collection.container.tag }, inOpts ); var containerSelector = options.tag + '.' + options.containerClassName.replace(/ /g, '.'); var hostNode = hostButton.node(); var close = function () { closed = true; _fadeOut($(containerSelector), options.fade, function () { $(this).detach(); }); $(dt.buttons('[aria-haspopup="dialog"][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="dialog"][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(containerSelector).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 = $('<' + options.tag + '/>') .addClass(options.containerClassName) .addClass(options.collectionLayout) .addClass(options.splitAlignClass) .addClass(mod) .css('display', 'none') .attr({ 'aria-modal': true, role: 'dialog' }); 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( '
' ); } else if (options.collectionTitle) { display.prepend( ' ' ); } if (options.closeButton) { display .prepend('