Feature/ranking question type vue3#3262
Open
datapumpernickel wants to merge 5 commits intonextcloud:mainfrom
Open
Feature/ranking question type vue3#3262datapumpernickel wants to merge 5 commits intonextcloud:mainfrom
datapumpernickel wants to merge 5 commits intonextcloud:mainfrom
Conversation
Adds a new 'ranking' question type that allows respondents to drag-and-drop predefined options into their preferred order. Based on refactor/vue3 branch, using vue-draggable-plus. Signed-off-by: paul bochtler <65470117+datapumpernickel@users.noreply.github.com>
Signed-off-by: paul bochtler <65470117+datapumpernickel@users.noreply.github.com>
Signed-off-by: paul bochtler <65470117+datapumpernickel@users.noreply.github.com>
…ttest for blank answer Signed-off-by: paul bochtler <65470117+datapumpernickel@users.noreply.github.com>
…e keyboard menu Signed-off-by: paul bochtler <65470117+datapumpernickel@users.noreply.github.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Chartman123
reviewed
Apr 2, 2026
Collaborator
Chartman123
left a comment
There was a problem hiding this comment.
Did a first review of your code and added some comments/suggestions.
Regarding the design:
- I think the current represantation of the unranked/ranked options is too massive.
- Please try to use CSS variable defined in core for pixel values
- I don't like that the page "jumps" as soon as all unranked options are selected (same when one of them is removed again from the ranked list
- Now that I see it, I'm no longer sure if we should really move the drag handle to the end.
For a more profound design review, cc @nextcloud/designers
Comment on lines
+1739
to
+1754
| if ($question['type'] === Constants::ANSWER_TYPE_RANKING) { | ||
| if (!$answerArray) { | ||
| return; | ||
| } | ||
|
|
||
| $answerEntity = new Answer(); | ||
| $answerEntity->setSubmissionId($submissionId); | ||
| $answerEntity->setQuestionId($question['id']); | ||
|
|
||
| $answerText = json_encode($answerArray); | ||
| $answerEntity->setText($answerText); | ||
| $this->answerMapper->insert($answerEntity); | ||
|
|
||
| return; | ||
| } | ||
|
|
Collaborator
There was a problem hiding this comment.
This if-branch is exactly the same as for grid questions. Please remove it and adjust the condition above.
| import IconGrid from 'vue-material-design-icons/Grid.vue' | ||
| import IconNumeric from 'vue-material-design-icons/Numeric.vue' | ||
| import IconRadioboxMarked from 'vue-material-design-icons/RadioboxMarked.vue' | ||
| import IconReorderHorizontal from 'vue-material-design-icons/ReorderHorizontal.vue' |
Collaborator
There was a problem hiding this comment.
Please use this icon, as Microsoft Forms also uses this.
Suggested change
| import IconReorderHorizontal from 'vue-material-design-icons/ReorderHorizontal.vue' | |
| import IconSwapVertical from 'vue-material-design-icons/SwapVertical.vue' |
|
|
||
| ranking: { | ||
| component: markRaw(QuestionRanking), | ||
| icon: markRaw(IconReorderHorizontal), |
Collaborator
There was a problem hiding this comment.
Suggested change
| icon: markRaw(IconReorderHorizontal), | |
| icon: markRaw(IconSwapVertical), |
| @@ -0,0 +1,454 @@ | |||
| <!-- | |||
| - SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors | |||
Collaborator
There was a problem hiding this comment.
Suggested change
| - SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors | |
| - SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors |
| </p> | ||
|
|
||
| <!-- Ranked list header (to separate from pool) --> | ||
| <p v-if="rankedOptions.length > 0" class="ranking-ranked__label"> |
Collaborator
There was a problem hiding this comment.
Suggested change
| <p v-if="rankedOptions.length > 0" class="ranking-ranked__label"> | |
| <p v-else class="ranking-ranked__label"> |
Comment on lines
+588
to
+592
| $sortedRanked = $rankedIds; | ||
| $sortedOptions = $optionIds; | ||
| sort($sortedRanked); | ||
| sort($sortedOptions); | ||
| if ($sortedRanked !== $sortedOptions) { |
Collaborator
There was a problem hiding this comment.
Suggested change
| $sortedRanked = $rankedIds; | |
| $sortedOptions = $optionIds; | |
| sort($sortedRanked); | |
| sort($sortedOptions); | |
| if ($sortedRanked !== $sortedOptions) { | |
| if (sort($rankedIds) !== sort($optionIds)) { |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
As proposed in #1278: Add Ranking question type
Refactored to Vue3, after closing: #3247
Rebased to main after closing: #3248
This adds a new question type that lets respondents rank options by dragging them into order.
Results are scored using Borda count — first place gets the most points, last place gets the least. The summary shows each option's total score and average rank with a visual bar, along with a short explanation of how scoring works.
For export, ranking questions get one column per option (like grid questions do), with the rank number as the value. Answers are stored as a JSON array of option IDs, which keeps things consistent with how grid answers are stored.
There's also an optional shuffle setting.
Again disclaimer: heavy use of genAI, I did try to check the code to the best of my abilities and ran it on nextcloud-docker-dev to actually interact with it and export data.
Ok, it is now implemented as a tap-and-drag interface, which allows to set a required-flag or leave it blank. 🎊
Sorry, for the forth-and-back.
See attached video for demonstration: https://github.com/user-attachments/assets/9745e93c-87db-49ae-990f-ed305827deee