Error with jsPsych Experiment on Pavlovia: Uncaught TypeError with 'version' and 'parameters'

I’ve coded an experiment which runs locally, however I am having some trouble getting it to run on pavlovia!

I believe that I have added the necessary plugins and variables as detailed here: Pavlovia
however all I am seeing is a blank screen with the following error:

Uncaught (in promise) TypeError: Cannot use ‘in’ operator to search for ‘version’ in undefined
at new Trial (jspsych.js:2271:22)
at jspsych.js:2637:115
at Array.map ()
at Timeline.instantiateChildNodes (jspsych.js:2636:54)
at Timeline.run (jspsych.js:2577:43)
at JsPsych.run (jspsych.js:2837:26)

It seems the problem is with the jspsych.js file which I downloaded from: The Basics: Hello World - jsPsych. I tried getting rid of the lines searching for “version” but I still got the following error:

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading ‘parameters’)
at Trial.processParameters (jspsych.js:2517:57)
at Trial.run (jspsych.js:2280:11)
at Timeline.run (jspsych.js:2565:32)
at JsPsych.run (jspsych.js:2821:26)

I’m not too sure why jspsych is having trouble communicating with the pavlovia server.

I’ve also attached my main html file here. If anyone had any suggestions on how to resolve this that would be greatly appreciated!

‘’’

My Experiment
<!-- Pavlovia  -->
<script src='jspsych/jquery-3.5.1.js'></script>
<script src='jspsych/jspsych-pavlovia-2022.1.1.js'></script>

<link href="jspsych/jspsych.css" rel="stylesheet" type="text/css" />
<link href="jspsych/classes.css" rel="stylesheet" type="text/css" />

<!-- Add scripts -->
<script src="survey_blocks/demo.js"></script>
<script src="survey_blocks/STAIY.js"></script>
<script src="survey_blocks/DERS.js"></script>
<script src="survey_blocks/PSQI.js"></script>
<script src="survey_blocks/DASS21.js"></script>
<script src="survey_blocks/IU.js"></script>
<script src="survey_blocks/BISBAS.js"></script>
<script src="survey_blocks/MIDI.js"></script>
<script src="survey_blocks/ERQ.js"></script>
<script src="survey_blocks/PSS.js"></script>
<script src="survey_blocks/MSPSS.js"></script>
<script src="./instructions.js"></script>
<script src="./drawing_tool.js"></script>
<script src="./completion.js"></script>
<script src="./stimuli.js"></script>

<!-- Set style -->
<style>
    /* Center-align everything */
    body {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        height: 100vh;
        margin: 0;
    }

    /* Center-align heading */
    h1 {
        text-align: center;
        margin-bottom: 20px;
    }

    /* Styled container matching the canvas dimensions and border */
    .stimulus-container {
        width: 800px; 
        height: 600px;
        border: 1px solid black; 
        display: flex;
        justify-content: center; 
        align-items: center; 
        text-align: center; 
        box-sizing: border-box; 
        margin-bottom: 20px;
    }

    .jspsych-content-wrapper {
    max-height: 80vh;
    overflow-y: auto;
}

    /* Set canvas border */
    canvas {
        border: 1px solid black;
    }
</style> 
</head> 
<body>
<h1></h1>
<script>

    var jsPsych = initJsPsych({
        use_webaudio: false,
        on_finish: function(){
            // jsPsych.data.displayData(); --> shift this to end?
            }
            });

    var pavlovia_init = {
        type: "pavlovia",
        command: "init"
    };
    
    var pavlovia_finish = {
        type: "pavlovia",
        command: "finish"
    };

    function shuffleArray(array) {
        for (let i = array.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1)); // Random index
            [array[i], array[j]] = [array[j], array[i]]; // Swap elements
        }
    }

    shuffleArray(stim);


    var trial_no = 0;  

    // Create scenario dynamically based on trial_no
    function createScenario() {
        return {
            type: jsPsychHtmlButtonResponse,
            stimulus: `
        <div class="stimulus-container" style="padding: 20px;">
            ${stim[trial_no].Scenario}
            <div style="position: absolute; bottom: 10px; right: 10px; font-size: 14px; color: #333;">
                Scenario ${trial_no + 1} / ${stim.length}
            </div>
        </div>`,
            choices: ['Continue to drawing'],
            on_finish: function() {
                // Initialize the drawing tool with a background
                initializeDrawingTool('background.png', stim[trial_no].Keyword);
                // Move to the next trial with drawing buttons
                jsPsych.run([drawingLoop]);
            }
        };
    }

    // Drawing tool logic
    const drawingToolButtons = {
        type: jsPsychHtmlButtonResponse,
        stimulus: '',
        choices: ['Reset Drawing', 'Submit Drawing'],
        response_ends_trial: true,
        on_finish: function(data) {
            if (data.response === 0) { 
                resetCanvas(); 
            } else if (data.response === 1) { 
                trial_no = trial_no + 1;  // update trial number
                saveData();
                if (trial_no == 30){
                    jsPsych.run(survey_blocks); 
                }
                else {
                    jsPsych.run([createScenario()]); // Create next scenario
                }
                
            }
        }
    };

    // Loop the drawingToolButtons until "Submit Drawing" is pressed
    const drawingLoop = {
        timeline: [drawingToolButtons],
        loop_function: function(data) {
            return data.values()[0].response !== 1; 
        }
    };



    var survey_blocks = [STAIY_block, DERS_block, DASS21_block, IU_block, BISBAS_block, MIDI_block, ERQ_block, PSS_block, MSPSS_block, completion_block, pavlovia_finish];

    jsPsych.run([
        pavlovia_init, 
        demo_block, 
        instructions_block, 
        createScenario()
    ]);
