Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.981
diff -u -p -d -p -r1.981 common.inc
--- includes/common.inc 31 Aug 2009 18:43:12 -0000 1.981
+++ includes/common.inc 3 Sep 2009 00:44:32 -0000
@@ -4640,6 +4640,9 @@ function drupal_common_theme() {
'form_element' => array(
'arguments' => array('element' => NULL),
),
+ 'form_element_label' => array(
+ 'arguments' => array('element' => NULL),
+ ),
'text_format_wrapper' => array(
'arguments' => array('element' => NULL),
),
Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.368
diff -u -p -d -p -r1.368 form.inc
--- includes/form.inc 29 Aug 2009 16:30:14 -0000 1.368
+++ includes/form.inc 3 Sep 2009 00:44:32 -0000
@@ -1082,6 +1082,11 @@ function form_builder($form_id, $element
$form_state['has_file_element'] = TRUE;
}
+ // Set the element's title attribute to the elements #title, if needed.
+ if (!empty($element['#title']) && isset($element['#show_title']) && $element['#show_title'] == FORM_ELEMENT_SHOW_TITLE_ATTRIBUTE) {
+ $element['#attributes']['title'] = $element['#title'];
+ }
+
if (isset($element['#type']) && $element['#type'] == 'form') {
// We are on the top form.
// If there is a file element, we set the form encoding.
@@ -1510,7 +1515,6 @@ function form_options_flatten($array, $r
* values are associative arrays in the normal $options format.
*/
function theme_select($element) {
- $select = '';
$size = $element['#size'] ? ' size="' . $element['#size'] . '"' : '';
_form_set_class($element, array('form-select'));
$multiple = $element['#multiple'];
@@ -1660,9 +1664,6 @@ function theme_radio($element) {
$output .= 'value="' . $element['#return_value'] . '" ';
$output .= (check_plain($element['#value']) == $element['#return_value']) ? ' checked="checked" ' : ' ';
$output .= drupal_attributes($element['#attributes']) . ' />';
- if (!is_null($element['#title'])) {
- $output = '';
- }
return $output;
}
@@ -1998,10 +1999,6 @@ function theme_checkbox($element) {
$checkbox .= $element['#value'] ? ' checked="checked" ' : ' ';
$checkbox .= drupal_attributes($element['#attributes']) . ' />';
- if (!is_null($element['#title'])) {
- $checkbox = '';
- }
-
return $checkbox;
}
@@ -2562,7 +2559,8 @@ function theme_file($element) {
*
* @param element
* An associative array containing the properties of the element.
- * Properties used: #title, #description, #id, #required, #children
+ * Properties used: #type, #name, #title, #show_title, #children,
+ * #description
* @return
* A string representing the form element.
*
@@ -2582,19 +2580,19 @@ function theme_form_element($element) {
}
$output = '
' . "\n";
- $required = !empty($element['#required']) ? '
*' : '';
- if (!empty($element['#title']) && empty($element['#form_element_skip_title'])) {
- $title = $element['#title'];
- if (!empty($element['#id'])) {
- $output .= '
\n";
- }
- else {
- $output .= '
\n";
- }
+ if (isset($element['#show_title']) && $element['#show_title'] == FORM_ELEMENT_SHOW_TITLE_AFTER) {
+ // Insert label in correct position, depending on #show_title.
+ $output .= $element['#children'] . ' ' . theme('form_element_label', $element) . "\n";
+ }
+ else if (isset($element['#show_title']) && $element['#show_title'] == FALSE) {
+ // #show_title is FALSE, so we continue with no label (not recommended).
+ $output .= ' ' . $element['#children'] . "\n";
+ }
+ else {
+ // Default (label before element) or FORM_ELEMENT_SHOW_TITLE_ATTRIBUTE.
+ $output .= theme('form_element_label', $element) . ' ' . $element['#children'] . "\n";
}
-
- $output .= " " . $element['#children'] . "\n";
if (!empty($element['#description'])) {
$output .= '
' . $element['#description'] . "
\n";
@@ -2606,6 +2604,44 @@ function theme_form_element($element) {
}
/**
+ * Theme a form element label and required mark.
+ *
+ * @param element
+ * An associative array containing the properties of the element.
+ * Properties used: #required, #show_title, #title, #label_option, #id
+ * @return
+ * A string representing the form element label and/or required mark.
+ *
+ * @ingroup themeable
+ */
+function theme_form_element_label($element) {
+ // This is also used in the installer, pre-database setup.
+ $t = get_t();
+
+ $required = !empty($element['#required']) ? '
*' : '';
+ // If the title is in an attribute we simply return any required mark here.
+ if (isset($element['#show_title']) && $element['#show_title'] == FORM_ELEMENT_SHOW_TITLE_ATTRIBUTE) {
+ return $required;
+ }
+
+ $title = $element['#title'];
+ // We don't want to return an empty label tag.
+ if (!empty($title)) {
+ $attributes = array();
+ if (isset($element['#label_option']) && $element['#label_option']) {
+ // This class styles the label as an option, inline with the element.
+ $attributes['class'] = 'option';
+ }
+ if (!empty($element['#id'])) {
+ $attributes['for'] = $element['#id'];
+ }
+ return '
\n";
+ }
+ // The element has no title or label, so return the required mark, if any.
+ return $required;
+}
+
+/**
* Sets a form element's class attribute.
*
* Adds 'required' and 'error' classes as needed.
Index: modules/simpletest/tests/form.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form.test,v
retrieving revision 1.14
diff -u -p -d -p -r1.14 form.test
--- modules/simpletest/tests/form.test 13 Jul 2009 21:51:41 -0000 1.14
+++ modules/simpletest/tests/form.test 3 Sep 2009 00:44:32 -0000
@@ -112,6 +112,58 @@ class FormsTestTypeCase extends DrupalUn
}
/**
+ * Test the form elements and labels for expected behavior.
+ */
+class FormsElementsLabelsTest extends DrupalWebTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => 'Form element and label output test',
+ 'description' => 'Test the form elements and labels for expected behavior',
+ 'group' => 'Form API',
+ );
+ }
+
+ function setUp() {
+ parent::setUp('form_test');
+ }
+
+ /**
+ * Test form elements, labels and title attibutes output correctly and have
+ * the correct label option class if needed.
+ */
+ function testFormLabels() {
+ $this->drupalGet('form_test/form-labels');
+
+ $elements = $this->xpath('//input[@id="edit-form-checkboxes-test-thirdcheckbox"]/following-sibling::label[@for="edit-form-checkboxes-test-thirdcheckbox" and @class="option"]');
+ $this->assertTrue(isset($elements[0]), t("Label follows field and label option class correct for regular checkboxes."));
+
+ $elements = $this->xpath('//input[@id="edit-form-radios-test-secondradio"]/following-sibling::label[@for="edit-form-radios-test-secondradio" and @class="option"]');
+ $this->assertTrue(isset($elements[0]), t("Label follows field and label option class correct for regular radios."));
+
+ $elements = $this->xpath('//input[@id="edit-form-checkbox-test"]/following-sibling::label[@for="edit-form-checkbox-test" and @class="option"]');
+ $this->assertTrue(isset($elements[0]), t("Label follows field and label option class correct for a checkbox."));
+
+ $elements = $this->xpath('//input[@id="edit-form-checkbox-test-attribute" and @title="Checkbox test with label as attribute"]');
+ $this->assertTrue(isset($elements[0]), t("Title as attribute for a checkbox is correct."));
+
+ $elements = $this->xpath('//input[@id="edit-form-checkbox-test-attribute"]/following-sibling::label');
+ $this->assertFalse(isset($elements[0]), t("Label does not follow title as attribute for a checkbox."));
+
+ $elements = $this->xpath('//input[@id="edit-form-checkbox-test-no-label-option"]/following-sibling::label[@for="edit-form-checkbox-test-no-label-option"]');
+ $this->assertTrue(isset($elements[0]), t("Label follows field for a checkbox without a label option class."));
+
+ $elements = $this->xpath('//input[@id="edit-form-checkbox-test-no-label-option"]/following-sibling::label[@for="edit-form-checkbox-test-no-label-option" and @class="option"]');
+ $this->assertFalse(isset($elements[0]), t("No label option class found for checkbox without a label option class."));
+
+ $elements = $this->xpath('//label[@for="edit-form-textfield-test-title-and-required"]/child::span[@class="form-required"]/parent::*/following-sibling::input[@id="edit-form-textfield-test-title-and-required"]');
+ $this->assertTrue(isset($elements[0]), t("Label preceeds textfield, with required marker."));
+
+
+ }
+}
+
+/**
* Test the tableselect form element for expected behavior.
*/
class FormsElementsTableSelectFunctionalTest extends DrupalWebTestCase {
Index: modules/simpletest/tests/form_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form_test.module,v
retrieving revision 1.8
diff -u -p -d -p -r1.8 form_test.module
--- modules/simpletest/tests/form_test.module 17 Aug 2009 07:12:16 -0000 1.8
+++ modules/simpletest/tests/form_test.module 3 Sep 2009 00:44:32 -0000
@@ -65,6 +65,15 @@ function form_test_menu() {
'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
);
+
+ $items['form_test/form-labels'] = array(
+ 'title' => 'Form label test',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('form_label_test_form'),
+ 'access arguments' => array('access content'),
+ 'type' => MENU_CALLBACK,
+ );
+
return $items;
}
@@ -365,3 +374,71 @@ function form_storage_test_form_submit($
$form_state['storage']['step']++;
drupal_set_message("Form constructions: ". $_SESSION['constructions']);
}
+
+/**
+ * A form for testing form labels.
+ */
+function form_label_test_form(&$form_state) {
+ $form['form_checkboxes_test'] = array(
+ '#type' => 'checkboxes',
+ '#title' => t('Checkboxes test'),
+ '#options' => array(
+ 'firstcheckbox' => t('First Checkbox'),
+ 'secondcheckbox' => t('Second Checkbox'),
+ 'thirdcheckbox' => t('Third Checkbox'),
+ ),
+ );
+ $form['form_radios_test'] = array(
+ '#type' => 'radios',
+ '#title' => t('Radios test'),
+ '#options' => array(
+ 'firstradio' => t('First Radio'),
+ 'secondradio' => t('Second Radio'),
+ 'thirdradio' => t('Third Radio'),
+ ),
+ );
+ $form['form_checkbox_test'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Checkbox test'),
+ );
+ $form['form_checkbox_test_attribute'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Checkbox test with label as attribute'),
+ '#show_title' => FORM_ELEMENT_SHOW_TITLE_ATTRIBUTE,
+ );
+ $form['form_checkbox_test_no_label_option'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Checkbox test with label not styled as an option'),
+ '#label_option' => FALSE,
+ );
+ $form['form_textfield_test_title_and_required'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Textfield test for required with title'),
+ '#required' => TRUE,
+ );
+ $form['form_textfield_test_required'] = array(
+ '#type' => 'textfield',
+ '#required' => TRUE,
+ );
+ $form['form_textfield_test_title'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Textfield test for title only'),
+ );
+ $form['form_textfield_test_title_attribute'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Textfield test for title as attribute'),
+ '#show_title' => FORM_ELEMENT_SHOW_TITLE_ATTRIBUTE,
+ );
+ $form['form_textfield_test_title_after'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Textfield test for title as attribute'),
+ '#show_title' => FORM_ELEMENT_SHOW_TITLE_AFTER,
+ );
+ $form['form_textfield_test_label_option'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Textfield test for title with label styled as an option'),
+ '#label_option' => TRUE,
+ );
+
+ return $form;
+}
\ No newline at end of file
Index: modules/system/system.api.php
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.api.php,v
retrieving revision 1.71
diff -u -p -d -p -r1.71 system.api.php
--- modules/system/system.api.php 1 Sep 2009 17:40:27 -0000 1.71
+++ modules/system/system.api.php 3 Sep 2009 00:44:32 -0000
@@ -215,6 +215,9 @@ function hook_db_rewrite_sql($query, $pr
* - "#pre_render": array of callback functions taking $element and $form_state.
* - "#post_render": array of callback functions taking $element and $form_state.
* - "#submit": array of callback functions taking $form and $form_state.
+ * - "#show_title": optionally one of the FORM_ELEMENT_SHOW_TITLE_* constants
+ * indicating how the #title should be added (defaults to before the element).
+ * - "#label_option": add a class to style the element label as an option.
*/
function hook_elements() {
$type['filter_format'] = array('#input' => TRUE);
Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.784
diff -u -p -d -p -r1.784 system.module
--- modules/system/system.module 1 Sep 2009 16:50:12 -0000 1.784
+++ modules/system/system.module 3 Sep 2009 00:44:32 -0000
@@ -86,6 +86,20 @@ define('REGIONS_VISIBLE', 'visible');
*/
define('REGIONS_ALL', 'all');
+/**
+ * Output form element titles as labels after form elements (rather than
+ * before the element, which is the default).
+ * @see system_elements().
+ */
+define('FORM_ELEMENT_SHOW_TITLE_AFTER', 'after');
+
+/**
+ * Output form element titles as the title attribute of form elements, rather
+ * than as a label.
+ * @see system_elements().
+ */
+define('FORM_ELEMENT_SHOW_TITLE_ATTRIBUTE', 'attribute');
+
/**
* Implement hook_help().
@@ -381,7 +395,8 @@ function system_elements() {
'#process' => array('ajax_process_form'),
'#theme' => 'radio',
'#theme_wrappers' => array('form_element'),
- '#form_element_skip_title' => TRUE,
+ '#show_title' => FORM_ELEMENT_SHOW_TITLE_AFTER,
+ '#label_option' => TRUE,
);
$type['checkboxes'] = array(
@@ -398,7 +413,8 @@ function system_elements() {
'#process' => array('ajax_process_form'),
'#theme' => 'checkbox',
'#theme_wrappers' => array('form_element'),
- '#form_element_skip_title' => TRUE,
+ '#show_title' => FORM_ELEMENT_SHOW_TITLE_AFTER,
+ '#label_option' => TRUE,
);
$type['select'] = array(