-
Notifications
You must be signed in to change notification settings - Fork 196
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
docs: add error messages to field validation examples #3685
Changes from 18 commits
6d35622
089a97d
3c48700
192cb64
fae6645
e8528b9
1d22a7f
65d2b5b
a01b27e
490be6d
e6bc38f
723b5c8
2dc6f2a
6963882
dbf3247
23efedd
b64e8da
7b53abd
2df3ee8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,9 @@ | ||
import 'Frontend/demo/init'; // hidden-source-line | ||
import '@vaadin/date-picker'; | ||
import { addDays, formatISO, isAfter, isBefore } from 'date-fns'; | ||
import dateFnsParse from 'date-fns/parse'; | ||
import { addDays, formatISO } from 'date-fns'; | ||
import { html, LitElement } from 'lit'; | ||
import { customElement, state } from 'lit/decorators.js'; | ||
import type { DatePickerChangeEvent } from '@vaadin/date-picker'; | ||
import type { DatePicker, DatePickerValidatedEvent } from '@vaadin/date-picker'; | ||
import { applyTheme } from 'Frontend/generated/theme'; | ||
|
||
@customElement('date-picker-validation') | ||
|
@@ -31,14 +30,19 @@ export class Example extends LitElement { | |
<vaadin-date-picker | ||
label="Appointment date" | ||
helper-text="Must be within 60 days from today" | ||
required | ||
.min="${formatISO(this.minDate, { representation: 'date' })}" | ||
.max="${formatISO(this.maxDate, { representation: 'date' })}" | ||
.errorMessage="${this.errorMessage}" | ||
@change="${({ target }: DatePickerChangeEvent) => { | ||
const date = dateFnsParse(target.value ?? '', 'yyyy-MM-dd', new Date()); | ||
if (isBefore(date, this.minDate)) { | ||
@validated="${(event: DatePickerValidatedEvent) => { | ||
const field = event.target as DatePicker; | ||
if (!field.value && (field.inputElement as HTMLInputElement).value) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've created a proposal to make the type of |
||
this.errorMessage = 'Invalid date format'; | ||
} else if (!field.value) { | ||
this.errorMessage = 'Field is required'; | ||
} else if (field.value < field.min!) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The ISO format is lexicographically ordered, so we can use JS comparison operators to compare date strings directly. This simplifies the solution a bit. |
||
this.errorMessage = 'Too early, choose another date'; | ||
} else if (isAfter(date, this.maxDate)) { | ||
} else if (field.value > field.max!) { | ||
this.errorMessage = 'Too late, choose another date'; | ||
} else { | ||
this.errorMessage = ''; | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,9 +1,9 @@ | ||||||||||||||||
import { reactExample } from 'Frontend/demo/react-example'; // hidden-source-line | ||||||||||||||||
import React from 'react'; // hidden-source-line | ||||||||||||||||
import { useSignals } from '@preact/signals-react/runtime'; // hidden-source-line | ||||||||||||||||
import { addDays, formatISO, isAfter, isBefore, parse } from 'date-fns'; | ||||||||||||||||
import { addDays, formatISO } from 'date-fns'; | ||||||||||||||||
import { useComputed, useSignal } from '@vaadin/hilla-react-signals'; | ||||||||||||||||
import { DatePicker } from '@vaadin/react-components/DatePicker.js'; | ||||||||||||||||
import { DatePicker, type DatePickerElement } from '@vaadin/react-components/DatePicker.js'; | ||||||||||||||||
|
||||||||||||||||
function Example() { | ||||||||||||||||
useSignals(); // hidden-source-line | ||||||||||||||||
|
@@ -16,14 +16,19 @@ function Example() { | |||||||||||||||
<DatePicker | ||||||||||||||||
label="Appointment date" | ||||||||||||||||
helperText="Must be within 60 days from today" | ||||||||||||||||
required | ||||||||||||||||
min={formatISO(minDate.value, { representation: 'date' })} | ||||||||||||||||
max={formatISO(maxDate.value, { representation: 'date' })} | ||||||||||||||||
errorMessage={errorMessage.value} | ||||||||||||||||
onChange={({ target }) => { | ||||||||||||||||
const date = parse(target.value ?? '', 'yyyy-MM-dd', new Date()); | ||||||||||||||||
if (isBefore(date, minDate.value)) { | ||||||||||||||||
onValidated={(event) => { | ||||||||||||||||
const field = event.target as DatePickerElement; | ||||||||||||||||
if (!field.value && (field.inputElement as HTMLInputElement).value) { | ||||||||||||||||
errorMessage.value = 'Invalid date format'; | ||||||||||||||||
} else if (!field.value) { | ||||||||||||||||
errorMessage.value = 'Field is required'; | ||||||||||||||||
Comment on lines
+25
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we could simplify this a bit to only check
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO, the current approach has a benefit that it keeps all error messages at the same indentation level, which makes them a bit easier to scan. |
||||||||||||||||
} else if (field.value < field.min!) { | ||||||||||||||||
errorMessage.value = 'Too early, choose another date'; | ||||||||||||||||
} else if (isAfter(date, maxDate.value)) { | ||||||||||||||||
} else if (field.value > field.max!) { | ||||||||||||||||
errorMessage.value = 'Too late, choose another date'; | ||||||||||||||||
} else { | ||||||||||||||||
errorMessage.value = ''; | ||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,45 @@ | ||
import { reactExample } from 'Frontend/demo/react-example'; // hidden-source-line | ||
import React from 'react'; // hidden-source-line | ||
import { useSignals } from '@preact/signals-react/runtime'; // hidden-source-line | ||
import { addDays, format, isAfter, isBefore, parseISO } from 'date-fns'; | ||
import { addDays, format } from 'date-fns'; | ||
import { useSignal } from '@vaadin/hilla-react-signals'; | ||
import { DateTimePicker } from '@vaadin/react-components/DateTimePicker.js'; | ||
import { | ||
type DatePickerElement, | ||
DateTimePicker, | ||
type DateTimePickerElement, | ||
type TimePickerElement, | ||
} from '@vaadin/react-components'; | ||
|
||
// tag::snippet[] | ||
function Example() { | ||
useSignals(); // hidden-source-line | ||
const errorMessage = useSignal(''); | ||
const value = useSignal(addDays(new Date(), 7)); | ||
const minDate = useSignal(new Date()); | ||
const maxDate = useSignal(addDays(new Date(), 60)); | ||
|
||
return ( | ||
<DateTimePicker | ||
label="Appointment date and time" | ||
helperText="Must be within 60 days from today" | ||
value={format(value.value, "yyyy-MM-dd'T'HH:00:00")} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The similar DatePicker and TimePicker examples don't have an initial value, so I decided that it's unnecessary for the DateTimePicker example too. This example seems to already contain a lot due to the validated listener. |
||
min={format(minDate.value, "yyyy-MM-dd'T'HH:00:00")} | ||
max={format(maxDate.value, "yyyy-MM-dd'T'HH:00:00")} | ||
min={format(minDate.value, `yyyy-MM-dd'T'HH:00:00`)} | ||
max={format(maxDate.value, `yyyy-MM-dd'T'HH:00:00`)} | ||
errorMessage={errorMessage.value} | ||
onValueChanged={({ detail: { value: newValue } }) => { | ||
const date = parseISO(newValue ?? ''); | ||
value.value = date; | ||
if (isBefore(date, minDate.value)) { | ||
onValidated={(event) => { | ||
const field = event.target as DateTimePickerElement; | ||
const datePicker: DatePickerElement = field.querySelector('[slot=date-picker]')!; | ||
const timePicker: TimePickerElement = field.querySelector('[slot=time-picker]')!; | ||
const hasBadDateInput = | ||
!datePicker.value && !!(datePicker.inputElement as HTMLInputElement).value; | ||
const hasBadTimeInput = | ||
!timePicker.value && !!(timePicker.inputElement as HTMLInputElement).value; | ||
|
||
if (hasBadDateInput || hasBadTimeInput) { | ||
errorMessage.value = 'Invalid date or time'; | ||
} else if (!field.value) { | ||
errorMessage.value = 'Field is required'; | ||
} else if (field.value < field.min!) { | ||
errorMessage.value = 'Too early, choose another date and time'; | ||
} else if (isAfter(date, maxDate.value)) { | ||
} else if (field.value > field.max!) { | ||
errorMessage.value = 'Too late, choose another date and time'; | ||
} else { | ||
errorMessage.value = ''; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've created a proposal to improve typing for
event.target
in CustomEvent-based component events: vaadin/web-components#7868