mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-09-12 21:58:57 +02:00
refactor: migrate the frontend from AngularJS to Vue 3
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import { reactive } from "vue";
|
||||
|
||||
function validationState() {
|
||||
return reactive({
|
||||
errors: {}, dirty: false, touched: false, submitted: false,
|
||||
get invalid() { return Object.values(this.errors).some(Boolean); },
|
||||
setValidity(key, valid) { this.errors[key] = !valid; },
|
||||
setDirty() { this.dirty = true; },
|
||||
});
|
||||
}
|
||||
function ensureForm(state, name) {
|
||||
if (!state[name]) {
|
||||
const form = validationState();
|
||||
Object.defineProperty(form, "invalid", { get() {
|
||||
return Object.values(form.errors).some(Boolean) || Object.keys(form).some(key => key !== "invalid" && form[key] && typeof form[key] === "object" && form[key].invalid);
|
||||
} });
|
||||
state[name] = form;
|
||||
}
|
||||
return state[name];
|
||||
}
|
||||
export const formDirective = {
|
||||
beforeMount(el, { value: state }) {
|
||||
el._formState = ensureForm(state, el.name);
|
||||
el.addEventListener("submit", event => event.preventDefault());
|
||||
},
|
||||
};
|
||||
function dateInput(value) {
|
||||
if (!(value instanceof Date)) return value || "";
|
||||
if (isNaN(value)) return "";
|
||||
return `${value.getFullYear()}-${String(value.getMonth() + 1).padStart(2, "0")}-${String(value.getDate()).padStart(2, "0")}`;
|
||||
}
|
||||
function sync(el) {
|
||||
const value = el._field.binding.value;
|
||||
if (el.type === "checkbox") el.checked = !!value;
|
||||
else if (el.type === "radio") el.checked = value == (el._value ?? el.value);
|
||||
else if (!el._field.timer) el.value = el.type === "date" ? dateInput(value) : value ?? "";
|
||||
validate(el);
|
||||
}
|
||||
function validate(el) {
|
||||
const field = el._field.validation;
|
||||
if (!field) return;
|
||||
const validity = el.validity;
|
||||
for (const [key, invalid] of Object.entries({ required: validity.valueMissing, pattern: validity.patternMismatch, min: validity.rangeUnderflow, max: validity.rangeOverflow, date: validity.badInput, email: validity.typeMismatch && el.type === "email", url: validity.typeMismatch && el.type === "url" })) {
|
||||
field.setValidity(key, el.disabled || !invalid);
|
||||
}
|
||||
}
|
||||
export const fieldDirective = {
|
||||
beforeMount(el, { value: binding }) {
|
||||
const form = binding.form && ensureForm(binding.state, binding.form);
|
||||
const validation = form && el.name ? (form[el.name] ||= validationState()) : null;
|
||||
el._field = { binding, validation, timer: null };
|
||||
const commit = () => {
|
||||
clearTimeout(el._field.timer); el._field.timer = null;
|
||||
const binding = el._field.binding;
|
||||
let value = el.type === "checkbox" ? el.checked : el._value ?? el.value;
|
||||
if (el.type === "radio" && !el.checked) return;
|
||||
if (el.type === "number" || el.type === "range") value = el.value === "" ? null : Number(el.value);
|
||||
if (el.type === "date") value = el.value ? new Date(el.value + "T00:00:00") : null;
|
||||
if (validation) {
|
||||
validation.setDirty();
|
||||
for (const key of Object.keys(validation.errors)) validation.setValidity(key, true);
|
||||
}
|
||||
binding.set(value);
|
||||
validate(el);
|
||||
binding.change?.();
|
||||
};
|
||||
el._field.commit = commit;
|
||||
const update = event => {
|
||||
const debounce = el._field.binding.options?.debounce || 0;
|
||||
const delay = typeof debounce === "number" ? debounce : debounce[event.type] ?? debounce.default ?? 0;
|
||||
clearTimeout(el._field.timer);
|
||||
if (delay) el._field.timer = setTimeout(commit, delay);
|
||||
else commit();
|
||||
};
|
||||
el.addEventListener(el.tagName === "SELECT" || ["checkbox", "radio", "date"].includes(el.type) ? "change" : "input", update);
|
||||
el.addEventListener("blur", () => { if (validation) validation.touched = true; if (el._field.timer) commit(); });
|
||||
},
|
||||
mounted: sync,
|
||||
updated(el, { value }) { el._field.binding = value; sync(el); },
|
||||
beforeUnmount(el) {
|
||||
clearTimeout(el._field.timer);
|
||||
const { binding, validation } = el._field;
|
||||
const form = binding.form && binding.state[binding.form];
|
||||
if (form && form[el.name] === validation) delete form[el.name];
|
||||
},
|
||||
};
|
||||
export function submitForm(event, callback) {
|
||||
const form = event.target.closest("form");
|
||||
if (form?._formState) form._formState.submitted = true;
|
||||
form?.querySelectorAll("input, select, textarea").forEach(el => {
|
||||
if (el._field?.timer) el._field.commit();
|
||||
if (el._field) validate(el);
|
||||
});
|
||||
if (form && !form.checkValidity()) return;
|
||||
return callback();
|
||||
}
|
||||
Reference in New Issue
Block a user