Search code examples
javascriptjspsych

Adding New Events in the Middle of a jsPsych Timeline


Goal

This simple demonstration should generate a task using jsPsych that ideally displays two or three screens, depending upon the choices made by the participant. The first event, ChoiceTrial, presents individuals with three buttons, labelled 'A', 'B', and 'C'. If A is pressed, a concluding message, Conclusion, ending the experiment is displayed. If any button but A is pressed, an interim message, Message, indicating A was not pressed should be displayed, followed by the concluding message.

Issue

While the on_finish logic of the ChoiceTrial event appears to make sence, because the timeline is defined and ran before any user input is collected, I cannot add the Message event contingent upon participant button selections.

Question

How could I dynamically add new events, based upon participant responses, in the middle of a jsPsych Timeline?

Solutions Tried

I initially programmed the task to simply as a timeline.push conditional upon 'A' not being selected. This of course did not work. I then tried coding a function which would insert a few event into the timeline by finding the current event index and splicing in a new event, and then callign this new function on_finish. This also did not work:

// Ideally a function which should modify the timeline and insert a new trial 
function insertTrialAfterCurrent(newTrial) {
    const currentTrialIndex = jsPsych.getProgress().current_trial_global;
    timeline.splice(currentTrialIndex + 1, 0, newTrial);
}

Lastly, I've tried switching to creating a new timeline, pushing to that new timeline, and then using the jsPsych.addNodeToEndOfTimeline function to add to the main timeline, as suggested in this forum post, but again no dice.

Example Code

I believe this example should be reproducible and demonstrate my issue. Thanks for your time and effort!

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <!-- Experiment Title -->
  <title>Experiment</title>

  <!-- Loading in libraries  -->
  <script src="https://unpkg.com/jspsych@7.3.3"></script>
  <script src="https://unpkg.com/@jspsych/plugin-html-keyboard-response@1.1.2"></script>
  <script src="https://unpkg.com/@jspsych/plugin-html-button-response@1.1.2"></script>
  <link href="https://unpkg.com/jspsych@7.3.3/css/jspsych.css" rel="stylesheet" type="text/css" />   
</head>

<!-- Determing general page style parameters -->
<body style="background-color: rgb(200, 200, 200);">
<script>
    
// | - - - - - - - - - - - - - - - - - - - - - - - - - - |
// | - - - - - - - - EXPERIMENT VARIABLES - - - - - - -  |
// | - - - - - - - - - - - - - - - - - - - - - - - - - - |   

// Initiating jsPsych
var jsPsych = initJsPsych({
  timeline: timeline,
});
    
// Creating an array of three choices, which we'll randomize the order of
var choice = jsPsych.randomization.sampleWithoutReplacement(['A', 'B', 'C'], 3);  

// | - - - - - - - - - - - - - - - - - - - - - - - - - - |
// | - - - - - - - - EXPERIMENT FUNCTIONS  - - - - - - - |
// | - - - - - - - - - - - - - - - - - - - - - - - - - - |  

// Creating an empty array which will act as our timeline
var timeline = [];

// Creating some message that will display if any button except A is pressed
var Message = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'Congrats, It worked! Press Space to Continue',
  choices: [' '],
};

// Creating a concluding message to indicate that the experiment has concluded
var Conclusion = {
  type: jsPsychHtmlKeyboardResponse,
  stimulus: 'The experiment Ended! Press Space to Continue',
  choices: [' '],
};

// Presenting three choices to choose from
var ChoiceTrial = {
  type: jsPsychHtmlButtonResponse,
  stimulus: 'Choose A to end, or B or C to see a message',
  choices: choice,
  on_finish: function (data) {
    var new_timeline = {
        timeline: [Message]
    }
    jsPsych.addNodeToEndOfTimeline(new_timeline)
    }
};

// | - - - - - - - - - - - - - - - - - - - - - - - - - - |
// | - - - - - - - - EXPERIMENT TIMELINE - - - - - - - - |
// | - - - - - - - - - - - - - - - - - - - - - - - - - - |    

  // Adding the choice trial to the timeline
  timeline.push(ChoiceTrial);

  // Adding the conclusion to the timeline
  var new_timeline = {
    timeline: [Conclusion]
  }
  jsPsych.addNodeToEndOfTimeline(new_timeline)

// | - - - - - - - - - - - - - - - - - - - - - - - - - - |
// | - - - - - - - - RUNNING EXPERIMENT  - - - - - - - - |
// | - - - - - - - - - - - - - - - - - - - - - - - - - - |   

    // Executing the timeline
    jsPsych.run(timeline);

</script>
</body>
</html>

Solution

  • Josh de Leeuw directed me to a solution for this specific problem. The missing piece was conditional functions, which allow me to add events that either are included or skipped depending upon responses to questions that precede the conditional one on the timeline. See the solution I used below:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
    
      <!-- Experiment Title -->
      <title>Experiment</title>
    
      <!-- Loading in libraries  -->
      <script src="https://unpkg.com/jspsych@7.3.3"></script>
      <script src="https://unpkg.com/@jspsych/plugin-html-keyboard-response@1.1.2"></script>
      <script src="https://unpkg.com/@jspsych/plugin-html-button-response@1.1.2"></script>
      <link href="https://unpkg.com/jspsych@7.3.3/css/jspsych.css" rel="stylesheet" type="text/css" />   
    </head>
    
    <!-- Determing general page style parameters -->
    <body style="background-color: rgb(200, 200, 200);">
    <script>
        
    // | - - - - - - - - - - - - - - - - - - - - - - - - - - |
    // | - - - - - - - - EXPERIMENT VARIABLES - - - - - - -  |
    // | - - - - - - - - - - - - - - - - - - - - - - - - - - |   
    
    // Initiating jsPsych
    var jsPsych = initJsPsych({
      timeline: timeline,
    });
        
    // Creating an array of three choices, which we'll randomize the order of
    var choice = jsPsych.randomization.sampleWithoutReplacement(['A', 'B', 'C'], 3);  
    var jsPsych = initJsPsych();
    
    
    // | - - - - - - - - - - - - - - - - - - - - - - - - - - |
    // | - - - - - - - - EXPERIMENT FUNCTIONS  - - - - - - - |
    // | - - - - - - - - - - - - - - - - - - - - - - - - - - |  
    
    // Creating an empty array which will act as our timeline
    var timeline = [];
    
    // Creating some message that will display if any button except A is pressed
    var Message = {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: 'Congrats, It worked! Press Space to Continue',
      choices: [' '],
    };
    
    // Creating a concluding message to indicate that the experiment has concluded
    var Conclusion = {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: 'The experiment Ended! Press Space to Continue',
      choices: [' '],
    };
    
    // Presenting three choices to choose from
    var ChoiceTrial = {
      type: jsPsychHtmlButtonResponse,
      stimulus: 'Choose A to end, or B or C to see a message',
      choices: choice,
    };
    
    // Incoporating a conditional function
    var if_node = {
      timeline: [Message],
      conditional_function: function(){
          // If the last value recorded within our data matches the index of choice A ...
          if (jsPsych.data.get().last(1).values()[0].response ==  choice.indexOf('A')) {
              // ... do not run this node within the timeline
              return false;
          } 
          // Otherwise ...
          else {
              // ... do run this node in the timeline
              return true;
          }
      }
    };
    
    // | - - - - - - - - - - - - - - - - - - - - - - - - - - |
    // | - - - - - - - - RUNNING EXPERIMENT  - - - - - - - - |
    // | - - - - - - - - - - - - - - - - - - - - - - - - - - |   
    
        // Executing the timeline
        jsPsych.run([ChoiceTrial, if_node, Conclusion]);
    
    </script>
    </body>
    </html>