diff --git includes/form.inc includes/form.inc index e0a7d08..9213c4f 100644 --- includes/form.inc +++ includes/form.inc @@ -883,6 +883,19 @@ function form_builder($form_id, $form, &$form_state) { if (isset($form['#input']) && $form['#input']) { _form_builder_handle_input_element($form_id, $form, $form_state, $complete_form); } + if (!isset($form['#id'])) { + $form['#id'] = form_clean_id('edit-' . implode('-', $form['#parents'])); + } + // 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['#defaults_loaded'] = TRUE; // We start off assuming all form elements are in the correct order. @@ -990,9 +1003,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'; @@ -1061,16 +1071,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); } @@ -1505,6 +1505,7 @@ function theme_fieldset($element) { $element['#attributes']['class'] .= ' collapsed'; } } + $element['#attributes']['id'] = $element['#id']; return '' . ($element['#title'] ? '' . $element['#title'] . '' : '') . (isset($element['#description']) && $element['#description'] ? '
' . $element['#description'] . '
' : '') . (!empty($element['#children']) ? $element['#children'] : '') . (isset($element['#value']) ? $element['#value'] : '') . "\n"; } @@ -1910,6 +1911,7 @@ function form_process_ahah($element) { 'method' => empty($element['#ahah']['method']) ? 'replace' : $element['#ahah']['method'], 'progress' => empty($element['#ahah']['progress']) ? array('type' => 'throbber') : $element['#ahah']['progress'], 'button' => isset($element['#executes_submit_callback']) ? array($element['#name'] => $element['#value']) : FALSE, + 'success_callback' => empty($element['#ahah']['success_callback']) ? 'default' : $element['#ahah']['success_callback'], ); // Convert a simple #ahah[progress] type string into an array. diff --git misc/ahah.js misc/ahah.js index c0ac01a..5d9a169 100644 --- misc/ahah.js +++ misc/ahah.js @@ -48,6 +48,7 @@ Drupal.ahah = function(base, element_settings) { this.method = element_settings.method; this.progress = element_settings.progress; this.button = element_settings.button || { }; + this.success_callback = element_settings.success_callback; if (this.effect == 'none') { this.showEffect = 'show'; @@ -81,11 +82,6 @@ Drupal.ahah = function(base, element_settings) { return ahah.beforeSubmit(form_values, element_settings, options); }, success: function(response, status) { - // Sanity check for browser support (object expected). - // When using iFrame uploads, responses must be returned as a string. - if (typeof(response) == 'string') { - response = Drupal.parseJson(response); - } return ahah.success(response, status); }, complete: function(response, status) { @@ -149,12 +145,13 @@ Drupal.ahah.prototype.beforeSubmit = function (form_values, element, options) { * Handler for the form redirection completion. */ Drupal.ahah.prototype.success = function (response, status) { - var wrapper = $(this.wrapper); - var form = $(this.element).parents('form'); - // Manually insert HTML into the jQuery object, using $() directly crashes - // Safari with long string lengths. http://dev.jquery.com/ticket/1152 - var new_content = $('
').html(response.data); + // Sanity check for browser support (object expected). + // When using iFrame uploads, responses must be returned as a string. + if (typeof(response) == 'string') { + response = Drupal.parseJson(response); + } + var form = $(this.element).parents('form'); // Restore the previous action and target to the form. form.attr('action', this.form_action); this.form_target ? form.attr('target', this.form_target) : form.removeAttr('target'); @@ -169,38 +166,64 @@ Drupal.ahah.prototype.success = function (response, status) { } $(this.element).removeClass('progress-disabled').attr('disabled', false); - // Add the new content to the page. - Drupal.freezeHeight(); - if (this.method == 'replace') { - wrapper.empty().append(new_content); - } - else { - wrapper[this.method](new_content); - } + // Call the success callback. + this.successCallbacks[this.success_callback](this, response, status); +} - // Immediately hide the new content if we're using any effects. - if (this.showEffect != 'show') { - new_content.hide(); - } +/** + * Container for AHAH success callback. + * + * New success callbacks can be added by defining a new function in the + * Drupal.ahah.prototype.successCallbacks namespace, and can then be set + * as 'success_callback' in an element's #ahah properties. + */ +Drupal.ahah.prototype.successCallbacks = { + /** + * The default AHAH success callback. + * + * Inserts the new content into the document using the method and wrapper + * specified in the element properties. + */ + default: function (element, response, status) { + var wrapper = $(element.wrapper); + + // Manually insert HTML into the jQuery object, using $() directly crashes + // Safari with long string lengths. http://dev.jquery.com/ticket/1152 + var new_content = $('
').html(response.data); + + // Add the new content to the page. + Drupal.freezeHeight(); + if (element.method == 'replace') { + wrapper.empty().append(new_content); + } + else { + wrapper[element.method](new_content); + } - // Determine what effect use and what content will receive the effect, then - // show the new content. - if ($('.ahah-new-content', new_content).size() > 0) { - $('.ahah-new-content', new_content).hide(); - new_content.show(); - $(".ahah-new-content", new_content)[this.showEffect](this.showSpeed); - } - else if (this.showEffect != 'show') { - new_content[this.showEffect](this.showSpeed); - } + // Immediately hide the new content if we're using any effects. + if (element.showEffect != 'show') { + new_content.hide(); + } - // Attach all javascript behaviors to the new content, if it was successfully - // added to the page, this if statement allows #ahah[wrapper] to be optional. - if (new_content.parents('html').length > 0) { - Drupal.attachBehaviors(new_content); - } + // Determine what effect use and what content will receive the effect, then + // show the new content. + if ($('.ahah-new-content', new_content).size() > 0) { + $('.ahah-new-content', new_content).hide(); + new_content.show(); + $(".ahah-new-content", new_content)[element.showEffect](element.showSpeed); + } + else if (element.showEffect != 'show') { + new_content[element.showEffect](element.showSpeed); + } - Drupal.unfreezeHeight(); + // Attach all javascript behaviors to the new content, if it was successfully + // added to the page, element if statement allows #ahah[wrapper] to be optional. + if (new_content.parents('html').length > 0) { + Drupal.attachBehaviors(new_content); + } + + Drupal.unfreezeHeight(); + } }; /** diff --git misc/vertical-tabs.css misc/vertical-tabs.css new file mode 100644 index 0000000..d3ab84b --- /dev/null +++ misc/vertical-tabs.css @@ -0,0 +1,80 @@ +/* $Id */ + +.vertical-tabs { + margin: 1em 0 1em 15em; + border: 1px solid #ccc; +} + +.vertical-tabs-list { + width: 15em; + list-style: none; + list-style-image: none; /* IE6 */ + border-top: 1px solid #ccc; + padding: 0; + position: relative; /* IE6 */ + margin: -1px 0 -1px -15em; + float: left; +} + +.vertical-tabs .vertical-tabs-panes fieldset.vertical-tabs-pane { + margin: 0; + padding: 0 1em; + border: 0; +} + +.vertical-tabs .vertical-tabs-panes fieldset.vertical-tabs-pane legend { + display: none; +} + + + +/* Layout of each tab */ +.vertical-tabs-list li { + background: #eee; + border: 1px solid #ccc; + border-top: 0; + padding: 0; + margin: 0; + height: 1%; +} + +.vertical-tabs-list li a { + display: block; + text-decoration: none; + padding: 0.4em 0.6em; + height: 1%; +} + + +.vertical-tabs-list li a:focus { + position:relative; + z-index: 5; +} + +.vertical-tabs-list li a:hover { + text-decoration: none; +} + +.vertical-tabs-list li strong { + font-weight:normal; +} + +.vertical-tabs-list li.selected { + background: #fff; + border-right: 0; + position: relative; +} + +.vertical-tabs-list li.selected strong { + font-weight: bold; + color: #000; +} + +.vertical-tabs-list .description { + display: block; +} + +.vertical-tabs ul.vertical-tabs-list .description { + line-height: normal; + margin-bottom: 0; +} diff --git misc/vertical-tabs.js misc/vertical-tabs.js new file mode 100644 index 0000000..1fbccb7 --- /dev/null +++ misc/vertical-tabs.js @@ -0,0 +1,81 @@ +// $Id$ + +(function($) { + +Drupal.behaviors.verticalTabs = { + attach: function(context) { + $('.vertical-tabs-panes:not(.vertical-tabs-processed)', context).each(function() { + // create the tabs + var list = $(''); + $(this).wrap('
').before(list); + + $('> fieldset', this).each(function(i) { + var tab = new Drupal.verticalTab({ title: $('> legend', this).text(), fieldset: $(this) }); + list.append(tab.item); + $(this) + .addClass('vertical-tabs-pane') + .data('verticalTab', tab) + .find('input, textarea, select') + .change(function() { + tab.updateDescription(); + }); + }); + + $('> li:first', list).addClass('first'); + $('> li:last', list).addClass('last'); + + $('> fieldset:first', this).data('verticalTab').focus(); + // attach the tab description update functions + }).addClass('vertical-tabs-processed'); + } +}; + +Drupal.verticalTab = function(settings) { + var that = this; + $.extend(this, settings, Drupal.theme('verticalTab', settings)); + this.link.click(function() { + that.focus(); + return false; + }); + this.updateDescription(); +}; + +Drupal.verticalTab.update = function(tab) { + var data = $(tab).data('verticalTab'); + if (data) { + data.updateDescription(); + } +}; + +Drupal.verticalTab.prototype = { + focus: function() { + this.fieldset.siblings().each(function() { + var tab = $(this).data('verticalTab'); + tab.fieldset.hide(); + tab.item.removeClass('selected'); + }); + + this.fieldset.show(); + this.item.addClass('selected'); + }, + + updateDescription: function() { + var callback = this.fieldset.data('verticalTabCallback'); + if (callback) { + this.description.html(callback.call(this)); + } + } +}; + +Drupal.theme.prototype.verticalTab = function(settings) { + var tab = {}; + tab.item = $('
  • ') + .append(tab.link = $('') + .append(tab.title = $('').text(settings.title)) + .append(tab.description = $('') + ) + ); + return tab; +}; + +})(jQuery); diff --git modules/book/book.js modules/book/book.js new file mode 100644 index 0000000..473864f --- /dev/null +++ modules/book/book.js @@ -0,0 +1,28 @@ +// $Id$ + +(function($) { + +Drupal.behaviors.nodeBookVTab = { + attach: function(context) { + $('#edit-book').data('verticalTabCallback', function() { + var val = $('#edit-book-bid').val(); + + if (val === '0') + return Drupal.t('Not in book'); + else if (val === 'new') + return Drupal.t('New book'); + else + return $('#edit-book-bid option[selected]').text(); + }).each(function() { + Drupal.verticalTab && Drupal.verticalTab.update(this); + }); + } +}; + +if (Drupal.jsEnabled) { + $(document).ready(function() { + $('#edit-book-pick-book').css('display', 'none'); + }); +} + +})(jQuery); diff --git modules/book/book.module modules/book/book.module index 15130d8..dc83439 100644 --- modules/book/book.module +++ modules/book/book.module @@ -349,7 +349,7 @@ function book_form_alter(&$form, $form_state, $form_id) { if ($access) { _book_add_form_elements($form, $node); - $form['book']['pick-book'] = array( + $form['miscellaneous']['book']['pick-book'] = array( '#type' => 'submit', '#value' => t('Change book (update list of parents)'), // Submit the node form so the parent select options get updated. @@ -415,27 +415,25 @@ function _book_parent_select($book_link) { function _book_add_form_elements(&$form, $node) { // Need this for AJAX. $form['#cache'] = TRUE; - drupal_add_js("if (Drupal.jsEnabled) { jQuery(function() { jQuery('#edit-book-pick-book').css('display', 'none'); }); }", 'inline'); + drupal_add_js(drupal_get_path('module', 'book') .'/book.js'); - $form['book'] = array( + $form['miscellaneous']['book'] = array( '#type' => 'fieldset', '#title' => t('Book outline'), '#weight' => 10, - '#collapsible' => TRUE, - '#collapsed' => TRUE, '#tree' => TRUE, '#attributes' => array('class' => 'book-outline-form'), ); foreach (array('menu_name', 'mlid', 'nid', 'router_path', 'has_children', 'options', 'module', 'original_bid', 'parent_depth_limit') as $key) { - $form['book'][$key] = array( + $form['miscellaneous']['book'][$key] = array( '#type' => 'value', '#value' => $node->book[$key], ); } - $form['book']['plid'] = _book_parent_select($node->book); + $form['miscellaneous']['book']['plid'] = _book_parent_select($node->book); - $form['book']['weight'] = array( + $form['miscellaneous']['book']['weight'] = array( '#type' => 'weight', '#title' => t('Weight'), '#default_value' => $node->book['weight'], @@ -466,7 +464,7 @@ function _book_add_form_elements(&$form, $node) { } // Add a drop-down to select the destination book. - $form['book']['bid'] = array( + $form['miscellaneous']['book']['bid'] = array( '#type' => 'select', '#title' => t('Book'), '#default_value' => $node->book['bid'], diff --git modules/comment/comment-node-form.js modules/comment/comment-node-form.js new file mode 100644 index 0000000..e65474c --- /dev/null +++ modules/comment/comment-node-form.js @@ -0,0 +1,15 @@ +// $Id$ + +(function($) { + +Drupal.behaviors.nodeCommentVTab = { + attach: function(context) { + $('#edit-comment-settings').data('verticalTabCallback', function() { + return $('input:checked', this.fieldset).parent().text(); + }).each(function() { + Drupal.verticalTab && Drupal.verticalTab.update(this); + }); + } +}; + +})(jQuery); diff --git modules/comment/comment.module modules/comment/comment.module index 15ab14f..76348e5 100644 --- modules/comment/comment.module +++ modules/comment/comment.module @@ -574,15 +574,14 @@ function comment_form_node_type_form_alter(&$form, $form_state) { function comment_form_alter(&$form, $form_state, $form_id) { if (!empty($form['#node_edit_form'])) { $node = $form['#node']; - $form['comment_settings'] = array( + drupal_add_js(drupal_get_path('module', 'comment') . '/comment-node-form.js'); + $form['miscellaneous']['comment_settings'] = array( '#type' => 'fieldset', '#access' => user_access('administer comments'), '#title' => t('Comment settings'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, '#weight' => 30, ); - $form['comment_settings']['comment'] = array( + $form['miscellaneous']['comment_settings']['comment'] = array( '#type' => 'radios', '#parents' => array('comment'), '#default_value' => $node->comment, diff --git modules/menu/menu.js modules/menu/menu.js new file mode 100644 index 0000000..720f8a5 --- /dev/null +++ modules/menu/menu.js @@ -0,0 +1,15 @@ +// $Id$ + +(function($) { + +Drupal.behaviors.nodeMenuVTab = { + attach: function(context) { + $('#edit-menu').data('verticalTabCallback', function() { + return $('#edit-menu-link-title', this.fieldset).val() || Drupal.t('Not in menu'); + }).each(function() { + Drupal.verticalTab && Drupal.verticalTab.update(this); + }); + } +}; + +})(jQuery); diff --git modules/menu/menu.module modules/menu/menu.module index 1964bdb..8c61cc5 100644 --- modules/menu/menu.module +++ modules/menu/menu.module @@ -388,38 +388,37 @@ function _menu_parent_depth_limit($item) { */ function menu_form_alter(&$form, $form_state, $form_id) { if (!empty($form['#node_edit_form'])) { + drupal_add_js(drupal_get_path('module', 'menu') . '/menu.js'); // Note - doing this to make sure the delete checkbox stays in the form. $form['#cache'] = TRUE; - $form['menu'] = array( + $form['miscellaneous']['menu'] = array( '#type' => 'fieldset', '#title' => t('Menu settings'), '#access' => user_access('administer menu'), - '#collapsible' => TRUE, - '#collapsed' => FALSE, '#tree' => TRUE, - '#weight' => -2, + '#weight' => 5, '#attributes' => array('class' => 'menu-item-form'), ); $item = $form['#node']->menu; if ($item['mlid']) { // There is an existing link. - $form['menu']['delete'] = array( + $form['miscellaneous']['menu']['delete'] = array( '#type' => 'checkbox', '#title' => t('Delete this menu item.'), ); } if (!$item['link_title']) { - $form['menu']['#collapsed'] = TRUE; + $form['miscellaneous']['menu']['#collapsed'] = TRUE; } foreach (array('mlid', 'module', 'hidden', 'has_children', 'customized', 'options', 'expanded', 'hidden', 'parent_depth_limit') as $key) { - $form['menu'][$key] = array('#type' => 'value', '#value' => $item[$key]); + $form['miscellaneous']['menu'][$key] = array('#type' => 'value', '#value' => $item[$key]); } - $form['menu']['#item'] = $item; + $form['miscellaneous']['menu']['#item'] = $item; - $form['menu']['link_title'] = array('#type' => 'textfield', + $form['miscellaneous']['menu']['link_title'] = array('#type' => 'textfield', '#title' => t('Menu link title'), '#default_value' => $item['link_title'], '#description' => t('The link text corresponding to this item that should appear in the menu. Leave blank if you do not wish to add this post to the menu.'), @@ -431,7 +430,7 @@ function menu_form_alter(&$form, $form_state, $form_id) { if (!isset($options[$default])) { $default = 'main-menu:0'; } - $form['menu']['parent'] = array( + $form['miscellaneous']['menu']['parent'] = array( '#type' => 'select', '#title' => t('Parent item'), '#default_value' => $default, @@ -441,7 +440,7 @@ function menu_form_alter(&$form, $form_state, $form_id) { ); $form['#submit'][] = 'menu_node_form_submit'; - $form['menu']['weight'] = array( + $form['miscellaneous']['menu']['weight'] = array( '#type' => 'weight', '#title' => t('Weight'), '#delta' => 50, diff --git modules/node/node.js modules/node/node.js new file mode 100644 index 0000000..9cd90a1 --- /dev/null +++ modules/node/node.js @@ -0,0 +1,53 @@ +// $Id$ + +(function($) { + +Drupal.behaviors.nodeRevisionVTab = { + attach: function(context) { + $('#edit-revision-information').data('verticalTabCallback', function() { + return $('#edit-revision', this.fieldset).is(':checked') ? + Drupal.t('Create new revision') : + Drupal.t('Don\'t create new revision'); + }).each(function() { + Drupal.verticalTab && Drupal.verticalTab.update(this); + }); + } +}; + +Drupal.behaviors.nodeAuthoringVTab = { + attach: function(context) { + $('#edit-author').data('verticalTabCallback', function() { + var name = $('#edit-name').val(), date = $('#edit-date').val(); + return date ? + Drupal.t('By @name on @date', { '@name': name, '@date': date }) : + Drupal.t('By @name', { '@name': name }); + }).each(function() { + Drupal.verticalTab && Drupal.verticalTab.update(this); + }); + } +}; + +Drupal.behaviors.nodePublishingVTab = { + attach: function(context) { + $('#edit-options').data('verticalTabCallback', function() { + var vals = []; + if ($('#edit-status').attr('checked')) { + vals.push(Drupal.t('Published')); + } + if ($('#edit-promote').attr('checked')) { + vals.push(Drupal.t('Promoted to front page')); + } + if ($('#edit-sticky').attr('checked')) { + vals.push(Drupal.t('Sticky on top of lists')); + } + if (vals.join(', ') == '') { + return Drupal.t('None'); + } + return vals.join(', '); + }).each(function() { + Drupal.verticalTab && Drupal.verticalTab.update(this); + }); + } +}; + +})(jQuery); diff --git modules/node/node.pages.inc modules/node/node.pages.inc index 089ee81..7afe44c 100644 --- modules/node/node.pages.inc +++ modules/node/node.pages.inc @@ -97,6 +97,7 @@ function node_object_prepare(&$node) { * Generate the node add/edit form array. */ function node_form(&$form_state, $node) { + drupal_add_js(drupal_get_path('module', 'node') . '/node.js'); global $user; if (isset($form_state['node'])) { @@ -146,24 +147,29 @@ function node_form(&$form_state, $node) { $form['#node'] = $node; + $form['miscellaneous'] = array( + '#prefix' => '
    ', + '#suffix' => '
    ', + ); + + drupal_add_js('misc/vertical-tabs.js', array('weight' => 0)); + drupal_add_css('misc/vertical-tabs.css'); + // 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')) { - $form['revision_information'] = array( + $form['miscellaneous']['revision_information'] = array( '#type' => 'fieldset', '#title' => t('Revision information'), - '#collapsible' => TRUE, - // Collapsed by default when "Create new revision" is unchecked - '#collapsed' => !$node->revision, '#weight' => 20, ); - $form['revision_information']['revision'] = array( + $form['miscellaneous']['revision_information']['revision'] = array( '#access' => user_access('administer nodes'), '#type' => 'checkbox', '#title' => t('Create new revision'), '#default_value' => $node->revision, ); - $form['revision_information']['log'] = array( + $form['miscellaneous']['revision_information']['log'] = array( '#type' => 'textarea', '#title' => t('Revision log message'), '#rows' => 2, @@ -172,15 +178,13 @@ function node_form(&$form_state, $node) { } // Node author information for administrators - $form['author'] = array( + $form['miscellaneous']['author'] = array( '#type' => 'fieldset', '#access' => user_access('administer nodes'), '#title' => t('Authoring information'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, '#weight' => 90, ); - $form['author']['name'] = array( + $form['miscellaneous']['author']['name'] = array( '#type' => 'textfield', '#title' => t('Authored by'), '#maxlength' => 60, @@ -189,7 +193,7 @@ function node_form(&$form_state, $node) { '#weight' => -1, '#description' => t('Leave blank for %anonymous.', array('%anonymous' => variable_get('anonymous', t('Anonymous')))), ); - $form['author']['date'] = array( + $form['miscellaneous']['author']['date'] = array( '#type' => 'textfield', '#title' => t('Authored on'), '#maxlength' => 25, @@ -197,29 +201,27 @@ function node_form(&$form_state, $node) { ); if (isset($node->date)) { - $form['author']['date']['#default_value'] = $node->date; + $form['miscellaneous']['author']['date']['#default_value'] = $node->date; } // Node options for administrators - $form['options'] = array( + $form['miscellaneous']['options'] = array( '#type' => 'fieldset', '#access' => user_access('administer nodes'), '#title' => t('Publishing options'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, '#weight' => 95, ); - $form['options']['status'] = array( + $form['miscellaneous']['options']['status'] = array( '#type' => 'checkbox', '#title' => t('Published'), '#default_value' => $node->status, ); - $form['options']['promote'] = array( + $form['miscellaneous']['options']['promote'] = array( '#type' => 'checkbox', '#title' => t('Promoted to front page'), '#default_value' => $node->promote, ); - $form['options']['sticky'] = array( + $form['miscellaneous']['options']['sticky'] = array( '#type' => 'checkbox', '#title' => t('Sticky at top of lists'), '#default_value' => $node->sticky, @@ -277,7 +279,7 @@ function node_body_field(&$node, $label, $word_count) { $form = array( '#after_build' => array('node_teaser_js', 'node_teaser_include_verify')); - $form['#prefix'] = '
    '; + $form['#prefix'] = '
    '; $form['#suffix'] = '
    '; $form['teaser_js'] = array( diff --git modules/path/path.js modules/path/path.js new file mode 100644 index 0000000..c36e5b1 --- /dev/null +++ modules/path/path.js @@ -0,0 +1,19 @@ +// $Id$ + +(function($) { + +Drupal.behaviors.nodePathVTab = { + attach: function(context) { + $('#edit-path').data('verticalTabCallback', function() { + var path = $('#edit-path-1').val(); + + return path ? + Drupal.t('Alias: @alias', { '@alias': path }) : + Drupal.t('No alias'); + }).each(function() { + Drupal.verticalTab && Drupal.verticalTab.update(this); + }); + } +}; + +})(jQuery); diff --git modules/path/path.module modules/path/path.module index 02b8f22..b629b11 100644 --- modules/path/path.module +++ modules/path/path.module @@ -188,16 +188,15 @@ function path_node_delete($node) { */ function path_form_alter(&$form, $form_state, $form_id) { if (!empty($form['#node_edit_form'])) { + drupal_add_js(drupal_get_path('module', 'path') .'/path.js'); $path = isset($form['#node']->path) ? $form['#node']->path : NULL; - $form['path'] = array( + $form['miscellaneous']['path'] = array( '#type' => 'fieldset', '#title' => t('URL path settings'), - '#collapsible' => TRUE, - '#collapsed' => empty($path), '#access' => user_access('create url aliases'), '#weight' => 30, ); - $form['path']['path'] = array( + $form['miscellaneous']['path']['path'] = array( '#type' => 'textfield', '#default_value' => $path, '#maxlength' => 128, @@ -206,7 +205,7 @@ function path_form_alter(&$form, $form_state, $form_id) { '#description' => t('Optionally specify an alternative URL by which this node can be accessed. For example, type "about" when writing an about page. Use a relative path and don\'t add a trailing slash or the URL alias won\'t work.'), ); if ($path) { - $form['path']['pid'] = array( + $form['miscellaneous']['path']['pid'] = array( '#type' => 'value', '#value' => db_result(db_query("SELECT pid FROM {url_alias} WHERE dst = '%s' AND language = '%s'", $path, $form['#node']->language)) ); diff --git modules/system/admin.css modules/system/admin.css index 0a4d01a..3fb8b7e 100644 --- modules/system/admin.css +++ modules/system/admin.css @@ -135,3 +135,23 @@ table.screenshot { html.js .custom-container label { visibility: hidden; } + +/** + * Formatting of the modules form. + */ +#system-modules #edit-search { + margin-bottom: 0; + border-bottom: none; +} + +#system-modules #edit-search #edit-search-string-wrapper, #system-modules #edit-search #edit-search-submit { + float: left; +} + +#system-modules #edit-search #edit-search-string-wrapper { + margin: 0 1em 0 0; +} + +#system-modules div.vertical-tabs { + margin-top: 0; +} \ No newline at end of file diff --git modules/system/system.admin.inc modules/system/system.admin.inc index 82eef0a..06d38a3 100644 --- modules/system/system.admin.inc +++ modules/system/system.admin.inc @@ -570,11 +570,44 @@ function _system_is_incompatible(&$incompatible, $files, $file) { * The form array. */ function system_modules($form_state = array()) { - // Clear all caches. - registry_rebuild(); - drupal_theme_rebuild(); - node_types_rebuild(); - cache_clear_all('schema', 'cache'); + drupal_add_js(drupal_get_path('module', 'system') . '/system.js'); + drupal_add_js('misc/vertical-tabs.js', array('weight' => 0)); + drupal_add_css('misc/vertical-tabs.css'); + + // Clear all caches, but not when searching the modules page to save time. + if (!isset($form_state['storage']['search_string'])) { + registry_rebuild(); + drupal_theme_rebuild(); + node_types_rebuild(); + cache_clear_all('schema', 'cache'); + } + + // Fieldset to search the modules form. + $form['search'] = array( + '#type' => 'fieldset', + '#weight' => -5, + '#collapsible' => FALSE, + '#title' => t('Search modules'), + ); + + $form['search']['search_string'] = array( + '#type' => 'textfield', + ); + + $form['search']['search_submit'] = array( + '#type' => 'submit', + '#value' => t('Search'), + '#submit' => array('system_modules_search_submit'), + '#ahah' => array( + 'callback' => 'system_modules_search_js', + 'wrapper' => 'edit-modules-search-pane', + 'method' => 'replace', + 'success_callback' => 'moduleFormSearch', + 'keypress' => TRUE, + 'event' => 'click', + ), + ); + // Get current list of modules. $files = module_rebuild_cache(); @@ -591,17 +624,133 @@ function system_modules($form_state = array()) { // and if there are unfilled required modules, then form_state['storage'] is // filled, triggering a rebuild. In this case we need to display a // confirmation form. - if (!empty($form_state['storage'])) { + if (!empty($form_state['storage']) && !isset($form_state['storage']['search_string'])) { return system_modules_confirm_form($files, $form_state['storage']); } $modules = array(); - $form['modules'] = array('#tree' => TRUE); + $form['modules'] = array( + '#tree' => TRUE, + '#prefix' => '
    ', + '#suffix' => '
    ', + ); + + // Add dependencies and additional information to the files array. + $files = _system_modules_build_files_for_form($files); + + // If the search button was clicked, add the search fieldset with matching modules. + if (isset($form_state['storage']['search_string'])) { + $form['modules']['search_pane'] = _system_modules_search_form($files, $form_state['storage']['search_string']); + } + // Otherwise, add all modules, with a fieldset for each category. + else { + // Iterate through each of the modules. + foreach ($files as $filename => $module) { + $form['modules'][$module->info['package']][$filename] = _system_modules_build_row($module->info, $module->extra); + } + + // Add basic information to the fieldsets. + foreach (element_children($form['modules']) as $package) { + $form['modules'][$package] += array( + '#type' => 'fieldset', + '#title' => t($package), + '#theme' => 'system_modules_fieldset', + '#header' => array( + array('data' => t('Enabled'), 'class' => 'checkbox'), + t('Name'), + t('Version'), + t('Description'), + ), + ); + } + } + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save configuration'), + ); + $form['#action'] = url('admin/build/modules/list/confirm'); + + return $form; +} +/** + * Submit callback for the search button on the modules form. + * + * If a search string is present, store it in $form_state['storage']. + */ +function system_modules_search_submit($form, &$form_state) { + if (isset($form_state['values']['search_string'])) { + $form_state['storage']['search_string'] = $form_state['values']['search_string']; + } + else { + unset($form_state['storage']['search_string']); + } +} + +/** + * AHAH callback for the search button on the modules form. + */ +function system_modules_search_js($form, &$form_state) { + if (isset($form['modules']['search_pane'])) { + $search_form = $form['modules']['search_pane']; + drupal_json(array('status' => TRUE, 'data' => drupal_render($search_form))); + } +} + +/** + * Creates the search result fieldset for the modules form. + * + * This returns a fieldset similar to the normal module category fieldsets, + * but containing modules whose names or descriptions match $search_string. + */ +function _system_modules_search_form($files, $search_string) { + $search_form = array( + '#type' => 'fieldset', + '#weight' => 9999, + '#title' => t('Search results'), + ); + + if (!empty($search_string)) { + // Iterate over the module files, and add rows for matching names or + // descriptions. + foreach ($files as $filename => $module) { + if (stripos($module->info['name'], $search_string) !== FALSE || + stripos($module->info['description'], $search_string) !== FALSE || + stripos($module->filename, $search_string) !== FALSE) { + $search_form[$filename] = _system_modules_build_row($module->info, $module->extra); + } + } + } + + if (element_children($search_form)) { + $search_form += array( + '#theme' => 'system_modules_fieldset', + '#header' => array( + array('data' => t('Enabled'), 'class' => 'checkbox'), + t('Name'), + t('Version'), + t('Description'), + ), + ); + } + else { + $search_form['info']['#markup'] = t('Your search did not return any results.'); + } + + return $search_form; +} + +/** + * Add information to the module files array that is needed on the modules form. + * + * Iterates over the module files and adds dependency information to be used + * in the modules form. + */ +function _system_modules_build_files_for_form($files) { // Used when checking if module implements a help page. $help_arg = module_exists('help') ? drupal_help_arg() : FALSE; - // Iterate through each of the modules. foreach ($files as $filename => $module) { $extra = array(); $extra['enabled'] = (bool) $module->status; @@ -641,31 +790,9 @@ function system_modules($form_state = array()) { } } } - $form['modules'][$module->info['package']][$filename] = _system_modules_build_row($module->info, $extra); + $files[$filename]->extra = $extra; } - // Add basic information to the fieldsets. - foreach (element_children($form['modules']) as $package) { - $form['modules'][$package] += array( - '#type' => 'fieldset', - '#title' => t($package), - '#collapsible' => TRUE, - '#theme' => 'system_modules_fieldset', - '#header' => array( - array('data' => t('Enabled'), 'class' => 'checkbox'), - t('Name'), - t('Version'), - t('Description'), - ), - ); - } - - $form['submit'] = array( - '#type' => 'submit', - '#value' => t('Save configuration'), - ); - $form['#action'] = url('admin/build/modules/list/confirm'); - - return $form; + return $files; } /** @@ -800,7 +927,7 @@ function system_modules_submit($form, &$form_state) { include_once DRUPAL_ROOT . '/includes/install.inc'; $modules = array(); // If we're not coming from the confirmation form, build the list of modules. - if (!isset($form_state['storage'])) { + if (!isset($form_state['storage']['modules'])) { foreach ($form_state['values']['modules'] as $group_name => $group) { foreach ($group as $module => $enabled) { $modules[$module] = array('group' => $group_name, 'enabled' => $enabled['enable']); @@ -812,7 +939,6 @@ function system_modules_submit($form, &$form_state) { // the modules out of $form_state. $modules = $form_state['storage']['modules']; } - // Get a list of all modules, it will be used to find which module requires // which. $files = module_rebuild_cache(); diff --git modules/system/system.js modules/system/system.js index 9ff6c08..f19b6dd 100644 --- modules/system/system.js +++ modules/system/system.js @@ -134,4 +134,69 @@ Drupal.behaviors.poweredByPreview = { } }; +/** + * AHAH success callback for the search button on the modules form. + * + * If no search fieldset is present, this inserts the search fieldset into + * the form and adds a vertical tab for it. If there is already a search fieldset, + * it is replaced by the new one. + */ +Drupal.ahah.prototype.successCallbacks.moduleFormSearch = function(element, response, status) { + new_content = $(response.data); + + // Check if there is already a search tab present, and if so, replace it with + // the new one. + // We copy over the existing verticalTab data property to reduce flickering. + if ($('#system-modules #edit-modules-search-pane').size()) { + var tab = $('#edit-modules-search-pane').data('verticalTab') + tab.fieldset = $(new_content); + $('#system-modules #edit-modules-search-pane').replaceWith(new_content); + } + else { + // Append the search fieldset to the wrapper and add a vertical tab for it. + $('#modules-form-wrapper').append(new_content); + var tab = new Drupal.verticalTab({ title: $('> legend', new_content).text(), fieldset: $(new_content) }); + var list = new_content.parents().find('.vertical-tabs-panes').siblings('ul.vertical-tabs-list'); + list.append(tab.item); + } + + // Add the tab behavior and styling to the new fieldset. + $(new_content) + .addClass('vertical-tabs-pane') + .data('verticalTab', tab) + .data('verticalTabCallback', Drupal.moduleFormVerticalTabCallback) + .find('input, textarea, select') + .change(function() { + tab.updateDescription(); + }); + + // Give the search tab the focus and update the description. + tab.focus(); + tab.updateDescription(); + + // Attach behaviors to the new content. + Drupal.attachBehaviors(new_content); +} + +/** + * Assing the vertical tab callback to update the description to all fieldsets + * on the modules form. + */ +Drupal.behaviors.moduleFormVTab = { + attach: function(context) { + $('#modules-form-wrapper > fieldset', context).each( function() { + $(this).data('verticalTabCallback', Drupal.moduleFormVerticalTabCallback); + }); + } +}; + +/** + * Vertical tab callback to update the descriptions on the modules form. + */ +Drupal.moduleFormVerticalTabCallback = function() { + modules = $('input.form-checkbox', this.fieldset).size(); + enabled = $('input.form-checkbox[checked]', this.fieldset).size(); + return modules + ' modules, ' + enabled + ' enabled.'; +} + })(jQuery); \ No newline at end of file diff --git modules/upload/upload.js modules/upload/upload.js new file mode 100644 index 0000000..3652958 --- /dev/null +++ modules/upload/upload.js @@ -0,0 +1,16 @@ +// $Id$ + +(function($) { + +Drupal.behaviors.nodeUploadVTab = { + attach: function(context) { + $('#edit-attachments').data('verticalTabCallback', function() { + var size = $('#upload-attachments tbody tr').size(); + return Drupal.formatPlural(size, '1 attachment', '@count attachments'); + }).each(function() { + Drupal.verticalTab && Drupal.verticalTab.update(this); + }); + } +}; + +})(jQuery); diff --git modules/upload/upload.module modules/upload/upload.module index 31e91d6..c0509fa 100644 --- modules/upload/upload.module +++ modules/upload/upload.module @@ -224,21 +224,19 @@ function upload_form_alter(&$form, $form_state, $form_id) { if (!empty($form['#node_edit_form'])) { $node = $form['#node']; if (variable_get("upload_$node->type", TRUE)) { + drupal_add_js(drupal_get_path('module', 'upload') .'/upload.js'); + // Attachments fieldset - $form['attachments'] = array( + $form['miscellaneous']['attachments'] = array( '#type' => 'fieldset', '#access' => user_access('upload files'), '#title' => t('File attachments'), - '#collapsible' => TRUE, - '#collapsed' => empty($node->files), '#description' => t('Changes made to the attachments are not permanent until you save this post. The first "listed" file will be included in RSS feeds.'), - '#prefix' => '
    ', - '#suffix' => '
    ', '#weight' => 30, ); // Wrapper for fieldset contents (used by ahah.js). - $form['attachments']['wrapper'] = array( + $form['miscellaneous']['attachments']['wrapper'] = array( '#prefix' => '
    ', '#suffix' => '
    ', ); @@ -249,16 +247,16 @@ function upload_form_alter(&$form, $form_state, $form_id) { $temp = file_directory_temp(); // Note: pass by reference if (!file_check_directory($path, FILE_CREATE_DIRECTORY) || !file_check_directory($temp, FILE_CREATE_DIRECTORY)) { - $form['attachments']['#description'] = t('File attachments are disabled. The file directories have not been properly configured.'); + $form['miscellaneous']['attachments']['#description'] = t('File attachments are disabled. The file directories have not been properly configured.'); if (user_access('administer site configuration')) { - $form['attachments']['#description'] .= ' ' . t('Please visit the file system configuration page.', array('@admin-file-system' => url('admin/settings/file-system'))); + $form['miscellaneous']['attachments']['#description'] .= ' ' . t('Please visit the file system configuration page.', array('@admin-file-system' => url('admin/settings/file-system'))); } else { - $form['attachments']['#description'] .= ' ' . t('Please contact the site administrator.'); + $form['miscellaneous']['attachments']['#description'] .= ' ' . t('Please contact the site administrator.'); } } else { - $form['attachments']['wrapper'] += _upload_form($node); + $form['miscellaneous']['attachments']['wrapper'] += _upload_form($node); $form['#attributes']['enctype'] = 'multipart/form-data'; } $form['#submit'][] = 'upload_node_form_submit'; diff --git themes/garland/fix-ie.css themes/garland/fix-ie.css index adc8da1..3752a5b 100644 --- themes/garland/fix-ie.css +++ themes/garland/fix-ie.css @@ -25,6 +25,10 @@ fieldset { background: none; } +div.vertical-tabs ul.vertical-tabs-list li.first { + background-image: none; +} + ul.primary { /* Fix missing top margin */ position: relative; /* LTR */ diff --git themes/garland/style.css themes/garland/style.css index 412346f..2627313 100644 --- themes/garland/style.css +++ themes/garland/style.css @@ -823,6 +823,13 @@ fieldset { background-color: transparent; } +/* Keep the background position at 0 for filters and vertical tabs. */ +*:first-child+html fieldset.filter-wrapper, +*:first-child+html fieldset.vertical-tabs-pane { + background-position: 0 0; +} + + *:first-child+html fieldset > .description, *:first-child+html fieldset .fieldset-wrapper .description { padding-top: 1em; } @@ -851,6 +858,42 @@ html.js fieldset.collapsed legend a { background: url(images/menu-collapsed.gif) no-repeat 0% 50%; /* LTR */ } + /** + * Vertical tabs. + */ +div.vertical-tabs { + margin-right: 5%; + border-color: #d9eaf5; +} + +div.vertical-tabs ul.vertical-tabs-list { + border-color: #d9eaf5; +} + +div.vertical-tabs ul.vertical-tabs-list li { + background-color: #edf5fa; + border-color: #d9eaf5; +} + +div.vertical-tabs ul.vertical-tabs-list li.selected { + background: #fff repeat-x 0 0; +} + +div.vertical-tabs ul.vertical-tabs-list li.selected.first { + background-image: url(images/gradient-inner.png); +} + +div.vertical-tabs ul.vertical-tabs-list li.selected a { + color: #494949; +} + +/** + * Modules form. + */ +#system-modules #edit-search { + margin-right: 5%; +} + /** * Syndication icons and block */