refactor(autosubmit): split and improuve autosubmit and turbo controller

This commit is contained in:
Paul Chavard 2023-01-11 21:45:41 +01:00
parent 247bd6128e
commit 88866d0413
4 changed files with 106 additions and 23 deletions

View file

@ -4,7 +4,7 @@ import debounce from 'debounce';
export type Detail = Record<string, unknown>;
export class ApplicationController extends Controller {
#debounced = new Map<() => void, () => void>();
#debounced = new Map<() => void, ReturnType<typeof debounce>>();
protected debounce(fn: () => void, interval: number): void {
this.globalDispatch('debounced:added');
@ -26,6 +26,10 @@ export class ApplicationController extends Controller {
debounced();
}
protected cancelDebounce(fn: () => void) {
this.#debounced.get(fn)?.clear();
}
protected globalDispatch<T = Detail>(type: string, detail?: T): void {
this.dispatch(type, {
detail: detail as object,

View file

@ -1,32 +1,79 @@
import {
isSelectElement,
isCheckboxOrRadioInputElement,
isTextInputElement,
isDateInputElement
} from '@utils';
import { ApplicationController } from './application_controller';
import { show, hide } from '@utils';
const AUTOSUBMIT_DEBOUNCE_DELAY = 5000;
const AUTOSUBMIT_DEBOUNCE_DELAY = 500;
const AUTOSUBMIT_DATE_DEBOUNCE_DELAY = 5000;
export class AutosubmitController extends ApplicationController {
static targets = ['form', 'spinner'];
static targets = ['submitter'];
declare readonly formTarget: HTMLFormElement;
declare readonly spinnerTarget: HTMLElement;
declare readonly hasSpinnerTarget: boolean;
declare readonly submitterTarget: HTMLButtonElement | HTMLInputElement;
declare readonly hasSubmitterTarget: boolean;
submit() {
this.formTarget.requestSubmit();
}
debouncedSubmit() {
this.debounce(this.submit, AUTOSUBMIT_DEBOUNCE_DELAY);
}
#dateTimeChangedInputs = new WeakSet<HTMLInputElement>();
connect() {
this.onGlobal('turbo:submit-start', () => {
if (this.hasSpinnerTarget) {
show(this.spinnerTarget);
this.on('input', (event) => this.onInput(event));
this.on('change', (event) => this.onChange(event));
this.on('blur', (event) => this.onBlur(event));
}
private onChange(event: Event) {
const target = event.target as HTMLInputElement;
if (target.disabled || target.hasAttribute('data-no-autosubmit')) return;
if (
isSelectElement(target) ||
isCheckboxOrRadioInputElement(target) ||
isTextInputElement(target)
) {
if (isDateInputElement(target)) {
if (target.value.trim() == '' || !isNaN(Date.parse(target.value))) {
this.#dateTimeChangedInputs.add(target);
this.debounce(this.submit, AUTOSUBMIT_DATE_DEBOUNCE_DELAY);
} else {
this.#dateTimeChangedInputs.delete(target);
this.cancelDebounce(this.submit);
}
} else {
this.cancelDebounce(this.submit);
this.submit();
}
});
this.onGlobal('turbo:submit-end', () => {
if (this.hasSpinnerTarget) {
hide(this.spinnerTarget);
}
});
}
}
private onInput(event: Event) {
const target = event.target as HTMLInputElement;
if (target.disabled || target.hasAttribute('data-no-autosubmit')) return;
if (!isDateInputElement(target) && isTextInputElement(target)) {
this.debounce(this.submit, AUTOSUBMIT_DEBOUNCE_DELAY);
}
}
private onBlur(event: Event) {
const target = event.target as HTMLInputElement;
if (target.disabled || target.hasAttribute('data-no-autosubmit')) return;
if (isDateInputElement(target)) {
Promise.resolve().then(() => {
if (this.#dateTimeChangedInputs.has(target)) {
this.cancelDebounce(this.submit);
this.submit();
}
});
}
}
private submit() {
const submitter = this.hasSubmitterTarget ? this.submitterTarget : null;
const form =
submitter?.form ?? this.element.closest<HTMLFormElement>('form');
form?.requestSubmit(submitter);
}
}

View file

@ -0,0 +1,23 @@
import { show, hide } from '@utils';
import { ApplicationController } from './application_controller';
export class TurboController extends ApplicationController {
static targets = ['spinner'];
declare readonly spinnerTarget: HTMLElement;
declare readonly hasSpinnerTarget: boolean;
connect() {
this.onGlobal('turbo:submit-start', () => {
if (this.hasSpinnerTarget) {
show(this.spinnerTarget);
}
});
this.onGlobal('turbo:submit-end', () => {
if (this.hasSpinnerTarget) {
hide(this.spinnerTarget);
}
});
}
}

View file

@ -290,6 +290,15 @@ export function isCheckboxOrRadioInputElement(
);
}
export function isDateInputElement(
element: HTMLElement & { type?: string }
): element is HTMLInputElement {
return (
element.tagName == 'INPUT' &&
(element.type == 'date' || element.type == 'datetime-local')
);
}
export function isTextInputElement(
element: HTMLElement & { type?: string }
): element is HTMLInputElement {