diff --git a/src/components/Questions/QuestionMultiple.vue b/src/components/Questions/QuestionMultiple.vue
index 69b64bc67..b6824727d 100644
--- a/src/components/Questions/QuestionMultiple.vue
+++ b/src/components/Questions/QuestionMultiple.vue
@@ -52,7 +52,7 @@
{{ t('forms', 'Other:') }}
-
+
@@ -97,9 +98,9 @@
item.startsWith(this.QUESTION_EXTRASETTINGS_OTHER_PREFIX))
- return checkedOtherAnswer[0] !== undefined
- },
- },
-
- watch: {
- inputOtherAnswer() {
- if (this.isUnique) {
- this.onChange(this.valueOtherAnswer)
- return
- }
-
- const values = this.values.filter(item => !item.startsWith(this.QUESTION_EXTRASETTINGS_OTHER_PREFIX))
- if (this.inputOtherAnswer !== '') {
- values.push(this.valueOtherAnswer)
+ /**
+ * Text of the "other" answer field
+ */
+ otherAnswerText() {
+ if (this.otherAnswer) {
+ return this.otherAnswer.slice(QUESTION_EXTRASETTINGS_OTHER_PREFIX.length)
}
+ return ''
+ },
- this.onChange(values)
+ /**
+ * The full "other" answer including prefix, undefined if no "other answer"
+ */
+ otherAnswer() {
+ return this.values.find((v) => v.startsWith(QUESTION_EXTRASETTINGS_OTHER_PREFIX))
},
},
@@ -228,6 +221,20 @@ export default {
this.$emit('update:values', this.isUnique ? [value] : value)
},
+ /**
+ * Called when the value of the "other" anwer is changed input
+ * @param {string} value the new text of the "other" answer
+ */
+ onChangeOther(value) {
+ // Prefix the value
+ const prefixedValue = `${QUESTION_EXTRASETTINGS_OTHER_PREFIX}${value}`
+ // emit the values and add the "other" answer
+ this.$emit(
+ 'update:values',
+ this.isUnique ? [prefixedValue] : [...this.values.filter((v) => !v.startsWith(QUESTION_EXTRASETTINGS_OTHER_PREFIX)), prefixedValue],
+ )
+ },
+
/**
* Is the provided answer required ?
* This is needed for checkboxes as html5
@@ -280,6 +287,7 @@ export default {
/**
* Update the options
+ * This will handle updating the form (emitting the changes) and update last changed property
*
* @param {Array} options options to change
*/
@@ -304,22 +312,24 @@ export default {
/**
* Add a new empty answer locally
+ * @param {InputEvent} event The input event that triggered adding a new entry
*/
- addNewEntry() {
+ addNewEntry({ target }) {
// Add local entry
const options = this.options.slice()
options.push({
id: GenRandomId(),
questionId: this.id,
- text: this.inputValue,
+ text: target?.value ?? '',
local: true,
})
- this.inputValue = ''
-
- // Update question
+ // Update questions
this.updateOptions(options)
+ // Reset the "new answer" input
+ this.$refs.pseudoInput.value = ''
+
this.$nextTick(() => {
this.focusIndex(options.length - 1)
@@ -412,14 +422,9 @@ export default {
*
* @param {boolean} allowOtherAnswer show/hide field for other answer
*/
- onAllowOtherAnswerChange(allowOtherAnswer) {
+ onAllowOtherAnswerChange(allowOtherAnswer) {
return this.onExtraSettingsChange('allowOtherAnswer', allowOtherAnswer)
},
-
- valueToInputOtherAnswer() {
- const otherAnswer = this.values.filter(item => item.startsWith(this.QUESTION_EXTRASETTINGS_OTHER_PREFIX))
- return otherAnswer[0] !== undefined ? otherAnswer[0].substring(this.QUESTION_EXTRASETTINGS_OTHER_PREFIX.length) : ''
- },
},
}
diff --git a/src/views/Submit.vue b/src/views/Submit.vue
index 504b4f033..1e6707e52 100644
--- a/src/views/Submit.vue
+++ b/src/views/Submit.vue
@@ -95,11 +95,11 @@
:answer-type="answerTypes[question.type]"
:index="index + 1"
:max-string-lengths="maxStringLengths"
+ :values="answers[question.id]"
v-bind="question"
- :values.sync="answers[question.id]"
@keydown.enter="onKeydownEnter"
@keydown.ctrl.enter="onKeydownCtrlEnter"
- @update:values="addFormFieldToLocalStorage(question)" />
+ @update:values="(values) => onUpdate(question, values)" />
{
// All questions must have a valid title
@@ -279,7 +272,7 @@ export default {
this.resetData()
// Fetch full form on change
this.fetchFullForm(this.form.id)
- this.initFromLocalHost()
+ this.initFromLocalStorage()
SetWindowTitle(this.formTitle)
},
},
@@ -300,15 +293,30 @@ export default {
}
SetWindowTitle(this.formTitle)
if (this.isLoggedIn) {
- this.initFromLocalHost()
+ this.initFromLocalStorage()
}
},
methods: {
- initFromLocalHost() {
- if (localStorage.getItem(`nextcloud_forms_${this.publicView ? this.shareHash : this.hash}`)) {
- for (const key in this.formValuesForLocalStorage) {
- const answer = this.formValuesForLocalStorage[key]
+ /**
+ * Load saved values for current form from LocalStorage
+ * @return {Record}
+ */
+ getFormValuesFromLocalStorage() {
+ const fromLocalStorage = localStorage.getItem(`nextcloud_forms_${this.publicView ? this.shareHash : this.hash}`)
+ if (fromLocalStorage) {
+ return JSON.parse(fromLocalStorage)
+ }
+ return null
+ },
+
+ /**
+ * Initialize answers from saved state in LocalStorage
+ */
+ initFromLocalStorage() {
+ const savedState = this.getFormValuesFromLocalStorage()
+ if (savedState) {
+ for (const [key, answer] of Object.entries(savedState)) {
const answers = []
switch (answer?.type) {
case 'QuestionMultiple':
@@ -324,6 +332,44 @@ export default {
}
}
},
+
+ /**
+ * Save updated answers for question to LocalStorage in case of browser crash / closes / etc
+ * @param {*} question Question to update
+ */
+ addFormFieldToLocalStorage(question) {
+ if (!this.isLoggedIn) {
+ return
+ }
+ // We make sure the values are updated by the `values.sync` handler
+ const state = {
+ ...(this.getFormValuesFromLocalStorage() ?? {}),
+ [`${question.id}`]: {
+ value: this.answers[question.id],
+ type: answerTypes[question.type].component.name,
+ },
+ }
+ const stringified = JSON.stringify(state)
+ localStorage.setItem(`nextcloud_forms_${this.publicView ? this.shareHash : this.hash}`, stringified)
+ },
+
+ deleteFormFieldFromLocalStorage() {
+ if (!this.isLoggedIn) {
+ return
+ }
+ localStorage.removeItem(`nextcloud_forms_${this.publicView ? this.shareHash : this.hash}`)
+ },
+
+ /**
+ * Update answers of a give value
+ * @param {{id: number}} question The question to answer
+ * @param {unknown[]} values The new values
+ */
+ onUpdate(question, values) {
+ this.answers = { ...this.answers, [question.id]: values }
+ this.addFormFieldToLocalStorage(question)
+ },
+
/**
* On Enter, focus next form-element
* Last form element is the submit button, the form submits on enter then
@@ -346,20 +392,6 @@ export default {
// Using button-click event to not bypass validity-checks and use our specified behaviour
this.$refs.submitButton.click()
},
- addFormFieldToLocalStorage(question) {
- if (!this.isLoggedIn) {
- return
- }
- this.formValuesForLocalStorage[`${question.id}`] = { value: this.answers[question.id], type: answerTypes[question.type].component.name }
- const parsed = JSON.stringify(this.formValuesForLocalStorage)
- localStorage.setItem(`nextcloud_forms_${this.publicView ? this.shareHash : this.hash}`, parsed)
- },
- deleteFormFieldFromLocalStorage() {
- if (!this.isLoggedIn) {
- return
- }
- localStorage.removeItem(`nextcloud_forms_${this.publicView ? this.shareHash : this.hash}`)
- },
/*
* Methods for catching unwanted unload events