JS for loop Pavlovia - multiple stimuli

jsPsych version (e.g. 7.3.1): unsure

I’ve created a Synonym Judgement Task in PsychoPy that works fine locally, and I’ve modified some of the code to fix translation issues in JavaScript. However, there are still a few things that I haven’t been able to translate properly, and I would really appreciate some help.

The main issue is related to the fact that I’m using xlrd in my python code. I’ve updated the JS code to import my stimuli using trial handlers, but I’m having trouble drawing 3 columns from the same file at once. I’m doing this because I need to display three stimuli at once (a cue in the middle, a target and a probe on either side of it).

This is my Python code:

#access word file
inbook_A2B2 = xlrd.open_workbook(A2B2_w)
insheet_A2B2 = inbook_A2B2.sheet_by_index(0)
#arrays
A2B2 = []
A2B2_target = []
A2B2_distractor = []

for rowx in list(range(1, A2B2_items +1)):
    #read in values of all columns on this row
    row = insheet_A2B2.row_values(rowx)
    #saving the word to the word array
    A2B2.append(row[0])
    A2B2_target.append(row[1])
    A2B2_distractor.append(row[2])

I implemented the TrialHandler in a routine before my loop in the Begin Experiment tab with only JS:

A2B2_w = 'resources/SocMot_A2B2_Run1.xls'
cond_mot = 'resources/Trial_Order_cleanSocMot.xlsx'

A2B2_stim = new TrialHandler({
    psychoJS: psychoJS,
    nReps: 1, method: TrialHandler.Method.SEQUENTIAL,
    extraInfo: expInfo, originPath: undefined,
    trialList: A2B2_w,
    seed: undefined, name: 'A2B2_stim'
});

And this was my attempt at the JS code. What I’m particularly struggling with is the for loop that would read the values of all columns from a row.

const A2B2_w= 'resources/SocMot_A2B2_Run1.xls'
const A2B2_items = 60;
const A2B2 = [];
const A2B2_target = [];
const A2B2_distractor = [];

for (let rowx = 1; rowx < (A2B2_items +1); rowx++) {
    let row = A2B2_stim.getRow(rowx);
    A2B2.push(parseInt(row[0]));
    A2B2_target.push(parseInt(row[1]));
    A2B2_distractor.push(parseInt(row[2]));
}

The most recent error that I’m getting is: “A2B2_stim.getRow is not a function”

How can I change the JS code to give me the values of all three columns for each row per trial?

Thanks in advance!

Have a look at PsychoPy Code Component Snippets - Google Docs

Access individual values using: aValue = myData.trialList[Idx][‘variableName’] for sequential and aValue = myData.trialList[sequenceIndices[Idx]][‘variableName’] for random.

Thanks for the suggestion! I’ve tried adding it in the Begin Experiment tab after the TrialHandler, but I’m still getting the same error. Is it supposed to go somewhere else?

A2B2_stim = new TrialHandler({
    psychoJS: psychoJS,
    nReps: 1, method: TrialHandler.Method.SEQUENTIAL,
    extraInfo: expInfo, originPath: undefined,
    trialList: A2B2_w,
    seed: undefined, name: 'A2B2_stim'
});

const A2B2 = A2B2_stim.trialList[0]['Cue']  // row 1
const A2B2_target = A2B2_stim.trialList[1]['Target']  
const A2B2_distractor = A2B2_stim.trialList[2]['Distractor']

If you still have a getRow error when you’ve replaced that code, try an incognito tab.

Personally I recommend putting all the code in Auto code components apart from the Trial handler code itself which does need editing.

Are you deliberately taking values from three different columns in three different rows?

I tired opening it in an incognito tab and I’m still getting the “getRow is not a function” error. I went back and switched to auto JS as suggested, I only had to go into the JS code to remove the xlrd related code. However, I’m getting the same error. I’m getting values from three different columns on the same row, as far as I know - it works the way it should with the local version. Am I doing something else with the JS? Would it help if I share the experiment or the full code?

Sure. If you upload the psyexp file then that might help.

Thank you!

SJT_Code_edits.psyexp (196.9 KB)

The task is alternating between blocks of a number judgement task (8 trials) and a synonym judgement task (16 trials). I have another version of the task using a different set of stimuli, and the two tasks will be presented to all participants in counterbalanced order (the second task will start after all trials from the first task have ended). I want the stimuli to be drawn from different conditions in a specific order, but the stimuli themselves should be random, so I have 2 conditions files as well. Let me know if you need any more information.

code_6 and Aud_code both still have getRow on the JS side.

