Skip to content

Commit

Permalink
implement live validations for volunteers example; add phx_debouce su…
Browse files Browse the repository at this point in the history
…pport to input helpers; add phx_change to form_for helper
  • Loading branch information
floodfx committed Feb 8, 2022
1 parent 3c7bfd9 commit 23cbbdf
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 30 deletions.
71 changes: 43 additions & 28 deletions src/examples/volunteers/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ export interface VolunteerContext {
changeset: LiveViewChangeset<Volunteer>
}

type VolunteerEvents = "save" | "validate";

export class VolunteerComponent extends BaseLiveViewComponent<VolunteerContext, unknown>
implements LiveViewExternalEventListener<VolunteerContext, "save", Volunteer> {
implements LiveViewExternalEventListener<VolunteerContext, VolunteerEvents, Volunteer> {

mount(params: LiveViewMountParams, session: Partial<SessionData>, socket: LiveViewSocket<VolunteerContext>) {
return {
Expand All @@ -27,15 +29,18 @@ export class VolunteerComponent extends BaseLiveViewComponent<VolunteerContext,
<h1>Volunteer Check-In</h1>
<div id="checkin">
${form_for<Volunteer>("#", { phx_submit: "save" })}
${form_for<Volunteer>("#", {
phx_submit: "save",
phx_change: "validate"
})}
<div class="field">
${text_input<Volunteer>(changeset, "name", { placeholder: "Name", autocomplete: "off" })}
${error_tag(changeset, "name")}
${text_input<Volunteer>(changeset, "name", { placeholder: "Name", autocomplete: "off", phx_debounce: 1000 })}
${error_tag(changeset, "name",)}
</div>
<div class="field">
${telephone_input<Volunteer>(changeset, "phone", { placeholder: "Phone", autocomplete: "off" })}
${telephone_input<Volunteer>(changeset, "phone", { placeholder: "Phone", autocomplete: "off", phx_debounce: "blur" })}
${error_tag(changeset, "phone")}
</div>
${submit("Check In", { phx_disable_with: "Saving..." })}
Expand Down Expand Up @@ -64,31 +69,41 @@ export class VolunteerComponent extends BaseLiveViewComponent<VolunteerContext,
`
}

handleEvent(event: "save", params: StringPropertyValues<Pick<Volunteer, "name" | "phone">>, socket: LiveViewSocket<VolunteerContext>): VolunteerContext {
const volunteer: Partial<Volunteer> = {
name: params.name,
phone: params.phone,
}
// attempt to create the volunteer from the form data
const createChangeset = create_volunteer(volunteer);

// valid form data
if (createChangeset.valid) {
const newVolunteer = createChangeset.data as Volunteer;
// only add new volunteer since we're using phx-update="prepend"
// which means the new volunteer will be added to the top of the list
const newVolunteers = [newVolunteer];
const emptyChangeset = changeset({}, {}); // reset form
handleEvent(event: VolunteerEvents, params: StringPropertyValues<Pick<Volunteer, "name" | "phone">>, socket: LiveViewSocket<VolunteerContext>): VolunteerContext {
if (event === "validate") {
const validateChangeset = changeset({}, params);
validateChangeset.action = "validate";
console.log("validate", params, validateChangeset);
return {
volunteers: newVolunteers,
changeset: emptyChangeset
volunteers: [],
changeset: validateChangeset
}
}
// form data was invalid
else {
return {
volunteers: [], // no volunteers to prepend
changeset: createChangeset // errors for form
} else {
const volunteer: Partial<Volunteer> = {
name: params.name,
phone: params.phone,
}
// attempt to create the volunteer from the form data
const createChangeset = create_volunteer(volunteer);

// valid form data
if (createChangeset.valid) {
const newVolunteer = createChangeset.data as Volunteer;
// only add new volunteer since we're using phx-update="prepend"
// which means the new volunteer will be added to the top of the list
const newVolunteers = [newVolunteer];
const emptyChangeset = changeset({}, {}); // reset form
return {
volunteers: newVolunteers,
changeset: emptyChangeset
}
}
// form data was invalid
else {
return {
volunteers: [], // no volunteers to prepend
changeset: createChangeset // errors for form
}
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/server/templates/helpers/form_for.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ import html from ".."

interface FormForOptions {
phx_submit?: string
phx_change?: string
method?: "get" | "post"
}

// TODO insert hidden input for CSRF token?
export const form_for = <T>(action: string, options?: FormForOptions) => {
const method = options?.method ?? "post";
const phx_submit = options?.phx_submit ? `phx-submit="${options.phx_submit}"` : "";
const phx_change = options?.phx_change ? `phx-change="${options.phx_change}"` : "";
return html`
<form action="${action}" method="${method}" ${phx_submit}>
<form action="${action}" method="${method}" ${phx_submit} ${phx_change}>
`
}
4 changes: 3 additions & 1 deletion src/server/templates/helpers/inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,19 @@ import html from ".."
interface InputOptions {
placeholder?: string
autocomplete?: "off" | "on"
phx_debounce?: number | "blur" | "focus"
type?: "text" | "tel"
}

export const text_input = <T>(changeset: LiveViewChangeset<T>, key: keyof T, options?: InputOptions) => {
const placeholder = options?.placeholder ? "placeholder=\"" + options.placeholder + "\"" : "";
const autocomplete = options?.autocomplete ? "autocomplete=\"" + options.autocomplete + "\"" : "";
const phx_debounce = options?.phx_debounce ? "phx-debounce=\"" + options.phx_debounce + "\"" : "";
const type = options?.type ?? "text";
const id = `input_${key}`;
const value = changeset.data[key] ?? "";
return html`
<input type="${type}" id="${id}" name="${key}" value="${value}" ${autocomplete} ${placeholder} />
<input type="${type}" id="${id}" name="${key}" value="${value}" ${autocomplete} ${placeholder} ${phx_debounce} />
`
}

Expand Down

0 comments on commit 23cbbdf

Please sign in to comment.