surveyjs / survey-library

Free JavaScript form builder library with integration for React, Angular, Vue, jQuery, and Knockout.
https://surveyjs.io/form-library
MIT License
4.12k stars 802 forks source link

[Bug] Survey is scrolling to first question on page after visibleIf is triggered #929

Closed kaphert closed 6 months ago

kaphert commented 6 years ago

Are you requesting a feature, reporting a bug or ask a question?

Bug

What is the current behavior?

If you have a long survey and at the end of the survey you have a question that has a visibleIf condition the survey is scrolled to the first question. And if the focusFirstQuestionAutomatic is set to true it also sets focus on the first question.

What is the expected behavior?

The survey should not scroll when something changes visibility.

How would you reproduce the current behavior (if this is a bug)?

  1. See: https://plnkr.co/edit/MZKiJsMoHgB9xcNhnbeZ?p=preview
  2. Enter some value in the big comment box
  3. You now have focus on the first field
  4. A field at the bottom appeared

Specify your

Culprit

The culprit is this: https://github.com/surveyjs/surveyjs/blob/35f8ee5c1178b09b46683714ff1030faabe34fd4/src/vue/page.vue#L37-L42

The page 'updated' is called when a question changes visibility. The fact that the page.updated method is called is good I think. But it should not scroll to the top of the page on every page updated event.

kaphert commented 6 years ago

what is the valid usecase for the call to scrollToTOpOnPageChange() ? A page change?

dmitry-kurmanov commented 6 years ago

yes, it should invokes only after page changed and scroll to the fisrt input. It works in other inlementations, for example in react : https://github.com/surveyjs/surveyjs/blob/ab905155e0a12a9333e1fb6e32e6443147a1e7fe/src/react/reactSurvey.tsx#L34 .

We will fix it in vue.

MuriloOnYou commented 2 years ago

Hello, I'm having exactly the same bug on survey-vue at version 1.9.18. Every time a question visibleIf condition is set to true, the survey scroll to the first question on the page. I noticed that the event 'onScrollingElementToTop' is called twice (one for the page and one for the first question) and I can't prevent it by setting options.cancel = true.

Best regards, Murilo R. Montano.

john-orthobridge commented 1 year ago

We're having this issue also - jumps to top (and clears fields) - painful

ojkaas commented 6 months ago

I am encountering a similar issue, If i select an option that is affecting the visibility of an other element ( in this case a page is added to the survey ) the survey scrolls to the top. I can prevent it with the code to catch the scroll event:

newModel.onScrollingElementToTop.add(function (sender, options) { options.cancel = true })

But this prevents all scroll actions which is not something that is wanted.

Video of the problem: https://www.awesomescreenshot.com/video/25636774?key=9a4b2b9672208df77a0ed81a7f2bd430 ( I am using react & next )

novikov82 commented 6 months ago

@ojkaas We tried to reproduce your issue but we have no enough information to do it. Could you share your JSON, or better create a minimal example in Plunkr or Codesandbox or Codepen for us to reproduce the issue on our side?