I recommend avoiding “Both” code components except when absolutely necessary. I usually both the bit I need to manually translate into a Both component and the rest of the code in an Auto code component. I also often put the manual JS translation in a comment on the Python side so I can switch to Auto and then back to Both and just copy and paste the edit.

Thanks! So you just wouldn’t have getRow at all and just use row = A2B2_stim.trialList[row] inside the loop instead?

getRow doesn’t work online so I don’t use it at all

Thanks! I’m still trying to figure it out, and I really appreciate your help so far. Since I’m loading my data in the beginning using the trial handler and also specifying the columns that I want to use:

A2B2_stim = new TrialHandler({
    psychoJS: psychoJS,
    nReps: 1, method: TrialHandler.Method.SEQUENTIAL,
    extraInfo: expInfo, originPath: undefined,
    trialList: A2B2_w,
    seed: undefined, name: 'A2B2_stim'
});

const A2B2 = A2B2_stim.trialList[0]['Cue']  
const A2B2_target = A2B2_stim.trialList[1]['Target']  
const A2B2_distractor = A2B2_stim.trialList[2]['Distractor']

Then my for loop shouldn’t need to define the “row” variable and I can completely get rid of the row = A2B2_stim.getRow(rowx) bit in this code:

for (var rowx, _pj_c = 0, _pj_a = list(util.range(1, (A2B2_items + 1))), _pj_b = _pj_a.length; (_pj_c < _pj_b); _pj_c += 1) {
    rowx = _pj_a[_pj_c];
    row = A2B2_stim.getRow(rowx);
    A2B2.push(row[0]);
    A2B2_target.push(row[1]);
    A2B2_distractor.push(row[2]);
}

And eventually replace row[0] with the variables created in the beginning like this:

for (var rowx, _pj_c = 0, _pj_a = list(util.range(1, (A2B2_items + 1))), _pj_b = _pj_a.length; (_pj_c < _pj_b); _pj_c += 1) {
    rowx = _pj_a[_pj_c];
    A2B2.push(A2B2);
    A2B2_target.push(A2B2_target);
    A2B2_distractor.push(A2B2_distractor);
}

If this is right, my only issue now is an error saying “A2B2.push is not a function”. Any suggestions? I thought this might be because I don’t have the empty arrays anymore (A2B2 = [ ]) but this led to the “assignment to constant variable” error…

What is this bit supposed to do?

I can’t interpret JS code very well…it’s easier to look at the Python code.
For example, .push() should work as the automatic translation of .append() but why are you appending stuff to itself? You should append to lists and A2B2 is the cue in row 0.

(I can’t work out what your loops are trying to do either)

That was my attempt at implementing this “aValue = myData.trialList[Idx][‘variableName’]” to get the values for each of the three stimuli columns (the probe, the target, and the distractor). They all need to be presented at the same time on the screen, so essentially the entire row at once.

The for loop in python is this:

    #read in values of all columns on this row
    row = insheet_A2B2.row_values(rowx)
    #saving the word to the word array
    A2B2.append(row[0])
    A2B2_target.append(row[1])
    A2B2_distractor.append(row[2])

I’m using it to present the same row from the three columns, which then get randomized.

My confusion is that you are appending from different columns (Cue, Target, and Distractor) and from different rows (0, 1 and 2) to different lists (A2B2, A2B2_target, and A2B2_distractor)

For example, if you wanted to read in all the Cues, Targets and Distractors to independent lists, so they can be shuffled separately, you could use:

A2B2 = [ ]
A2B2_target = [ ]
A2B2_distractor = [ ]
for row in A2B2_stim.trialList:
     A2B2.append(row['Cue'])
     A2B2_target.append(row['Target'])
     A2B2_distractor.append(row['Distractor'])

But this might not be what you are trying to do.

I get why that might be confusing, but the “row[0]” or “row[1]” code is actually calling the different columns… while insheet_A2B2.row_values(rowx) is calling an entire row. Sorry if this is what you meant. I got this bit form this tutorial https://www.youtube.com/watch?v=toQ2enxAv1E&list=PL6PJquR5BWXllUt585cRJWcRTly55iXTm&index=10&ab_channel=JasonOzubko and it’s doing what it’s supposed to. The cue, target, and distractor are kind of tied to each other, so I need them to be shuffled together (e.g., rock-stone-bottle will always be show together to all participants but the trials will be in a different order). I tired the code you suggested just in case but I’m getting the same “push is not a function” error.

That tutorial is for independent randomisation. It also uses xlrd where using a loop or Trial Handler would be less confusing and could work online. See my independent randomisation demo PsychoPy Online Demos

If you want your cue, target and distractor to appear together, why aren’t you just using a loop?