import { elementFromHtml } from "./element-from-html";
import { useBootstrapAlerts } from "./bootstrap-alerts";
type PalmtreeFormOptions = {
url: string;
method: string;
removeSubmitButton: boolean;
controlStates: string[];
};
type Response = {
success: boolean;
data: {
message: string;
errors: Record;
};
};
const defaults: PalmtreeFormOptions = {
url: "",
method: "GET",
removeSubmitButton: true,
controlStates: ["valid", "invalid"],
};
type FormControl = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
document.addEventListener("DOMContentLoaded", () => {
document.querySelectorAll(".palmtree-form.is-ajax").forEach((form) => {
ajax(form);
});
});
type PalmtreeFormInstance = {
clearState: (formControls: FormControl[]) => void;
};
const instances = new Map();
declare global {
interface Window {
palmtreeForm: {
getInstance: (form: HTMLFormElement) => PalmtreeFormInstance | undefined;
};
}
}
window.palmtreeForm = {
getInstance: (form: HTMLFormElement) => instances.get(form),
};
export const ajax = (form: HTMLFormElement, options: Partial = {}) => {
const config = { ...defaults, ...options };
const submitBtn = form.querySelector('button[type="submit"]');
const createAlert = useBootstrapAlerts(form);
const setControlStates = (formControls: FormControl[], errors: Record) => {
formControls.forEach((formControl) => {
const errorKey = formControl.dataset.name;
const formGroup = formControl.closest(".form-group");
if (formGroup && errors && errorKey && typeof errors[errorKey] !== "undefined") {
let feedback = formGroup.querySelector(".palmtree-invalid-feedback");
if (!feedback) {
feedback = elementFromHtml(form.dataset.invalid_element || "");
}
feedback.innerHTML = errors[errorKey];
formGroup.append(feedback);
setState([formControl], "invalid");
const listener = () => {
const state = formControl.value.length ? "" : "invalid";
setState([formControl], state);
};
["input", "change"].forEach((eventName) => {
formControl.removeEventListener(`${eventName}.palmtreeForm`, listener);
formControl.addEventListener(`${eventName}.palmtreeForm`, listener);
});
} else {
clearState([formControl]);
}
});
};
const clearState = (formControls: FormControl[]) => {
setState(formControls, "");
};
const setState = (formControls: FormControl[], state: string) => {
formControls.forEach((formControl) => {
const formGroup = formControl.closest(".form-group");
if (formGroup) {
config.controlStates.forEach((controlState) => {
formControl.classList.remove(`is-${controlState}`);
});
const feedback = formGroup.querySelector(".palmtree-invalid-feedback");
if (!state) {
feedback?.classList.add("d-none");
} else if (config.controlStates.includes(state)) {
formControl.classList.add(`is-${state}`);
feedback?.classList.remove("d-none");
}
}
});
};
form.addEventListener("submit", async (event) => {
event.preventDefault();
form.querySelectorAll(".alert").forEach((alert) => {
alert.remove();
});
form.classList.add("is-submitting");
if (submitBtn) {
submitBtn.disabled = true;
}
const formData = new FormData(form);
const response = await fetch(form.action, {
method: form.method,
body: formData,
headers: {
"X-Requested-With": "XMLHttpRequest",
},
});
const json: Response = await response.json();
const formControls = Array.from(form.querySelectorAll(".palmtree-form-control"));
// Clear all form control states
clearState(formControls);
if (json.success) {
form.reset();
if (json.data.message) {
createAlert(json.data.message);
}
if (config.removeSubmitButton) {
form.querySelectorAll("[type=submit]").forEach((button) => {
button.remove();
});
}
form.dispatchEvent(
new CustomEvent("palmtreeForm.success", {
detail: {
responseData: json.data,
},
}),
);
} else {
const errors = json.data.errors || null;
setControlStates(formControls, errors);
const firstInvalidFormControl = formControls.find((formControl) => formControl.classList.contains("is-invalid"));
firstInvalidFormControl?.focus();
if (json.data.message) {
createAlert(json.data.message, "danger");
}
form.dispatchEvent(
new CustomEvent("palmtreeForm.error", {
detail: {
responseData: json.data,
},
}),
);
}
if (!response.ok) {
console.error(response.statusText);
}
form.classList.remove("is-submitting");
if (submitBtn) {
submitBtn.disabled = false;
}
});
instances.set(form, { clearState });
};