Commit d26e8a7d authored by webchick's avatar webchick

Issue #1168246 by sun, mgifford, Manuel Garcia, mbrett5062, ry5n, Everett...

Issue #1168246 by sun, mgifford, Manuel Garcia, mbrett5062, ry5n, Everett Zufelt, nod_, aspilicious, deviantintegral, tim.plunkett: Freedom For Fieldsets! Long Live The DETAILS.
parent 436fc3ce
......@@ -158,7 +158,7 @@ function _authorize_filetransfer_connection_settings($backend) {
*
* The default settings for the file transfer connection forms are saved in
* the database. The settings are stored as a nested array in the case of a
* settings form that has fieldsets or otherwise uses a nested structure.
* settings form that has details or otherwise uses a nested structure.
* Therefore, to properly add defaults, we need to walk through all the
* children form elements and process those defaults recursively.
*
......@@ -170,14 +170,14 @@ function _authorize_filetransfer_connection_settings($backend) {
* The default settings for the file transfer backend we're operating on.
*/
function _authorize_filetransfer_connection_settings_set_defaults(&$element, $key, array $defaults) {
// If we're operating on a form element which isn't a fieldset, and we have
// If we're operating on a form element which isn't a details, and we have
// a default setting saved, stash it in #default_value.
if (!empty($key) && isset($defaults[$key]) && isset($element['#type']) && $element['#type'] != 'fieldset') {
if (!empty($key) && isset($defaults[$key]) && isset($element['#type']) && $element['#type'] != 'details') {
$element['#default_value'] = $defaults[$key];
}
// Now, we walk through all the child elements, and recursively invoke
// ourself on each one. Since the $defaults settings array can be nested
// (because of #tree, any values inside fieldsets will be nested), if
// (because of #tree, any values inside details will be nested), if
// there's a subarray of settings for the form key we're currently
// processing, pass in that subarray to the recursive call. Otherwise, just
// pass on the whole $defaults array.
......
......@@ -5375,8 +5375,8 @@ function drupal_render_page($page) {
*
* The #theme_wrappers property contains an array of theme functions which will
* be called, in order, after #theme has run. These can be used to add further
* markup around the rendered children; e.g., fieldsets add the required markup
* for a fieldset around their rendered child elements. All wrapper theme
* markup around the rendered children; e.g., details add the required markup
* for a details element around their rendered child elements. All wrapper theme
* functions have to include the element's #children property in their output,
* as it contains the output of the previous theme functions and the rendered
* children.
......
......@@ -256,8 +256,8 @@ function drupal_get_form($form_id) {
* handler, and is also used in Ajax handlers.
* - has_file_element: Internal. If TRUE, there is a file element and Form API
* will set the appropriate 'enctype' HTML attribute on the form.
* - groups: Internal. An array containing references to fieldsets to render
* them within vertical tabs.
* - groups: Internal. An array containing references to details elements to
* render them within vertical tabs.
* - storage: $form_state['storage'] is not a special key, and no specific
* support is provided for it in the Form API. By tradition it was
* the location where application-specific data was stored for communication
......@@ -1706,9 +1706,9 @@ function form_error(&$element, $message = '') {
* type, one of the functions in this array is form_process_date(), which adds
* the individual 'year', 'month', 'day', etc. child elements. These functions
* can also be used to set additional properties or implement special logic
* other than adding child elements: for example, for the 'fieldset' element
* type, one of the functions in this array is form_process_fieldset(), which
* adds the attributes and JavaScript needed to make the fieldset collapsible
* other than adding child elements: for example, for the 'details' element
* type, one of the functions in this array is form_process_details(), which
* adds the attributes and JavaScript needed to make the details collapsible
* if the #collapsible property is set. The #process functions are called in
* preorder traversal, meaning they are called for the parent element first,
* then for the child elements.
......@@ -2813,6 +2813,39 @@ function theme_fieldset($variables) {
return $output;
}
/**
* Returns HTML for a details form element and its children.
*
* @param $variables
* An associative array containing:
* - element: An associative array containing the properties of the element.
* Properties used: #attributes, #children, #collapsed, #collapsible,
* #description, #id, #title, #value.
*
* @ingroup themeable
*/
function theme_details($variables) {
$element = $variables['element'];
element_set_attributes($element, array('id'));
_form_set_attributes($element, array('form-wrapper'));
$output = '<details' . new Attribute($element['#attributes']) . '>';
if (!empty($element['#title'])) {
$output .= '<summary>' . $element['#title'] . '</summary>';
}
$output .= '<div class="details-wrapper">';
if (!empty($element['#description'])) {
$output .= '<div class="details-description">' . $element['#description'] . '</div>';
}
$output .= $element['#children'];
if (isset($element['#value'])) {
$output .= $element['#value'];
}
$output .= '</div>';
$output .= "</details>\n";
return $output;
}
/**
* Returns HTML for a radio button form element.
*
......@@ -3670,29 +3703,29 @@ function form_validate_machine_name(&$element, &$form_state) {
}
/**
* Arranges fieldsets into groups.
* Arranges details into groups.
*
* @param $element
* An associative array containing the properties and children of the
* fieldset. Note that $element must be taken by reference here, so processed
* details. Note that $element must be taken by reference here, so processed
* child elements are taken over into $form_state.
* @param $form_state
* The $form_state array for the form this fieldset belongs to.
* The $form_state array for the form this details belongs to.
*
* @return
* The processed element.
*/
function form_process_fieldset(&$element, &$form_state) {
function form_process_details(&$element, &$form_state) {
$parents = implode('][', $element['#parents']);
// Each fieldset forms a new group. The #type 'vertical_tabs' basically only
// injects a new fieldset.
// Each details element forms a new group. The #type 'vertical_tabs' basically
// only injects a new details element.
$form_state['groups'][$parents]['#group_exists'] = TRUE;
$element['#groups'] = &$form_state['groups'];
// Process vertical tabs group member fieldsets.
// Process vertical tabs group member details elements.
if (isset($element['#group'])) {
// Add this fieldset to the defined group (by reference).
// Add this details element to the defined group (by reference).
$group = $element['#group'];
$form_state['groups'][$group][] = &$element;
}
......@@ -3708,28 +3741,28 @@ function form_process_fieldset(&$element, &$form_state) {
*
* @param $element
* An associative array containing the properties and children of the
* fieldset.
* details.
*
* @return
* The modified element with all group members.
*/
function form_pre_render_fieldset($element) {
// The .form-wrapper class is required for #states to treat fieldsets like
function form_pre_render_details($element) {
// The .form-wrapper class is required for #states to treat details like
// containers.
if (!isset($element['#attributes']['class'])) {
$element['#attributes']['class'] = array();
}
// Collapsible fieldsets
// Collapsible details.
if (!empty($element['#collapsible'])) {
$element['#attached']['library'][] = array('system', 'drupal.collapse');
$element['#attributes']['class'][] = 'collapsible';
if (!empty($element['#collapsed'])) {
$element['#attributes']['class'][] = 'collapsed';
}
}
if (empty($element['#collapsed'])) {
$element['#attributes']['open'] = 'open';
}
// Fieldsets may be rendered outside of a Form API context.
// Details may be rendered outside of a Form API context.
if (!isset($element['#parents']) || !isset($element['#groups'])) {
return $element;
}
......@@ -3741,7 +3774,7 @@ function form_pre_render_fieldset($element) {
// Break references and indicate that the element should be rendered as
// group member.
$child = (array) $element['#groups'][$parents][$key];
$child['#group_fieldset'] = TRUE;
$child['#group_details'] = TRUE;
// Inject the element as new child element.
$element[] = $child;
......@@ -3761,7 +3794,7 @@ function form_pre_render_fieldset($element) {
// Intentionally empty to clarify the flow; we simply return $element.
}
// If we injected this element into the group, then we want to render it.
elseif (!empty($element['#group_fieldset'])) {
elseif (!empty($element['#group_details'])) {
// Intentionally empty to clarify the flow; we simply return $element.
}
// Otherwise, this element belongs to a group and the group exists, so we do
......@@ -3779,7 +3812,7 @@ function form_pre_render_fieldset($element) {
*
* @param $element
* An associative array containing the properties and children of the
* fieldset.
* details element.
* @param $form_state
* The $form_state array for the form this vertical tab widget belongs to.
*
......@@ -3787,10 +3820,10 @@ function form_pre_render_fieldset($element) {
* The processed element.
*/
function form_process_vertical_tabs($element, &$form_state) {
// Inject a new fieldset as child, so that form_process_fieldset() processes
// this fieldset like any other fieldset.
// Inject a new details as child, so that form_process_details() processes
// this details element like any other details.
$element['group'] = array(
'#type' => 'fieldset',
'#type' => 'details',
'#theme_wrappers' => array(),
'#parents' => $element['#parents'],
);
......@@ -3813,12 +3846,12 @@ function form_process_vertical_tabs($element, &$form_state) {
}
/**
* Returns HTML for an element's children fieldsets as vertical tabs.
* Returns HTML for an element's children details as vertical tabs.
*
* @param $variables
* An associative array containing:
* - element: An associative array containing the properties and children of
* the fieldset. Properties used: #children.
* the details element. Properties used: #children.
*
* @ingroup themeable
*/
......
......@@ -1880,7 +1880,7 @@ function install_check_requirements($install_state) {
*/
function _install_configure_form($form, &$form_state, &$install_state) {
$form['site_information'] = array(
'#type' => 'fieldset',
'#type' => 'details',
'#title' => st('Site information'),
'#collapsible' => FALSE,
);
......@@ -1899,7 +1899,7 @@ function _install_configure_form($form, &$form_state, &$install_state) {
'#weight' => -15,
);
$form['admin_account'] = array(
'#type' => 'fieldset',
'#type' => 'details',
'#title' => st('Site maintenance account'),
'#collapsible' => FALSE,
);
......@@ -1928,7 +1928,7 @@ function _install_configure_form($form, &$form_state, &$install_state) {
);
$form['server_settings'] = array(
'#type' => 'fieldset',
'#type' => 'details',
'#title' => st('Server settings'),
'#collapsible' => FALSE,
);
......@@ -1955,7 +1955,7 @@ function _install_configure_form($form, &$form_state, &$install_state) {
);
$form['update_notifications'] = array(
'#type' => 'fieldset',
'#type' => 'details',
'#title' => st('Update notifications'),
'#collapsible' => FALSE,
);
......
......@@ -3121,6 +3121,9 @@ function drupal_common_theme() {
'fieldset' => array(
'render element' => 'element',
),
'details' => array(
'render element' => 'element',
),
'radio' => array(
'render element' => 'element',
),
......
......@@ -232,7 +232,7 @@ public function getFormOptions($database) {
);
$form['advanced_options'] = array(
'#type' => 'fieldset',
'#type' => 'details',
'#title' => st('Advanced options'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
......
......@@ -406,7 +406,7 @@ public function getSettingsForm() {
'#description' => t('Your password is not saved in the database and is only used to establish a connection.'),
);
$form['advanced'] = array(
'#type' => 'fieldset',
'#type' => 'details',
'#title' => t('Advanced settings'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
......
......@@ -3,22 +3,57 @@
"use strict";
/**
* The collapsible fieldset object represents a single collapsible fieldset.
* Details feature detection.
*
* @todo This is a stop-gap fix only. collapse.js needs to be replaced with a
* proper HTML5 details polyfill.
*
* @author Mathias Bynens
* @see http://mathiasbynens.be/notes/html5-details-jquery
*/
function CollapsibleFieldset(node, settings) {
var isDetailsSupported = (function (doc) {
var el = doc.createElement('details'),
fake,
root,
diff;
if (!('open' in el)) {
return false;
}
root = doc.body || (function () {
var de = doc.documentElement;
fake = true;
return de.insertBefore(doc.createElement('body'), de.firstElementChild || de.firstChild);
}());
el.innerHTML = '<summary>a</summary>b';
el.style.display = 'block';
root.appendChild(el);
diff = el.offsetHeight;
el.open = true;
diff = diff != el.offsetHeight;
root.removeChild(el);
if (fake) {
root.parentNode.removeChild(root);
}
return diff;
}(document));
/**
* The collapsible details object represents a single collapsible details element.
*/
function CollapsibleDetails(node, settings) {
this.$node = $(node);
this.$node.data('fieldset', this);
this.$node.data('details', this);
this.settings = $.extend({
duration:'fast',
easing:'linear'
},
settings
);
// Expand fieldset if there are errors inside, or if it contains an
// Expand details if there are errors inside, or if it contains an
// element that is targeted by the URI fragment identifier.
var anchor = location.hash && location.hash !== '#' ? ', ' + location.hash : '';
if (this.$node.find('.error' + anchor).length) {
this.$node.removeClass('collapsed');
this.$node.attr('open', true);
}
// Initialize and setup the summary,
this.setupSummary();
......@@ -27,19 +62,19 @@ function CollapsibleFieldset(node, settings) {
}
/**
* Extend CollapsibleFieldset function.
* Extend CollapsibleDetails function.
*/
$.extend(CollapsibleFieldset, {
$.extend(CollapsibleDetails, {
/**
* Holds references to instantiated CollapsibleFieldset objects.
* Holds references to instantiated CollapsibleDetails objects.
*/
fieldsets: []
instances: []
});
/**
* Extend CollapsibleFieldset prototype.
* Extend CollapsibleDetails prototype.
*/
$.extend(CollapsibleFieldset.prototype, {
$.extend(CollapsibleDetails.prototype, {
/**
* Flag preventing multiple simultaneous animations.
*/
......@@ -57,17 +92,16 @@ $.extend(CollapsibleFieldset.prototype, {
* Initialize and setup legend markup.
*/
setupLegend: function () {
// Turn the legend into a clickable link, but retain span.fieldset-legend
// for CSS positioning.
var $legend = this.$node.find('> legend .fieldset-legend');
// Turn the summary into a clickable link.
var $legend = this.$node.find('> summary');
$('<span class="fieldset-legend-prefix element-invisible"></span>')
.append(this.$node.hasClass('collapsed') ? Drupal.t('Show') : Drupal.t('Hide'))
$('<span class="details-summary-prefix element-invisible"></span>')
.append(this.$node.attr('open') ? Drupal.t('Hide') : Drupal.t('Show'))
.prependTo($legend)
.after(' ');
// .wrapInner() does not retain bound events.
var $link = $('<a class="fieldset-title" href="#"></a>')
var $link = $('<a class="details-title" href="#"></a>')
.prepend($legend.contents())
.appendTo($legend)
.click($.proxy(this.onLegendClick, this));
......@@ -88,19 +122,18 @@ $.extend(CollapsibleFieldset.prototype, {
this.$summary.html(text ? ' (' + text + ')' : '');
},
/**
* Toggle the visibility of a fieldset using smooth animations.
* Toggle the visibility of a details element using smooth animations.
*/
toggle: function () {
// Don't animate multiple times.
if (this.animating) {
return;
}
if (this.$node.is('.collapsed')) {
var $content = this.$node.find('> .fieldset-wrapper').hide();
if (!this.$node.attr('open')) {
var $content = this.$node.find('> .details-wrapper').hide();
this.$node
.removeClass('collapsed')
.trigger({ type:'collapsed', value:false })
.find('> legend span.fieldset-legend-prefix').html(Drupal.t('Hide'));
.find('> summary span.details-summary-prefix').html(Drupal.t('Hide'));
$content.slideDown(
$.extend(this.settings, {
complete:$.proxy(this.onCompleteSlideDown, this)
......@@ -109,7 +142,7 @@ $.extend(CollapsibleFieldset.prototype, {
}
else {
this.$node.trigger({ type:'collapsed', value:true });
this.$node.find('> .fieldset-wrapper').slideUp(
this.$node.find('> .details-wrapper').slideUp(
$.extend(this.settings, {
complete:$.proxy(this.onCompleteSlideUp, this)
})
......@@ -117,19 +150,20 @@ $.extend(CollapsibleFieldset.prototype, {
}
},
/**
* Completed opening fieldset.
* Completed opening details element.
*/
onCompleteSlideDown: function () {
this.$node.attr('open', true);
this.$node.trigger('completeSlideDown');
this.animating = false;
},
/**
* Completed closing fieldset.
* Completed closing details element.
*/
onCompleteSlideUp: function () {
this.$node.attr('open', false);
this.$node
.addClass('collapsed')
.find('> legend span.fieldset-legend-prefix').html(Drupal.t('Show'));
.find('> summary span.details-summary-prefix').html(Drupal.t('Show'));
this.$node.trigger('completeSlideUp');
this.animating = false;
}
......@@ -137,16 +171,19 @@ $.extend(CollapsibleFieldset.prototype, {
Drupal.behaviors.collapse = {
attach: function (context, settings) {
var $collapsibleFieldsets = $(context).find('fieldset.collapsible').once('collapse');
if ($collapsibleFieldsets.length) {
for (var i = 0; i < $collapsibleFieldsets.length; i++) {
CollapsibleFieldset.fieldsets.push(new CollapsibleFieldset($collapsibleFieldsets[i], settings.collapsibleFieldset));
if (isDetailsSupported) {
return;
}
var $collapsibleDetails = $(context).find('details.collapsible').once('collapse');
if ($collapsibleDetails.length) {
for (var i = 0; i < $collapsibleDetails.length; i++) {
CollapsibleDetails.instances.push(new CollapsibleDetails($collapsibleDetails[i], settings.collapsibleDetails));
}
}
}
};
// Expose constructor in the public space.
Drupal.CollapsibleFieldset = CollapsibleFieldset;
Drupal.CollapsibleDetails = CollapsibleDetails;
})(jQuery, Drupal);
......@@ -420,7 +420,7 @@ states.Trigger.states = {
collapsed: {
'collapsed': function(e) {
return (typeof e !== 'undefined' && 'value' in e) ? e.value : this.is('.collapsed');
return (typeof e !== 'undefined' && 'value' in e) ? e.value : !this.is('[open]');
}
}
};
......@@ -477,6 +477,8 @@ states.State.aliases = {
'unchecked': '!checked',
'irrelevant': '!relevant',
'expanded': '!collapsed',
'open': '!collapsed',
'closed': 'collapsed',
'readwrite': '!readonly'
};
......@@ -537,8 +539,8 @@ $(document).bind('state:checked', function(e) {
$(document).bind('state:collapsed', function(e) {
if (e.trigger) {
if ($(e.target).is('.collapsed') !== e.value) {
$(e.target).find('> legend a').click();
if ($(e.target).is('[open]') === e.value) {
$(e.target).find('> summary a').click();
}
}
});
......
......@@ -11,12 +11,11 @@ div.vertical-tabs {
margin: -1px 0 -1px -15em; /* LTR */
float: left; /* LTR */
}
.vertical-tabs fieldset.vertical-tabs-pane {
margin: 0 !important;
padding: 0 1em;
.vertical-tabs .vertical-tabs-pane {
margin: 0;
border: 0;
}
fieldset.vertical-tabs-pane > legend {
.vertical-tabs-pane > summary {
display: none;
}
......@@ -62,7 +61,7 @@ fieldset.vertical-tabs-pane > legend {
* with "box-sizing" to prevent box model issues from occurring in most browsers.
*/
.vertical-tabs .form-type-textfield input {
width: 100%;
max-width: 100%;
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
......
......@@ -3,13 +3,13 @@
"use strict";
/**
* This script transforms a set of fieldsets into a stack of vertical
* This script transforms a set of details into a stack of vertical
* tabs. Another tab pane can be selected by clicking on the respective
* tab.
*
* Each tab may have a summary which can be updated by another
* script. For that to work, each fieldset has an associated
* 'verticalTabCallback' (with jQuery.data() attached to the fieldset),
* script. For that to work, each details element has an associated
* 'verticalTabCallback' (with jQuery.data() attached to the details),
* which is called every time the user performs an update to a form
* element inside the tab pane.
*/
......@@ -25,9 +25,9 @@ Drupal.behaviors.verticalTabs = {
var focusID = $this.find(':hidden.vertical-tabs-active-tab').val();
var tab_focus;
// Check if there are some fieldsets that can be converted to vertical-tabs
var $fieldsets = $this.find('> fieldset');
if ($fieldsets.length === 0) {
// Check if there are some details that can be converted to vertical-tabs
var $details = $this.find('> details');
if ($details.length === 0) {
return;
}
......@@ -35,16 +35,17 @@ Drupal.behaviors.verticalTabs = {
var tab_list = $('<ul class="vertical-tabs-list"></ul>');
$this.wrap('<div class="vertical-tabs clearfix"></div>').before(tab_list);
// Transform each fieldset into a tab.
$fieldsets.each(function () {
// Transform each details into a tab.
$details.each(function () {
var $this = $(this);
var vertical_tab = new Drupal.verticalTab({
title: $this.find('> legend').text(),
fieldset: $this
title: $this.find('> summary').text(),
details: $this
});
tab_list.append(vertical_tab.item);
$this
.removeClass('collapsible collapsed')
.attr('open', true)
.addClass('vertical-tabs-pane')
.data('verticalTab', vertical_tab);
if (this.id === focusID) {
......@@ -79,7 +80,7 @@ Drupal.behaviors.verticalTabs = {
* @param settings
* An object with the following keys:
* - title: The name of the tab.
* - fieldset: The jQuery object of the fieldset that is the tab pane.
* - details: The jQuery object of the details element that is the tab pane.
*/
Drupal.verticalTab = function (settings) {
var self = this;
......@@ -96,12 +97,12 @@ Drupal.verticalTab = function (settings) {
event.preventDefault();
if (event.keyCode === 13) {
self.focus();
// Set focus on the first input field of the visible fieldset/tab pane.
$("fieldset.vertical-tabs-pane :input:visible:enabled:first").focus();
// Set focus on the first input field of the visible details/tab pane.
$(".vertical-tabs-pane :input:visible:enabled:first").focus();
}
});
this.fieldset
this.details
.bind('summaryUpdated', function () {
self.updateSummary();
})
......@@ -113,17 +114,17 @@ Drupal.verticalTab.prototype = {
* Displays the tab's content pane.
*/
focus: function () {
this.fieldset
.siblings('fieldset.vertical-tabs-pane')
this.details
.siblings('.vertical-tabs-pane')
.each(function () {
var tab = $(this).data('verticalTab');
tab.fieldset.hide();
tab.details.hide();
tab.item.removeClass('selected');
})
.end()
.show()
.siblings(':hidden.vertical-tabs-active-tab')
.val(this.fieldset.attr('id'));
.val(this.details.attr('id'));
this.item.addClass('selected');
// Mark the active tab for screen readers.
$('#active-vertical-tab').remove();
......@@ -134,7 +135,7 @@ Drupal.verticalTab.prototype = {
* Updates the tab's summary.
*/
updateSummary: function () {
this.summary.html(this.fieldset.drupalGetSummary());
this.summary.html(this.details.drupalGetSummary());
},
/**
......@@ -148,8 +149,8 @@ Drupal.verticalTab.prototype = {
// method.
this.item.parent().children('.vertical-tab-button').removeClass('first')
.filter(':visible:first').addClass('first');
// Display the fieldset.
this.fieldset.removeClass('vertical-tab-hidden').show();
// Display the details element.
this.details.removeClass('vertical-tab-hidden').show();
// Focus this tab.
this.focus();
return this;
......@@ -166,10 +167,10 @@ Drupal.verticalTab.prototype = {
// method.
this.item.parent().children('.vertical-tab-button').removeClass('first')
.filter(':visible:first').addClass('first');
// Hide the fieldset.
this.fieldset.addClass('vertical-tab-hidden').hide();
// Hide the details element.
this.details.addClass('vertical-tab-hidden').hide();
// Focus the first visible tab (if there is one).
var $firstTab = this.fieldset.siblings('.vertical-tabs-pane:not(.vertical-tab-hidden):first');
var $firstTab = this.details.siblings('.vertical-tabs-pane:not(.vertical-tab-hidden):first');
if ($firstTab.length) {
$firstTab.data('verticalTab').focus();
}
......
......@@ -96,7 +96,7 @@ function action_admin_manage() {
*/
function action_admin_manage_form($form, &$form_state, $options = array()) {
$form['parent'] = array(
'#type' => 'fieldset',
'#type' => 'details',
'#title' => t('Create an advanced action'),
'#attributes' => array('class' => array('container-inline')),
);
......
......@@ -538,7 +538,7 @@ function aggregator_admin_form($form, $form_state) {
}
if (count($basic_conf)) {