Skip to content

Routing on first page does not work. #9899

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

Closed
SamMousa opened this issue May 16, 2025 · 16 comments
Closed

Routing on first page does not work. #9899

SamMousa opened this issue May 16, 2025 · 16 comments
Assignees
Labels
user issue An issue or bug reported by users

Comments

@SamMousa
Copy link
Contributor

SamMousa commented May 16, 2025

Describe the bug
We have a survey that has routing on the first visible page like this:

{
  "pages": [
    {
      "name": "page1",
      "visibleIf": "{group} = 2",
...

We initialize data for this survey like this:

const survey = new Survey.Model(json);
survey.setValue('group', 2);
...
survey.render(document.getElementById("surveyElement"));

When the survey is rendered we see page2, not page 1.

Steps to reproduce

Expected behavior
I'd expect to be shown the first page, because according to the conditions the question should be visible.

Screenshots

Image

Please complete the following information:

  • Browser: [e.g. chrome, safari] N/A
  • Browser version: [e.g. 22] N/A
  • JS framework/library: [e.g. React, Angular, Vue 2 or 3, Knockout, jQuery, Vanilla JS] Vanilla
  • SurveyJS version: [e.g. v1.12.7] v2.0.9
  • Device: [e.g. PC, iPhone 6] N/A

Additional context
Full survey json:

const json ={
  "pages": [
    {
      "name": "page1",
      "visibleIf": "{group} = 2",
      "elements": [
        {
          "type": "text",
          "name": "question1"
        },
        {
          "type": "radiogroup",
          "name": "question4",
          "choices": [
            "Item 1",
            "Item 2",
            "Item 3"
          ]
        }
      ]
    },
    {
      "name": "page2",
      "elements": [
        {
          "type": "text",
          "name": "question2"
        }
      ]
    },
    {
      "name": "page3",
      "elements": [
        {
          "type": "text",
          "name": "question3"
        }
      ]
    }
  ]
};
@JaneSjs JaneSjs self-assigned this May 20, 2025
@JaneSjs JaneSjs added the user issue An issue or bug reported by users label May 20, 2025
@JaneSjs
Copy link
Contributor

JaneSjs commented May 20, 2025

Hello @SamMousa,
Thank you for sharing a demo. If you wish to start a survey from the first page, please set the SurveyModel.currentPageNo property to 0 after calling the SurveyModel.setValue function.

survey.setValue('group', 2);
survey.currentPageNo = 0;

View Demo

In this usage scenario, Page 1 is initially invisible. After the survey model is created from a JSON object, Page 1 doesn't contain any visible elements because of this visibleIf condition: "visibleIf": "{group} = 2". As a result, once the model is initialized, the survey state changes from "loading" to "running," and Page 2 becomes the current page.
You then set the group value and Page 1 becomes visible. However, the survey does not automatically switch the current page when a previously invisible page becomes visible in "running" mode. Therefore, if you load a response and want the survey to start from the first page, you’ll need to manually set the current page number using currentPageNo.

I hope this clarifies the situation.
Should you have any further questions, please let us know.

@JaneSjs JaneSjs closed this as completed May 20, 2025
@SamMousa
Copy link
Contributor Author

Should you have any further questions, please let us know.

Does this work for all survey modes? Can I set currentPageNo even in questionPerPage or inputPerPage mode?

@SamMousa
Copy link
Contributor Author

I can confirm it does not work for those modes.
Is there any reason why the survey model goes to the running state before it is rendered? If we just delay that it will be more performant and we don't have to jump through hoops.

@andrewtelnov
Copy link
Member

@SamMousa For this modes you have to set survey.currentSurveyElement.
I do not see how can we make currentPageNo works for this mode, since we do not change the page structure any more any you can have just one page with 100 questions on the first page in questionPerPage mode.

Thank you,
Andrew

@SamMousa
Copy link
Contributor Author

I understand, but this simple and common scenario now is way overcomplicated.

What about adding an optional data param to the constructor? That way people can load data and you don't have to do all calculations twice (and on top of that users don't have to manually manage state of the survey )

@JaneSjs JaneSjs reopened this May 20, 2025
@andrewtelnov
Copy link
Member

@SamMousa We can make currentElementName property (get/set) it will work with pages in standard mode and with panels/questions otherwise. It will cover all cases.

Thank you,
Andrew

@andrewtelnov andrewtelnov assigned andrewtelnov and unassigned JaneSjs May 20, 2025
@SamMousa
Copy link
Contributor Author

But i dont know what element i want to set it to. The goal is to load data and then start the survey. All other approaches are workarounds.

Why calculate state twice when you can do it only once?

@andrewtelnov
Copy link
Member

@SamMousa What do you mean? It has string type

get currentElementName(): string;
set currentElementName(val: string);

@SamMousa
Copy link
Contributor Author

The goal is to load data into the survey.
Currently this is done after construction when all logic has been calculated already (survey is in running state).
If we do it in the constructor we can do it after loading before running.

What you're discussing is implementation detail of a bad solution (bad because it doesn't solve the above issues, it's more of a workaround)

@andrewtelnov
Copy link
Member

andrewtelnov commented May 20, 2025

@SamMousa How do you want to solve it? I just don't get it. There is no current page in these modes.
The solution is to store/restore an element by name that will make it work in all modes.

Thank you,
Andrew

@SamMousa
Copy link
Contributor Author

My proposal is to have an optional data param to the survey model constructor.
This removes the need for setting the page or element because we have the data that all visibleIf expressions depend on already loaded.

@andrewtelnov
Copy link
Member

@SamMousa Could you please propse the code, because I am likely do not understand the issue you are trying to solve.

@SamMousa
Copy link
Contributor Author

SamMousa commented May 20, 2025

Okay, I'll try in some more detail.
The challenge:

  • We have a survey, let's say it has 2 pages.
  • Page 1 is visible only if {channel} = "facebook".
  • Page 2 is always visible.

{channel} is a variable that our system provides, so we must load it into the survey.

Currently this is the sequence:

// Survey is loaded from JSON
const survey = new SurveyModel(json);
// Survey is now in running state, page 1 is not visible.
survey.data = {channel}

// This is the problem spot
// - The survey is already on page 2, even though we want it to be on page 1.
// - All conditions / visibleIf things have been calculated twice, after the constructor and after changing the data.
// - We need to reset the survey to the start, which depends on the running mode (to which you proposed a solution with "currentElementName", which means we have to figure out the mode, and then the first element based on that)

// Render the survey
survey.render('#survey');

My proposal would be to instead run code like this:

// Survey and DATA is loaded from JSON
const initialData = {channel}
const survey = new SurveyModel(json, null, initialData);

Because the constructor has the data it can load it before the initial calculation of any logic. This makes it more performant and simpler to use for the user.

For what its worth, the current best way that I found to move the survey to the beginning is to call survey.clear(false);.
This will reset it, redoing the calculations it just did in the constructor, but at least doesn't require the user to inspect the questionOnPageMode and do something for each mode.

Alternatively, if you want provide even more control to the end user with a simpler API to maintain on your side, you could use an initializer callback.
That allows us to modify the survey before it transitions from loading to running.

const survey = new SurveyModel(json, null, (survey) => {
    survey.data = {channe}
});

Example implementation, see this as pseudocode, of course I don't know the code as well as you do, so this might not be the best place to implement it.

interface ILoadFromJSONOptions {
  validatePropertyValues?: boolean;
  initializer?: (survey: SurveyModel) => void;
}
constructor(jsonObj: any = null, renderedElement: any = null, initializer?: (survey: SurveyModel) => void) {
    super();
    
    ...
    this.fromJSON(jsonObj, {initializer});
    ...
  }

public fromJSON(json: any, options?: ILoadFromJSONOptions, initializer): void {
    if (!json) return;
    this.resetHasLogo();
    this.resetPropertyValue("titleIsEmpty");
    this.questionHashesClear();
    this.jsonErrors = null;
    this.sjsVersion = undefined;
    const jsonConverter = new JsonObject();
    jsonConverter.toObject(json, this, options);
    if (jsonConverter.errors.length > 0) {
      this.jsonErrors = jsonConverter.errors;
    }
    initializer(this); // THIS IS THE RELEVANT LINE
    this.onStateAndCurrentPageChanged();
    this.endLoading();
    this.updateState();
    if (!!this.sjsVersion && !!settings.version) {
      if (Helpers.compareVerions(this.sjsVersion, settings.version) > 0) {
        ConsoleWarnings.warn("The version of the survey JSON schema (v"
          + this.sjsVersion + ") is newer than your current Form Library version ("
          + settings.version + "). Please update the Form Library to make sure that all survey features work as expected.");
      }
    }
  }

@andrewtelnov
Copy link
Member

@SamMousa This code makes the perfect sense if you want to increase the performance for a large survey. So you do not run all expressions two times, with empty data and then with the assign data. However, I beleive the following code would lead to the same result:

const survey = new SurveyModel();
survey.data = yourData;
survey.fromJSON(json);

My idea was a little different. Let's say you want to store your survey progress and you want to restore later the entered data & the current page. Since currentPage (name and no) doesn't make sense for some modes then currentElementName would solve this issue.

const survey = new SurveyModel(json);
survey.data = savedData;
survey.currentElementName = savedElementName; //stored in your database 

@SamMousa
Copy link
Contributor Author

I like your idea too, but not for this issue. (I created a different issue for it but it was closed) I'll try to find that.

Hmm your example, it feels like undocumented behaviour, but if you say that works I'm happy to try that instead of the callback approach!

I'll report back tomorrow.

@SamMousa
Copy link
Contributor Author

SamMousa commented Jun 4, 2025

This works for me.

@SamMousa SamMousa closed this as completed Jun 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
user issue An issue or bug reported by users
Projects
None yet
Development

No branches or pull requests

3 participants