diff --git a/core/lib/Drupal/Core/Form/FormErrorHandler.php b/core/lib/Drupal/Core/Form/FormErrorHandler.php index e2b3090..75ef779 100644 --- a/core/lib/Drupal/Core/Form/FormErrorHandler.php +++ b/core/lib/Drupal/Core/Form/FormErrorHandler.php @@ -24,6 +24,14 @@ class FormErrorHandler implements FormErrorHandlerInterface { use LinkGeneratorTrait; /** + * Associated array of elements with errors which need to be linked to from + * the error message + * + * @var array[] + */ + protected $errorLinkElements = []; + + /** * Constructs a new FormErrorHandler. * * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation @@ -40,37 +48,57 @@ public function __construct(TranslationInterface $string_translation, LinkGenera * {@inheritdoc} */ public function handleFormErrors(array &$form, FormStateInterface $form_state) { - // Display error messages for each element. - $this->displayErrorMessages($form, $form_state); - // After validation, loop through and assign each element its errors. $this->setElementErrorsFromFormState($form, $form_state); + // Display error messages for each element. + $this->displayErrorMessages($form_state, $this->errorLinkElements); + + // Reset the list of elements with errors + $this->errorLinkElements = []; + return $this; } /** * Loops through and displays all form errors. * - * @param array $form - * An associative array containing the structure of the form. * @param \Drupal\Core\Form\FormStateInterface $form_state * The current state of the form. + * @param array[] $error_elements + * An associative array of elements with errors */ - protected function displayErrorMessages(array $form, FormStateInterface $form_state) { + protected function displayErrorMessages(FormStateInterface $form_state, array $error_elements = []) { $error_links = []; - // Loop through all form errors and display a link for each error that is - // associated with a visible form element. - foreach ($form_state->getErrors() as $key => $error) { - if (($form_element = FormElementHelper::getElementByName($key, $form)) && Element::isVisibleElement($form_element)) { - $title = FormElementHelper::getElementTitle($form_element); - $error_links[] = $this->l($title, Url::fromRoute('', [], ['fragment' => 'edit-' . str_replace('_', '-', $key), 'external' => TRUE])); - } - else { - $this->drupalSetMessage($error, 'error'); + $errors = $form_state->getErrors(); + + // Create error links + foreach ($error_elements as $form_element) { + $title = FormElementHelper::getElementTitle($form_element); + + // Only show links to erroneous elements that are visible + $is_visible_element = Element::isVisibleElement($form_element) ? TRUE : FALSE; + // And don't show links to elements which use their parent element for inline errors + $not_use_parent = empty($form_element['#error_use_parent']); + // And only show links for elements that have a title themselves or have children with a title + $has_title = !empty($title); + // And only show links for elements with an id + $has_id = !empty($form_element['#id']); + + if ($is_visible_element && $not_use_parent && $has_title && $has_id) { + $error_links[] = $this->l($title, Url::fromRoute('', [], [ + 'fragment' => $form_element['#id'], + 'external' => TRUE + ])); + unset($errors[implode('][', $form_element['#parents'])]); } } + // For all left over errors set normal error messages + foreach ($errors as $error) { + $this->drupalSetMessage($error, 'error'); + } + if (!empty($error_links)) { // We need to pass this through SafeMarkup::format() so // drupal_set_message() does not encode the links. @@ -88,7 +116,8 @@ protected function displayErrorMessages(array $form, FormStateInterface $form_st } /** - * Stores the errors of each element directly on the element. + * Stores the errors of each element directly on the element and keep a list + * of these elements * * We must provide a way for non-form functions to check the errors for a * specific element. The most common usage of this is a #pre_render callback. @@ -105,8 +134,14 @@ protected function setElementErrorsFromFormState(array &$elements, FormStateInte $this->setElementErrorsFromFormState($elements[$key], $form_state); } } - // Store the errors for this element on the element directly. - $elements['#errors'] = $form_state->getError($elements); + + // Store the errors for this element on the element directly and + // keep a list of elements with errors + // @todo get the element name differently when $elements['#name'] is not set + // like from #parents or the form structure + if (($elements['#errors'] = $form_state->getError($elements)) && !empty($elements['#name'])) { + $this->errorLinkElements[$elements['#name']] = $elements; + }; } /** diff --git a/core/themes/seven/css/components/form.css b/core/themes/seven/css/components/form.css index 7db0a7f..bb9e1b4 100644 --- a/core/themes/seven/css/components/form.css +++ b/core/themes/seven/css/components/form.css @@ -43,7 +43,6 @@ label[for] { .form-disabled label { color: #737373; } - .form-disabled input.form-text, .form-disabled input.form-tel, .form-disabled input.form-email, @@ -58,7 +57,6 @@ label[for] { background-color: hsla(0, 0%, 0%, .08); box-shadow: none; } - .form-item input.error, .form-item textarea.error, .form-item select.error { @@ -68,12 +66,10 @@ label[for] { box-shadow: inset 0 5px 5px -5px #b8b8b8; color: #a51b00; } - .form-item textarea.error + .cke { border-width: 1px; border-color: #e62600; } - .form-item input.error:focus, .form-item textarea.error:focus, .form-item select.error:focus { @@ -88,16 +84,13 @@ label[for] { width: 7px; height: 7px; } - .form-error-message { margin-top: 0.15em; color: #ea2800; } - .fieldset-wrapper > .form-error-message { margin-top: 0; } - .text-format-wrapper .form-error-message { border: solid #ccc; border-width: 0 1px;