diff --git a/frontend/resources/vue/components/ChoiceCombobox.vue b/frontend/resources/vue/components/ChoiceCombobox.vue index 35e1893..3c11692 100644 --- a/frontend/resources/vue/components/ChoiceCombobox.vue +++ b/frontend/resources/vue/components/ChoiceCombobox.vue @@ -152,8 +152,11 @@ function selectedChoiceIndex(choices) { function openList() { document.dispatchEvent(new CustomEvent(closeOthersEvent, { detail: { id: props.id } })) + const wasClosed = !isOpen.value isOpen.value = true - highlightedIndex.value = selectedChoiceIndex(filteredChoices.value) + if (wasClosed) { + highlightedIndex.value = selectedChoiceIndex(filteredChoices.value) + } } function closeList() { diff --git a/frontend/resources/vue/components/EntityDefinitionSection.vue b/frontend/resources/vue/components/EntityDefinitionSection.vue index 29e06ba..b6defea 100644 --- a/frontend/resources/vue/components/EntityDefinitionSection.vue +++ b/frontend/resources/vue/components/EntityDefinitionSection.vue @@ -72,6 +72,7 @@ const pageSize = ref(10) const tableError = ref('') let fetchTimer = null + let fetchSequence = 0 const hasTable = computed(() => (props.definition.properties?.length ?? 0) > 0) @@ -120,6 +121,7 @@ return } + const requestId = ++fetchSequence tableError.value = '' try { const response = await window.client.getEntities({ @@ -129,10 +131,18 @@ pageSize: pageSize.value }) + if (requestId !== fetchSequence) { + return + } + const definition = response.entityDefinitions?.find(def => def.title === props.definition.title) tableInstances.value = definition?.instances ?? [] totalInstances.value = definition?.totalInstances ?? 0 } catch (err) { + if (requestId !== fetchSequence) { + return + } + console.error('Failed to fetch entity instances:', err) tableError.value = 'Failed to load entity instances.' tableInstances.value = [] diff --git a/frontend/resources/vue/utils/choiceChecklistHelpers.js b/frontend/resources/vue/utils/choiceChecklistHelpers.js index 1737d33..8c48f10 100644 --- a/frontend/resources/vue/utils/choiceChecklistHelpers.js +++ b/frontend/resources/vue/utils/choiceChecklistHelpers.js @@ -1,9 +1,27 @@ +function parseLegacyChecklistValue(value) { + return value.split(',').map((segment) => segment.trim()).filter((segment) => segment !== '') +} + export function parseChecklistValue(value) { if (!value || value === '') { return [] } - return value.split(',').map((segment) => segment.trim()).filter((segment) => segment !== '') + const trimmed = value.trim() + if (trimmed.startsWith('[')) { + try { + const parsed = JSON.parse(trimmed) + if (!Array.isArray(parsed)) { + return [] + } + + return parsed.map((segment) => String(segment).trim()).filter((segment) => segment !== '') + } catch { + return [] + } + } + + return parseLegacyChecklistValue(value) } export function formatChecklistValue(selected) { @@ -11,7 +29,7 @@ export function formatChecklistValue(selected) { return '' } - return selected.join(',') + return JSON.stringify(selected) } export function toggleChoice(selected, value) { diff --git a/frontend/resources/vue/utils/choiceChecklistHelpers.test.mjs b/frontend/resources/vue/utils/choiceChecklistHelpers.test.mjs index 3ccb7d0..bee56b1 100644 --- a/frontend/resources/vue/utils/choiceChecklistHelpers.test.mjs +++ b/frontend/resources/vue/utils/choiceChecklistHelpers.test.mjs @@ -13,14 +13,20 @@ const choices = [ { title: 'Photos', value: 'photos' } ] -test('parseChecklistValue splits comma-delimited values', () => { - assert.deepEqual(parseChecklistValue('documents,photos'), ['documents', 'photos']) - assert.deepEqual(parseChecklistValue('documents, photos'), ['documents', 'photos']) + test('parseChecklistValue parses JSON-encoded values', () => { + assert.deepEqual(parseChecklistValue('["documents","photos"]'), ['documents', 'photos']) + assert.deepEqual(parseChecklistValue('["kitchen,bedroom","hallway"]'), ['kitchen,bedroom', 'hallway']) assert.deepEqual(parseChecklistValue(''), []) }) -test('formatChecklistValue joins selected values', () => { - assert.equal(formatChecklistValue(['documents', 'photos']), 'documents,photos') +test('parseChecklistValue accepts legacy comma-delimited values', () => { + assert.deepEqual(parseChecklistValue('documents,photos'), ['documents', 'photos']) + assert.deepEqual(parseChecklistValue('documents, photos'), ['documents', 'photos']) +}) + +test('formatChecklistValue joins selected values as JSON', () => { + assert.equal(formatChecklistValue(['documents', 'photos']), '["documents","photos"]') + assert.equal(formatChecklistValue(['kitchen,bedroom']), '["kitchen,bedroom"]') assert.equal(formatChecklistValue([]), '') }) diff --git a/frontend/resources/vue/utils/prefilledArguments.js b/frontend/resources/vue/utils/prefilledArguments.js index ee8c7e6..78a2554 100644 --- a/frontend/resources/vue/utils/prefilledArguments.js +++ b/frontend/resources/vue/utils/prefilledArguments.js @@ -7,11 +7,15 @@ export function readPrefilledArgumentsFromNavigation() { return {} } -export function getInitialArgumentValue(paramName, prefilledArguments) { - if (Object.prototype.hasOwnProperty.call(prefilledArguments, paramName)) { - return prefilledArguments[paramName] +export function getInitialArgumentValue(paramName, prefilledArguments = {}) { + const safePrefilledArguments = prefilledArguments && typeof prefilledArguments === 'object' + ? prefilledArguments + : {} + + if (Object.prototype.hasOwnProperty.call(safePrefilledArguments, paramName)) { + return safePrefilledArguments[paramName] } - const params = new URLSearchParams(window.location.search.substring(1)) + const params = new URLSearchParams(window.location.search) return params.get(paramName) } diff --git a/frontend/resources/vue/views/ArgumentForm.vue b/frontend/resources/vue/views/ArgumentForm.vue index a2dd095..e7a92ad 100644 --- a/frontend/resources/vue/views/ArgumentForm.vue +++ b/frontend/resources/vue/views/ArgumentForm.vue @@ -19,9 +19,12 @@