Skip to content

Commit 60a3be0

Browse files
authored
Merge pull request #3413 from nextcloud/fix/validation-error
fix: show validation error for QuestionShort in NcNoteCard
2 parents fc5cf63 + c0baba5 commit 60a3be0

8 files changed

Lines changed: 47 additions & 27 deletions

File tree

src/components/Questions/QuestionColor.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
:modelValue="pickedColor"
2020
advancedFields
2121
:aria-required="isRequired"
22+
:aria-errormessage="hasError ? errorId : undefined"
23+
:aria-invalid="hasError ? 'true' : undefined"
2224
@update:modelValue="onUpdatePickedColor">
2325
<NcButton :disabled="!readOnly">
2426
{{ colorPickerPlaceholder }}

src/components/Questions/QuestionDate.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@
9797
:disabledDate="disabledDates"
9898
:disabledTime="disabledTimes"
9999
:aria-required="isRequired"
100+
:aria-errormessage="hasError ? errorId : undefined"
101+
:aria-invalid="hasError ? 'true' : undefined"
100102
clearable
101103
@update:modelValue="onValueChange" />
102104
</div>

src/components/Questions/QuestionDropdown.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
:searchable="false"
4242
label="text"
4343
:aria-label-combobox="selectOptionPlaceholder"
44+
:aria-errormessage="hasError ? errorId : undefined"
45+
:aria-invalid="hasError ? 'true' : undefined"
4446
@invalid.prevent="validate"
4547
@update:modelValue="onInput" />
4648
</div>

src/components/Questions/QuestionFile.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@
111111
class="question__input-wrapper"
112112
role="group"
113113
:aria-labelledby="titleId"
114-
:aria-describedby="description ? descriptionId : undefined">
114+
:aria-describedby="description ? descriptionId : undefined"
115+
:aria-errormessage="hasError ? errorId : undefined"
116+
:aria-invalid="hasError ? 'true' : undefined">
115117
<label>
116118
{{ t('forms', 'Add new file as answer') }}
117119
<input

src/components/Questions/QuestionLinearScale.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@
8181
<NcCheckboxRadioSwitch
8282
:id="`linear-scale-${id}-${option}`"
8383
:aria-describedby="index === 0 ? labelId : undefined"
84+
:aria-errormessage="hasError ? errorId : undefined"
85+
:aria-invalid="hasError ? 'true' : undefined"
8486
:disabled="!readOnly"
8587
:modelValue="questionValues"
8688
:value="option.toString()"

src/components/Questions/QuestionLong.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
ref="textarea"
1616
:aria-labelledby="titleId"
1717
:aria-describedby="description ? descriptionId : undefined"
18+
:aria-errormessage="hasError ? errorId : undefined"
19+
:aria-invalid="hasError ? 'true' : undefined"
1820
:placeholder="submissionInputPlaceholder"
1921
:disabled="!readOnly"
2022
:required="isRequired"

src/components/Questions/QuestionRanking.vue

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
class="question__content"
3333
role="list"
3434
:aria-labelledby="titleId"
35-
:aria-describedby="description ? descriptionId : undefined">
35+
:aria-describedby="description ? descriptionId : undefined"
36+
:aria-errormessage="hasError ? errorId : undefined"
37+
:aria-invalid="hasError ? 'true' : undefined">
3638
<!-- Unranked pool -->
3739
<div class="ranking-unranked">
3840
<Draggable

src/components/Questions/QuestionShort.vue

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
ref="input"
1616
:aria-labelledby="titleId"
1717
:aria-describedby="description ? descriptionId : undefined"
18+
:aria-errormessage="hasError ? errorId : undefined"
19+
:aria-invalid="hasError ? 'true' : undefined"
1820
:placeholder="submissionInputPlaceholder"
1921
:disabled="!readOnly"
2022
:name="name || undefined"
@@ -78,12 +80,14 @@
7880

7981
<script>
8082
import IconRegex from '@material-symbols/svg-400/outlined/regular_expression.svg?raw'
83+
import debounce from 'debounce'
8184
import NcActionInput from '@nextcloud/vue/components/NcActionInput'
8285
import NcActionRadio from '@nextcloud/vue/components/NcActionRadio'
8386
import NcActions from '@nextcloud/vue/components/NcActions'
8487
import NcIconSvgWrapper from '@nextcloud/vue/components/NcIconSvgWrapper'
8588
import Question from './Question.vue'
8689
import QuestionMixin from '../../mixins/QuestionMixin.js'
90+
import { INPUT_DEBOUNCE_MS } from '../../models/Constants.ts'
8791
import validationTypes from '../../models/ValidationTypes.js'
8892
import { splitRegex, validateExpression } from '../../utils/RegularExpression.js'
8993
@@ -159,43 +163,45 @@ export default {
159163
160164
methods: {
161165
async validate() {
162-
if (
163-
this.isRequired
164-
&& (this.values.length === 0 || this.values[0] === '')
165-
) {
166+
/** @type {HTMLInputElement} */
167+
const input = this.$refs.input
168+
const value = input.value
169+
170+
// Clear the previous custom error before checking native validity.
171+
input.setCustomValidity('')
172+
173+
if (this.isRequired && input.validity.valueMissing) {
166174
this.errorMessage = t('forms', 'You must answer this question')
167175
return false
168176
}
169177
178+
const isCustomValid =
179+
!value
180+
|| this.validationObject.validate(
181+
value,
182+
splitRegex(this.validationRegex),
183+
)
184+
185+
if (!input.validity.valid || !isCustomValid) {
186+
input.setCustomValidity(this.validationObject.errorMessage)
187+
this.errorMessage = this.validationObject.errorMessage
188+
return false
189+
}
190+
170191
this.errorMessage = null
171192
return true
172193
},
173194
195+
debounceValidate: debounce(async function () {
196+
this.validate()
197+
}, INPUT_DEBOUNCE_MS),
198+
174199
onInput() {
175-
/** @type {HTMLObjectElement} */
200+
/** @type {HTMLInputElement} */
176201
const input = this.$refs.input
177-
/** @type {string} */
178202
const value = input.value
179-
180-
input.setCustomValidity('')
181-
182-
// Only check non empty values, this question might not be required, if not already invalid
183-
if (value) {
184-
// Then check native browser validation (might be better then our)
185-
// If the browsers validation succeeds either the browser does not implement a validation
186-
// or it is valid, so we double check by running our custom validation.
187-
if (
188-
!input.checkValidity()
189-
|| !this.validationObject.validate(
190-
value,
191-
splitRegex(this.validationRegex),
192-
)
193-
) {
194-
input.setCustomValidity(this.validationObject.errorMessage)
195-
}
196-
}
197-
198203
this.$emit('update:values', [value])
204+
this.debounceValidate()
199205
},
200206
201207
/**

0 commit comments

Comments
 (0)