declare let ajaxyFormsSettings; class Form { element: HTMLFormElement; submitButton: HTMLButtonElement | null; listeners: { [T: string]: any } = {}; _validate: ((element: HTMLFormElement) => boolean) | undefined; _authenticated = false; errorElms: NodeListOf; messageElm: any; constructor(element: HTMLFormElement, submitButton?: HTMLButtonElement | null, validate?: (element: HTMLFormElement) => boolean, _normalSubmit?: boolean) { if (!element || element.tagName != "FORM") { console.warn("The element you passed to the form is invalid", element); return; } this.element = element; this.errorElms = this.element.querySelectorAll(".field-error"); this.messageElm = this.element.querySelector(".form-message"); this._validate = validate; this.submitButton = submitButton ? submitButton : this.element.querySelector('[type="submit"]'); if (!this.submitButton) { console.warn("The form have no submit button", element); return; } this.element.addEventListener("submit", (e) => { e.preventDefault(); this.element.dispatchEvent && this.element.dispatchEvent(new CustomEvent("beforesubmit", { bubbles: true, cancelable: true, detail: { form: this.element, submitButton: this.submitButton, }, })); this.element.classList.remove("invalid"); if (this.submitButton && this.submitButton.classList.contains("loading")) return; else this.submitButton?.classList.add("loading"); if (!this._validate || this._validate(this.element)) { if (this.listeners["submit"]) { return this.trigger("submit", [ this.element, () => { this.submitButton?.classList.remove("loading"); }, ]); } const data = new FormData(this.element); const headers = this.getHeaders(); // data.append("action", "af_submit"); data.append("form_name", this.element.name); let method = (this.element.method ? this.element.method : "POST").toUpperCase(); let fetchData = { headers: headers, method: method, }; let action = this.element.action; if (method == "GET" || method == "HEAD") { //@ts-ignore action = action + "?" + new URLSearchParams(data).toString(); } else { fetchData["body"] = data; } fetch(action, fetchData) .then((response: any) => { response .json() .then((json) => { this.clearErrors(); this.trigger("response", json); this.submitButton?.classList.remove("loading"); if (json) { if (json.status == "error") { if (json.fields) { this.errorElms = this.element.querySelectorAll(".field-error"); let fields = json.fields; let names = Object.keys(fields); for (let i = 0; i < names.length; i++) { this.addErrors(names[i], fields[names[i]]); } } } else { if (json.status == "success") { this.element.reset(); this.trigger("success", json); } } if (json.message) { this.setMessage(json.message, json.status); } if (json._token) { let token: HTMLInputElement | null = this.element.querySelector('[name*="[_token]"]'); if (token) { token.value = json._token; } } } }) .catch((error) => { this.clearErrors(); this.trigger("error", error); this.submitButton?.classList.remove("loading"); this.setMessage(error.message, "error"); }); }) .catch((e) => { this.trigger("error", e); this.submitButton?.classList.remove("loading"); console.error(e); this.setMessage(e.message, "error"); }); } else { this.submitButton?.classList.remove("loading"); this.element.classList.add("invalid"); } }); this.element.setAttribute("rendered", "1"); } public addListener = (name: string, callback: (response: any) => void) => { this.listeners[name] = callback; }; public trigger(name: string, params?: [HTMLFormElement, () => void] | any): void { if (this.listeners[name]) { this.listeners[name].call(this, params); } } public serialize() { const result: Array = []; Array.prototype.slice.call(this.element.elements).forEach(function (control: HTMLSelectElement | HTMLInputElement) { if (control.name && !control.disabled && ["file", "reset", "submit", "button"].indexOf(control.type) === -1) if (control.type === "select-multiple") Array.prototype.slice.call((control as HTMLSelectElement).options).forEach(function (option: HTMLOptionElement) { if (option.selected) result.push(encodeURIComponent(control.name) + "=" + encodeURIComponent(option.value)); }); else if (["checkbox", "radio"].indexOf(control.type) === -1 || (control as HTMLInputElement).checked) result.push(encodeURIComponent(control.name) + "=" + encodeURIComponent(control.value)); else if (control.value != "") result.push(encodeURIComponent(control.name) + "=" + encodeURIComponent(control.value)); }); return result.join("&").replace(/%20/g, "+"); } public setAuthenticated() { this._authenticated = true; } public getHeaders(): Headers { const headers = new Headers(); headers.set("accept", "application/json, application/xml, text/plain, text/html, *.*"); headers.set("X-WP-Nonce", ajaxyFormsSettings.nonce); headers.set("cache", "no-cache"); headers.set("redirect", "follow"); return headers; } public clearErrors() { if (this.errorElms) { this.errorElms.forEach((elm) => (elm.innerHTML = "")); } if (this.messageElm) { this.messageElm.innerHTML = ""; } } public isObject = (value) => { return ( value != null && // Exclude null typeof value === "object" && !Array.isArray(value) ); // Exclude arrays }; public addErrors(field: string, errors: Array | { [x: string]: Array }) { if (this.errorElms) { if (this.isObject(errors)) { let subnames = Object.keys(errors); for (let i = 0; i < subnames.length; i++) { this.addErrors(subnames[i], errors[subnames[i]]); } return; } else if (typeof errors == "string") { errors = [errors]; } if (Array.isArray(errors)) { this.errorElms.forEach((elm) => { if (elm.classList.contains("field-" + field)) { let ul = document.createElement("ul"); //@ts-ignore errors.forEach((error) => { let li = document.createElement("li"); li.innerHTML = error; ul.appendChild(li); }); elm.innerHTML = ""; elm.appendChild(ul); } }); } } } public setMessage(message: string, type: string = "success") { if (!this.messageElm || !message || message == "") { return; } this.messageElm.classList.remove("success", "error"); this.messageElm.innerHTML = message; this.messageElm.classList.add(type); } } export { Form };