From 7a422fb2723dc482baed84f3dca4a06d98ef716b Mon Sep 17 00:00:00 2001 From: Matej Date: Tue, 26 Nov 2024 12:05:04 +0100 Subject: [PATCH] Add the script that can render dynamic form #12 --- WebApp/dynamic_form.html | 2 +- WebApp/scripts/dynamic_form_render.js | 191 ++++++++++++++++++++++++++ 2 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 WebApp/scripts/dynamic_form_render.js diff --git a/WebApp/dynamic_form.html b/WebApp/dynamic_form.html index b70d03d..3997e64 100644 --- a/WebApp/dynamic_form.html +++ b/WebApp/dynamic_form.html @@ -11,6 +11,6 @@

Dynamic JSON Query Form

- + diff --git a/WebApp/scripts/dynamic_form_render.js b/WebApp/scripts/dynamic_form_render.js new file mode 100644 index 0000000..f0ee068 --- /dev/null +++ b/WebApp/scripts/dynamic_form_render.js @@ -0,0 +1,191 @@ +function renderForm(schema) { + // Render the form based on JSON structure description. + // Basically, the form sections are divided into 3 categories: + // + // SIMPLE + // -> The field / section which contains only input fields of + // some sort and do not contain any subfileds (i.e Goal section of the step) + // + // COMPLEX + // -> The field / section which can contain subfields or subsections + // of some sort, i.e Aggregation (has subsections group by and order by) + // + // REPEATABLE + // -> Both SIMPLE and COMPLEX sections / fields can be repeatable, if + // it makes sense to have multiple instance of them rendered (step, group-by columns etc.) + // + // This fucntion reads the JSON structure specification and renders individual fields + // based on it. + // + // Calls special functions for generating each of the above described types of + // sections. + const form = document.getElementById("dynamicForm"); + + schema.form.sections.forEach((section) => { + const sectionDiv = document.createElement("div"); + sectionDiv.classList.add("form-section"); + sectionDiv.id = section.id; + + const sectionTitle = document.createElement("h2"); + sectionTitle.textContent = section.title; + sectionDiv.appendChild(sectionTitle); + + switch (section.type) { + case "REPEATABLE": + renderRepeatableField(sectionDiv, section); + break; + case "SIMPLE": + renderSimpleField(sectionDiv, section); + break; + case "COMPLEX": + renderComplexField(sectionDiv, section); + break; + } + + form.appendChild(sectionDiv); + }); +} + +function renderSimpleField(container, field) { + // Function for rendering SIMPLE field / section. + const fieldDiv = document.createElement("div"); + fieldDiv.classList.add(field.id) + const label = document.createElement("label"); + label.textContent = field.label; + label.setAttribute("for", field.id); + + const input = document.createElement(field.inputType === "select" ? "select" : "input"); + if (field.inputType === "select") { + field.choices.forEach((choice) => { + const option = document.createElement("option"); + option.value = choice; + option.textContent = choice; + input.appendChild(option); + }); + } else { + input.type = field.inputType; + input.placeholder = field.placeholder || ""; + if (field.readonly) { + input.setAttribute("readonly", true); + } + } + input.id = field.id; + + fieldDiv.appendChild(label); + fieldDiv.appendChild(input); + container.appendChild(fieldDiv); +} + +function renderRepeatableField(container, field) { + // Function for rendering REPEATABLE field / section. + const fieldDiv = document.createElement("div"); + fieldDiv.classList.add(field.id); + const label = document.createElement("h3"); + label.textContent = field.label; + fieldDiv.appendChild(label); + + const addButton = document.createElement("button"); + addButton.textContent = `Add ${field.label}`; + addButton.type = "button"; + + const repeatableContainer = document.createElement("div"); + repeatableContainer.classList.add("repeatable-container"); + + addButton.onclick = () => { + const rowDiv = document.createElement("div"); + rowDiv.classList.add("repeatable-row"); + + if (field.fields) { + // If it's a REPEATABLE section with nested fields + field.fields.forEach((nestedField) => { + switch (nestedField.type) { + case "SIMPLE": + renderSimpleField(rowDiv, nestedField); + break; + case "REPEATABLE": + renderRepeatableField(rowDiv, nestedField); + break; + case "COMPLEX": + renderComplexField(rowDiv, nestedField); + break; + } + }); + } else if (field.columns) { + // Render REPEATABLE rows with multiple columns + field.columns.forEach((column) => { + const input = document.createElement(column.inputType === "select" ? "select" : "input"); + if (column.inputType === "select") { + column.choices.forEach((choice) => { + const option = document.createElement("option"); + option.value = choice; + option.textContent = choice; + input.appendChild(option); + }); + } else { + input.type = column.inputType; + input.placeholder = column.label; + } + rowDiv.appendChild(input); + }); + } else { + // Render a simple REPEATABLE field + const input = document.createElement("input"); + input.type = field.inputType; + input.placeholder = field.placeholder || ""; + rowDiv.appendChild(input); + } + + const removeButton = document.createElement("button"); + removeButton.textContent = "-"; + removeButton.type = "button"; + removeButton.onclick = () => rowDiv.remove(); + + rowDiv.appendChild(removeButton); + repeatableContainer.appendChild(rowDiv); + }; + + // fieldDiv.appendChild(label); + fieldDiv.appendChild(repeatableContainer); + fieldDiv.appendChild(addButton); + container.appendChild(fieldDiv); +} + +function renderComplexField(container, field) { + // Function for rendering COMPLEX fields / sections + const complexDiv = document.createElement("div"); + complexDiv.classList.add(field.id) + const label = document.createElement("h3"); + label.textContent = field.label; + complexDiv.appendChild(label); + + field.subfields.forEach((subfield) => { + switch (subfield.type) { + case "SIMPLE": + renderSimpleField(complexDiv, subfield); + break; + case "REPEATABLE": + renderRepeatableField(complexDiv, subfield); + break; + } + }); + + + container.appendChild(complexDiv); +} + +document.addEventListener("DOMContentLoaded", () => { + fetch("form_structure.json") + .then((response) => { + if (!response.ok) { + throw new Error(`Failed to load form schema: ${response.status}`); + } + return response.json(); + }) + .then((schema) => { + renderForm(schema); + }) + .catch((error) => { + console.error("Error rendering form:", error); + }); +}); +