diff --git elements.module elements.module index ed66a30..5527997 100644 --- elements.module +++ elements.module @@ -51,11 +51,45 @@ function elements_element_info() { '#theme' => 'rangefield', '#theme_wrappers' => array('form_element'), ); + $types['horizontal_tabs'] = array( + '#theme_wrappers' => array('horizontal_tabs'), + '#default_tab' => '', + '#process' => array('form_process_horizontal_tabs'), + '#attached' => array( + 'library' => array( + array('elements', 'horizontal-tabs'), + array('system', 'drupal.form'), + ), + ), + ); return $types; } /** + * Implements hook_library(). + */ +function elements_library() { + + $path = drupal_get_path('module', 'elements'); + + // Horizontal Tabs. + $libraries['horizontal-tabs'] = array( + 'title' => 'Horizontal Tabs', + 'website' => 'http://drupal.org/node/323112', + 'version' => '1.0', + 'js' => array( + $path . '/horizontal-tabs/horizontal-tabs.js' => array(), + ), + 'css' => array( + $path . '/horizontal-tabs/horizontal-tabs.css' => array(), + ), + ); + + return $libraries; +} + +/** * Implements hook_element_info_alter(). */ function elements_element_info_alter(&$types) { @@ -105,6 +139,11 @@ function elements_theme() { 'render element' => 'element', 'file' => 'elements.theme.inc', ), + 'horizontal_tabs' => array( + 'arguments' => array('element' => NULL), + 'render element' => 'element', + 'file' => 'elements.theme.inc', + ), ); } @@ -154,3 +193,40 @@ function form_process_placeholder($element) { } return $element; } + +/** + * 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) { + // Inject a new fieldset as child, so that form_process_fieldset() processes + // this fieldset like any other fieldset. + $element['group'] = array( + '#type' => 'fieldset', + '#theme_wrappers' => array(), + '#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' => array('horizontal-tabs-active-tab')), + ); + + return $element; +} diff --git elements.theme.inc elements.theme.inc index 12b1706..b049a36 100644 --- elements.theme.inc +++ elements.theme.inc @@ -142,3 +142,22 @@ function theme_rangefield($variables) { return $output; } + +/** + * Returns HTML for an element's children fieldsets as horizontal tabs. + * + * @param $variables + * An associative array containing: + * - element: An associative array containing the properties and children of the + * fieldset. Properties used: #children. + * + * @ingroup themeable + */ +function theme_horizontal_tabs($variables) { + $element = $variables['element']; + + $output = '

' . (!empty($element['#title']) ? $element['#title'] : t('Horizontal Tabs')) . '

'; + $output .= '
' . $element['#children'] . '
'; + + return $output; +} diff --git horizontal-tabs/horizontal-tabs.css horizontal-tabs/horizontal-tabs.css new file mode 100644 index 0000000..2399893 --- /dev/null +++ horizontal-tabs/horizontal-tabs.css @@ -0,0 +1,92 @@ +@CHARSET "UTF-8"; + +div.horizontal-tabs { + margin: 0 0 1em 0; /* LTR */ + padding: 0; + border: 1px solid #ccc; + position: relative; /* IE6/7 */ +} + +.horizontal-tabs ul.horizontal-tabs-list { + display: inline-block; + margin: 0; + border: 0; + padding: 0px; + position: relative; /* IE6 */ + list-style: none; + list-style-image: none; /* IE6 */ + background-color: #dedede; + border-right: 1px solid #dedede; + width: 100%; + height: auto; + clear: both; +} + +.horizontal-tabs fieldset.horizontal-tabs-pane { + padding: 0 1em; + border: 0; +} + +.horizontal-tabs-pane>legend { + display: none; +} + +/* Layout of each tab */ +.horizontal-tabs ul.horizontal-tabs-list li { + background: #eee; + border-right: 1px solid #ccc; + padding: 1px; + padding-top: 0; + margin: 0; + min-width: 5em; /* IE7 */ + float: left; +} +.horizontal-tabs ul.horizontal-tabs-list li.selected { + background-color: #fff; + padding: 0 0 1px 0; +} +.horizontal-tabs ul.horizontal-tabs-list li a { + display: block; + text-decoration: none; + padding: 0.5em 0.6em; +} +.horizontal-tabs ul.horizontal-tabs-list li a:hover { + outline: none; + background-color: #ededdd; +} +.horizontal-tabs ul.horizontal-tabs-list li:hover, +.horizontal-tabs ul.horizontal-tabs-list li:focus { + background-color: #ddd; +} +.horizontal-tabs ul.horizontal-tabs-list li a:focus strong, +.horizontal-tabs ul.horizontal-tabs-list li a:active strong, +.horizontal-tabs ul.horizontal-tabs-list li a:hover strong { + text-decoration: none; + outline: none; +} +.horizontal-tabs ul.horizontal-tabs-list li a, +.horizontal-tabs ul.horizontal-tabs-list li.selected a { + display: block; + text-decoration: none; + padding: 0.5em 0.6em 0.3em 0.6em; + position:relative; + top: 0px; +} +.horizontal-tabs ul.horizontal-tabs-list .selected strong { + color: #000; +} +.horizontal-tabs ul.horizontal-tabs-list .summary { + display: block; +} +.horizontal-tabs ul.horizontal-tabs ul.horizontal-tabs-list .summary { + line-height: normal; + margin-bottom: 0; +} + +/** + * tab content + */ +div.field-group-htabs-wrapper .field-group-format-wrapper { + clear: both; + padding: 0 0 0.6em; +} \ No newline at end of file diff --git horizontal-tabs/horizontal-tabs.js horizontal-tabs/horizontal-tabs.js new file mode 100644 index 0000000..30da15a --- /dev/null +++ horizontal-tabs/horizontal-tabs.js @@ -0,0 +1,204 @@ +(function ($) { + +/** + * This script transforms a set of fieldsets into a stack of horizontal + * 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 + * 'horizontalTabCallback' (with jQuery.data() attached to the fieldset), + * which is called every time the user performs an update to a form + * element inside the tab pane. + */ +Drupal.behaviors.horizontalTabs = { + attach: function (context) { + $('.horizontal-tabs-panes', context).once('horizontal-tabs', function () { + var focusID = $(':hidden.horizontal-tabs-active-tab', this).val(); + var tab_focus; + + // Check if there are some fieldsets that can be converted to horizontal-tabs + var $fieldsets = $('> fieldset', this); + if ($fieldsets.length == 0) { + return; + }; + + // Create the tab column. + var tab_list = $(''); + $(this).wrap('
').before(tab_list); + + // Transform each fieldset into a tab. + $fieldsets.each(function () { + var horizontal_tab = new Drupal.horizontalTab({ + title: $('> legend', this).text(), + fieldset: $(this) + }); + tab_list.append(horizontal_tab.item); + $(this) + .removeClass('collapsible collapsed') + .addClass('horizontal-tabs-pane') + .data('horizontalTab', horizontal_tab); + if (this.id == focusID) { + tab_focus = $(this); + } + }); + + $('> li:first', tab_list).addClass('first'); + $('> li:last', tab_list).addClass('last'); + + if (!tab_focus) { + // If the current URL has a fragment and one of the tabs contains an + // element that matches the URL fragment, activate that tab. + if (window.location.hash && $(window.location.hash, this).length) { + tab_focus = $(window.location.hash, this).closest('.horizontal-tabs-pane'); + } + else { + tab_focus = $('> .horizontal-tabs-pane:first', this); + } + } + if (tab_focus.length) { + tab_focus.data('horizontalTab').focus(); + } + }); + } +}; + +/** + * The horizontal tab object represents a single tab within a tab group. + * + * @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. + */ +Drupal.horizontalTab = function (settings) { + var self = this; + $.extend(this, settings, Drupal.theme('horizontalTab', settings)); + + this.link.click(function () { + self.focus(); + return false; + }); + + // Keyboard events added: + // Pressing the Enter key will open the tab pane. + this.link.keydown(function(event) { + if (event.keyCode == 13) { + self.focus(); + // Set focus on the first input field of the visible fieldset/tab pane. + $("fieldset.horizontal-tabs-pane :input:visible:enabled:first").focus(); + return false; + } + }); + + // Pressing the Enter key lets you leave the tab again. + this.fieldset.keydown(function(event) { + // Enter key should not trigger inside