diff --git a/static/js/widget.js b/static/js/widget.js
index ce27f10..2c7dc0b 100644
--- a/static/js/widget.js
+++ b/static/js/widget.js
@@ -74,27 +74,6 @@ const ComboBoxWidget = (() => {
sorted.forEach(option => selectElement.appendChild(option));
}
- function handleComboAdd(inputId, listId, stateArray, label = 'entry') {
- const input = document.getElementById(inputId);
- const value = input.value.trim();
- if (!value) {
- alert(`Please enter a ${label}.`);
- return;
- }
-
- const select = document.getElementById(listId);
- const exists = Array.from(select.options).some(opt => opt.textContent === value);
- if (exists) {
- alert(`${label.charAt(0).toUpperCase() + label.slice(1)} "${value}" already exists.`);
- return;
- }
-
- const option = createOption(value); // Already built to handle temp IDs
- select.add(option);
- formState[stateArray].push(value);
- input.value = '';
- }
-
function initComboBox(ns, config = {}) {
const input = document.querySelector(`#${ns}-input`);
const list = document.querySelector(`#${ns}-list`);
@@ -168,9 +147,6 @@ const ComboBoxWidget = (() => {
list.appendChild(option);
const key = config.stateArray ?? `${ns}s`; // fallback to pluralization
- if (Array.isArray(formState?.[key])) {
- formState[key].push({ name: newItem });
- }
if (config.sort !== false) {
sortOptions(list);
@@ -203,7 +179,6 @@ const ComboBoxWidget = (() => {
initComboBox,
createOption,
sortOptions,
- handleComboAdd,
createTempId
};
})();
diff --git a/templates/fragments/_combobox_fragment.html b/templates/fragments/_combobox_fragment.html
index f6ce00a..756a999 100644
--- a/templates/fragments/_combobox_fragment.html
+++ b/templates/fragments/_combobox_fragment.html
@@ -1,44 +1,40 @@
{% import "fragments/_icon_fragment.html" as icons %}
-{% macro render_combobox(id, options, label=none, placeholder=none, onAdd=none, onRemove=none, onEdit=none, data_attributes=none) %}
-
+{% macro render_combobox(id, options, label=none, placeholder=none, onAdd=none, onRemove=none, onEdit=none,
+data_attributes=none) %}
+
- {% if label %}
-
- {% endif %}
-
-
-
-
-
-
-
+{% if label %}
+
+{% endif %}
+
-
-{% endmacro %}
+
+{% endmacro %}
\ No newline at end of file
diff --git a/templates/settings.html b/templates/settings.html
index fcc9202..4a350f0 100644
--- a/templates/settings.html
+++ b/templates/settings.html
@@ -140,39 +140,52 @@
{% endblock %}
{% block script %}
- const formState = {
- brands: {{ brands | tojson }},
- types: {{ types | tojson }},
- sections: {{ sections | tojson }},
- functions: {{ functions | tojson }},
- rooms: {{ rooms | tojson }},
- };
+ function isSerializable(obj) {
+ try {
+ JSON.stringify(obj);
+ return true;
+ } catch {
+ return false;
+ }
+ }
function buildFormState() {
function extractOptions(id) {
const select = document.getElementById(`${id}-list`);
- return Array.from(select.options).map(opt => ({
- name: opt.textContent.trim(),
- id: opt.value || undefined
- }));
+ return Array.from(select.options).map(opt => {
+ const name = opt.textContent.trim();
+ const rawId = opt.value?.trim();
+ return {
+ name,
+ ...(rawId ? { id: /^\d+$/.test(rawId) ? parseInt(rawId, 10) : rawId } : {})
+ };
+ });
}
function sanitizeFk(val) {
- return val && val !== "null" && val !== "" ? val : null;
+ if (val && val !== "null" && val !== "") {
+ return /^\d+$/.test(val) ? parseInt(val, 10) : val;
+ }
+ return null;
}
const roomOptions = Array.from(document.getElementById('room-list').options);
+
const rooms = roomOptions.map(opt => {
- const data = opt.dataset;
+ const id = opt.value?.trim();
+ const name = opt.textContent.trim();
+ const sectionId = sanitizeFk(opt.dataset.sectionId);
+ const functionId = sanitizeFk(opt.dataset.functionId);
- return {
- id: opt.value || undefined,
- name: opt.textContent.trim(),
- section_id: sanitizeFk(data.sectionId),
- function_id: sanitizeFk(data.functionId),
+ const result = {
+ name,
+ ...(id ? { id } : {}),
+ ...(sectionId ? { section_id: sectionId } : {}),
+ ...(functionId ? { function_id: functionId } : {})
};
- });
+ return result;
+ });
return {
brands: extractOptions("brand"),
@@ -223,9 +236,15 @@
form.addEventListener('submit', async (event) => {
event.preventDefault();
+ const state = buildFormState();
+
+ if (!isSerializable(state)) {
+ console.warn("🚨 Payload is not serializable:", state);
+ alert("Invalid payload — check console.");
+ return;
+ }
+
try {
- const state = buildFormState();
-
const response = await fetch('/api/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -264,42 +283,26 @@
alert('Please enter a room name.');
return;
}
-
+
const roomList = document.getElementById('room-list');
let existingOption = Array.from(roomList.options).find(opt => opt.value === idRaw);
-
- // If it's a brand new ID, generate one (string-based!)
+
if (!idRaw) {
idRaw = ComboBoxWidget.createTempId("room");
}
-
+
if (!existingOption) {
existingOption = ComboBoxWidget.createOption(name, idRaw);
roomList.appendChild(existingOption);
}
-
+
existingOption.textContent = name;
existingOption.value = idRaw;
existingOption.dataset.sectionId = sectionVal || "";
existingOption.dataset.functionId = funcVal || "";
-
+
ComboBoxWidget.sortOptions(roomList);
-
- // Update formState.rooms
- const index = formState.rooms.findIndex(r => r.id === idRaw);
- const payload = {
- id: idRaw,
- name,
- section_id: sectionVal !== "" ? sectionVal : null,
- function_id: funcVal !== "" ? funcVal : null
- };
-
- if (index >= 0) {
- formState.rooms[index] = payload;
- } else {
- formState.rooms.push(payload);
- }
-
+
bootstrap.Modal.getInstance(modal).hide();
});