Skip to content

Commit

Permalink
Added a new feature: 'Other' option for radio and checkbox questions.
Browse files Browse the repository at this point in the history
Signed-off-by: Andrii Ilkiv <a.ilkiv.ye@gmail.com>
  • Loading branch information
AIlkiv committed Aug 20, 2023
1 parent 38cae22 commit 021de39
Show file tree
Hide file tree
Showing 7 changed files with 302 additions and 31 deletions.
2 changes: 2 additions & 0 deletions lib/Constants.php
Expand Up @@ -131,4 +131,6 @@ class Constants {
*/
public const EMPTY_NOTFOUND = 'notfound';
public const EMPTY_EXPIRED = 'expired';

public const PREFIX_FOR_OTHER_ANSWER = 'system-other-answer:';
}
21 changes: 12 additions & 9 deletions lib/Controller/ApiController.php
Expand Up @@ -1002,27 +1002,30 @@ public function insertSubmission(int $formId, array $answers, string $shareHash
$questionIndex = array_search($questionId, array_column($questions, 'id'));
if ($questionIndex === false) {
continue;
} else {
$question = $questions[$questionIndex];
}

$question = $questions[$questionIndex];

foreach ($answerArray as $answer) {
$answerText = '';

// Are we using answer ids as values
if (in_array($question['type'], Constants::ANSWER_TYPES_PREDEFINED)) {
// Search corresponding option, skip processing if not found
$optionIndex = array_search($answer, array_column($question['options'], 'id'));
if ($optionIndex === false) {
continue;
} else {
$option = $question['options'][$optionIndex];
if ($optionIndex !== false) {
$answerText = $question['options'][$optionIndex]['text'];
} elseif (!empty($question['extraSettings']->otherAnswer) && strpos($answer, Constants::PREFIX_FOR_OTHER_ANSWER) === 0) {
$answerText = str_replace(Constants::PREFIX_FOR_OTHER_ANSWER, "", $answer);
}

// Load option-text
$answerText = $option['text'];
} else {
$answerText = $answer; // Not a multiple-question, answerText is given answer
}

if ($answerText === "") {
continue;
}

$answerEntity = new Answer();
$answerEntity->setSubmissionId($submissionId);
$answerEntity->setQuestionId($question['id']);
Expand Down
5 changes: 3 additions & 2 deletions lib/Service/SubmissionService.php
Expand Up @@ -309,7 +309,8 @@ public function validateSubmission(array $questions, array $answers): bool {
$questionAnswered = array_key_exists($questionId, $answers);

// Check if all required questions have an answer
if ($question['isRequired'] && (!$questionAnswered || !array_filter($answers[$questionId], 'strlen'))) {
if ($question['isRequired'] &&
(!$questionAnswered || !array_filter($answers[$questionId], 'strlen') || !array_filter($answers[$questionId], fn ($value) => $value !== Constants::PREFIX_FOR_OTHER_ANSWER))) {
return false;
}

Expand All @@ -330,7 +331,7 @@ public function validateSubmission(array $questions, array $answers): bool {
}

// Check if all answers are within the possible options
if (in_array($question['type'], Constants::ANSWER_TYPES_PREDEFINED)) {
if (in_array($question['type'], Constants::ANSWER_TYPES_PREDEFINED) && empty($question['extraSettings']->otherAnswer)) {
foreach ($answers[$questionId] as $answer) {
// Search corresponding option, return false if non-existent
if (array_search($answer, array_column($question['options'], 'id')) === false) {
Expand Down
81 changes: 67 additions & 14 deletions src/components/Questions/QuestionMultiple.vue
Expand Up @@ -43,6 +43,10 @@
@update:checked="onShuffleOptionsChange">
{{ t('forms', 'Shuffle options') }}
</NcActionCheckbox>
<NcActionCheckbox :checked="extraSettings?.otherAnswer"
@update:checked="onOtherAnswerChange">
{{ t('forms', 'Input for "other"') }}
</NcActionCheckbox>
</template>
<template v-if="!edit">
<fieldset :name="name || undefined" :aria-labelledby="titleId">
Expand All @@ -57,6 +61,16 @@
@keydown.enter.exact.prevent="onKeydownEnter">
{{ answer.text }}
</NcCheckboxRadioSwitch>
<NcCheckboxRadioSwitch v-if="isShowOtherAnswer"
:checked.sync="questionValues"
:value="valueOtherAnswer"
:name="`${id}-answer`"
:type="isUnique ? 'radio' : 'checkbox'"
:required="checkRequired('other-answer')"
@update:checked="onChange"
@keydown.enter.exact.prevent="onKeydownEnter">
{{ t('forms', 'Other:') }}&nbsp;<NcInputField placeholder="answer..." :value.sync="inputOtherAnswer" class="question__input" />
</NcCheckboxRadioSwitch>
</fieldset>
</template>
Expand All @@ -76,7 +90,16 @@
@delete="deleteOption"
@update:answer="updateAnswer" />
</template>
<li v-if="isShowOtherAnswer" class="question__item">
<div :is="pseudoIcon" class="question__item__pseudoInput" />
<input :aria-label="t('forms', 'Add a new answer')"
:placeholder="t('forms', 'Field for Other answer')"
class="question__input"
:maxlength="maxStringLengths.optionText"
minlength="1"
type="text"
:readonly="edit">
</li>
<li v-if="(edit && !isLastEmpty) || hasNoAnswer" class="question__item">
<div :is="pseudoIcon" class="question__item__pseudoInput" />
<input :aria-label="t('forms', 'Add a new answer')"
Expand All @@ -100,6 +123,7 @@ import { generateOcsUrl } from '@nextcloud/router'
import axios from '@nextcloud/axios'
import NcActionCheckbox from '@nextcloud/vue/dist/Components/NcActionCheckbox.js'
import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js'
import NcInputField from '@nextcloud/vue/dist/Components/NcInputField.js'
import IconCheckboxBlankOutline from 'vue-material-design-icons/CheckboxBlankOutline.vue'
import IconRadioboxBlank from 'vue-material-design-icons/RadioboxBlank.vue'
Expand All @@ -117,13 +141,16 @@ export default {
IconRadioboxBlank,
NcActionCheckbox,
NcCheckboxRadioSwitch,
NcInputField,
},
mixins: [QuestionMixin],
data() {
return {
questionValues: this.values,
inputOtherAnswer: this.valueToInputOtherAnswer(),
PREFIX_FOR_OTHER_ANSWER: 'system-other-answer:',
}
},
Expand Down Expand Up @@ -160,6 +187,14 @@ export default {
titleId() {
return `q${this.$attrs.index}_title`
},
isShowOtherAnswer() {
return this.extraSettings?.otherAnswer
},
valueOtherAnswer() {
return this.PREFIX_FOR_OTHER_ANSWER + this.inputOtherAnswer
},
},
watch: {
Expand All @@ -178,23 +213,27 @@ export default {
this.updateOptions(options)
}
},
},
methods: {
onChange() {
// Checkbox: convert to array of Numbers
if (!this.isUnique) {
const arrOfNum = []
this.questionValues.forEach(str => {
arrOfNum.push(Number(str))
})
this.$emit('update:values', arrOfNum)
inputOtherAnswer() {
if (this.isUnique) {
this.questionValues = this.valueOtherAnswer
this.onChange()
return
}
// Radio: create array
this.$emit('update:values', [this.questionValues])
this.questionValues = this.questionValues.filter(item => !item.startsWith(this.PREFIX_FOR_OTHER_ANSWER))
if (this.inputOtherAnswer !== '') {
this.questionValues.push(this.valueOtherAnswer)
}
this.onChange()
},
},
methods: {
onChange() {
this.$emit('update:values', this.isUnique ? [this.questionValues] : this.questionValues)
},
/**
Expand Down Expand Up @@ -348,6 +387,20 @@ export default {
input.focus()
}
},
/**
* Update status extra setting otherAnswer and save on DB
*
* @param {boolean} showInputOtherAnswer show/hide field for other answer
*/
onOtherAnswerChange(showInputOtherAnswer) {
return this.onExtraSettingsChange('otherAnswer', showInputOtherAnswer)
},
valueToInputOtherAnswer() {
const otherAnswer = this.values.filter(item => item.startsWith(this.PREFIX_FOR_OTHER_ANSWER))
return otherAnswer[0] !== undefined ? otherAnswer[0].substring(this.PREFIX_FOR_OTHER_ANSWER.length) : ''
},
},
}
</script>
Expand Down
13 changes: 8 additions & 5 deletions src/components/Results/ResultsSummary.vue
Expand Up @@ -92,6 +92,13 @@ export default {
percentage: 0,
}))
// Also record 'Other'
questionOptionsStats.unshift({
text: t('forms', 'Other'),
count: 0,
percentage: 0,
})
// Also record 'No response'
questionOptionsStats.unshift({
// TRANSLATORS Counts on Results-Summary, how many users did not respond to this question.
Expand All @@ -112,11 +119,7 @@ export default {
answers.forEach(answer => {
const optionsStatIndex = questionOptionsStats.findIndex(option => option.text === answer.text)
if (optionsStatIndex < 0) {
questionOptionsStats.push({
text: answer.text,
count: 1,
percentage: 0,
})
questionOptionsStats[1].count++
} else {
questionOptionsStats[optionsStatIndex].count++
}
Expand Down

0 comments on commit 021de39

Please sign in to comment.