Skip to content

Re-Validate form on input change or manually re-validate #1004

Open
@kennethenglisch

Description

@kennethenglisch

Subject of the issue

I have a form with date range picker and two time pickers. The time pickers validation is based on the input of the other time picker and the range picker.

If the date range is only one day, the start time should be before the end time. Also I can only validate the time pickers when the user entered a date range first.

Is it possible to re-trigger the whole form validation on form-submit or re-validate the whole form after any input has changed?

The problem is that if I enter the times and try to submit the time pickers say I need to pick a date first. Which is correct. If I now enter the date range and submit again, the time pickers are still tagged as invalid and are not revalidated at this point.

Your environment

  • version of this package: 4.8.1
  • version of Laravel: 10.44.0

Steps to reproduce

TimeRule

class TimeRule implements ValidationRule
{
    private string|null $date_range;
    private string|null $time_start;
    private string|null $time_end;

    private string $date_range_label;
    private string $time_start_label;
    private string $time_end_label;

    public function __construct(string|null $date_range, string|null $time_start, string|null $time_end, string $date_range_label, string $time_start_label, string $time_end_label)
    {
        $this->date_range   = $date_range;
        $this->time_start   = $time_start;
        $this->time_end     = $time_end;

        $this->date_range_label = $date_range_label;
        $this->time_start_label = $time_start_label;
        $this->time_end_label   = $time_end_label;
    }

    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if($this->date_range === null) {
            $fail(t('vacation::create-edit.choose-range-first', ['attr' => $this->date_range_label]));
            return;
        }

        if($this->time_start === null) {
            $fail(t('vacation::create-edit.choose-time-start-first', ['attr' => $this->time_start_label]));
            return;
        }

        if($this->time_end === null) {
            $fail(t('vacation::create-edit.choose-time-end-first', ['attr' => $this->time_end_label]));
            return;
        }

        $dates = explode(' ', $this->date_range);
        if(sizeof($dates) === 3) {
            return;
        }

        $isStartTime = $value === $this->time_start;
        $now = date(config('app.params.timeFormat'));
        $today = date(config('app.params.dateFormat'));
        try {
            $date = strtotime($dates[0]);

            if(!$date) {
                $fail(t('dateRangeRules.error'));
                return;
            }

            $date = date(config('app.params.dateFormat'), $date);
            $start_date = strtotime($dates[0] . ' ' . $this->time_start);
            $end_date = strtotime($dates[0] . ' ' . $this->time_end);

            if(!$start_date || !$end_date) {
                $fail(t('vacation::create-edit.time-invalid'));
                return;
            }

            $start_date = date(config('app.params.dateTimeFormat'), $start_date);
            $end_date = date(config('app.params.dateTimeFormat'), $end_date);

            if($date === $today) {
                $today_now = strtotime($today . ' ' . $now);

                if(!$today_now) {
                    $fail(t('dateRangeRules.error'));
                    return;
                }

                $today_now = date(config('app.params.dateTimeFormat'), $today_now);
                if($today_now > $start_date && $isStartTime || $today_now > $end_date && !$isStartTime) {
                    $fail(t('vacation::create-edit.time-before-now'));
                    return;
                }
            }

            if($start_date >= $end_date) {
                $fail(t('vacation::create-edit.end-time-before-start-time', ['start' => $this->time_start_label, 'end' => $this->time_end_label]));
                return;
            }
        } catch (Exception $e) {
            $fail(t('dateRangeRules.error'));
            return;
        }
    }
}

Rules in Request Class

/**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
     */
    public function rules(): array
    {
        $vacation_types = VacationTypes::forUserRoles(auth()->user()->getUserRolesIds())->toArray();
        $vacation_types_list = '';

        foreach($vacation_types as $vacation_type) {
            $vacation_types_list .= $vacation_type['id'] . ',';
        }

        return [
            'vacation-type' => ['required', 'integer', 'in:' . rtrim($vacation_types_list, ',')],
            'date-range'    => ['required', new DateRangeRule()],
            'full-day'      => ['integer', 'in:1,null', 'nullable'],
            'time-start'    => ['required_unless:full-day,1', 'min:5', 'max:5', 'regex:/^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$/', 'nullable', new TimeRule($this->input('date-range'), $this->input('time-start'), $this->input('time-end'), t('vacation::create-edit.date-range-label'), t('vacation::create-edit.time-start-label'), t('vacation::create-edit.time-end-label'))],
            'time-end'      => ['required_unless:full-day,1', 'min:5', 'max:5', 'regex:/^(0[0-9]|1[0-9]|2[0-3]):[0-5][0-9]$/', 'nullable', new TimeRule($this->input('date-range'), $this->input('time-start'), $this->input('time-end'), t('vacation::create-edit.date-range-label'), t('vacation::create-edit.time-start-label'), t('vacation::create-edit.time-end-label'))],
        ];
    }

Expected behaviour

The form should be revalidated when the user submits the form.
Better would be to have an option to revalidate everything after one input has changed

Actual behaviour

The input fields tagged as invalid should be valid after the user entered the date range but the fields are still tagges as invalid stating that the user should enter the date range first.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions