let smarten_forms_debounce_timeout; function smarten_forms() { if (smarten_forms_debounce_timeout) { clearTimeout(smarten_forms_debounce_timeout); } smarten_forms_debounce_timeout = setTimeout(() => { smarten_forms_debounce_timeout = undefined; const forms = document?.body?.querySelectorAll("form[data-smart]:not([data-smartened])") ?? []; for (const form of forms) { async function on_submit(event) { event.preventDefault(); form.disabled = true; form.__submitted_at = new Date(); if (form.on_submit) { const result = await form.on_submit(event); if (result === false) { form.disabled = false; return; } } const url = eval("`" + (form.attributes.url?.textContent ?? form.action) + "`"); const method = form.dataset.method ?? "POST"; const json = {}; const form_data = new FormData(form); for (const [key, value] of form_data.entries()) { const input = form.querySelector(`[name="${key}"]`); if (input.dataset.skip) { continue; } if (input.type === "file") { if (input.dataset["smartformsSaveToHome"]) { form.uploaded = []; form.errors = []; const user = APP.user; if (!user) { throw new Error("You must be logged in to upload files here."); } for await (const file of input.files) { const body = new FormData(); body.append("file", file, encodeURIComponent(file.name)); const file_path = "/files/users/" + user.id + "/" + encodeURIComponent(file.name); const file_upload_response = await api.fetch(file_path, { method: "PUT", body, }); if (!file_upload_response.ok) { const error = await file_upload_response.json(); form.errors.push(error?.error?.message ?? "Unknown error."); continue; } const file_url = window.location.protocol + "//" + window.location.host + file_path; form.uploaded.push(file_url); } if (form.errors.length) { const errors = form.errors.join("\n\n"); alert(errors); return false; } continue; } } if (key.length === 0) { continue; } const generator = input.getAttribute("generator"); const generated_value = typeof generator === "string" && generator.length ? eval(generator)(input, form) : undefined; const resolved_value = typeof value === "string" && value.length ? value : generated_value; if (typeof resolved_value === "undefined") { const should_submit_empty = input && input.dataset["smartformsSubmitEmpty"]; if (!should_submit_empty) { continue; } } const elements = key.split("."); let current = json; for (const element of elements.slice(0, elements.length - 1)) { current[element] = current[element] ?? {}; current = current[element]; } current[elements.slice(elements.length - 1).shift()] = resolved_value; } if (form.uploaded?.length > 0) { json.data = json.data ?? {}; json.data.media = [...form.uploaded]; } const on_parsed = form.on_parsed ?? (form.getAttribute("on_parsed") ? eval(form.getAttribute("on_parsed")) : undefined); if (on_parsed) { await on_parsed(json); } try { const options = { method, headers: { Accept: "application/json", }, }; if (["POST", "PUT", "PATCH"].includes(method)) { options.json = json; } const response = await api.fetch(url, options); if (!response.ok) { const error_body = await response.json(); const error = error_body?.error; if (form.on_error) { return form.on_error(error); } alert(error.message ?? "Unknown error:\n\n" + error); return; } if (form.on_response) { await form.on_response(response); } const response_body = await response.json(); const on_reply = form.on_reply ?? (form.getAttribute("on_reply") ? eval(form.getAttribute("on_reply")) : undefined); if (on_reply) { try { await on_reply(response_body); } catch (error) { console.trace(error); } } const inputs_for_reset = form.querySelectorAll("[reset-on-submit]"); for (const input of inputs_for_reset) { const reset_value = input.getAttribute("reset-on-submit"); input.value = reset_value ?? ""; } form.querySelector("[focus-on-submit]")?.focus(); } catch (error) { console.dir({ error, }); if (form.on_error) { return form.on_error(error); } alert(error); } finally { form.disabled = false; } } form.querySelectorAll("[enter-key-submits]").forEach((element) => { element.addEventListener("keypress", (event) => { if (event.key === "Enter" && !event.shiftKey) { event.preventDefault(); form.requestSubmit(); } }); }); form.addEventListener("submit", on_submit); form.dataset.smartened = true; } }, 10); } const smarten_forms_observer = new MutationObserver(smarten_forms); smarten_forms_observer.observe(document, { childList: true, subtree: true, });