</script>
'''

Most likely an issue of having the wrong version of jsPsych, and/or using your own copy of the jsPsych code instead of the versions which are stored on Pavlovia and are designed to be compatible with it. For reference, here is how my jsPsych experiments on Pavlovia start:

    <script src="lib/vendors/jspsych-7.1.2/jspsych.js"></script>
    <script src="lib/vendors/jspsych-7.1.2/plugin-html-keyboard-response.js"></script>
    <script src="lib/vendors/jspsych-7.1.2/plugin-image-keyboard-response.js"></script>
    <script src="lib/vendors/jspsych-7.1.2/plugin-preload.js"></script>
    <link rel="stylesheet" href="lib/vendors/jspsych-7.1.2/jspsych.css">

    <script type="text/javascript" src="lib/jspsych-7-pavlovia-2022.1.1.js"></script>

Note that all these plugins and the jsPsych library itself are pulled from a library stored on Pavlovia itself (the lib/vendors/jspsych-7.1.2/ folder). This folder is accessible to every experiment on Pavlovia, so you don’t need to upload your own copy of jspsych.js. I think this will fix your problem.

1 Like

Hi, thanks heaps for you suggestion! I’ve given it a go and the experiment definitely starts up which is a good sign however it doesn’t fully run. I’m getting this error in the console: jspsych.js:2055 Trial level node is missing the “type” parameter.
I’m guessing its due to the way I’ve initiated the trial in my main html file. How did you go about initiating and running your trial?
Thanks

I think it has to do with how you’re trying to make your dynamic trial type (createScenario). jsPsych has a set of tools for dynamic stimulus parameters, they’re called “timeline variables” (see here: Creating an Experiment: The Timeline - jsPsych).

Basically I do all my timeline creation and then just call “run”, but everything dynamic (e.g., looping, filling in a parameter from a set of conditions) is handled by a “timeline node” that uses these timeline variables and has settings that handle looping and randomization.

I have a demo study I made at one point that shows an example of this: index.html · master · Jonathan Kominsky / jspsych stroop example · GitLab

I’ll snip together the most relevant parts for you:

    var trial_types = [
        {'word': 'Red', 'color': 'red', 'type': 'match'},
        {'word': 'Red', 'color': 'blue', 'type': 'mismatch'},
        {'word': 'Blue', 'color': 'blue', 'type': 'match'},
        {'word': 'Blue', 'color': 'red', 'type': 'mismatch'},
//...and several more
];

This is a list of dictionaries, each one has three parameters, and those parameters will be set every time a particular trial type is presented.

  var stroop_trial = {
        type: jsPsychHtmlKeyboardResponse,
        stimulus: function(){
            var html=`<p style="font-size:24px; color:${jspsych.timelineVariable('color')}"><b>${jspsych.timelineVariable('word')}</b></p>`;
            return html;
        },
        prompt: "F = match, J = mismatch",
        choices: ['f','j'],
        on_finish: function(data){
            if(jspsych.timelineVariable('type') == 'match'){
                if(jspsych.pluginAPI.compareKeys(data.response, 'f')){
                    data.correct = true;
                    data.score = 1;
                } else {
                    data.correct = false;
                    data.score = 0;
                }
            } else {
                if(jspsych.pluginAPI.compareKeys(data.response, 'j')){
                    data.correct = true;
                    data.score = 1;
                } else {
                    data.correct = false;
                    data.score = 0;
                }
            }

        }
    };
    trial_block.push(stroop_trial);

This is the trial type. Note the “stimulus” argument in particular uses jspsych.timelineVariable() with a key that corresponds to one of the keys from the trial types dictionary.

 var trial_node = { // this is essentially a sub-timeline.
        timeline: trial_block,
        timeline_variables: trial_types,
        randomize_order: true, //won't randomize the trial order, only the variables.
        repetitions: 2
    };
    timeline.push(trial_node);

This is where the magic happens. This node takes a sub-timeline (the trial type I defined above and a feedback trial) and presents it “repetitions” times, and every time a trial is presented anything that refers to a timeline variable will populate from the “timeline_variables” argument. Randomization controls whether it goes through the timeline variable list (trial_types) in order or shuffles it.

There are various advanced tricks you can do as well, like ending one of these loops based on meeting some accuracy threshold or what have you. It’s all in the documentation I linked.