Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Ask for restarting submission if form was changed #2319

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions src/components/Questions/QuestionDate.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
v-on="commonListeners">
<div class="question__content">
<NcDateTimePicker
v-model="time"
:value="time"
:disabled="!readOnly"
:formatter="formatter"
:placeholder="datetimePickerPlaceholder"
Expand Down Expand Up @@ -57,7 +57,6 @@ export default {

data() {
return {
time: null,
formatter: {
stringify: this.stringify,
parse: this.parse,
Expand All @@ -84,13 +83,10 @@ export default {
name: this.name || undefined,
}
},
},

mounted() {
// Init time from values prop
if (this.values) {
this.time = this.parse(this.values[0])
}
time() {
return this.values ? this.parse(this.values[0]) : null
},
},

methods: {
Expand Down
17 changes: 9 additions & 8 deletions src/components/Questions/QuestionDropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
</template>
<NcSelect
v-if="readOnly"
v-model="selectedOption"
:value="selectedOption"
:name="name || undefined"
:placeholder="selectOptionPlaceholder"
:multiple="isMultiple"
Expand Down Expand Up @@ -133,7 +133,6 @@ export default {

data() {
return {
selectedOption: null,
inputValue: '',
isOptionDialogShown: false,
isLoading: false,
Expand Down Expand Up @@ -173,16 +172,18 @@ export default {
shiftDragHandle() {
return !this.readOnly && this.options.length !== 0 && !this.isLastEmpty
},
},

mounted() {
// Init selected options from values prop
if (this.values) {
selectedOption() {
if (!this.values) {
return null
}

const selected = this.values.map((id) =>
this.options.find((option) => option.id === id),
)
this.selectedOption = this.isMultiple ? selected : selected[0]
}

return this.isMultiple ? selected : selected[0]
},
},

methods: {
Expand Down
15 changes: 12 additions & 3 deletions src/components/Questions/QuestionLong.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,27 @@ export default {
},
},

mounted() {
this.autoSizeText()
watch: {
values: {
handler() {
this.$nextTick(() => {
this.autoSizeText()
})
},
immediate: true,
},
},

methods: {
onInput() {
const textarea = this.$refs.textarea
this.$emit('update:values', [textarea.value])
this.autoSizeText()
},
autoSizeText() {
const textarea = this.$refs.textarea
if (!textarea) {
return
}
textarea.style.cssText = 'height:auto; padding:0'
textarea.style.cssText = `height: ${textarea.scrollHeight + 28}px`
},
Expand Down
128 changes: 102 additions & 26 deletions src/views/Submit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -145,17 +145,31 @@
@keydown.ctrl.enter="onKeydownCtrlEnter"
@update:values="(values) => onUpdate(question, values)" />
</ul>
<NcButton
alignment="center-reverse"
class="submit-button"
:disabled="loading"
native-type="submit"
type="primary">
<template #icon>
<NcIconSvgWrapper :svg="IconSendSvg" />
</template>
{{ t('forms', 'Submit') }}
</NcButton>
<div class="form-buttons">
<NcButton
alignment="center-reverse"
class="submit-button"
:disabled="!hasAnswers"
native-type="reset"
type="tertiary-no-background"
@click.prevent="showClearFormDialog = true">
<template #icon>
<NcIconSvgWrapper :svg="IconRefreshSvg" />
</template>
{{ t('forms', 'Clear form') }}
</NcButton>
<NcButton
alignment="center-reverse"
class="submit-button"
:disabled="loading"
native-type="submit"
type="primary">
<template #icon>
<NcIconSvgWrapper :svg="IconSendSvg" />
</template>
{{ t('forms', 'Submit') }}
</NcButton>
</div>
</form>

<!-- Confirmation dialog if form is empty submitted -->
Expand All @@ -179,6 +193,27 @@
:buttons="confirmLeaveFormButtons"
:can-close="false"
:close-on-click-outside="false" />
<!-- Confirmation dialog for clear form -->
<NcDialog
:open.sync="showClearFormDialog"
:name="t('forms', 'Clear form')"
:message="t('forms', 'Do you want to clear all answers?')"
:buttons="confirmClearFormButtons"
:can-close="false"
:close-on-click-outside="false" />
<!-- Confirmation dialog if form was changed -->
<NcDialog
:open.sync="showClearFormDueToChangeDialog"
:name="t('forms', 'Clear form')"
:message="
t(
'forms',
'The form has changed since your last visit. Do you want to clear all answers?',
)
"
:buttons="confirmClearFormButtons"
:can-close="false"
:close-on-click-outside="false" />
</template>
</NcAppContent>
</template>
Expand All @@ -201,6 +236,7 @@ import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'

import IconCancelSvg from '@mdi/svg/svg/cancel.svg?raw'
import IconCheckSvg from '@mdi/svg/svg/check.svg?raw'
import IconRefreshSvg from '@mdi/svg/svg/refresh.svg?raw'
import IconSendSvg from '@mdi/svg/svg/send.svg?raw'

import { FormState } from '../models/FormStates.ts'
Expand Down Expand Up @@ -277,6 +313,7 @@ export default {
// Non reactive properties
return {
IconCheckSvg,
IconRefreshSvg,
IconSendSvg,

maxStringLengths: loadState('forms', 'maxStringLengths'),
Expand All @@ -297,6 +334,8 @@ export default {
submitForm: false,
showConfirmEmptyModal: false,
showConfirmLeaveDialog: false,
showClearFormDialog: false,
showClearFormDueToChangeDialog: false,
}
},

Expand All @@ -316,6 +355,10 @@ export default {
})
},

validQuestionsIds() {
return new Set(this.validQuestions.map((question) => question.id))
},

isRequiredUsed() {
return this.form.questions.reduce(
(isUsed, question) => isUsed || question.isRequired,
Expand Down Expand Up @@ -413,6 +456,29 @@ export default {
},
]
},

/**
* Buttons for the "confirm clear form" dialog
*/
confirmClearFormButtons() {
return [
{
label: t('forms', 'Abort'),
icon: IconCancelSvg,
callback: () => {},
},
Koc marked this conversation as resolved.
Show resolved Hide resolved
{
label: t('forms', 'Clear'),
icon: IconCheckSvg,
type: 'primary',
callback: () => this.onResetSubmission(),
},
]
},

hasAnswers() {
return Object.keys(this.answers).length > 0
},
},

watch: {
Expand All @@ -437,17 +503,19 @@ export default {
window.addEventListener('beforeunload', this.beforeWindowUnload)
},

beforeMount() {
async beforeMount() {
// Public Views get their form by initial-state from parent. No fetch necessary.
if (this.publicView) {
this.isLoadingForm = false
} else {
this.fetchFullForm(this.form.id)
await this.fetchFullForm(this.form.id)
}
SetWindowTitle(this.formTitle)

if (this.isLoggedIn) {
this.initFromLocalStorage()
}

SetWindowTitle(this.formTitle)
},

methods: {
Expand All @@ -473,8 +541,16 @@ export default {
if (!savedState) {
return
}

const answers = {}
for (const [questionId, answer] of Object.entries(savedState)) {
// Clean up answers for questions that do not exist anymore
if (!this.validQuestionsIds.has(parseInt(questionId))) {
this.showClearFormDueToChangeDialog = true
logger.debug('Question does not exist anymore', { questionId })
continue
}

answers[questionId] =
answer.type === 'QuestionMultiple'
? answer.value.map(String)
Expand Down Expand Up @@ -585,17 +661,6 @@ export default {
async (question) => await question.validate(),
)

// Clean up answers for questions that do not exist anymore
const questionIds = new Map(
this.validQuestions.map((question) => [question.id, true]),
)
for (const questionId of Object.keys(this.answers)) {
if (!questionIds.has(parseInt(questionId))) {
logger.debug('Question does not exist anymore', { questionId })
delete this.answers[questionId]
}
}

try {
// wait for all to be validated
const result = await Promise.all(validation)
Expand Down Expand Up @@ -654,13 +719,20 @@ export default {
}
},

onResetSubmission() {
this.deleteFormFieldFromLocalStorage()
this.resetData()
},

/**
* Reset View-Data
*/
resetData() {
this.answers = {}
this.loading = false
this.showConfirmLeaveDialog = false
this.showClearFormDialog = false
this.showClearFormDueToChangeDialog = false
this.success = false
this.submitForm = false
},
Expand Down Expand Up @@ -749,8 +821,12 @@ export default {
padding-inline-start: var(--default-clickable-area);
}

.form-buttons {
display: flex;
justify-content: flex-end;
}

.submit-button {
align-self: flex-end;
margin: 5px;
margin-block-end: 160px;
padding-inline-start: 20px;
Expand Down
Loading