Skip to content

Conditional questions: go to a question based on the answer - #3833

Draft
AndyScherzinger wants to merge 4 commits into
mainfrom
feat/conditional-questions-1-base
Draft

AndyScherzinger wants to merge 4 commits into
mainfrom
feat/conditional-questions-1-base

Conversation

@AndyScherzinger

@AndyScherzinger AndyScherzinger commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

Conditional questions as in Microsoft and Google Forms, based on #3123 by @mickenordin with the fixes by @timedin-de, split into a stack so it can be reviewed in parts (as suggested by @Chartman123 in #3123 (comment)).

Fixes #358

Stack

Each PR is based on the one before it. Review and merge from the top.

  1. Conditional questions: go to a question based on the answer #3833 Conditional questions: go to a question based on the answer ← this PR
  2. Conditional questions: go to a question after checkbox questions #3834 Conditional questions: go to a question after checkbox questions
  3. Conditional questions: go to a question after date and time questions #3835 Conditional questions: go to a question after date and time questions
  4. Conditional questions: go to a question based on the value of a linear scale #3837 Conditional questions: go to a question based on the value of a linear scale

This PR

An answer can lead to a later question or to the end of the form. The form stays a flat list of questions; the jumps live in the question's extraSettings, as suggested by @Chartman123 in #3833 (review).

Editor

  • "Go to based on answer": radio and dropdown questions get a select per option and for "Other": next question, a later question, or end of the form.
  • Text, file, color, ranking and grid questions get one select "After question N".
  • Jumps only go forward: a move that would break this asks to move back or remove the jumps.
  • Warnings and a toast name broken jumps and questions that became unreachable or skippable.

Data

  • goTo holds one target per answer, next one target after the question; a type has one of them, never both.
  • Targets are later questions of the same form or "submit"; ids from other forms or questions are rejected with a 400.
  • The server removes jumps that go back on update, reorder and copy, and reports them in the new header X-Forms-Adjusted (RFC 8941, e.g. removed-jumps=(3 7)); status and payload shape stay the same.
  • Deleting an option removes its jumps and reports it in X-Forms-Adjusted.
  • Copying and importing a form map the targets and option ids, and drop jumps with an invalid structure.

Answers

  • The path starts at the first question and follows the jumps; a target that no longer exists counts as the next question. Only the first value of an answer decides, and answers to questions the submit view does not show do not count.
  • The submit view shows only the questions on the path and announces changes.
  • The server ignores answers to skipped questions and requires only questions on the path.
  • tests/fixtures/jump-paths.json holds the path cases, run by PHPUnit and Playwright, so both sides walk the same way.

Follow-ups

