diff --git a/core/includes/form.inc b/core/includes/form.inc index 2e8e9e1..40103cd 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -1105,6 +1105,7 @@ function form_process_password_confirm($element) { '#value' => empty($element['#value']) ? NULL : $element['#value']['pass1'], '#required' => $element['#required'], '#attributes' => array('class' => array('password-field')), + '#error_use_parent' => TRUE, ); $element['pass2'] = array( '#type' => 'password', @@ -1112,6 +1113,7 @@ function form_process_password_confirm($element) { '#value' => empty($element['#value']) ? NULL : $element['#value']['pass2'], '#required' => $element['#required'], '#attributes' => array('class' => array('password-confirm')), + '#error_use_parent' => TRUE, ); $element['#element_validate'] = array('password_confirm_validate'); $element['#tree'] = TRUE; @@ -1215,6 +1217,8 @@ function form_process_radios($element) { '#parents' => $element['#parents'], '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)), '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, + // Errors should only be shown on the parent radios element. + '#error_use_parent' => TRUE, '#weight' => $weight, ); } @@ -1365,6 +1369,8 @@ function form_process_checkboxes($element) { '#default_value' => isset($value[$key]) ? $key : NULL, '#attributes' => $element['#attributes'], '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, + // Errors should only be shown on the parent checkboxes element. + '#error_use_parent' => TRUE, '#weight' => $weight, ); } @@ -2771,8 +2777,11 @@ function theme_form_element($variables) { // may not necessarily have been processed by form_builder(). $element += array( '#title_display' => 'before', + '#parents' => array(), ); + $variables['errors'] = \Drupal::formBuilder()->getError($element); + // Take over any #wrapper_attributes defined by the element. // @todo Temporary hack for #type 'item'. // @see http://drupal.org/node/1829202 @@ -2795,8 +2804,17 @@ function theme_form_element($variables) { if (!empty($element['#attributes']['disabled'])) { $attributes['class'][] = 'form-disabled'; } + // Add a class if an error exists. + if (!empty($variables['errors'])) { + $attributes['class'][] = 'form-error'; + } $output = '' . "\n"; + // Display any error messages. + if ($variables['errors'] && !$element['#error_use_parent']) { + $output .= ' ' . theme('form_error_message', $variables); + } + // If #title is not set, we don't display any label or required marker. if (!isset($element['#title'])) { $element['#title_display'] = 'none'; @@ -2907,6 +2925,25 @@ function theme_form_element_label($variables) { } /** + * Returns HTML for an inline error associated with a specific form element. + * + * @param $variables + * An associative array containing: + * - element: An associative array containing the properties of the element. + * Properties used: error. + * - errors: The errors associated with the current element as returned by + * form_get_error($element). + * + * @ingroup themeable + */ +function theme_form_error_message($variables) { + $output = '
'; + $output .= '' . t('Error') . ': ' . $variables['errors'] . ''; + $output .= '
'; + return $output; +} + +/** * Sets a form element's class attribute. * * Adds 'required' and 'error' classes as needed. diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 9a0d713..357e7c4 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -3143,6 +3143,9 @@ function drupal_common_theme() { 'form_element_label' => array( 'render element' => 'element', ), + 'form_error_message' => array( + 'render element' => 'element', + ), 'vertical_tabs' => array( 'render element' => 'element', ), diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php index 38bfab7..0b74072 100644 --- a/core/lib/Drupal/Core/Form/FormBuilder.php +++ b/core/lib/Drupal/Core/Form/FormBuilder.php @@ -905,6 +905,9 @@ public function validateForm($form_id, &$form, &$form_state) { } $form_state['values'] = $values; } + if (!$form_state['programmed']) { + $this->displayErrors($form); + } } /** @@ -1284,6 +1287,7 @@ public function doBuildForm($form_id, &$element, &$form_state) { '#required' => FALSE, '#attributes' => array(), '#title_display' => 'before', + '#error_use_parent' => FALSE, ); // Special handling if we're on the top level form element. @@ -1793,4 +1797,82 @@ public function setRequest(Request $request) { $this->request = $request; } + /** + * Displays the given form's errors and links each error to the form element + * in question. + * + * @param array $form + * An associative array containing the structure of the form. + */ + protected function displayErrors($form) { + if ($errors = $this->getErrors()) { + $error_links = array(); + foreach ($errors as $key => $error) { + $element = $this->getElement($key, $form); + if ($element) { + $title = $this->getElementTitle($element); + $error_links[] = l($title, '', array('fragment' => 'edit-' . str_replace('_', '-', $key), 'external' => TRUE)); + } + else { + drupal_set_message($error, 'error'); + unset($errors[$key]); + } + } + + if (!empty($error_links)) { + drupal_set_message(format_plural(count($error_links), '1 error has been found', '@count errors have been found') . ': ' . implode(', ', $error_links), 'error'); + } + } + } + + /** + * Given a form and an element key, this function returns the element no matter + * how deep within the form array the key exists. If the key is not found an + * empty array is returned. + * + * @param string $element_key + * The key to search for. + * + * @param array $form + * A structured form array to search. + * + * @return array + */ + protected function getElement($element_key, $form) { + $element = array(); + foreach (element_children($form) as $key) { + if ($key === $element_key) { + $element = $form[$key]; + break; + } + else { + if (is_array($form[$key])) { + $element = $this->getElement($element_key, $form[$key]); + if (!empty($element)) { + break; + } + } + } + } + return $element; + } + + /** + * Returns the title the highest element in the hierarchy that has a title. If + * no title is found, then NULL is returned. + */ + protected function getElementTitle(array $element) { + if (isset($element['#title'])) { + return $element['#title']; + } + else { + foreach (element_children($element) as $key) { + $title = $this->getElementTitle($element[$key]); + if (isset($title)) { + return $title; + } + } + } + } + } diff --git a/core/modules/system/css/system.theme.css b/core/modules/system/css/system.theme.css index c6ed012..e2a6504 100644 --- a/core/modules/system/css/system.theme.css +++ b/core/modules/system/css/system.theme.css @@ -44,6 +44,16 @@ td.active { /** * Markup generated by Form API. */ +div.form-error { + background-color: #fef5f1; + border: 1px solid #ed541d; + color: #8c2e0b; + padding: 10px; +} +div.form-error-message { + margin-bottom: 10px; + min-height: 25px; +} .form-item, .form-actions { margin-top: 1em;