Merge pull request 'Added form preloading, and autofocus on the first input element' (#26) from BordedDev/snek:main into main
Reviewed-on: https://molodetz.nl/retoor/snek/pulls/26 Reviewed-by: retoor <retoor@noreply@molodetz.nl>
This commit is contained in:
		
						commit
						d9ac1813ba
					
				| @ -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}`; | ||||
|     } | ||||
|  | ||||
| @ -11,6 +11,6 @@ | ||||
| {% block main %} | ||||
|     <div class="back-form"> | ||||
|         <fancy-button url="/back" text="Back" size="auto"></fancy-button> | ||||
|         <generic-form class="center" url="/login.json"></generic-form> | ||||
|         <generic-form class="center" url="/login.json" preloaded-structure='{{ form|tojson|safe }}'></generic-form> | ||||
|     </div> | ||||
| {% endblock %} | ||||
|  | ||||
| @ -12,6 +12,6 @@ | ||||
|     <div class="back-form"> | ||||
|         <fancy-button url="/back" text="Back" size="auto"></fancy-button> | ||||
| 
 | ||||
|         <generic-form class="center" url="/register.json"></generic-form> | ||||
|         <generic-form class="center" url="/register.json" preloaded-structure='{{ form|tojson|safe }}'></generic-form> | ||||
|     </div> | ||||
| {% endblock %} | ||||
| @ -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: | ||||
|  | ||||
| @ -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( | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user