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 { |       button { | ||||||
|           width: 50%; |         width: 50%; | ||||||
|           padding: 10px; |         padding: 10px; | ||||||
|           background-color: #f05a28; |         background-color: #f05a28; | ||||||
|           border: none; |         border: none; | ||||||
|           float: right; |         float: right; | ||||||
|           margin-top: 10px; |         margin-top: 10px; | ||||||
|           margin-left: 10px; |         margin-left: 10px; | ||||||
|           margin-right: 10px; |         margin-right: 10px; | ||||||
|           border-radius: 5px; |         border-radius: 5px; | ||||||
|           color: white; |         color: white; | ||||||
|           font-size: 1em; |         font-size: 1em; | ||||||
|           font-weight: bold; |         font-weight: bold; | ||||||
|           cursor: pointer; |         cursor: pointer; | ||||||
|           transition: background-color 0.3s; |         transition: background-color 0.3s; | ||||||
|           clear: both; |         clear: both; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       button:hover { |       button:hover { | ||||||
|           background-color: #e04924; |         background-color: #e04924; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       a { |       a { | ||||||
|           color: #f05a28; |         color: #f05a28; | ||||||
|           text-decoration: none; |         text-decoration: none; | ||||||
|           display: block; |         display: block; | ||||||
|           margin-top: 15px; |         margin-top: 15px; | ||||||
|           font-size: 0.9em; |         font-size: 0.9em; | ||||||
|           transition: color 0.3s; |         transition: color 0.3s; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       a:hover { |       a:hover { | ||||||
|           color: #e04924; |         color: #e04924; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       .valid { |       .valid { | ||||||
|           border: 1px solid green; |         border: 1px solid green; | ||||||
|           color: green; |         color: green; | ||||||
|           font-size: 0.9em; |         font-size: 0.9em; | ||||||
|           margin-top: 5px; |         margin-top: 5px; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       .error { |       .error { | ||||||
|         border: 3px solid red; |         border: 3px solid red; | ||||||
|           color: #d8000c; |         color: #d8000c; | ||||||
|           font-size: 0.9em; |         font-size: 0.9em; | ||||||
|           margin-top: 5px; |         margin-top: 5px; | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|       @media (max-width: 500px) { |       @media (max-width: 500px) { | ||||||
|           input { |         input { | ||||||
|               width: 90%; |           width: 90%; | ||||||
|           } |         } | ||||||
|       } |       } | ||||||
|     `;
 |     `;
 | ||||||
|     this.container.appendChild(this.styleElement); |     this.container.appendChild(this.styleElement); | ||||||
| @ -165,7 +165,13 @@ class GenericField extends HTMLElement { | |||||||
|     this[name] = value; |     this[name] = value; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   focus(options) { | ||||||
|  |     this.inputElement?.focus(options); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   updateAttributes() { |   updateAttributes() { | ||||||
|  |     const inputUpdate = this.inputElement != null; | ||||||
|  | 
 | ||||||
|     if (this.inputElement == null && this.field) { |     if (this.inputElement == null && this.field) { | ||||||
|       this.inputElement = document.createElement(this.field.tag); |       this.inputElement = document.createElement(this.field.tag); | ||||||
|       if (this.field.tag === 'button' && this.field.value === "submit") { |       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.setAttribute("tabindex", this.field.index); | ||||||
|     this.inputElement.classList.add(this.field.name); |     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; |     let place_holder = this.field.place_holder ?? null; | ||||||
|     if (this.field.required && place_holder) { |     if (this.field.required && place_holder) { | ||||||
| @ -281,6 +289,15 @@ class GenericForm extends HTMLElement { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   connectedCallback() { |   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'); |     const url = this.getAttribute('url'); | ||||||
|     if (url) { |     if (url) { | ||||||
|       const fullUrl = url.startsWith("/") ? window.location.origin + url : new URL(window.location.origin + "/http-get"); |       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 { |     try { | ||||||
|       const response = await fetch(url); |       this.form = formPayload; | ||||||
|       if (!response.ok) { |  | ||||||
|         throw new Error(`Failed to fetch: ${response.status} ${response.statusText}`); |  | ||||||
|       } |  | ||||||
|       this.form = await response.json(); |  | ||||||
| 
 | 
 | ||||||
|       let fields = Object.values(this.form.fields); |       let fields = Object.values(this.form.fields); | ||||||
| 
 | 
 | ||||||
|  |       let hasAutoFocus = Object.keys(this.fields).length !== 0; | ||||||
|  | 
 | ||||||
|       fields.sort((a, b) => a.index - b.index); |       fields.sort((a, b) => a.index - b.index); | ||||||
|       fields.forEach(field => { |       fields.forEach(field => { | ||||||
|         const fieldElement = document.createElement('generic-field'); |         const updatingField = field.name in this.fields | ||||||
|         this.fields[field.name] = fieldElement; | 
 | ||||||
|  |         this.fields[field.name] ??= document.createElement('generic-field'); | ||||||
|  | 
 | ||||||
|  |         const fieldElement = this.fields[field.name]; | ||||||
|  | 
 | ||||||
|         fieldElement.setAttribute("form", this); |         fieldElement.setAttribute("form", this); | ||||||
|         fieldElement.setAttribute("field", field); |         fieldElement.setAttribute("field", field); | ||||||
|         this.container.appendChild(fieldElement); | 
 | ||||||
|         fieldElement.updateAttributes(); |         fieldElement.updateAttributes(); | ||||||
| 
 | 
 | ||||||
|         fieldElement.addEventListener("change", (e) => { |         if (!updatingField) { | ||||||
|           this.form.fields[e.detail.name].value = e.detail.value; |           this.container.appendChild(fieldElement); | ||||||
|         }); |  | ||||||
| 
 | 
 | ||||||
|         fieldElement.addEventListener("click", async (e) => { |           if (!hasAutoFocus && field.tag === "input") { | ||||||
|           if (e.detail.type === "button" && e.detail.value === "submit") { |             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(); |             const isValid = await this.validate(); | ||||||
|             if (isValid) { |             if (isValid) { | ||||||
|               const saveResult = await this.submit(); |               const saveResult = await this.submit(); | ||||||
| @ -325,20 +363,22 @@ class GenericForm extends HTMLElement { | |||||||
|                 window.location.pathname = 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(); |  | ||||||
|             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) { |     } catch (error) { | ||||||
|       this.container.textContent = `Error: ${error.message}`; |       this.container.textContent = `Error: ${error.message}`; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -11,6 +11,6 @@ | |||||||
| {% block main %} | {% block main %} | ||||||
|     <div class="back-form"> |     <div class="back-form"> | ||||||
|         <fancy-button url="/back" text="Back" size="auto"></fancy-button> |         <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> |     </div> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  | |||||||
| @ -12,6 +12,6 @@ | |||||||
|     <div class="back-form"> |     <div class="back-form"> | ||||||
|         <fancy-button url="/back" text="Back" size="auto"></fancy-button> |         <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> |     </div> | ||||||
| {% endblock %} | {% endblock %} | ||||||
| @ -18,7 +18,7 @@ class LoginView(BaseFormView): | |||||||
|             return web.HTTPFound("/web.html") |             return web.HTTPFound("/web.html") | ||||||
|         if self.request.path.endswith(".json"): |         if self.request.path.endswith(".json"): | ||||||
|             return await super().get() |             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): |     async def submit(self, form): | ||||||
|         if await form.is_valid: |         if await form.is_valid: | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ class RegisterView(BaseFormView): | |||||||
|             return web.HTTPFound("/web.html") |             return web.HTTPFound("/web.html") | ||||||
|         if self.request.path.endswith(".json"): |         if self.request.path.endswith(".json"): | ||||||
|             return await super().get() |             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): |     async def submit(self, form): | ||||||
|         result = await self.app.services.user.register( |         result = await self.app.services.user.register( | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user