ojkaas commented 6 months ago
export const json = {
  title: 'Test',
  completedHtml: '<h3>Bedankt voor het afronden voor de vragenlijst</h3>',
  completedBeforeHtml: '<h3>We zien dat u deze vragenlijst al hebt ingevuld.</h3>',
  loadingHtml: '<h3>Vragenlijst wordt geladen...</h3>',
  pages: [
    {
      name: 'Watzijnuwklacthen',
      elements: [
        {
          type: 'html',
          name: 'Welkom',
          html: '<p>Test test.</p>',
        },
        {
          type: 'radiogroup',
          name: 'AQuestion',
          title: 'Test:  ',
          isRequired: true,
          choices: [
            {
              value: 'Item 1',
              text: 'One',
            },
            {
              value: 'Item 2',
              text: 'Two',
            },
            {
              value: 'Item 3',
              text: 'Both',
            },
            {
              value: 'Item 4',
              text: 'Both but different',
            },
          ],
        },
        {
          type: 'checkbox',
          name: 'Klachten',
          visibleIf: "{AQuestion} anyof ['Item 1', 'Item 2', 'Item 3']",
          title: 'Reason?',
          isRequired: true,
          choices: [
            {
              value: 'Item 1',
              text: 'ReasonA',
            },
            {
              value: 'Item 2',
              text: 'ReasonB',
            },
            {
              value: 'Item 3',
              text: 'ReasonC',
            },
            {
              value: 'Item 4',
              text: 'ReasonD',
            },
            {
              value: 'Item 5',
              text: 'ReasonE',
            },
            {
              value: 'Item 6',
              text: 'ReasonF',
            },
            {
              value: 'Item 7',
              text: 'ReasonG',
            },
          ],
          showSelectAllItem: true,
          selectAllText: 'All',
        },
        {
          type: 'checkbox',
          name: 'KlachtenLinks',
          visibleIf: "{AQuestion} anyof ['Item 4']",
          title: 'Wat is de reden om contact op te nemen voor het Linker oog?',
          isRequired: true,
          choices: [
            {
              value: 'Item 1',
              text: 'ReasonA',
            },
            {
              value: 'Item 2',
              text: 'ReasonB',
            },
            {
              value: 'Item 3',
              text: 'ReasonC',
            },
            {
              value: 'Item 4',
              text: 'ReasonD',
            },
            {
              value: 'Item 5',
              text: 'ReasonE',
            },
            {
              value: 'Item 6',
              text: 'ReasonF',
            },
            {
              value: 'Item 7',
              text: 'ReasonG',
            },
          ],
          showSelectAllItem: true,
          selectAllText: 'All',
        },
        {
          type: 'checkbox',
          name: 'KlachtenRechts',
          visibleIf: "{AQuestion} anyof ['Item 4']",
          title: 'Wat is de reden om contact op te nemen voor het Rechter oog?',
          isRequired: true,
          choices: [
            {
              value: 'Item 1',
              text: 'ReasonA',
            },
            {
              value: 'Item 2',
              text: 'ReasonB',
            },
            {
              value: 'Item 3',
              text: 'ReasonC',
            },
            {
              value: 'Item 4',
              text: 'ReasonD',
            },
            {
              value: 'Item 5',
              text: 'ReasonE',
            },
            {
              value: 'Item 6',
              text: 'ReasonF',
            },
            {
              value: 'Item 7',
              text: 'ReasonG',
            },
          ],
          showSelectAllItem: true,
          selectAllText: 'All',
        },
      ],
      title: 'Bla?',
    },
    {
      name: 'Algemeen',
      elements: [
        {
          type: 'radiogroup',
          name: 'Vraag1',
          title: 'Leeftijd',
          isRequired: true,
          choices: [
            {
              value: 'Item 1',
              text: '0 - 12 Jaar',
            },
            {
              value: 'Item 2',
              text: '12- 20 Jaar',
            },
            {
              value: 'Item 3',
              text: '20 - 35 jaar',
            },
            {
              value: 'Item 4',
              text: '50 Jaar en ouder',
            },
          ],
        },
        {
          type: 'radiogroup',
          name: 'Vraag3',
          title: 'Geslacht',
          isRequired: true,
          choices: [
            {
              value: 'Item 1',
              text: 'Man',
            },
            {
              value: 'Item 2',
              text: 'Vrouw',
            },
            {
              value: 'Item 3',
              text: 'Anders',
            },
          ],
        },
        {
          type: 'boolean',
          name: 'Vraag4',
          title: 'Question',
          isRequired: true,
          labelTrue: 'Ja',
          labelFalse: 'Nee',
          swapOrder: true,
        },
        {
          type: 'boolean',
          name: 'Vraag7',
          title: 'Question',
          labelTrue: 'Ja',
          labelFalse: 'Nee',
          swapOrder: true,
        },
        {
          type: 'boolean',
          name: 'Vraag8',
          title: 'Question',
          isRequired: true,
          labelTrue: 'Ja',
          labelFalse: 'Nee',
          swapOrder: true,
        },
        {
          type: 'boolean',
          name: 'Vraag9',
          title: 'Question',
          isRequired: true,
          labelTrue: 'Ja',
          labelFalse: 'Nee',
          swapOrder: true,
        },
        {
          type: 'boolean',
          name: 'Vraag10',
          title: 'Question',
          isRequired: true,
          labelTrue: 'Ja',
          labelFalse: 'Nee',
          swapOrder: true,
        },
        {
          type: 'checkbox',
          name: 'Vraag11',
          title: 'Question',
          isRequired: true,
          choices: [
            {
              value: 'Item 1',
              text: 'A',
            },
            {
              value: 'Item 2',
              text: 'B',
            },
            {
              value: 'Item 3',
              text: 'C',
            },
          ],
        },
      ],
      title: 'Algemene vragen',
    },
    {
      name: 'verminderdevisus',
      elements: [
        {
          type: 'radiogroup',
          name: 'Vraag12',
          title: 'Test?',
          isRequired: true,
          choices: [
            {
              value: 'Item 1',
              text: 'a',
            },
            {
              value: 'Item 2',
              text: 'b',
            },
            {
              value: 'Item 3',
              text: 'c',
            },
          ],
        },
      ],
      visibleIf: "{Klachten} contains 'Item 1' or {KlachtenLinks} contains 'Item 1' or {KlachtenRechts} contains 'Item 1'",
      title: 'PageA',
    },
    {
      name: 'Pagina1',
      elements: [
        {
          type: 'radiogroup',
          name: 'Vraag13',
          title: 'Question?',
          isRequired: true,
          choices: [
            {
              value: 'Item 1',
              text: 'One',
            },
            {
              value: 'Item 2',
              text: 'Two',
            },
          ],
        },
        {
          type: 'boolean',
          name: 'Vraag14',
          title: 'Question',
          isRequired: true,
          labelTrue: 'Ja',
          labelFalse: 'Nee',
          swapOrder: true,
        },
        {
          type: 'imagepicker',
          name: 'Vraag2',
          title: 'Which?',
          choices: [
            {
              value: 'Image 1',
              imageLink: 'https://upload.wikimedia.org/wikipedia/commons/thumb/6/66/SMPTE_Color_Bars.svg/200px-SMPTE_Color_Bars.svg.png',
            },
            {
              value: 'Image 2',
              imageLink: 'https://upload.wikimedia.org/wikipedia/commons/thumb/6/66/SMPTE_Color_Bars.svg/200px-SMPTE_Color_Bars.svg.png',
            },
            {
              value: 'Image 3',
              imageLink: 'https://upload.wikimedia.org/wikipedia/commons/thumb/6/66/SMPTE_Color_Bars.svg/200px-SMPTE_Color_Bars.svg.png',
            },
            {
              value: 'Image 4',
              imageLink: 'https://upload.wikimedia.org/wikipedia/commons/thumb/6/66/SMPTE_Color_Bars.svg/200px-SMPTE_Color_Bars.svg.png',
            },
          ],
          imageFit: 'cover',
        },
      ],
      visibleIf: "{Klachten} contains 'Item 2' or {KlachtenLinks} contains 'Item 2' or {KlachtenRechts} contains 'Item 2'",
      title: 'Page2',
    },
  ],
  showProgressBar: 'bottom',
  progressBarShowPageTitles: true,
  startSurveyText: 'Starten',
  pagePrevText: 'Vorige',
  pageNextText: 'Volgende',
  completeText: 'Afronden',
  firstPageIsStarted: true,
}
ojkaas commented 6 months ago

