Index: includes/common.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/common.inc,v retrieving revision 1.925 diff -u -p -r1.925 common.inc --- includes/common.inc 18 Jun 2009 21:19:01 -0000 1.925 +++ includes/common.inc 19 Jun 2009 13:10:00 -0000 @@ -4109,6 +4109,9 @@ function drupal_common_theme() { 'vertical_tabs' => array( 'arguments' => array('element' => NULL), ), + 'horizontal_tabs' => array( + 'arguments' => array('element' => NULL), + ), ); } Index: includes/form.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/form.inc,v retrieving revision 1.342 diff -u -p -r1.342 form.inc --- includes/form.inc 18 Jun 2009 15:48:13 -0000 1.342 +++ includes/form.inc 19 Jun 2009 13:10:00 -0000 @@ -2366,7 +2366,151 @@ function theme_vertical_tabs($element) { drupal_add_js('misc/vertical-tabs.js', array('weight' => JS_DEFAULT - 1)); drupal_add_css('misc/vertical-tabs.css'); - return '
' . $element['#children'] . '
'; + return '
' . $element['#children'] . '
'; +} + +/** + * Creates a group formatted as horizontal 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 horizontal tab widget belongs to. + * @return + * The processed element. + */ +function form_process_horizontal_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' => 'horizontal-tabs-active-tab'), + ); + + return $element; +} + +/** + * A process function for the base form element, which iterates through the + * form elements and assigns them recursively to horizontal tabs. + */ +function form_expand_horizontal_tabs($element, &$form_state) { + return isset($element['#tabs']) ? _form_expand_horizontal_tabs($element, $element['#tabs']) : $element; +} + +/** + * Expand the elements of a form such that they are sorted into the appropriate + * horizontal tabs based on their #group attribute. Elements with no specificly + * defined #group attribute or an invalid #group attribute will behave as their + * parent elements behaved; if an element has no parent element either, it will + * be sent to the primary tab. Note that this function is recursive and changes + * the structure of the form array. + * + * @param $form + * The form to expand into proper Forms API notation for horizontal tabs. + * @param $tabs + * The index of the element within the form that all items in the form are to + * be sorted into. + * @param $tabs_element + * Internal use only. + * @param $parent_tab + * Internal use only. + * @param $path + * Internal use only. + * @return + * The expanded form. + */ +function _form_expand_horizontal_tabs($form, $tabs, $tabs_element = NULL, $parent_tab = NULL, $path = array()) { + static $full_form; + $root = FALSE; + if (!count($path)) { + $root = TRUE; + $tabs_element = $form[$tabs]; + $full_form = $form; + foreach ($tabs_element['#tabs'] as $tab => $label) { + $full_form[$tabs][$tab] = array( + '#prefix' => '
', + '#suffix' => '
', + ); + } + } + foreach (element_children($form) as $key) { + if (($key == $tabs) && $root) { + continue; + } + $element = $form[$key]; + if ((isset($element['#group']) && !isset($tabs_element['#tabs'][$element['#group']]) && $element['#group'] != 'none') || (!isset($element['#group']) && is_null($parent_tab))) { + $element['#group'] = $tabs_element['#primary_tab']; + } + elseif (!isset($element['#group'])) { + $element['#group'] = $parent_tab; + } + if (isset($element['#group']) && $element['#group'] != 'none') { + $new_key = $key; + while (isset($full_form[$tabs][$element['#group']][$new_key])) { + $new_key = '_' . $new_key; + } + $full_form[$tabs][$element['#group']][$new_key] = $element; + unset($form[$key]); + $to_be_deleted = &$full_form; + foreach ($path as $path_part) { + $to_be_deleted = &$to_be_deleted[$path_part]; + } + unset($to_be_deleted[$key]); + } + else { + $form[$key] = form_expand_horizontal_tabs($element, $tabs, $tabs_element, $element['#group'] == 'none' ? NULL : $element['#group'], array_merge($path, array($key))); + } + } + if ($root) { + return $full_form; + } + return $form; +} + +/** + * Assigns the element's children into horizontal tabs. + * + * @param $element + * An associative array containing the properties and children of the + * fieldset. + */ +function theme_horizontal_tabs($element) { + // Add required JavaScript and Stylesheet. + drupal_add_js('misc/horizontal-tabs.js', array('weight' => JS_DEFAULT - 1)); + drupal_add_css('misc/horizontal-tabs.css'); + + // Make sure not to overwrite classes. + if (isset($element['#attributes']['class'])) { + $element['#attributes']['class'] = 'horizontal-tabs ' . $element['#attributes']['class']; + } + else { + $element['#attributes']['class'] = 'horizontal-tabs'; + } + + drupal_add_js(array('horizontalTabs' => array($element['#id'] => $element['#tabs'])), 'setting'); + + $prefix = isset($element['#prefix']) ? $element['#prefix'] : ''; + $suffix = isset($element['#suffix']) ? $element['#suffix'] : ''; + return $prefix . '
' . $element['#children'] . '
' . $suffix; } /** Index: misc/horizontal-tabs-rtl.css =================================================================== RCS file: misc/horizontal-tabs-rtl.css diff -N misc/horizontal-tabs-rtl.css --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ misc/horizontal-tabs-rtl.css 15 Jun 2009 17:52:44 -0000 @@ -0,0 +1,6 @@ +/* $Id$ */ + +.horizontal-tabs > ul > li > a { + margin-right: 0; + margin-left: 0.5em; +} Index: misc/horizontal-tabs.css =================================================================== RCS file: misc/horizontal-tabs.css diff -N misc/horizontal-tabs.css --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ misc/horizontal-tabs.css 15 Jun 2009 17:52:44 -0000 @@ -0,0 +1,40 @@ +/* $Id$ */ + +/* +** Horizontal tabs. +*/ +.horizontal-tabs > ul { + border-collapse: collapse; + white-space: nowrap; + list-style: none; + height: auto; + line-height: normal; + border-bottom: 1px solid #bbb; +} + +.horizontal-tabs > ul > li { + display: inline; +} + +.horizontal-tabs > ul > li > a { + background-color: #ddd; + border-color: #bbb; + border-width: 1px; + border-style: solid solid none solid; + height: auto; + margin-right: 0.5em; /* LTR */ + padding: 0 1em; + text-decoration: none; +} + +.horizontal-tabs > ul > li.active > a { + background-color: #fff; + border: 1px solid #bbb; + border-bottom: #fff 1px solid; +} + +.horizontal-tabs > ul > li > a:hover { + background-color: #eee; + border-color: #ccc; + border-bottom-color: #eee; +} \ No newline at end of file Index: misc/horizontal-tabs.js =================================================================== RCS file: misc/horizontal-tabs.js diff -N misc/horizontal-tabs.js --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ misc/horizontal-tabs.js 15 Jun 2009 18:09:41 -0000 @@ -0,0 +1,35 @@ +// $Id$ + +(function ($) { + +/** + * This script sorts a set of elements into a set of horizontal tabs. Another + * tab pane can be selected by clicking on the respective tab. + */ + +Drupal.behaviors.horizontalTabs = { + attach: function (context) { + for (horizontalTabs in Drupal.settings.horizontalTabs) { + $('#horizontal-tabs-panes-' + horizontalTabs + ':not(.horizontal-tabs-processed)', context).each(function () { + for (horizontalTab in Drupal.settings.horizontalTabs[horizontalTabs]) { + label = Drupal.settings.horizontalTabs[horizontalTabs][horizontalTab]; + $(this).append('
  • ' + Drupal.checkPlain(label) + '
  • '); + $('#horizontal-tabs-tab-' + Drupal.checkPlain(horizontalTab) + ' > a').click(function() { + $(this).parent().parent().parent().find('.horizontal-tab-section').hide(); + $('#horizontal-tab-section-' + $(this).parent().attr('id').substr(20)).show(); + $(this).parent().parent().find('> li').removeClass('active').find('> a').removeClass('active'); + $(this).addClass('active').parent().addClass('active'); + return false; + }); + } + $(this).addClass('horizontal-tabs-processed'); + var active_tab = $('#' + horizontalTabs + '--active-tab').val(); + $('#horizontal-tabs-panes-' + horizontalTabs).parent().find('.horizontal-tab-section').hide(); + $('#horizontal-tab-section-' + active_tab).show(); + $('#horizontal-tabs-tab-' + active_tab).addClass('active').find('> a').addClass('active'); + }); + } + } +}; + +})(jQuery); \ No newline at end of file Index: modules/comment/comment.module =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v retrieving revision 1.724 diff -u -p -r1.724 comment.module --- modules/comment/comment.module 18 Jun 2009 10:36:22 -0000 1.724 +++ modules/comment/comment.module 19 Jun 2009 13:10:26 -0000 @@ -598,6 +598,7 @@ function comment_form_alter(&$form, $for '#group' => 'additional_settings', '#attached_js' => array(drupal_get_path('module', 'comment') . '/comment-node-form.js'), '#weight' => 30, + '#group' => 'meta', ); $comment_count = isset($node->nid) ? db_query('SELECT comment_count FROM {node_comment_statistics} WHERE nid = :nid', array(':nid' => $node->nid))->fetchField() : 0; $comment_settings = ($node->comment == COMMENT_NODE_HIDDEN && empty($comment_count)) ? COMMENT_NODE_CLOSED : $node->comment; Index: modules/menu/menu.module =================================================================== RCS file: /cvs/drupal/drupal/modules/menu/menu.module,v retrieving revision 1.192 diff -u -p -r1.192 menu.module --- modules/menu/menu.module 27 May 2009 18:33:58 -0000 1.192 +++ modules/menu/menu.module 19 Jun 2009 13:10:26 -0000 @@ -407,6 +407,7 @@ function menu_form_alter(&$form, $form_s '#attached_js' => array(drupal_get_path('module', 'menu') . '/menu.js'), '#tree' => TRUE, '#weight' => -2, + '#group' => 'meta', '#attributes' => array('class' => 'menu-item-form'), ); $item = $form['#node']->menu; Index: modules/node/node.css =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.css,v retrieving revision 1.7 diff -u -p -r1.7 node.css --- modules/node/node.css 30 Mar 2009 03:15:40 -0000 1.7 +++ modules/node/node.css 19 Jun 2009 13:53:04 -0000 @@ -43,3 +43,33 @@ td.revision-current { display: inline; } +#node-form-buttons { + clear: both; +} + +#node-form .node-form .horizontal-tab-section { + float: left; + width: 80%; +} + +#node-form-floating-buttons { + margin-top: 0.5em; + float: right; +} + +.node-form-floating-buttons-fixed { + position: fixed; + top: 1px; +} + +#node-form-floating-buttons > .node-form-button { + display: block; + height: 1px; + width: 120px; +} + +#node-form-floating-buttons-wrapper .node-form-button { + display: block; + margin: 0.5em 0; + width: 120px; +} Index: modules/node/node.js =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.js,v retrieving revision 1.4 diff -u -p -r1.4 node.js --- modules/node/node.js 27 Apr 2009 20:19:37 -0000 1.4 +++ modules/node/node.js 19 Jun 2009 13:52:29 -0000 @@ -32,4 +32,24 @@ Drupal.behaviors.nodeFieldsetSummaries = } }; +Drupal.behaviors.nodeFloatingButtons = { + attach: function (context) { + if (!$('#node-form-floating-buttons:not(.node-floating-buttons-processed)', context).size()) { + return; + } + var buttons = $('#node-form-floating-buttons'); + buttons.wrapInner('
    '); + buttons.prepend('
     
    '); + buttons.addClass('node-floating-buttons-processed'); + $(window).scroll(function() { + if ($(window).scrollTop() > buttons.offset().top - 1) { + buttons.find('> #node-form-floating-buttons-wrapper').addClass('node-form-floating-buttons-fixed'); + } + else { + buttons.find('> #node-form-floating-buttons-wrapper').removeClass('node-form-floating-buttons-fixed'); + } + }); + } +}; + })(jQuery); Index: modules/node/node.pages.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.pages.inc,v retrieving revision 1.69 diff -u -p -r1.69 node.pages.inc --- modules/node/node.pages.inc 12 Jun 2009 08:39:38 -0000 1.69 +++ modules/node/node.pages.inc 19 Jun 2009 13:29:46 -0000 @@ -139,6 +139,18 @@ function node_form(&$form_state, $node) ); } + $form['#tabs'] = 'tabs'; + $form['tabs'] = array( + '#type' => 'horizontal_tabs', + '#tabs' => array( + 'content' => t('Content'), + 'meta' => t('Meta information'), + ), + '#default_tab' => 'content', + '#primary_tab' => 'content', + '#weight' => -10, + ); + // Changed must be sent to the client, for later overwrite error checking. $form['changed'] = array( '#type' => 'hidden', @@ -154,10 +166,6 @@ function node_form(&$form_state, $node) $form['#node'] = $node; - $form['additional_settings'] = array( - '#type' => 'vertical_tabs', - ); - // Add a log field if the "Create new revision" option is checked, or if the // current user has the ability to check that option. if (!empty($node->revision) || user_access('administer nodes')) { @@ -167,7 +175,7 @@ function node_form(&$form_state, $node) '#collapsible' => TRUE, // Collapsed by default when "Create new revision" is unchecked '#collapsed' => !$node->revision, - '#group' => 'additional_settings', + '#group' => 'meta', '#attached_js' => array(drupal_get_path('module', 'node') . '/node.js'), '#weight' => 20, ); @@ -193,7 +201,7 @@ function node_form(&$form_state, $node) '#title' => t('Authoring information'), '#collapsible' => TRUE, '#collapsed' => TRUE, - '#group' => 'additional_settings', + '#group' => 'meta', '#attached_js' => array(drupal_get_path('module', 'node') . '/node.js'), '#weight' => 90, ); @@ -224,7 +232,7 @@ function node_form(&$form_state, $node) '#title' => t('Publishing options'), '#collapsible' => TRUE, '#collapsed' => TRUE, - '#group' => 'additional_settings', + '#group' => 'meta', '#attached_js' => array(drupal_get_path('module', 'node') . '/node.js'), '#weight' => 95, ); @@ -254,13 +262,17 @@ function node_form(&$form_state, $node) // Add the buttons. $form['buttons'] = array(); - $form['buttons']['#weight'] = 100; + $form['buttons']['#weight'] = 120; + $form['buttons']['#group'] = 'none'; + $form['buttons']['#prefix'] = '
    '; + $form['buttons']['#suffix'] = '
    '; $form['buttons']['submit'] = array( '#type' => 'submit', '#access' => variable_get('node_preview_' . $node->type, 1) != DRUPAL_REQUIRED || (!form_get_errors() && isset($form_state['node_preview'])), '#value' => t('Save'), '#weight' => 5, '#submit' => array('node_form_submit'), + '#attributes' => array('class' => 'node-form-button'), ); $form['buttons']['preview'] = array( '#access' => variable_get('node_preview_' . $node->type, 1) != DRUPAL_DISABLED, @@ -268,6 +280,7 @@ function node_form(&$form_state, $node) '#value' => t('Preview'), '#weight' => 10, '#submit' => array('node_form_build_preview'), + '#attributes' => array('class' => 'node-form-button'), ); if (!empty($node->nid) && node_access('delete', $node)) { $form['buttons']['delete'] = array( @@ -275,8 +288,14 @@ function node_form(&$form_state, $node) '#value' => t('Delete'), '#weight' => 15, '#submit' => array('node_form_delete_submit'), + '#attributes' => array('class' => 'node-form-button'), ); } + // Make a copy of the buttons for the floating buttons. + $form['floating_buttons'] = $form['buttons']; + $form['floating_buttons']['#weight'] = 100; + $form['floating_buttons']['#prefix'] = '
    '; + $form['#validate'][] = 'node_form_validate'; $form['#theme'] = array($node->type . '_node_form', 'node_form'); Index: modules/path/path.module =================================================================== RCS file: /cvs/drupal/drupal/modules/path/path.module,v retrieving revision 1.159 diff -u -p -r1.159 path.module --- modules/path/path.module 11 Jun 2009 04:59:26 -0000 1.159 +++ modules/path/path.module 19 Jun 2009 13:10:26 -0000 @@ -228,7 +228,7 @@ function path_form_alter(&$form, $form_s '#title' => t('URL path settings'), '#collapsible' => TRUE, '#collapsed' => empty($path), - '#group' => 'additional_settings', + '#group' => 'meta', '#attached_js' => array(drupal_get_path('module', 'path') . '/path.js'), '#access' => user_access('create url aliases'), '#weight' => 30, Index: modules/poll/poll.module =================================================================== RCS file: /cvs/drupal/drupal/modules/poll/poll.module,v retrieving revision 1.299 diff -u -p -r1.299 poll.module --- modules/poll/poll.module 4 Jun 2009 03:33:28 -0000 1.299 +++ modules/poll/poll.module 19 Jun 2009 13:10:26 -0000 @@ -369,7 +369,7 @@ function _poll_choice_form($key, $chid = * Menu callback for AHAH additions. Render the new poll choices. */ function poll_choice_js($form, $form_state) { - $choice_form = $form['choice_wrapper']['choice']; + $choice_form = $form['tabs']['content']['choice_wrapper']['choice']; // Prevent duplicate wrappers. unset($choice_form['#prefix'], $choice_form['#suffix']); Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.712 diff -u -p -r1.712 system.module --- modules/system/system.module 16 Jun 2009 08:41:35 -0000 1.712 +++ modules/system/system.module 19 Jun 2009 13:10:26 -0000 @@ -251,6 +251,7 @@ function system_elements() { '#method' => 'post', '#action' => request_uri(), '#theme_wrapper' => 'form', + '#process' => array('form_expand_horizontal_tabs'), ); $type['page'] = array( @@ -446,6 +447,14 @@ function system_elements() { '#process' => array('form_process_vertical_tabs'), ); + $type['horizontal_tabs'] = array( + '#theme_wrapper' => 'horizontal_tabs', + '#default_tab' => '', + '#primary_tab' => 'none', + '#tabs' => array(), + '#process' => array('form_process_horizontal_tabs'), + ); + $type['token'] = array( '#input' => TRUE, '#theme' => array('hidden'), Index: themes/garland/style-rtl.css =================================================================== RCS file: /cvs/drupal/drupal/themes/garland/style-rtl.css,v retrieving revision 1.13 diff -u -p -r1.13 style-rtl.css --- themes/garland/style-rtl.css 3 Jun 2009 06:58:57 -0000 1.13 +++ themes/garland/style-rtl.css 19 Jun 2009 13:10:26 -0000 @@ -208,6 +208,19 @@ div.vertical-tabs { } /** + * Horizontal tabs. + */ +ul.horizontal-tabs-panes { + text-align: left; + margin: -0.2em 0px 1em -26px; + padding: 0 0px 0.2em 26px; +} + +ul.horizontal-tabs-panes li a, ul.horizontal-tabs-panes li.active a, ul.horizontal-tabs-panes li a:hover, ul.horizontal-tabs-panes li a:visited { + margin: 0 1px 0 0; +} + +/** * Syndication Block */ #block-node-syndicate h2 { Index: themes/garland/style.css =================================================================== RCS file: /cvs/drupal/drupal/themes/garland/style.css,v retrieving revision 1.57 diff -u -p -r1.57 style.css --- themes/garland/style.css 31 May 2009 00:51:41 -0000 1.57 +++ themes/garland/style.css 19 Jun 2009 13:10:26 -0000 @@ -911,6 +911,44 @@ div.vertical-tabs ul.vertical-tabs-list } /** + * Horizontal tabs. + */ +ul.horizontal-tabs-panes, ul.horizontal-tabs-panes li { + border: 0; + background: none; + margin: 0; + padding: 0; +} + +ul.horizontal-tabs-panes { + clear: both; + text-align: right; /* LTR */ + border-bottom: 1px solid #e9eff3; + margin: -0.2em -26px 1em; /* LTR */ + padding: 0 26px 0.2em; /* LTR */ +} + +ul.horizontal-tabs-panes li a, ul.horizontal-tabs-panes li.active a, ul.horizontal-tabs-panes li a:hover, ul.horizontal-tabs-panes li a:visited { + border: 0; + background: transparent; + padding: 4px 1em; + margin: 0 0 0 1px; /* LTR */ + height: auto; + text-decoration: none; + position: relative; + top: -1px; +} + +ul.horizontal-tabs-panes li.active a, ul.horizontal-tabs-panes li.active a:link, ul.horizontal-tabs-panes li.active a:visited, ul.horizontal-tabs-panes li a:hover { + background: url(images/bg-tab.png) repeat-x 0 50%; + color: #fff; +} + +ul.horizontal-tabs-panes li.active a { + font-weight: bold; +} + +/** * Syndication icons and block */ #block-node-syndicate h2 {