diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 1caebfa..7400cb8 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1695,7 +1695,6 @@ function theme_links($variables) { */ function template_preprocess_dropbutton(&$variables) { $variables['attributes']['class'][] = 'dropbutton'; - $variables['attributes']['id'] = drupal_html_id('dropbutton'); $variables['element'] = array( '#theme' => 'links', '#links' => $variables['links'], @@ -1706,6 +1705,10 @@ function template_preprocess_dropbutton(&$variables) { ), ), ); + if (!empty($variables['links'])) { + $variables['element']['#prefix'] = '
'; + $variables['element']['#suffix'] = '
'; + } // Pass through title to the dropbutton options. if (isset($variables['title'])) { $variables['element']['#attached']['js'][] = array( diff --git a/core/misc/dropbutton/dropbutton.js b/core/misc/dropbutton/dropbutton.js index ec62757..6d75fae 100644 --- a/core/misc/dropbutton/dropbutton.js +++ b/core/misc/dropbutton/dropbutton.js @@ -1,4 +1,4 @@ -(function ($, Drupal, window) { +(function ($, Drupal) { "use strict"; @@ -7,41 +7,76 @@ */ Drupal.behaviors.dropButton = { attach: function (context, settings) { - settings = ('dropbutton' in settings) ? settings.dropbutton : {}; - var $dropbuttons = $(context).find('.dropbutton').once('dropbutton'); - for (var i = 0, il = $dropbuttons.length; i < il; i++) { - DropButton.dropbuttons.push(new DropButton($dropbuttons[i], settings)); + var $dropbuttons = $(context).find('.dropbutton-wrapper').once('dropbutton'); + if ($dropbuttons.length) { + // Adds the delegated handler that will toggle dropdowns on click. + var $body = $('body').once('dropbutton-click'); + if ($body.length) { + $body.on('click', '.dropbutton-link', dropbuttonClickHandler); + } + // Initialize all buttons. + for (var i = 0, il = $dropbuttons.length; i < il; i++) { + DropButton.dropbuttons.push(new DropButton($dropbuttons[i], settings.dropbutton)); + } } } }; /** + * Delegated callback for for opening and closing dropbutton secondary actions. + */ +function dropbuttonClickHandler (e) { + e.preventDefault(); + $(e.target).closest('.dropbutton-wrapper').toggleClass('open'); +} + +/** * A DropButton presents an HTML list as a button with a primary action. * * All secondary actions beyond the first in the list are presented in a * dropdown list accessible through a toggle arrow associated with the button. * - * @param {DOMElement} dropbutton - * An HTMLListElement. - * @param {Object} options + * @param {jQuery} $dropbutton + * A jQuery element. + * + * @param {Object} settings * A list of options including: - * - title: The text inside the toggle link element. This text is hidden from - * visual UAs. + * - {String} title: The text inside the toggle link element. This text is + * hidden from visual UAs. */ function DropButton (dropbutton, settings) { - this.dropbutton = dropbutton; - this.$list = $(dropbutton); - // State properties. - this.isOpen = false; - this.isHovered = false; - this.timerID = 0; // Merge defaults with settings. - this.defaults = { - 'title': Drupal.t('More') - }; - var options = $.extend(this.defaults, settings); - // Build the view. - this.build(options); + var options = $.extend({'title': Drupal.t('More')}, settings); + var $dropbutton = $(dropbutton); + this.$dropbutton = $dropbutton; + this.$list = $dropbutton.find('.dropbutton'); + this.$actions = this.$list.find('li'); + + // Move the classes from .dropbutton up to .dropbutton-wrapper + this.$dropbutton.addClass(this.$list[0].className); + this.$dropbutton.attr('id', this.$list[0].id); + this.$list.attr({id: '', 'class': 'dropbutton-content'}); + + // Add the special dropdown only if there are hidden actions. + if (this.$actions.length > 1) { + // Remove the first element of the collection and create a new jQuery + // collection for the secondary actions. + $(this.$actions.splice(1)).addClass('secondary-actions'); + // Add toggle link. + this.$list.before(Drupal.theme('dropbuttonToggle', options)); + // Bind mouse events. + this.$dropbutton.addClass('dropbutton-multiple') + .on({ + /** + * Adds a timeout to close the dropdown on mouseleave. + */ + 'mouseleave.dropbutton': $.proxy(this.hoverOut, this), + /** + * Clears timeout when mouseout of the dropdown. + */ + 'mouseenter.dropbutton': $.proxy(this.hoverIn, this) + }); + } } /** @@ -61,47 +96,16 @@ $.extend(DropButton, { */ $.extend(DropButton.prototype, { /** - * Render the dropbutton view. + * Toggle the dropbutton open and closed. + * + * @param {Boolean} show + * (optional) Force the dropbutton to open by passing true or to close by + * passing false. */ - build: function (options) { - // Save the ID and class attributes so that they can be applied to the - // widget wrapper below. - options.id = this.$list.attr('id') || ''; - options['class'] = this.$list.attr('class').split(' ') || []; - options['class'].push('dropbutton-wrapper'); - // Remove the class and ID attributes. The values of these attributes - // will be applied to the dropbutton wrapper. - this.$list.removeAttr('class id'); - // Mark-up the list with dropbutton structure. - this.$list.addClass('dropbutton-content'); - // Mark the secondary actions. - this.$secondaryActions = this.$list - .find('li') - .not(':first') - .addClass('secondary-actions'); - // Add a class to indicate the dropbutton has more than one action. - if (this.$secondaryActions.length) { - options['class'].push('dropbutton-multiple'); - } - // Theme the output. - this.$dropbutton = this.$list - .wrap($(Drupal.theme.dropbutton(options))) - .closest('.dropbutton-wrapper') - // Add a toggle if this dropbutton has multiple actions. - .filter('.dropbutton-multiple') - .children('.dropbutton-widget') - // Build and insert the dropbutton toggle. - .prepend(Drupal.theme.toggle(options)) - // Stop the children traversal and return to $dropbutton. - .end() - // Bind events. - .on({ - 'mouseenter.dropbutton': $.proxy(this, 'hoverIn'), - 'mouseleave.dropbutton': $.proxy(this, 'hoverOut') - }) - .on({ - 'click.dropbutton': $.proxy(this, 'toggleClickHandler') - }, '.dropbutton-link'); + toggle: function (show) { + var isBool = typeof show === 'boolean'; + show = isBool ? show : !this.$dropbutton.hasClass('open'); + this.$dropbutton.toggleClass('open', show); }, hoverIn: function () { @@ -109,81 +113,40 @@ $.extend(DropButton.prototype, { if (this.timerID) { window.clearTimeout(this.timerID); } - this.isHovered = true; }, hoverOut: function () { - this.isHovered = false; - this.toggle(); - }, - - toggleClickHandler: function (event) { - event.preventDefault(); - var closeIt = (this.isOpen) ? true : false; - this.toggle(closeIt); + // Wait half a second before closing. + this.timerID = window.setTimeout($.proxy(this, 'close'), 500); }, - /** - * Toggle the dropbutton open and closed. - * - * @param {Boolean} closeIt - * (optional) Force the dropbutton to close by passing true or to close by - * passing false. - */ - toggle: function (closeIt) { - // Open or close the dropbutton immediately if closeIt is defined. - if (closeIt !== undefined) { - this[(closeIt) ? 'close' : 'open'](); - return; - } - // If the dropbutton is open and the dropbutton is not hovered, - // then prepare to close it. - if (this.isOpen && !this.isHovered) { - // Wait half a second before closing. - this.timerID = window.setTimeout($.proxy(this, 'close'), 500); - } + open: function () { + this.toggle(true); }, close: function () { - this.isOpen = false; - this.$dropbutton.removeClass('open'); - }, - - open: function () { - this.isOpen = true; - this.$dropbutton.addClass('open'); + this.toggle(false); } }); -/** - * A toggle is an interactive element often bound to a click handler. - * - * @param Object options - * - String title: (optional) The HTML anchor title attribute and text for the - * inner span element. - * - * @return String - * A string representing a DOM fragment. - */ -Drupal.theme.toggle = function (options) { - return '' + options.title + ''; -}; -/** - * A wrapping DOM fragment representing a dropbutton widget. - * - * @param Object options - * - String id: (optional) An ID to apply to the outer wrapping div. - * - Array class: (optional) Classes to apply to the outer wrapping div. - * - * @return String - * A string representing a DOM fragment. - */ -Drupal.theme.dropbutton = function (options) { - return '
'; -}; +$.extend(Drupal.theme, { + /** + * A toggle is an interactive element often bound to a click handler. + * + * @param {Object} options + * - {String} title: (optional) The HTML anchor title attribute and text for the + * inner span element. + * + * @return {String} + * A string representing a DOM fragment. + */ + dropbuttonToggle: function (options) { + return '' + options.title + ''; + } +}); // Expose constructor in the public space. Drupal.DropButton = DropButton; -})(jQuery, Drupal, window); +})(jQuery, Drupal); diff --git a/core/modules/system/system.module b/core/modules/system/system.module index b03f647..c6156fc 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -1406,8 +1406,9 @@ function system_library_info() { ), 'dependencies' => array( array('system', 'jquery'), - array('system', 'jquery.once'), array('system', 'drupal'), + array('system', 'drupal.settings'), + array('system', 'jquery.once'), ), );