diff options
author | Angie Byron <webchick@24967.no-reply.drupal.org> | 2009-04-11 22:19:46 +0000 |
---|---|---|
committer | Angie Byron <webchick@24967.no-reply.drupal.org> | 2009-04-11 22:19:46 +0000 |
commit | e888f0061cb7ca2f07b38ad4ff09da99faa75580 (patch) | |
tree | b1ffa36ead61e0061e99fdb4c33a8ea823eb4af5 /includes | |
parent | f278da9e50815f279ce4c7bda993f5d21b43efa3 (diff) | |
download | brdo-e888f0061cb7ca2f07b38ad4ff09da99faa75580.tar.gz brdo-e888f0061cb7ca2f07b38ad4ff09da99faa75580.tar.bz2 |
#323112 by dmitrig01, kkaefer, quicksketch, frando and many many more: Now presenting... Vertical Tabs. Fantastic new UI improvement for node forms and hopefully more in the future.
Diffstat (limited to 'includes')
-rw-r--r-- | includes/common.inc | 25 | ||||
-rw-r--r-- | includes/form.inc | 176 |
2 files changed, 183 insertions, 18 deletions
diff --git a/includes/common.inc b/includes/common.inc index b36c4b2c4..f17256c67 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -3368,6 +3368,28 @@ function drupal_render(&$elements) { } } + // Add additional CSS and JavaScript files associated with this element. + foreach (array('css', 'js') as $kind) { + if (!empty($elements['#attached_' . $kind]) && is_array($elements['#attached_' . $kind])) { + foreach ($elements['#attached_' . $kind] as $data => $options) { + // If the value is not an array, it's a filename and passed as first + // (and only) argument. + if (!is_array($options)) { + $data = $options; + $options = NULL; + } + // When drupal_add_js with 'type' => 'setting' is called, the first + // parameter ($data) is an array. Arrays can't be keys in PHP, so we + // have to get $data from the value array. + if (is_numeric($data)) { + $data = $options['data']; + unset($options['data']); + } + call_user_func('drupal_add_' . $kind, $data, $options); + } + } + } + // Get the children of the element, sorted by weight. $children = element_children($elements, TRUE); @@ -3750,6 +3772,9 @@ function drupal_common_theme() { 'text_format_wrapper' => array( 'arguments' => array('element' => NULL), ), + 'vertical_tabs' => array( + 'arguments' => array('element' => NULL), + ), ); } diff --git a/includes/form.inc b/includes/form.inc index f6a131a53..5058ffdf2 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -227,6 +227,7 @@ function form_state_defaults() { 'method' => 'post', 'rerender' => TRUE, 'programmed' => FALSE, + 'groups' => array(), ); } @@ -286,6 +287,10 @@ function drupal_rebuild_form($form_id, &$form_state, $form_build_id = NULL) { // 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 @@ function form_error(&$element, $message = '') { /** * 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 @@ function form_builder($form_id, $form, &$form_state) { } } + 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 @@ function form_builder($form_id, $form, &$form_state) { /** * 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 @@ function _form_builder_handle_input_element($form_id, &$form, &$form_state, $com } 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 @@ function _form_builder_handle_input_element($form_id, &$form, &$form_state, $com } } } - // 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 @@ function theme_fieldset($element) { $element['#attributes']['class'] .= ' collapsed'; } } + $element['#attributes']['id'] = $element['#id']; return '<fieldset' . drupal_attributes($element['#attributes']) . '>' . ($element['#title'] ? '<legend>' . $element['#title'] . '</legend>' : '') . (isset($element['#description']) && $element['#description'] ? '<div class="description">' . $element['#description'] . '</div>' : '') . (!empty($element['#children']) ? $element['#children'] : '') . (isset($element['#value']) ? $element['#value'] : '') . "</fieldset>\n"; } @@ -2143,7 +2149,6 @@ function form_process_checkboxes($element) { * 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 @@ function theme_tableselect($element) { * @param $element * An associative array containing the properties and children of the * tableselect element. - * * @return * The processed element. */ @@ -2248,6 +2252,142 @@ function form_process_tableselect($element) { } /** + * 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. This only happens when the + // group is actually defined ('#group_exists' is TRUE). This prevents + // fieldsets from disappearing when the group they are associated to + // does not exist. + // If the group does not exist yet, the element's #printed value is left + // as is. As soon as the group is processed (fieldsets are also groups; + // see below), this element's #printed value is set to TRUE to prevent + // rendering in the original context. + $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; + // There might already be elements associated with this group. Since the + // group did not exist yet at the time they were added to this group, they + // couldn't set #printed to TRUE (see above). We now know that this group + // does in fact exist and set #printed to TRUE to prevent rendering in the + // original context. + 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) { + // To save us from modifying the existing element and changing its #type, + // a new form element is created as a child. The default #process hooks + // are called automatically by the form renderer and we don't have to do + // that manually. + $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 '<div class="vertical-tabs-panes">' . $element['#children'] . '</div>'; +} + +/** * Theme a form submit button. * * @ingroup themeable |