If I have a form with required fields and I add an exception date, it shows an error message in office hours.

Comments

zenimagine created an issue. See original summary.

johnv’s picture

Title: Problem with required fields » Problem with required field w/ "Exception Day"
zenimagine’s picture

StatusFileSize
new154.85 KB
johnv’s picture

Priority: Normal » Major
johnv’s picture

I tried to solve this, but found no resolution.
Problem is that in this case, two widgets are built, but they are not declared as 'multiple widgets'.
In the course of #2862302: [Season] Add Recurrence Rule for Views (Full)Calendar integration (ISO-8601 or RRULE), it think we will need to fix this, which will also solve this problem.

  • johnv committed ad6d3230 on 8.x-1.x
    Issue #3296405 by zenimagine: Problem with required field w/ Exception...
johnv’s picture

Status: Active » Fixed

In the end, it was very easy.
For the record, and to out my frustration and admiration, please find below my experiences in solving this issue.

In the end, we just need a oneliner '#limit_validation_errors' => [],, as shown below:

    $element['exceptions']['add_more'] = [
      '#type' => 'submit',
      '#value' => $this->t('Add exception'),
      '#ajax' => [
        'callback' => [get_class($this), 'addMoreAjax'],
        'wrapper' => 'exceptions-container',
        'effect' => 'fade',
      ],
      // No form validation when this button is clicked.
      // E.g., when the Field is required, and no Weekday items exist, yet.
      '#limit_validation_errors' => [],
      '#submit' => [
        [static::class, 'addMoreSubmit'],
      ],
    ];

This is documented in the function setErrorByName() in FormStateInterface, and apparently this is used by core in order to validate the required status:

interface FormStateInterface {
...
  /**
   * Files an error against a form element.
   *
   * ...
   *
   * The standard behavior of this method can be changed if a button provides
   * the #limit_validation_errors property. Multistep forms not wanting to
   * validate the whole form can set #limit_validation_errors on buttons to
   * limit validation errors to only certain elements. For example, pressing the
   * "Previous" button in a multistep form should not fire validation errors
   * just because the current step has invalid values. If
   * #limit_validation_errors is set on a clicked button, the button must also
   * define a #submit property (may be set to an empty array). Any #submit
   * handlers will be executed even if there is invalid input, so extreme care
   * should be taken with respect to any actions taken by them. This is
   * typically not a problem with buttons like "Previous" or "Add more" that do
   * not invoke persistent storage of the submitted form values. Do not use the
   * #limit_validation_errors property on buttons that trigger saving of form
   * values to the database.
   *
   * The #limit_validation_errors property is a list of "sections" within
   * $form_state->getValues() that must contain valid values. Each "section" is
   * an array with the ordered set of keys needed to reach that part of
   * $form_state->getValues() (i.e., the #parents property of the element).
   *
   * Example 1: Allow the "Previous" button to function, regardless of whether
   * any user input is valid.
   *
   * @code
   *   $form['actions']['previous'] = array(
   *     '#type' => 'submit',
   *     '#value' => t('Previous'),
   *     '#limit_validation_errors' => array(),       // No validation.
   *     '#submit' => array('some_submit_function'),  // #submit required.
   *   );
   * @endcode
   *
   * Example 2: Require some, but not all, user input to be valid to process the
   * submission of a "Previous" button.
   *
   * @code
   *   $form['actions']['previous'] = array(
   *     '#type' => 'submit',
   *     '#value' => t('Previous'),
   *     '#limit_validation_errors' => array(
   *       // Validate $form_state->getValue('step1').
   *       array('step1'),
   *       // Validate $form_state->getValue(array('foo', 'bar')).
   *       array('foo', 'bar'),
   *     ),
   *     '#submit' => array('some_submit_function'), // #submit required.
   *   );
   * @endcode
   *
   * ...
   */
  public function setErrorByName($name, $message = '');
...
}
johnv’s picture

SO, I did not need to add a separated validation, as I tried to do below:

diff --git a/src/Plugin/Field/FieldWidget/OfficeHoursExceptionsWeekWidget.php b/src/Plugin/Field/FieldWidget/OfficeHoursExceptionsWeekWidget.php
index 58e4093..508cd44 100644
--- a/src/Plugin/Field/FieldWidget/OfficeHoursExceptionsWeekWidget.php
+++ b/src/Plugin/Field/FieldWidget/OfficeHoursExceptionsWeekWidget.php
@@ -36,6 +39,11 @@ class OfficeHoursExceptionsWeekWidget extends OfficeHoursWeekWidget {
 
     // First, create a Week widget for the normal weekdays.
     $element = parent::formElement($items, $delta, $element, $form, $form_state);
+
+    if ($this->fieldDefinition->isRequired()) {
+      $element['value']['#element_validate'][] = [static::class, 'validateRequired'];
+    }
+
     $items->filterEmptyItems();
 
     // Then, add a List Widget for the Exception days.
@@ -205,4 +216,55 @@ class OfficeHoursExceptionsWeekWidget extends OfficeHoursWeekWidget {
     return $values;
   }
 
+  /**
+   * Validates whether the widget is required and contains values.
+   *
+   * @param array $element
+   *   The form element.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   * @param array $form
+   *   The form array.
+   */
+  public static function validateRequired(array $element, FormStateInterface $form_state, array $form) {
+    // Only if the 'Add exception' button triggered submit,
+    // the 'required' validation isn't needed.
+    if (!in_array([static::class, 'addMoreSubmit'], $form_state->getSubmitHandlers(), TRUE)) {
+      return;
+    }
+
+    $field_name = 'field_office_hours'; // $field_definition->setName($this->name);
+
+    $input_exists = FALSE;
+    $input = NestedArray::getValue($form_state->getValues(), $element, $input_exists);
+    $input = $form_state->getValue($field_name);
+    // parent::valueCallback($element, $input, $form_state);
+
+    // Convert $element value into ItemList with OfficeHoursItem objects.
+    $plugin_id = 'office_hours';
+    $field_definition = BaseFieldDefinition::create($plugin_id)
+      ->setName($field_name);
+    $itemList = OfficeHoursItemList::createInstance($field_definition, '', NULL);
+    $itemList->setValue($input['value']);
+    $itemList->filterEmptyItems();
+    if ($itemList->isEmpty()) {
+
+      $form_state->setError($element, new TranslatableMarkup('@name field is required.', ['@name' => $element['#title']]));
+      // $this->fieldDefinition->setRequired(FALSE);
+      $element['needs_validation'] = FALSE;
+      $form_state->getErrors();
+    }
+    return;
+  }
 }

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.