Sections with "go to section" (#357, #624) follow as a separate feature, see #3664; a section will be a valid jump target without a format change.

Tests

  • PHPUnit passes, except the 3 IUserFolder errors main also has on the test instance.
  • psalm, php-cs-fixer and eslint are clean; vue-tsc shows no new errors; every commit builds.
  • The Playwright suite passes (94 tests, one worker as on CI).
  • Validated on a running instance with every jump type of the stack: API, submit view and editor checks, keyboard navigation, and axe without violations.

🤖 AI (if applicable)

  • The content of this PR was partly or fully generated using AI

@codecov

codecov Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.17352% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
lib/Service/SubmissionService.php 94.44% 3 Missing ⚠️
lib/Controller/ApiController.php 98.24% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@AndyScherzinger AndyScherzinger changed the title Conditional questions: radio and dropdown triggers Conditional questions: base implementation plus support for radio and dropdown triggers Nov 4, 2026
@AndyScherzinger

Copy link
Copy Markdown
Member Author

I'll work on fixing CI checks and test coverage next

@AndyScherzinger
AndyScherzinger force-pushed the feat/conditional-questions-1-base branch from b833f24 to 9d9b414 Compare November 4, 2026 14:07

@Chartman123 Chartman123 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a few general comments/questions.

One thing that came to my mind: would it be easier to not have a new "question type" conditional but to make this part of the props of a "normal" question?

Comment thread docs/DataStructure.md Resolved
Comment thread lib/Controller/ApiController.php Resolved
Comment thread src/components/QuestionList.vue Resolved
@AndyScherzinger
AndyScherzinger force-pushed the feat/conditional-questions-1-base branch 2 times, most recently from 21f29c0 to c2cf51a Compare November 5, 2026 11:10
@timedin-de

Copy link
Copy Markdown

Added a few general comments/questions.

One thing that came to my mind: would it be easier to not have a new "question type" conditional but to make this part of the props of a "normal" question?

@Chartman123

I personally also would prefer this Version as this could allow trigger questions to be seperated from the conditional question. (E.g Last question is only shown when first question is selected).
It would also easily allow Nestes conditions and is the same logic like in MS-Forms. Although this might get a larger refactor from current implementation.

Grouping functionality would get implemented independently by other running PRs.

@AndyScherzinger AndyScherzinger changed the title Conditional questions: base implementation plus support for radio and dropdown triggers Conditional questions: base with radio and dropdown questions Nov 7, 2026
@AndyScherzinger
AndyScherzinger force-pushed the feat/conditional-questions-1-base branch from b444385 to 025620c Compare November 7, 2026 15:41
@AndyScherzinger

Copy link
Copy Markdown
Member Author

One thing that came to my mind: would it be easier to not have a new "question type" conditional but to make this part of the props of a "normal" question?

Agreed, and done: there is no conditional question type anymore. Branches are part of a normal question's extraSettings, and follow-up questions are answered like any other question. See 025620c and the background in #3123 (comment). The other question types follow in #3834–#3838.

@AndyScherzinger

Copy link
Copy Markdown
Member Author

@timedin-de thanks, that is the direction the stack has taken now: there is no conditional question type anymore, branches are part of a normal question (see 025620c and #3123 (comment)).

I looked at how MS Forms does it. There, each option of a choice question jumps forward to a later question, a section or the end of the form, and "nested" comes from chaining those jumps. Two things from your comment are not covered by this stack yet:

  1. A later question that depends on an earlier answer ("last question is only shown when the first question is selected"). In this stack a follow-up question always appears directly below its question. For a question that should stay where it is, I'd rather add a display condition on the question itself ("show only if question 1 is …"), reusing the condition types and matching we have now, than copy MS Forms' jumps to single questions. A jump from question 1 over questions 2–4 is the same as "show only if question 1 is not X" on questions 2–4, and the condition sits on the question it affects.
  2. Nested conditions: follow-up questions are one level deep for now. Allowing branches on follow-up questions, up to a depth of 2 (numbered 1.2.1), is a small step on top of this model, because a follow-up is a normal question.

Both would come as follow-up PRs after this stack, so the stack stays reviewable. Section navigation ("after this section go to …", "go to section" per option, "end of the form") belongs to the sections work in #3664.

Does that cover what you had in mind, or do you need jumps to single questions?

@AndyScherzinger
AndyScherzinger force-pushed the feat/conditional-questions-1-base branch 2 times, most recently from ce2e434 to 06bd420 Compare November 9, 2026 11:40
@AndyScherzinger
AndyScherzinger marked this pull request as draft November 9, 2026 21:37
@AndyScherzinger AndyScherzinger added 2. developing Work in progress and removed 3. to review Waiting for reviews labels Nov 9, 2026
AndyScherzinger and others added 4 commits November 11, 2026 11:17
The check for answers to unknown questions compares the question ids
strictly, as the validation of each question already does, and names the
id as it was sent.

Option ids are compared as canonical integers with the ids of the
options of the question. Answers to questions that allow the answer
"Other" are an option id of the question or an answer "Other", and
storing an answer uses the same check for the answer "Other" as the
validation.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
As in Microsoft and Google Forms, an answer can lead to a later question
or to the end of the form, while the form stays a flat list of
questions. Radio and dropdown questions store a target per option and
for the answer "Other" in extraSettings.goTo. The other question types
with jumps (short and long text, file, color, ranking, grid) store one
target after the question in extraSettings.next. A question type has
either goTo or next, never both: sending the other one is rejected as
invalid extraSettings, and an answer without a jump leads to the
following question. A target is the id of a question of the same form or
"submit", and an option id belongs to the question; ids of questions of
other forms and of options that are not options of the question are
rejected, while ids of deleted questions of the form are accepted.

The path of a respondent starts at the first question and follows these
targets. Only the first value of an answer decides, which keeps the walk
linear in the size of a submission, and answers to questions the submit
view does not show, such as questions without a title, do not count, so
they cannot skip required questions. A target that is not a later
question of the form, for example after deleting it, counts as the
following question. Answers to the questions off the path are ignored,
also when a form changed while it was answered, and required only
applies on the path. An answer "Other" needs a text, as it decides the
path.

Jumps never go back. Updating, reordering and copying a question remove
the jumps to the question itself or to an earlier question; the request
still succeeds with its usual status and a payload of the same shape,
and the response header X-Forms-Adjusted lists the questions concerned
as removed-jumps. A reorder returns the new extraSettings of these
questions in its own response type QuestionOrder. Copying or importing a
form maps the targets and option ids and drops such jumps as well,
without the header, together with jumps whose target or option was not
copied, jumps per answer with an invalid structure, and goTo or next on
a question type without it. Exports keep the option ids for that.

tests/fixtures/jump-paths.json holds the path cases, shared with the
frontend.

Based on the conditional questions of #3123 by Micke Nordin and
TimedIn.

Co-Authored-By: Micke Nordin <kano@sunet.se>
Co-Authored-By: TimedIn <git@timedin.net>
Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
"Go to based on answer" in the question menu shows a select at the end
of each option row of radio and dropdown questions, and of the row
"Other": the next question, a later question or the end of the form. The
other question types with jumps get the select "After question N" below
them instead. The accessible names of the selects of the answers include
the question number as well. Below a width of 480px the select of an
option moves onto its own line. A target that was deleted or moved
before the question is shown as going to the next question. Unchecking
"Go to" closes the menu and asks before it removes the jumps of the
question. The warnings let jumps of questions the submit view does not
show lead nowhere, and count a jump of the answer "Other" as broken
while "Other" is off, as a reorder removes it as well.

Changes are kept in the question and saved as complete lists. Extra
settings the server adjusted replace the local ones unless they changed
in the meantime, and deleting an option drops its jumps once the server
confirms the deletion. playwright/e2e/jump-paths.spec.ts runs the path
cases shared with the server against the frontend walk.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
The submit view shows the questions on the path of the current answers
and announces when questions are added or skipped. Answers to skipped
questions are kept while the form is open, but not submitted.

Assisted-by: Claude Code:claude-opus-5-5
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2. developing Work in progress enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support conditional logic

3 participants