Skip to content

Instantly share code, notes, and snippets.

@godismyjudge95
Created July 30, 2023 18:46
Show Gist options
  • Save godismyjudge95/b56d7ae7dbd09f4516c7bbc5b954c747 to your computer and use it in GitHub Desktop.
Save godismyjudge95/b56d7ae7dbd09f4516c7bbc5b954c747 to your computer and use it in GitHub Desktop.
A custom wrapper for Hybridly's useForm helper that enables autosubmitting and autosaving (via LocalStorage)
import { SearchableObject, Path } from '@clickbar/dot-diver';
import { UnwrapRef } from 'vue';
export const defaultAutosaveDebounce = 750;
export const defaultAutosubmitDebounce = 750;
type FormOptions<
T extends SearchableObject,
P extends Path<T> & string = Path<T> & string,
> = Parameters<typeof useForm<T, P>>[0];
export interface CustomFormOptions<
T extends SearchableObject,
P extends Path<T> & string = Path<T> & string,
> extends FormOptions<T, P> {
autosave?: {
key: string;
debounce?: number;
only?: (keyof T)[];
except?: (keyof T)[];
};
autosubmit?: number | boolean;
defaults?: T;
}
function filterObject<T>(
obj: T | UnwrapRef<T> | Partial<T> | undefined | null,
callback: (key: keyof Partial<T>, value: unknown, index: number) => boolean,
): Partial<T> {
const filteredObj: Partial<T> = {};
let index = 0;
if (obj) {
let key: keyof Partial<T>;
for (key in obj) {
if (callback(key, (obj as Partial<T>)[key] as unknown, index)) {
filteredObj[key] = (obj as Partial<T>)[key];
}
index++;
}
}
return filteredObj;
}
function filterAutosaveFields<
T extends SearchableObject,
P extends Path<T> & string = Path<T> & string,
>(options: CustomFormOptions<T, P>, fields: UnwrapRef<T> | Partial<T>) {
return filterObject(
fields,
key =>
(!options.autosave?.except && !options.autosave?.only) ||
((options.autosave?.except?.indexOf(key) ?? -1) === -1 &&
(options.autosave?.only?.indexOf(key) ?? -1) !== -1),
);
}
export function useCustomForm<
T extends SearchableObject,
P extends Path<T> & string = Path<T> & string,
>(options: CustomFormOptions<T, P>) {
if (options.defaults) {
const oldTransform = options.transform;
options.transform = data =>
filterParams<T>(oldTransform ? oldTransform(data) : data, options.defaults);
}
const form = useForm<T, P>(options);
if (options.defaults) {
form.setInitial(options.defaults);
}
if (options.autosave) {
const formStorage = useStorage(
options.autosave.key,
filterAutosaveFields<T, P>(options, options.defaults ?? ({} as Partial<T>)),
);
if (formStorage.value !== undefined) {
for (const key in form.fields) {
if (key in formStorage.value) {
form.fields[key] = (formStorage.value as UnwrapRef<T>)[key];
}
}
}
debouncedWatch(
() => form.fields,
() => (formStorage.value = filterAutosaveFields<T, P>(options, form.fields)),
{
deep: true,
debounce: options.autosave?.debounce ?? defaultAutosaveDebounce,
},
);
}
if (options.autosubmit) {
debouncedWatch(
() => form.fields,
() => (options.autosubmit ? form.submit() : null),
{
deep: true,
debounce:
typeof options.autosubmit === 'number'
? options.autosubmit
: defaultAutosubmitDebounce,
},
);
}
return form;
}
@godismyjudge95
Copy link
Author

Can be used like so:

const form = useCustomForm<App.Data.SearchFieldsData>({
    url: route('search'),
    method: 'GET',
    preserveState: true,
    only: ['results', 'time'],
    reset: false,
    fields: props.fields,
    defaults: props.defaults,
    autosubmit: true,
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment