jsPsych icon indicating copy to clipboard operation
jsPsych copied to clipboard

conditional follow up questions when the main question "YES" is selected.

Open NanChen5353 opened this issue 1 year ago • 3 comments

I have been trying to set up a questionnaire full of questions working like this: for each question, the follow up question is displayed when yes is selected.

But right now, the code I have only works: each main question is on a seperate page, and only after answering all the questions, the follow up questions is showed. (i.e. Question 1, Question 2, follow up question for Q1; follow up question for Q2). just wondering if anyone have a idea how can I modify it to make at least the follow up question is on the same page with its question and ideally, all questions are on the same page? ) Thanks in advance.

Screenshot 2024-02-14 at 12 01 20
var jsPsych = initJsPsych();

var timeline = [];
var caps_qn = [
  '1) Do you ever notice that sounds are much louder than they normally would be?',
  '2) Do you ever sense the presence of another being, despite being unable to see any evidence?',
  // '3) Do you ever hear your own thoughts repeated or echoed?',
  // '4) Do you ever see shapes, lights, or colors even though there is nothing really there?',
  // '5) Do you ever experience unusual burning sensations or other strange feelings in or on your body?',
  // '6) Do you ever hear noises or sounds when there is nothing about to explain them?',
  //     '7) Do you ever hear your own thoughts spoken aloud in your head, so that someone near might be able to hear them?',
  //     '8) Do you ever detect smells which don’t seem to come from your surroundings?',
  //     '9) Do you ever have the sensation that your body, or a part of it, is changing or has changed shape?',
  //     '10) Do you ever have the sensation that your limbs might not be your own or might not be properly connected to your body?',
  //     '11) Do you ever hear voices commenting on what you are thinking or doing?',
  //     '12) Do you ever feel that someone is touching you, but when you look nobody is there?',
  //     '13) Do you ever hear voices saying words or sentences when there is no-one around that might account for it?',
  //     '14) Do you ever experience unexplained tastes in your mouth?',
  //     '15) Do you ever find that sensations happen all at once and flood you with information?',
  //     '16) Do you ever find that sounds are distorted in strange or unusual ways?',
  //     '17) Do you ever have difficulty distinguishing one sensation from another?',
  //     '18) Do you ever smell everyday odors and think that they are unusually strong?',
  //     '19) Do you ever find the appearance of things or people seems to change in a puzzling way, e.g., distorted shapes or sizes or color?',
  //     '20) Do you ever find that your skin is more sensitive to touch, heat, or cold than usual?',
  //     '21) Do you ever think that food or drink tastes much stronger than it normally would?',
  //     '22) Do you ever look in the mirror and think that your face seems different from usual?',
  //     '23) Do you ever have days where lights or colors seem brighter or more intense than usual?',
  //     '24) Do you ever have the feeling of being uplifted, as if driving or rolling over a road while sitting quietly?',
  //     '25) Do you ever find that common smells sometimes seem unusually different?',
  //     '26) Do you ever think that everyday things look abnormal to you?',
  //     '27) Do you ever find that your experience of time changes dramatically?',
  //     '28) Have you ever heard two or more unexplained voices talking with each other?',
  //     '29) Do you ever notice smells or odors that people next to you seem unaware of?',
  //     '30) Do you ever notice that food or drink seems to have an unusual taste?',
  //     '31) Do you ever see things that other people cannot?',
  //     '32) Do you ever hear sounds or music that people near you don’t hear?'
];

// try

function createFollowUpLikert(questionName) {
  return {
    type: jsPsychSurveyLikert,
    questions: [
      { prompt: "Not at all distressing to Very distressing", name: questionName + 'Distressing', labels: ["1", "2", "3", "4", "5"], required: true },
      { prompt: "Not at all distracting to Completely intrusive", name: questionName + 'Distracting', labels: ["1", "2", "3", "4", "5"], required: true },
      { prompt: "Happens hardly at all to Happens all the time", name: questionName + 'Frequency', labels: ["1", "2", "3", "4", "5"], required: true }
    ]
  };
}

function createMainQuestionTrial(questionText, index) {
  var questionName = 'Q' + (index + 1);
  return {
    type: jsPsychSurveyMultiChoice,
    questions: [{ prompt: questionText, options: ['NO', 'YES'], required: true, name: questionName }],
    on_finish: function(data) {
      if(data.response[questionName] === 'YES') {
        // If the response is YES, add the follow-up Likert scale questions to the timeline
        jsPsych.addNodeToEndOfTimeline(createFollowUpLikert(questionName));
      }
    }
  };
}

// Adding each main question trial to the timeline
caps_qn.forEach(function(questionText, index) {
  timeline.push(createMainQuestionTrial(questionText, index));
});

// Start the experiment
jsPsych.run(timeline);

NanChen5353 avatar Feb 14 '24 12:02 NanChen5353

Each trial is bound to be displayed on a separate page, and you are adding the questions and follow-up questions by inserting multiple trials. The correct way would be to display the questions in one multi-choice trial and use on_load to control the addition of follow-up questions.

The solution is given below. You might want to take a closer look at the comments to better understand it and for further customization.

The javascript code:

let jsPsych = initJsPsych();

let timeline = [];

let caps_qn = [
  '1) Do you ever notice that sounds are much louder than they normally would be?',
  '2) Do you ever sense the presence of another being, despite being unable to see any evidence?',
];

// Add the follow up using multi-choice instead of likert
// After all, the latter can be regarded as a special multi-choice question.
//
// The prefix indicates the name of the question it follows up.
//
// Note though, we are prefixing the name with "follow-up". We are using this name to identify the follow-up querstions
// later.
//
// We are also making the choices display horizontally so that it resembles the real likert survey more.
function create_follow_up_question(prefix) {
    let options = [1, 2, 3, 4, 5];

    let create_name = (name) => `follow-up-${prefix}-${name}`;

    return [
        { prompt: "Not at all distressing to Very distressing", name: create_name('Distressing'), options, horizontal: true },
        { prompt: "Not at all distracting to Completely intrusive", name: create_name('Distracting'), options, horizontal: true  },
        { prompt: "Happens hardly at all to Happens all the time", name: create_name('Frequency'), options, horizontal: true  }
    ]
}

let trial = {
    type: jsPsychSurveyMultiChoice,
    questions: caps_qn.map(function (value, index) {
        return [
            {
                prompt: value,
                required: true,
                name: `Q${index}`,
                options: ['yes', 'no'],
            },
            create_follow_up_question(`Q${index}`),
        ];
    // flat the array as it looks like this prior to flattening:
    //
    // [
    //     [{Q0}, [{follow-up-Q0-Distressing, follow-up-Q0-Distracting}]],
    //     [{Q1}, [{follow-up-Q1-Distressing, follow-up-Q1-Distracting}]],
    // ]
    //
    // Which is why we have to apply a depth=2 flattening
    }).flat(2),
    // custom css_classes
    // go check out the css code now before proceeding
    css_classes: ['survey'],
    on_load: function () {
        // selects the wrapper div of questions whose name begins with Q, i.e., the non-follow-up questions
        for (let elem of document.querySelectorAll('.jspsych-survey-multi-choice-question[data-name^="Q"]')) {
            // the radio elements are the child nodes of the wrapper div
            let radios = elem.querySelectorAll('input');
            for (let radio of radios) {
                // listens to click event on the individual radio elements
                // if the radio element's value is "yes", show the follow up questions; otherwise, hide them
                radio.addEventListener('click', function (event) {
                    let show_follow_up = event.target.value === 'yes';

                    // The name is stored in the data-name property of the wrapper div
                    let name = elem.dataset.name;

                    // We will be using next-sibling combinator to select the wrapper divs of the follow-up questions
                    // This is a base of the selector
                    let selector = `.jspsych-survey-multi-choice-question[data-name^="${name}"]`;

                    for (let i = 0; i < 3; i++) {
                        // Move the selector one element further
                        selector += ' + .jspsych-survey-multi-choice-question';
                        let follow_up = document.querySelector(selector);

                        follow_up.style.display = show_follow_up ? 'block' : 'none';

                        // Do not forget to set the follow-up question as required
                        follow_up.querySelectorAll('input').forEach(function (elem) {
                            elem.required = show_follow_up;
                        })
                    }
                });
            }
        }
    },
}

jsPsych.run([trial]);

The CSS code:

/* The .survey is defined in the javascript code by `css_classes` */
.survey .jspsych-survey-multi-choice-text {
    text-align: left !important;
}

/*
 * Remember back when we created the follow-up questions, where we prefixed
 * each name with a follow-up? We are now selecting these follow-questions and
 * hiding them at the beginning stage.
 */
.survey .jspsych-survey-multi-choice-question[data-name^="follow-up"] {
    display: none;
}

Shaobin-Jiang avatar Feb 15 '24 02:02 Shaobin-Jiang

Thank you very much. really appreciate it! just wondering how do we decide when to use multi-choice, when to use likert then?

NanChen5353 avatar Apr 08 '24 14:04 NanChen5353

I choose to use multi-choice here because, with the addition of the follow-up question, the trial does not consist only of likert items. A yes-or-no question would be troublesome should we try to implement it in a likert trial. If you never need to make much modification to the plugin and require only likert items, use likert.

Shaobin-Jiang avatar Apr 08 '24 23:04 Shaobin-Jiang