Commit fc303a43 authored by webchick's avatar webchick

Issue #1608878 by jessebeach, tim.plunkett, dead_arm, nod_, sun, effulgentsia,...

Issue #1608878 by jessebeach, tim.plunkett, dead_arm, nod_, sun, effulgentsia, Everett Zufelt, geerlingguy: Added CTools dropbutton to core.
parent 7518561a
......@@ -5461,6 +5461,26 @@ function drupal_pre_render_links($element) {
return $element;
}
/**
* Pre-render callback: Attaches the dropbutton library and required markup.
*/
function drupal_pre_render_dropbutton($element) {
$element['#attached']['library'][] = array('system', 'drupal.dropbutton');
$element['#attributes']['class'][] = 'dropbutton';
if (!isset($element['#theme_wrappers'])) {
$element['#theme_wrappers'] = array();
}
array_unshift($element['#theme_wrappers'], 'dropbutton_wrapper');
// Enable targeted theming of specific dropbuttons (e.g., 'operations' or
// 'operations__node').
if (isset($element['#subtype'])) {
$element['#theme'] .= '__' . $element['#subtype'];
}
return $element;
}
/**
* Pre-render callback: Appends contents in #markup to #children.
*
......
......@@ -1707,6 +1707,18 @@ function theme_links($variables) {
return $output;
}
/**
* Returns HTML for wrapping a dropbutton menu.
*
* @param array $variables
* An associative array containing:
* - element: An associative array containing the properties and children of
* the dropbutton menu. Properties used: #children.
*/
function theme_dropbutton_wrapper($variables) {
return '<div class="dropbutton-wrapper"><div class="dropbutton-widget">' . $variables['element']['#children'] . '</div></div>';
}
/**
* Returns HTML for an image.
*
......@@ -2905,6 +2917,9 @@ function drupal_common_theme() {
'links' => array(
'variables' => array('links' => array(), 'attributes' => array('class' => array('links')), 'heading' => array()),
),
'dropbutton_wrapper' => array(
'render element' => 'element',
),
'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,
......
/**
* @file
* Base RTL styles for dropbuttons.
*/
/**
* The dropbutton arrow.
*/
.dropbutton-toggle {
left: 0;
right: auto;
}
.dropbutton-arrow {
left: 0.6667em;
right: auto;
}
.dropbutton-multiple .dropbutton-widget {
padding-left: 2em;
padding-right: 0;
}
/**
* @file
* Base styles for dropbuttons.
*/
/**
* When a dropbutton has only one option, it is simply a button.
*/
.dropbutton-wrapper,
.dropbutton-wrapper div {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.dropbutton-wrapper {
display: inline-block;
}
.dropbutton-widget {
position: relative;
}
/* UL styles are over-scoped in core, so this selector needs weight parity. */
.dropbutton-widget .dropbutton {
list-style-image: none;
list-style-type: none;
margin: 0;
padding: 0;
}
.dropbutton li,
.dropbutton a {
display: block;
}
/**
* The dropbutton styling.
*
* A dropbutton is a widget that displays a list of action links as a button
* with a primary action. Secondary actions are hidden behind a click on a
* twisty arrow.
*
* The arrow is created using border on a zero-width, zero-height span.
* The arrow inherits the link color, but can be overridden with border colors.
*/
.dropbutton-multiple .dropbutton-widget {
padding-right: 2em; /* LTR */
}
.dropbutton-multiple .dropbutton .secondary-action {
display: none;
}
.dropbutton-multiple.open .dropbutton .secondary-action {
display: block;
}
.dropbutton-toggle {
bottom: 0;
display: block;
position: absolute;
right: 0; /* LTR */
text-indent: 110%;
top: 0;
white-space: nowrap;
width: 2em;
}
.dropbutton-toggle button {
background: none;
border: 0;
cursor: pointer;
display: block;
height: 100%;
margin: 0;
padding: 0;
width: 100%;
}
.dropbutton-arrow {
border-bottom-color: transparent;
border-left-color: transparent;
border-right-color: transparent;
border-style: solid;
border-width: 0.3333em 0.3333em 0;
display: block;
height: 0;
line-height: 0;
position: absolute;
right: 40%; /* 0.6667em; */ /* LTR */
top: 0.9em;
width: 0;
}
.dropbutton-multiple.open .dropbutton-arrow {
border-bottom: 0.3333em solid;
border-top-color: transparent;
top: 0.6667em;
}
(function ($, Drupal) {
"use strict";
/**
* Process elements with the .dropbutton class on page load.
*/
Drupal.behaviors.dropButton = {
attach: function (context, 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-toggle', 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 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 {jQuery} $dropbutton
* A jQuery element.
*
* @param {Object} settings
* A list of options including:
* - {String} title: The text inside the toggle link element. This text is
* hidden from visual UAs.
*/
function DropButton (dropbutton, settings) {
// Merge defaults with settings.
var options = $.extend({'title': Drupal.t('List additional actions')}, settings);
var $dropbutton = $(dropbutton);
this.$dropbutton = $dropbutton;
this.$list = $dropbutton.find('.dropbutton');
// Find actions and mark them.
this.$actions = this.$list.find('li').addClass('dropbutton-action');
// Add the special dropdown only if there are hidden actions.
if (this.$actions.length > 1) {
// Identify the first element of the collection.
var $primary = this.$actions.slice(0,1);
// Identify the secondary actions.
var $secondary = this.$actions.slice(1);
$($secondary).addClass('secondary-action');
// Add toggle link.
$primary.after(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)
});
}
}
/**
* Extend the DropButton constructor.
*/
$.extend(DropButton, {
/**
* Store all processed DropButtons.
*
* @type {Array}
*/
dropbuttons: []
});
/**
* Extend the DropButton prototype.
*/
$.extend(DropButton.prototype, {
/**
* Toggle the dropbutton open and closed.
*
* @param {Boolean} show
* (optional) Force the dropbutton to open by passing true or to close by
* passing false.
*/
toggle: function (show) {
var isBool = typeof show === 'boolean';
show = isBool ? show : !this.$dropbutton.hasClass('open');
this.$dropbutton.toggleClass('open', show);
},
hoverIn: function () {
// Clear any previous timer we were using.
if (this.timerID) {
window.clearTimeout(this.timerID);
}
},
hoverOut: function () {
// Wait half a second before closing.
this.timerID = window.setTimeout($.proxy(this, 'close'), 500);
},
open: function () {
this.toggle(true);
},
close: function () {
this.toggle(false);
}
});
$.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 '<li class="dropbutton-toggle"><button type="button" role="button"><span class="dropbutton-arrow"><span class="element-invisible">' + options.title + '</span></span></button></li>';
}
});
// Expose constructor in the public space.
Drupal.DropButton = DropButton;
})(jQuery, Drupal);
/**
* @file
* General RTL styles for dropbuttons.
*/
.dropbutton-multiple .dropbutton {
border-left: 1px solid #e8e8e8;
border-right: 0 none;
}
.dropbutton-multiple .dropbutton li > * {
margin-left: 0.25em;
margin-right: 0;
}
/**
* @file
* General styles for dropbuttons.
*/
.dropbutton-wrapper {
cursor: pointer;
}
.dropbutton-widget {
background-color: white;
border: 1px solid #cccccc;
}
.dropbutton-widget:hover {
border-color: #b8b8b8;
}
.dropbutton .dropbutton-action > * {
padding: 0.1em 0.5em;
white-space: nowrap;
}
.dropbutton .secondary-action {
border-top: 1px solid #e8e8e8;
}
.dropbutton-multiple .dropbutton {
border-right: 1px solid #e8e8e8; /* LTR */
}
.dropbutton-multiple .dropbutton .dropbutton-action > * {
margin-right: 0.25em; /* LTR */
}
/**
* Operations dropbuttons
*/
.dropbutton-widget {
left: 0;
right: auto;
}
/**
* @file
* Styles for administration pages.
......@@ -10,3 +9,27 @@
.revision-current {
background: #ffc;
}
/**
* Operations dropbuttons
*/
.dropbutton-wrapper {
display: block;
min-height: 2em;
position: relative;
}
.dropbutton-widget {
position: absolute;
right: 0; /* LTR */
}
.dropbutton-wrapper,
.dropbutton-widget {
max-width: 100%;
}
.dropbutton-multiple.open,
.dropbutton-multiple.open .dropbutton-widget {
max-width: none;
}
.dropbutton-multiple.open {
z-index: 100;
}
......@@ -412,6 +412,9 @@ function node_admin_nodes() {
'#title' => t('Update options'),
'#attributes' => array('class' => array('container-inline')),
'#access' => $admin_access,
'#attached' => array (
'css' => array(drupal_get_path('module', 'node') . '/node.admin.css'),
),
);
$options = array();
foreach (module_invoke_all('node_operations') as $operation => $array) {
......@@ -539,9 +542,9 @@ function node_admin_nodes() {
// Render an unordered list of operations links.
$options[$node->nid]['operations'] = array(
'data' => array(
'#theme' => 'links__node_operations',
'#type' => 'operations',
'#subtype' => 'node',
'#links' => $operations,
'#attributes' => array('class' => array('links', 'inline')),
),
);
}
......
/**
* @file
* Generic theme-independent base styles.
......@@ -144,6 +143,7 @@ div.tree-child-horizontal {
table.sticky-header {
background-color: #fff;
margin-top: 0;
z-index: 500;
}
/**
......
......@@ -535,6 +535,14 @@ function system_element_info() {
'#default_tab' => '',
'#process' => array('form_process_vertical_tabs'),
);
$types['dropbutton'] = array(
'#theme' => 'links__dropbutton',
'#pre_render' => array('drupal_pre_render_dropbutton'),
);
$types['operations'] = array(
'#theme' => 'links__dropbutton__operations',
'#pre_render' => array('drupal_pre_render_dropbutton'),
);
$types['container'] = array(
'#theme_wrappers' => array('container'),
......@@ -1331,6 +1339,26 @@ function system_library_info() {
),
);
// Dropbutton.
$libraries['drupal.dropbutton'] = array(
'title' => 'Dropbutton',
'website' => 'http://drupal.org/node/1608878',
'version' => '1.0',
'js' => array(
'core/misc/dropbutton/dropbutton.js' => array(),
),
'css' => array(
'core/misc/dropbutton/dropbutton.base.css' => array(),
'core/misc/dropbutton/dropbutton.theme.css' => array(),
),
'dependencies' => array(
array('system', 'jquery'),
array('system', 'drupal'),
array('system', 'drupalSettings'),
array('system', 'jquery.once'),
),
);
// Vertical Tabs.
$libraries['drupal.vertical-tabs'] = array(
'title' => 'Vertical Tabs',
......
......@@ -58,6 +58,19 @@ function theme_test_menu() {
'access callback' => TRUE,
'type' => MENU_CALLBACK,
);
$items['theme-test'] = array(
'title' => 'Theme test',
'page callback' => 'system_admin_menu_block_page',
'access callback' => TRUE,
'file path' => drupal_get_path('module', 'system'),
'file' => 'system.admin.inc',
);
$items['theme-test/list/operations'] = array(
'title' => 'Operations',
'page callback' => '_theme_test_list_operations',
'access callback' => TRUE,
);
return $items;
}
......@@ -146,3 +159,81 @@ function theme_test_preprocess_html(&$variables) {
function theme_theme_test_foo($variables) {
return $variables['foo'];
}
/**
* Page callback for manual testing of operation links.
*/
function _theme_test_list_operations() {
$build = array(
'#theme' => 'table',
'#header' => array('Label', 'Created', 'Operations'),
'#rows' => array(),
);
// Add an item with a very long label, using common operations.
$build['#rows']['long']['label'] = l('An item with a very insanely long label, which offers quite a couple of common operations', 'item/long');
$build['#rows']['long']['created'] = format_interval(3200);
$build['#rows']['long']['operations'] = array(
'data' => array(
'#type' => 'operations',
'#subtype' => 'node',
'#links' => array(
'edit' => array(
'title' => 'edit',
'href' => 'item/long/edit',
),
'disable' => array(
'title' => 'disable',
'href' => 'item/long/disable',
),
'clone' => array(
'title' => 'clone',
'href' => 'item/long/clone',
),
'delete' => array(
'title' => 'delete',
'href' => 'item/long/delete',
),
),
),
);
// Add another item, using common operations.
$build['#rows']['another']['label'] = l('Another item, using common operations', 'item/another');
$build['#rows']['another']['created'] = format_interval(8600);
$build['#rows']['another']['operations'] = $build['#rows']['long']['operations'];
// Add an item with only one operation.
$build['#rows']['one']['label'] = l('An item with only one operation', 'item/one');
$build['#rows']['one']['created'] = format_interval(12400);
$build['#rows']['one']['operations'] = array(
'data' => array(
'#type' => 'operations',
'#subtype' => 'node',
'#links' => array(
'edit' => array(
'title' => 'edit',
'href' => 'item/long/edit',
),
),
),
);
// Add an item that can only be viewed.
$build['#rows']['view']['label'] = l('An item that can only be viewed', 'item/view');
$build['#rows']['view']['created'] = format_interval(12400);
$build['#rows']['view']['operations'] = array(
'data' => array(
'#type' => 'operations',
'#subtype' => 'node',
'#links' => array(),
),
);
// Add an item for which the default operation is denied.
$build['#rows']['denied']['label'] = l('An item for which the default operation is denied', 'item/denied');
$build['#rows']['denied']['created'] = format_interval(18600);
$build['#rows']['denied']['operations'] = $build['#rows']['long']['operations'];
unset($build['#rows']['denied']['operations']['data']['#links']['edit']);
return $build;
}
......@@ -1608,6 +1608,19 @@ div.admin-panel .description {
margin: 0;
}
/* ---------- Dropbutton ----------- */
.dropbutton-widget {
background-color: white;
border-radius: 5px;
}
.dropbutton-widget:hover {
background-color: #f8f8f8;
border-color: #b8b8b8;
}
.dropbutton-multiple.open .dropbutton-widget:hover {
background-color: white;
}
/* ----------- media queries ------------------------------- */
@media all and (min-width: 461px) and (max-width: 900px) {
......
......@@ -971,6 +971,31 @@ div.add-or-remove-shortcuts {
margin-left: 1.5em; /* LTR */
}
/* Dropbutton */
.dropbutton-widget {
background-color: #fff;
background-image: -moz-linear-gradient(-90deg, rgba(255, 255, 255, 0), #e7e7e7);
background-image: -o-linear-gradient(-90deg, rgba(255, 255, 255, 0), #e7e7e7);
background-image: -webkit-linear-gradient(-90deg, rgba(255, 255, 255, 0), #e7e7e7);
background-image: linear-gradient(-90deg, rgba(255, 255, 255, 0), #e7e7e7);
border-radius: 5px;
}
.dropbutton-widget:hover {
background-color: #f0f0f0;
border-color: #b8b8b8;
}
.dropbutton-multiple.open .dropbutton-widget:hover {
background-color: #fff;
}
.dropbutton-content li:first-child > * {
text-overflow: ellipsis;
}
.dropbutton-multiple.open .dropbutton-content li:first-child > * {
text-overflow: clip;
}
.dropbutton-arrow {
right: 35%;
}
/* Disable overlay message */
#overlay-disable-message {
background-color: #addafc;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment