Chris@0: /** Chris@0: * @file Chris@0: * Dropbutton feature. Chris@0: */ Chris@0: Chris@17: (function($, Drupal) { Chris@0: /** Chris@0: * A DropButton presents an HTML list as a button with a primary action. Chris@0: * Chris@0: * All secondary actions beyond the first in the list are presented in a Chris@0: * dropdown list accessible through a toggle arrow associated with the button. Chris@0: * Chris@0: * @constructor Drupal.DropButton Chris@0: * Chris@0: * @param {HTMLElement} dropbutton Chris@0: * A DOM element. Chris@0: * @param {object} settings Chris@0: * A list of options including: Chris@0: * @param {string} settings.title Chris@0: * The text inside the toggle link element. This text is hidden Chris@0: * from visual UAs. Chris@0: */ Chris@0: function DropButton(dropbutton, settings) { Chris@0: // Merge defaults with settings. Chris@17: const options = $.extend( Chris@17: { title: Drupal.t('List additional actions') }, Chris@17: settings, Chris@17: ); Chris@0: const $dropbutton = $(dropbutton); Chris@0: Chris@0: /** Chris@0: * @type {jQuery} Chris@0: */ Chris@0: this.$dropbutton = $dropbutton; Chris@0: Chris@0: /** Chris@0: * @type {jQuery} Chris@0: */ Chris@0: this.$list = $dropbutton.find('.dropbutton'); Chris@0: Chris@0: /** Chris@0: * Find actions and mark them. Chris@0: * Chris@0: * @type {jQuery} Chris@0: */ Chris@0: this.$actions = this.$list.find('li').addClass('dropbutton-action'); Chris@0: Chris@0: // Add the special dropdown only if there are hidden actions. Chris@0: if (this.$actions.length > 1) { Chris@0: // Identify the first element of the collection. Chris@0: const $primary = this.$actions.slice(0, 1); Chris@0: // Identify the secondary actions. Chris@0: const $secondary = this.$actions.slice(1); Chris@0: $secondary.addClass('secondary-action'); Chris@0: // Add toggle link. Chris@0: $primary.after(Drupal.theme('dropbuttonToggle', options)); Chris@0: // Bind mouse events. Chris@17: this.$dropbutton.addClass('dropbutton-multiple').on({ Chris@17: /** Chris@17: * Adds a timeout to close the dropdown on mouseleave. Chris@17: * Chris@17: * @ignore Chris@17: */ Chris@17: 'mouseleave.dropbutton': $.proxy(this.hoverOut, this), Chris@0: Chris@17: /** Chris@17: * Clears timeout when mouseout of the dropdown. Chris@17: * Chris@17: * @ignore Chris@17: */ Chris@17: 'mouseenter.dropbutton': $.proxy(this.hoverIn, this), Chris@0: Chris@17: /** Chris@17: * Similar to mouseleave/mouseenter, but for keyboard navigation. Chris@17: * Chris@17: * @ignore Chris@17: */ Chris@17: 'focusout.dropbutton': $.proxy(this.focusOut, this), Chris@0: Chris@17: /** Chris@17: * @ignore Chris@17: */ Chris@17: 'focusin.dropbutton': $.proxy(this.focusIn, this), Chris@17: }); Chris@17: } else { Chris@0: this.$dropbutton.addClass('dropbutton-single'); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@17: * Delegated callback for opening and closing dropbutton secondary actions. Chris@17: * Chris@17: * @function Drupal.DropButton~dropbuttonClickHandler Chris@17: * Chris@17: * @param {jQuery.Event} e Chris@17: * The event triggered. Chris@17: */ Chris@17: function dropbuttonClickHandler(e) { Chris@17: e.preventDefault(); Chris@17: $(e.target) Chris@17: .closest('.dropbutton-wrapper') Chris@17: .toggleClass('open'); Chris@17: } Chris@17: Chris@17: /** Chris@17: * Process elements with the .dropbutton class on page load. Chris@17: * Chris@17: * @type {Drupal~behavior} Chris@17: * Chris@17: * @prop {Drupal~behaviorAttach} attach Chris@17: * Attaches dropButton behaviors. Chris@17: */ Chris@17: Drupal.behaviors.dropButton = { Chris@17: attach(context, settings) { Chris@17: const $dropbuttons = $(context) Chris@17: .find('.dropbutton-wrapper') Chris@17: .once('dropbutton'); Chris@17: if ($dropbuttons.length) { Chris@17: // Adds the delegated handler that will toggle dropdowns on click. Chris@17: const $body = $('body').once('dropbutton-click'); Chris@17: if ($body.length) { Chris@17: $body.on('click', '.dropbutton-toggle', dropbuttonClickHandler); Chris@17: } Chris@17: // Initialize all buttons. Chris@17: const il = $dropbuttons.length; Chris@17: for (let i = 0; i < il; i++) { Chris@17: DropButton.dropbuttons.push( Chris@17: new DropButton($dropbuttons[i], settings.dropbutton), Chris@17: ); Chris@17: } Chris@17: } Chris@17: }, Chris@17: }; Chris@17: Chris@17: /** Chris@0: * Extend the DropButton constructor. Chris@0: */ Chris@17: $.extend( Chris@17: DropButton, Chris@17: /** @lends Drupal.DropButton */ { Chris@17: /** Chris@17: * Store all processed DropButtons. Chris@17: * Chris@17: * @type {Array.} Chris@17: */ Chris@17: dropbuttons: [], Chris@17: }, Chris@17: ); Chris@0: Chris@0: /** Chris@0: * Extend the DropButton prototype. Chris@0: */ Chris@17: $.extend( Chris@17: DropButton.prototype, Chris@17: /** @lends Drupal.DropButton# */ { Chris@17: /** Chris@17: * Toggle the dropbutton open and closed. Chris@17: * Chris@17: * @param {bool} [show] Chris@17: * Force the dropbutton to open by passing true or to close by Chris@17: * passing false. Chris@17: */ Chris@17: toggle(show) { Chris@17: const isBool = typeof show === 'boolean'; Chris@17: show = isBool ? show : !this.$dropbutton.hasClass('open'); Chris@17: this.$dropbutton.toggleClass('open', show); Chris@17: }, Chris@0: Chris@17: /** Chris@17: * @method Chris@17: */ Chris@17: hoverIn() { Chris@17: // Clear any previous timer we were using. Chris@17: if (this.timerID) { Chris@17: window.clearTimeout(this.timerID); Chris@17: } Chris@17: }, Chris@17: Chris@17: /** Chris@17: * @method Chris@17: */ Chris@17: hoverOut() { Chris@17: // Wait half a second before closing. Chris@17: this.timerID = window.setTimeout($.proxy(this, 'close'), 500); Chris@17: }, Chris@17: Chris@17: /** Chris@17: * @method Chris@17: */ Chris@17: open() { Chris@17: this.toggle(true); Chris@17: }, Chris@17: Chris@17: /** Chris@17: * @method Chris@17: */ Chris@17: close() { Chris@17: this.toggle(false); Chris@17: }, Chris@17: Chris@17: /** Chris@17: * @param {jQuery.Event} e Chris@17: * The event triggered. Chris@17: */ Chris@17: focusOut(e) { Chris@17: this.hoverOut.call(this, e); Chris@17: }, Chris@17: Chris@17: /** Chris@17: * @param {jQuery.Event} e Chris@17: * The event triggered. Chris@17: */ Chris@17: focusIn(e) { Chris@17: this.hoverIn.call(this, e); Chris@17: }, Chris@0: }, Chris@17: ); Chris@0: Chris@17: $.extend( Chris@17: Drupal.theme, Chris@17: /** @lends Drupal.theme */ { Chris@17: /** Chris@17: * A toggle is an interactive element often bound to a click handler. Chris@17: * Chris@17: * @param {object} options Chris@17: * Options object. Chris@17: * @param {string} [options.title] Chris@17: * The button text. Chris@17: * Chris@17: * @return {string} Chris@17: * A string representing a DOM fragment. Chris@17: */ Chris@17: dropbuttonToggle(options) { Chris@17: return `
  • `; Chris@17: }, Chris@0: }, Chris@17: ); Chris@0: Chris@0: // Expose constructor in the public space. Chris@0: Drupal.DropButton = DropButton; Chris@17: })(jQuery, Drupal);