diff --git a/src/snek/static/generic-form.js b/src/snek/static/generic-form.js index 11647dc..319d7d3 100644 --- a/src/snek/static/generic-form.js +++ b/src/snek/static/generic-form.js @@ -98,58 +98,58 @@ class GenericField extends HTMLElement { } button { - width: 50%; - padding: 10px; - background-color: #f05a28; - border: none; - float: right; - margin-top: 10px; - margin-left: 10px; - margin-right: 10px; - border-radius: 5px; - color: white; - font-size: 1em; - font-weight: bold; - cursor: pointer; - transition: background-color 0.3s; - clear: both; + width: 50%; + padding: 10px; + background-color: #f05a28; + border: none; + float: right; + margin-top: 10px; + margin-left: 10px; + margin-right: 10px; + border-radius: 5px; + color: white; + font-size: 1em; + font-weight: bold; + cursor: pointer; + transition: background-color 0.3s; + clear: both; } button:hover { - background-color: #e04924; + background-color: #e04924; } a { - color: #f05a28; - text-decoration: none; - display: block; - margin-top: 15px; - font-size: 0.9em; - transition: color 0.3s; + color: #f05a28; + text-decoration: none; + display: block; + margin-top: 15px; + font-size: 0.9em; + transition: color 0.3s; } a:hover { - color: #e04924; + color: #e04924; } .valid { - border: 1px solid green; - color: green; - font-size: 0.9em; - margin-top: 5px; + border: 1px solid green; + color: green; + font-size: 0.9em; + margin-top: 5px; } .error { border: 3px solid red; - color: #d8000c; - font-size: 0.9em; - margin-top: 5px; + color: #d8000c; + font-size: 0.9em; + margin-top: 5px; } @media (max-width: 500px) { - input { - width: 90%; - } + input { + width: 90%; + } } `; this.container.appendChild(this.styleElement); @@ -165,7 +165,13 @@ class GenericField extends HTMLElement { this[name] = value; } + focus(options) { + this.inputElement?.focus(options); + } + updateAttributes() { + const inputUpdate = this.inputElement != null; + if (this.inputElement == null && this.field) { this.inputElement = document.createElement(this.field.tag); if (this.field.tag === 'button' && this.field.value === "submit") { @@ -218,7 +224,9 @@ class GenericField extends HTMLElement { } this.inputElement.setAttribute("tabindex", this.field.index); this.inputElement.classList.add(this.field.name); - this.value = this.field.value; + if (this.field.value != null || !inputUpdate) { + this.value = this.field.value; + } let place_holder = this.field.place_holder ?? null; if (this.field.required && place_holder) { @@ -281,6 +289,15 @@ class GenericForm extends HTMLElement { } connectedCallback() { + const preloadedForm = this.getAttribute('preloaded-structure'); + if (preloadedForm) { + try { + const form = JSON.parse(preloadedForm); + this.constructForm(form) + } catch (error) { + console.error(error, preloadedForm); + } + } const url = this.getAttribute('url'); if (url) { const fullUrl = url.startsWith("/") ? window.location.origin + url : new URL(window.location.origin + "/http-get"); @@ -293,31 +310,52 @@ class GenericForm extends HTMLElement { } } - async loadForm(url) { + async constructForm(formPayload) { try { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`); - } - this.form = await response.json(); + this.form = formPayload; let fields = Object.values(this.form.fields); + let hasAutoFocus = Object.keys(this.fields).length !== 0; + fields.sort((a, b) => a.index - b.index); fields.forEach(field => { - const fieldElement = document.createElement('generic-field'); - this.fields[field.name] = fieldElement; + const updatingField = field.name in this.fields + + this.fields[field.name] ??= document.createElement('generic-field'); + + const fieldElement = this.fields[field.name]; + fieldElement.setAttribute("form", this); fieldElement.setAttribute("field", field); - this.container.appendChild(fieldElement); + fieldElement.updateAttributes(); - fieldElement.addEventListener("change", (e) => { - this.form.fields[e.detail.name].value = e.detail.value; - }); + if (!updatingField) { + this.container.appendChild(fieldElement); - fieldElement.addEventListener("click", async (e) => { - if (e.detail.type === "button" && e.detail.value === "submit") { + if (!hasAutoFocus && field.tag === "input") { + fieldElement.focus(); + hasAutoFocus = true; + } + + fieldElement.addEventListener("change", (e) => { + this.form.fields[e.detail.name].value = e.detail.value; + }); + + fieldElement.addEventListener("click", async (e) => { + if (e.detail.type === "button" && e.detail.value === "submit") { + const isValid = await this.validate(); + if (isValid) { + const saveResult = await this.submit(); + if (saveResult.redirect_url) { + window.location.pathname = saveResult.redirect_url; + } + } + } + }); + + fieldElement.addEventListener("submit", async (e) => { const isValid = await this.validate(); if (isValid) { const saveResult = await this.submit(); @@ -325,20 +363,22 @@ class GenericForm extends HTMLElement { window.location.pathname = saveResult.redirect_url; } } - } - }); - - fieldElement.addEventListener("submit", async (e) => { - const isValid = await this.validate(); - if (isValid) { - const saveResult = await this.submit(); - if (saveResult.redirect_url) { - window.location.pathname = saveResult.redirect_url; - } - } - }) + }) + } }); + } catch (error) { + this.container.textContent = `Error: ${error.message}`; + } + } + async loadForm(url) { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`); + } + + await this.constructForm(await response.json()); } catch (error) { this.container.textContent = `Error: ${error.message}`; } diff --git a/src/snek/templates/login.html b/src/snek/templates/login.html index ed81224..d91920c 100644 --- a/src/snek/templates/login.html +++ b/src/snek/templates/login.html @@ -11,6 +11,6 @@ {% block main %}
- +
{% endblock %} diff --git a/src/snek/templates/register.html b/src/snek/templates/register.html index 2fa89d3..21f8fe0 100644 --- a/src/snek/templates/register.html +++ b/src/snek/templates/register.html @@ -12,6 +12,6 @@
- +
{% endblock %} \ No newline at end of file diff --git a/src/snek/view/login.py b/src/snek/view/login.py index 580655f..6d6d6ad 100644 --- a/src/snek/view/login.py +++ b/src/snek/view/login.py @@ -18,7 +18,7 @@ class LoginView(BaseFormView): return web.HTTPFound("/web.html") if self.request.path.endswith(".json"): return await super().get() - return await self.render_template("login.html") + return await self.render_template("login.html", {"form": await self.form(app=self.app).to_json()}) async def submit(self, form): if await form.is_valid: diff --git a/src/snek/view/register.py b/src/snek/view/register.py index fdcc9ad..db812b5 100644 --- a/src/snek/view/register.py +++ b/src/snek/view/register.py @@ -18,7 +18,7 @@ class RegisterView(BaseFormView): return web.HTTPFound("/web.html") if self.request.path.endswith(".json"): return await super().get() - return await self.render_template("register.html") + return await self.render_template("register.html", {"form": await self.form(app=self.app).to_json()}) async def submit(self, form): result = await self.app.services.user.register(