diff --git a/core/includes/common.inc b/core/includes/common.inc index c9913a2..fc5fa52 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -6539,6 +6539,9 @@ function drupal_common_theme() { 'links' => array( 'variables' => array('links' => array(), 'attributes' => array('class' => array('links')), 'heading' => array()), ), + 'dropbutton' => array( + 'variables' => array('title' => NULL, 'links' => NULL, 'image' => FALSE, 'class' => NULL), + ), 'image' => array( // HTML 4 and XHTML 1.0 always require an alt attribute. The HTML 5 draft // allows the alt attribute to be omitted in some cases. Therefore, diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 2d1d375..88add35 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1691,6 +1691,93 @@ function theme_links($variables) { } /** + * Create a dropbutton menu. + * + * @param $title + * The text to place in the clickable area to activate the dropbutton. This + * text is indented to -9999px by default. + * @param $links + * A list of links to provide within the dropbutton, suitable for use + * in via Drupal's theme('links'). + * @param $image + * If true, the dropbutton link is an image and will not get extra decorations + * that a text dropbutton link will. + * @param $class + * An optional class to add to the dropbutton's container div to allow you + * to style a single dropbutton however you like without interfering with + * other dropbuttons. + */ +function theme_dropbutton($variables) { + // Check to see if the number of links is greater than 1; + // otherwise just treat this like a button. + if (!empty($variables['links'])) { + $is_drop_button = (count($variables['links']) > 1); + + // Add needed files + if ($is_drop_button) { + drupal_add_js('core/misc/dropbutton.js'); + } + + // Provide a unique identifier for every button on the page. + static $id = 0; + $id++; + + // Wrapping div + $class = 'dropbutton-no-js'; + $class .= ($is_drop_button) ? ' dropbutton' : ''; + $class .= ' button'; + if (!empty($variables['class'])) { + $class .= ($variables['class']) ? (' ' . implode(' ', $variables['class'])) : ''; + } + + $output = ''; + + $output .= '
'; + + // Add a twisty if this is a dropbutton + if ($is_drop_button) { + $variables['title'] = ($variables['title'] ? check_plain($variables['title']) : t('open')); + + $output .= ''; // dropbutton-link + } + + // The button content + $output .= '
'; + + // Check for attributes. theme_links expects an array(). + $variables['attributes'] = (!empty($variables['attributes'])) ? $variables['attributes'] : array(); + + // Remove the inline and links classes from links if they exist. + // These classes are added and styled by Drupal core and mess up the default + // styling of any link list. + if (!empty($variables['attributes']['class'])) { + $classes = $variables['attributes']['class']; + foreach ($classes as $key => $class) { + if ($class === 'inline' || $class === 'links') { + unset($variables['attributes']['class'][$key]); + } + } + } + + // Call theme_links to render the list of links. + $output .= theme_links(array('links' => $variables['links'], 'attributes' => $variables['attributes'], 'heading' => '')); + $output .= '
'; // dropbutton-content + $output .= '
'; // dropbutton + return $output; + } + else { + return ''; + } +} + +/** * Returns HTML for an image. * * @param $variables diff --git a/core/misc/dropbutton.js b/core/misc/dropbutton.js new file mode 100644 index 0000000..9824c9e --- /dev/null +++ b/core/misc/dropbutton.js @@ -0,0 +1,97 @@ +// $Id$ +/** + * @file + * Implement a simple, clickable dropbutton menu. + * + * See dropbutton.theme.inc for primary documentation. + * + * The javascript relies on four classes: + * - The dropbutton must be fully contained in a div with the class + * dropbutton. It must also contain the class dropbutton-no-js + * which will be immediately removed by the javascript; this allows for + * graceful degradation. + * - The trigger that opens the dropbutton must be an a tag wit hthe class + * dropbutton-link. The href should just be '#' as this will never + * be allowed to complete. + * - The part of the dropbutton that will appear when the link is clicked must + * be a div with class dropbutton-container. + * - Finally, dropbutton-hover will be placed on any link that is being + * hovered over, so that the browser can restyle the links. + * + * This tool isn't meant to replace click-tips or anything, it is specifically + * meant to work well presenting menus. + */ + +(function ($) { + +Drupal.behaviors.dropbutton = { + attach: function () { + // Process buttons. All dropbuttons are buttons. + $('.button') + .once('button') + .removeClass('dropbutton-no-js'); + + // Process dropbuttons. Not all buttons are dropbuttons. + $('.dropbutton').once('dropbutton', function () { + var $dropbutton = $(this); + var $button = $dropbutton.find('.dropbutton-content'); + var $secondaryActions = $button.find('li').not(':first'); + var $twisty = $dropbutton.find(".dropbutton-link"); + var open = false; + var hovering = false; + var timerID = 0; + + var toggle = function (close) { + // if it's open or we're told to close it, close it. + if (open || close) { + // If we're just toggling it, close it immediately. + if (!close) { + open = false; + $secondaryActions.slideUp(100); + $dropbutton.removeClass('open'); + } + else { + // If we were told to close it, wait half a second to make + // sure that's what the user wanted. + // Clear any previous timer we were using. + if (timerID) { + clearTimeout(timerID); + } + timerID = setTimeout(function () { + if (!hovering) { + open = false; + $secondaryActions.slideUp(100); + $dropbutton.removeClass('open'); + }}, 500); + } + } + else { + // open it. + open = true; + $secondaryActions.animate({height: "show", opacity: "show"}, 100); + $dropbutton.addClass('open'); + } + } + // Hide the secondary actions initially. + $secondaryActions.hide(); + + $twisty.click(function () { + toggle(); + return false; + }); + + $dropbutton.hover( + function () { + hovering = true; + }, // hover in + function () { // hover out + hovering = false; + toggle(true); + return false; + } + ); + }); + } +} + +})(jQuery); diff --git a/core/modules/system/system.base.css b/core/modules/system/system.base.css index 610d94d..ac91e72 100644 --- a/core/modules/system/system.base.css +++ b/core/modules/system/system.base.css @@ -286,3 +286,93 @@ section, summary { display: block; } + +.dropbutton { + padding-right: 18px; + position: relative; + background-color: inherit; + border-style: solid; + border-width: 1px; + display: inline-block; + line-height: 1; +} + +.dropbutton:hover { + cursor: pointer; +} + +.dropbutton.open { + z-index: 200; +} + +.dropbutton .dropbutton-content { + padding-bottom: 2px; + padding-top: 2px; +} + +.dropbutton .dropbutton-content li, +.dropbutton .dropbutton-content a { + display: block; +} +.dropbutton li { + line-height: 1.3333; +} + +.dropbutton li a { + padding-left: 12px; + padding-right: 12px; +} + +.dropbutton .dropbutton-link { + bottom: 0; + display: block; + height: auto; + position: absolute; + right: 0; + text-indent: -9999px; /* LTR */ + top: 0; + width: 17px; +} + +.dropbutton .dropbutton-link a { + overflow: hidden; +} + +.dropbutton .dropbutton-content ul { + margin: 0; + overflow: hidden; + list-style-image: none; + list-style-type: none; +} + +.dropbutton.open li + li { + padding-top: 4px; +} + +/** + * This creates the dropbutton arrow and inherits the link color + */ +.dropbutton-twisty { + border-bottom-color: transparent; + border-left-color: transparent; + border-right-color: transparent; + border-style: solid; + border-width: 4px 4px 0; + line-height: 0; + right: 6px; + position: absolute; + top: 0.75em; +} + +.dropbutton.open .dropbutton-twisty { + border-bottom: 4px solid; + border-left-color: transparent; + border-right-color: transparent; + border-top-color: transparent; + top: 0.5em; +} + +.dropbutton-no-js .dropbutton-twisty { + display: none; +} +