I forked your https://github.com/surveyjs/surveyjs-nextjs to run this. The only change i made in the Survey component are these:

'use client'

import { Model } from 'survey-core'
import { Survey } from 'survey-react-ui'
import 'survey-core/defaultV2.css'
import { json } from '../../data/survey_json.js'
import { useEffect, useState } from 'react'
import { restoreSurveyData, saveSurveyData } from '@/components/shared/SurveyHelper'

export default function SurveyComponent() {
  let [model, setModel] = useState<Model | undefined>()

  const SSR = typeof window === 'undefined'

  useEffect(() => {
    const surveyModel = new Model(json)
    restoreSurveyData(surveyModel)
    surveyModel.onValueChanged.add(saveSurveyData)
    surveyModel.onCurrentPageChanged.add(saveSurveyData)
    surveyModel.onScrollingElementToTop.add(function (sender, options) {
      //options.cancel = true
    })
    setModel(surveyModel)
  }, [])

  return (
    <>
      {!SSR && model && (
        <div style={{ height: '90vh', width: '100%' }}>
          <Survey model={model} />
        </div>
      )}
    </>
  )
}

As I had some problems with document/window not being initialised, so i loaded the survey model with an useEffect.

You can reproduce the problem by selecting 'both buth different' for the first question and then in the next two question check/uncheck option 'ReasonB'

novikov82 commented 6 months ago

@ojkaas I finally could reproduce the issue on my side. I investigated the problem and noticed that you set firstPageIsStarted: true. Why did you set it? A start page usually shows an introduction to your survey. See https://surveyjs.io/form-library/documentation/design-survey/create-a-multi-page-survey#start-page for more details. If you remove firstPageIsStarted: true from JSON the issue seems to be solved.

ojkaas commented 6 months ago

I did this to hide the progress bar on the first page, my survey is setup so that the initial questions the user answers will structure wich next pages will become available to them to answer. Based on the boxes checked on the first page there will be 1/2/3/4 pages which specific question related to the checkbox checked. So that why i decided to have the first page as an introduction page. Is this something that can be fixed, or maybe you have an other suggestion how to approach the flow that I want to implement?

I could always disable the progress bar for now an disable the firstPageIsStarted flag as a workaround, but then my users will not be informed about their progress anymore.

Thanks for putting in the effort to find the cause, hope to find a good solution for this!