refactor(autosubmit): split and improuve autosubmit and turbo controller
This commit is contained in:
parent
247bd6128e
commit
88866d0413
4 changed files with 106 additions and 23 deletions
|
@ -4,7 +4,7 @@ import debounce from 'debounce';
|
||||||
export type Detail = Record<string, unknown>;
|
export type Detail = Record<string, unknown>;
|
||||||
|
|
||||||
export class ApplicationController extends Controller {
|
export class ApplicationController extends Controller {
|
||||||
#debounced = new Map<() => void, () => void>();
|
#debounced = new Map<() => void, ReturnType<typeof debounce>>();
|
||||||
|
|
||||||
protected debounce(fn: () => void, interval: number): void {
|
protected debounce(fn: () => void, interval: number): void {
|
||||||
this.globalDispatch('debounced:added');
|
this.globalDispatch('debounced:added');
|
||||||
|
@ -26,6 +26,10 @@ export class ApplicationController extends Controller {
|
||||||
debounced();
|
debounced();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected cancelDebounce(fn: () => void) {
|
||||||
|
this.#debounced.get(fn)?.clear();
|
||||||
|
}
|
||||||
|
|
||||||
protected globalDispatch<T = Detail>(type: string, detail?: T): void {
|
protected globalDispatch<T = Detail>(type: string, detail?: T): void {
|
||||||
this.dispatch(type, {
|
this.dispatch(type, {
|
||||||
detail: detail as object,
|
detail: detail as object,
|
||||||
|
|
|
@ -1,32 +1,79 @@
|
||||||
|
import {
|
||||||
|
isSelectElement,
|
||||||
|
isCheckboxOrRadioInputElement,
|
||||||
|
isTextInputElement,
|
||||||
|
isDateInputElement
|
||||||
|
} from '@utils';
|
||||||
import { ApplicationController } from './application_controller';
|
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 {
|
export class AutosubmitController extends ApplicationController {
|
||||||
static targets = ['form', 'spinner'];
|
static targets = ['submitter'];
|
||||||
|
|
||||||
declare readonly formTarget: HTMLFormElement;
|
declare readonly submitterTarget: HTMLButtonElement | HTMLInputElement;
|
||||||
declare readonly spinnerTarget: HTMLElement;
|
declare readonly hasSubmitterTarget: boolean;
|
||||||
declare readonly hasSpinnerTarget: boolean;
|
|
||||||
|
|
||||||
submit() {
|
#dateTimeChangedInputs = new WeakSet<HTMLInputElement>();
|
||||||
this.formTarget.requestSubmit();
|
|
||||||
}
|
|
||||||
|
|
||||||
debouncedSubmit() {
|
|
||||||
this.debounce(this.submit, AUTOSUBMIT_DEBOUNCE_DELAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
connect() {
|
connect() {
|
||||||
this.onGlobal('turbo:submit-start', () => {
|
this.on('input', (event) => this.onInput(event));
|
||||||
if (this.hasSpinnerTarget) {
|
this.on('change', (event) => this.onChange(event));
|
||||||
show(this.spinnerTarget);
|
this.on('blur', (event) => this.onBlur(event));
|
||||||
}
|
}
|
||||||
});
|
|
||||||
this.onGlobal('turbo:submit-end', () => {
|
private onChange(event: Event) {
|
||||||
if (this.hasSpinnerTarget) {
|
const target = event.target as HTMLInputElement;
|
||||||
hide(this.spinnerTarget);
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
23
app/javascript/controllers/turbo_controller.ts
Normal file
23
app/javascript/controllers/turbo_controller.ts
Normal 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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(
|
export function isTextInputElement(
|
||||||
element: HTMLElement & { type?: string }
|
element: HTMLElement & { type?: string }
|
||||||
): element is HTMLInputElement {
|
): element is HTMLInputElement {
|
||||||
|
|
Loading…
Add table
Reference in a new issue