Index: misc/collapse.js =================================================================== RCS file: /cvs/drupal/drupal/misc/collapse.js,v retrieving revision 1.20 diff -u -r1.20 collapse.js --- misc/collapse.js 13 Mar 2009 23:15:08 -0000 1.20 +++ misc/collapse.js 9 Apr 2009 23:09:16 -0000 @@ -60,6 +60,14 @@ fieldset.removeClass('collapsed'); } + var summary = $(''); + fieldset. + bind('summaryUpdated', function() { + var text = $.trim(fieldset.getSummary()); + summary.html(text ? ' (' + text + ')' : ''); + }) + .trigger('summaryUpdated'); + // Turn the legend into a clickable link and wrap the contents of the fieldset // in a div for easier animation var text = this.innerHTML; @@ -72,6 +80,7 @@ } return false; })) + .append(summary) .after($('
') .append(fieldset.children(':not(legend):not(.action)'))) .addClass('collapse-processed'); Index: misc/form.js =================================================================== RCS file: /cvs/drupal/drupal/misc/form.js,v retrieving revision 1.6 diff -u -r1.6 form.js --- misc/form.js 30 Mar 2009 03:15:40 -0000 1.6 +++ misc/form.js 9 Apr 2009 23:09:16 -0000 @@ -1,6 +1,51 @@ // $Id: form.js,v 1.6 2009/03/30 03:15:40 webchick Exp $ (function($) { +/** + * Retrieves the summary for the first element. + */ +$.fn.getSummary = function() { + var callback = this.data('summaryCallback'); + return (this[0] && callback) ? $.trim(callback(this[0])) : ''; +}; + +/** + * Sets the summary for all matched elements. + * + * @param callback + * Either a function that will be called each time the summary is + * retrieved or a string (which is returned each time). + */ +$.fn.setSummary = function(callback) { + var that = this; + if (typeof callback != 'function') { + var val = callback; + callback = function() { return val; }; + } + + return this + .data('summaryCallback', callback) + .unbind('formUpdated.summary') + .bind('formUpdated.summary', function() { + that.trigger('summaryUpdated'); + }) + .trigger('summaryUpdated'); +}; + +/** + * Sends a 'formUpdated' event each time a form element is modified. + */ +Drupal.behaviors.formUpdated = { + attach: function(context) { + var events = 'change.formUpdated click.formUpdated blur.formUpdated keyup.formUpdated'; + $(context) + .find(':select').andSelf().filter(':select') + .unbind(events).bind(events, function() { + $(this).trigger('formUpdated'); + }); + } +}; + Drupal.behaviors.multiselectSelector = { attach: function(context, settings) { // Automatically selects the right radio button in a multiselect control. Index: includes/form.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/form.inc,v retrieving revision 1.326 diff -u -r1.326 form.inc --- includes/form.inc 30 Mar 2009 03:15:40 -0000 1.326 +++ includes/form.inc 9 Apr 2009 23:09:16 -0000 @@ -227,6 +227,7 @@ 'method' => 'post', 'rerender' => TRUE, 'programmed' => FALSE, + 'groups' => array(), ); } @@ -286,6 +287,10 @@ // then process the form for rendering. $form_state['input'] = array(); + // Also clear out all group associations as these might be different + // when rerendering the form. + $form_state['groups'] = array(); + // Do not call drupal_process_form(), since it would prevent the rebuilt form // to submit. $form = form_builder($form_id, $form, $form_state); @@ -939,7 +944,8 @@ /** * Walk through the structured form array, adding any required * properties to each element and mapping the incoming $_POST - * data to the proper elements. + * data to the proper elements. Also, execute any #process handlers + * attached to a specific element. * * @param $form_id * A unique string identifying the form for validation, submission, @@ -972,9 +978,22 @@ } } + if (!isset($form['#id'])) { + $form['#id'] = form_clean_id('edit-' . implode('-', $form['#parents'])); + } if (isset($form['#input']) && $form['#input']) { _form_builder_handle_input_element($form_id, $form, $form_state, $complete_form); } + // Allow for elements to expand to multiple elements, e.g., radios, + // checkboxes and files. + if (isset($form['#process']) && !$form['#processed']) { + foreach ($form['#process'] as $process) { + if (drupal_function_exists($process)) { + $form = $process($form, $form_state, $complete_form); + } + } + $form['#processed'] = TRUE; + } $form['#defaults_loaded'] = TRUE; // We start off assuming all form elements are in the correct order. @@ -1062,8 +1081,7 @@ /** * Populate the #value and #name properties of input elements so they - * can be processed and rendered. Also, execute any #process handlers - * attached to a specific element. + * can be processed and rendered. */ function _form_builder_handle_input_element($form_id, &$form, &$form_state, $complete_form) { if (!isset($form['#name'])) { @@ -1080,9 +1098,6 @@ } array_unshift($form['#parents'], $name); } - if (!isset($form['#id'])) { - $form['#id'] = form_clean_id('edit-' . implode('-', $form['#parents'])); - } if (!empty($form['#disabled'])) { $form['#attributes']['disabled'] = 'disabled'; @@ -1151,16 +1166,6 @@ } } } - // Allow for elements to expand to multiple elements, e.g., radios, - // checkboxes and files. - if (isset($form['#process']) && !$form['#processed']) { - foreach ($form['#process'] as $process) { - if (drupal_function_exists($process)) { - $form = $process($form, isset($edit) ? $edit : NULL, $form_state, $complete_form); - } - } - $form['#processed'] = TRUE; - } form_set_value($form, $form['#value'], $form_state); } @@ -1597,6 +1602,7 @@ $element['#attributes']['class'] .= ' collapsed'; } } + $element['#attributes']['id'] = $element['#id']; return '\n"; } @@ -2143,7 +2149,6 @@ * An associative array containing the properties and children of the * tableselect element. * Properties used: header, options, empty, js_select. - * * @return * A themed HTML string representing the table. * @@ -2186,7 +2191,6 @@ * @param $element * An associative array containing the properties and children of the * tableselect element. - * * @return * The processed element. */ @@ -2248,6 +2252,128 @@ } /** + * Adds fieldsets to the specified group or adds group members to this + * fieldset. + * + * @param $element + * An associative array containing the properties and children of the + * fieldset. + * @param $form_state + * The $form_state array for the form this fieldset belongs to. + * @return + * The processed element. + */ +function form_process_fieldset(&$element, &$form_state) { + $parents = implode('][', $element['#parents']); + + // Add this fieldset to a group if one is set and if it's not being + // added to itself. + if (isset($element['#group']) && $element['#group'] != $parents) { + if (isset($form_state['groups'][$element['#group']]) && !empty($form_state['groups'][$element['#group']]['#group_exists'])) { + // Trick drupal_render() into believing this has already been output. + // The group widget will rerender this later. It's only set to + // #printed if the group already exists. That way, we can be sure + // that the fieldset /will/ be rendered later. + $element['#printed'] = TRUE; + } + + // Store a reference to this fieldset for the vertical tabs processing function. + $form_state['groups'][$element['#group']][] = &$element; + } + + // Each fieldset can be a group itself and gets a reference to all + // elements in its group. + $form_state['groups'][$parents]['#group_exists'] = TRUE; + foreach (element_children($form_state['groups'][$parents]) as $key) { + $form_state['groups'][$parents][$key]['#printed'] = TRUE; + } + $element['#group_members'] = &$form_state['groups'][$parents]; + + // Contains form element summary functionalities. + drupal_add_js('misc/form.js', array('weight' => JS_LIBRARY + 1)); + + return $element; +} + +/** + * Adds members of this group as actual elements for rendering. + * + * @param $element + * An associative array containing the properties and children of the + * fieldset. + * @return + * The modified element with all group members. + */ +function form_pre_render_fieldset($element) { + if (!empty($element['#group_members'])) { + // Add the group members to this fieldset for rendering purposes only. + foreach (element_children($element['#group_members']) as $key) { + // This was set in form_process_fieldset so that fieldsets which are + // added to groups are not rendered at their original location. + // drupal_render_children() will set this back to TRUE. + unset($element['#group_members'][$key]['#printed']); + $element[] = &$element['#group_members'][$key]; + } + + // Resort the element's children after the group members have been added. + $element['#sorted'] = FALSE; + } + + return $element; +} + +/** + * Creates a group formatted as vertical tabs. + * + * @param $element + * An associative array containing the properties and children of the + * fieldset. + * @param $form_state + * The $form_state array for the form this vertical tab widget belongs to. + * @return + * The processed element. + */ +function form_process_vertical_tabs($element, &$form_state) { + $element['group'] = array( + '#type' => 'fieldset', + '#theme_wrapper' => '', + '#parents' => $element['#parents'], + ); + + + // The JavaScript stores the currently selected tab in this hidden + // field so that the active tab can be restored the next time the + // form is rendered, e.g. on preview pages or when form validation + // fails. + $name = implode('__', $element['#parents']); + if (isset($form_state['values'][$name . '__active_tab'])) { + $element['#default_tab'] = $form_state['values'][$name . '__active_tab']; + } + $element[$name . '__active_tab'] = array( + '#type' => 'hidden', + '#default_value' => $element['#default_tab'], + '#attributes' => array('class' => 'vertical-tabs-active-tab'), + ); + + return $element; +} + +/** + * Makes the element's children fieldsets be vertical tabs. + * + * @param $element + * An associative array containing the properties and children of the + * fieldset. + */ +function theme_vertical_tabs(&$element) { + // Add required JavaScript and Stylesheet. + drupal_add_js('misc/vertical-tabs.js', array('weight' => JS_DEFAULT - 1)); + drupal_add_css('misc/vertical-tabs.css'); + + return '