var jsPsychSerialReactionTime = (function (jspsych) {
'use strict';
const info = {
name: "serial-reaction-time",
parameters: {
/** This nested array represents the grid of boxes shown on the screen, where each inner array is a row, and each entry in the inner array is a column. */
grid: {
type: jspsych.ParameterType.BOOL,
pretty_name: "Grid",
array: true,
default: [[1, 1, 1, 1]],
},
/** The location of the target. The array should be the [row, column] of the target. */
target: {
type: jspsych.ParameterType.INT,
pretty_name: "Target",
array: true,
default: undefined,
},
/** Nested array with dimensions that match the grid. Each entry in this array is the key that should be pressed for that corresponding location in the grid. */
choices: {
type: jspsych.ParameterType.KEYS,
pretty_name: "Choices",
array: true,
default: [["3", "5", "7", "9"]],
},
/** The width and height in pixels of each square in the grid. */
grid_square_size: {
type: jspsych.ParameterType.INT,
pretty_name: "Grid square size",
default: 100,
},
/** The color of the target square. */
target_color: {
type: jspsych.ParameterType.STRING,
pretty_name: "Target color",
default: "#999",
},
/** If true, trial ends when user makes a response */
response_ends_trial: {
type: jspsych.ParameterType.BOOL,
pretty_name: "Response ends trial",
default: true,
},
/** The number of milliseconds to display the grid before the target changes color. */
pre_target_duration: {
type: jspsych.ParameterType.INT,
pretty_name: "Pre-target duration",
default: 0,
},
/** How long to show the trial. */
trial_duration: {
type: jspsych.ParameterType.INT,
pretty_name: "Trial duration",
default: null,
},
/** If true, show feedback indicating where the user responded and whether it was correct. */
show_response_feedback: {
type: jspsych.ParameterType.BOOL,
pretty_name: "Show response feedback",
default: false,
},
/** The length of time in milliseconds to show the feedback. */
feedback_duration: {
type: jspsych.ParameterType.INT,
pretty_name: "Feedback duration",
default: 200,
},
/** If a positive number, the target will progressively change color at the start of the trial, with the transition lasting this many milliseconds. */
fade_duration: {
type: jspsych.ParameterType.INT,
pretty_name: "Fade duration",
default: null,
},
/** Any content here will be displayed below the stimulus. */
prompt: {
type: jspsych.ParameterType.HTML_STRING,
pretty_name: "Prompt",
default: null,
no_function: false,
},
},
};
/**
* **serial-reaction-time**
*
* jsPsych plugin for running a serial reaction time task with keypress responses
*
* @author Josh de Leeuw
* @see {@link https://www.jspsych.org/plugins/jspsych-serial-reaction-time/ serial-reaction-time plugin documentation on jspsych.org}
*/
class SerialReactionTimePlugin {
constructor(jsPsych) {
this.jsPsych = jsPsych;
this.stimulus = function (grid, square_size, target, target_color, labels) {
// TO DO: types for nested arrays of numbers/strings?
var stimulus = "
";
for (var i = 0; i < grid.length; i++) {
stimulus +=
"
";
for (var j = 0; j < grid[i].length; j++) {
stimulus +=
"
";
if (typeof labels !== "undefined" && labels[i][j] !== false) {
stimulus += labels[i][j];
}
stimulus += "
";
}
stimulus += "
";
}
stimulus += "
";
return stimulus;
};
}
trial(display_element, trial) {
// create a flattened version of the choices array
var flat_choices = trial.choices.flat();
while (flat_choices.indexOf("") > -1) {
flat_choices.splice(flat_choices.indexOf(""), 1);
}
var keyboardListener;
var response = {
rt: null,
key: false,
correct: false,
};
const endTrial = () => {
// kill any remaining setTimeout handlers
this.jsPsych.pluginAPI.clearAllTimeouts();
// kill keyboard listeners
this.jsPsych.pluginAPI.cancelKeyboardResponse(keyboardListener);
// gather the data to store for the trial
var trial_data = {
rt: response.rt,
response: response.key,
correct: response.correct,
grid: trial.grid,
target: trial.target,
};
// clear the display
display_element.innerHTML = "";
// move on to the next trial
this.jsPsych.finishTrial(trial_data);
};
const showFeedback = () => {
if (response.rt == null || trial.show_response_feedback == false) {
endTrial();
}
else {
var color = response.correct ? "#0f0" : "#f00";
display_element.querySelector("#jspsych-serial-reaction-time-stimulus-cell-" +
response.responseLoc[0] +
"-" +
response.responseLoc[1]).style.transition = "";
display_element.querySelector("#jspsych-serial-reaction-time-stimulus-cell-" +
response.responseLoc[0] +
"-" +
response.responseLoc[1]).style.backgroundColor = color;
this.jsPsych.pluginAPI.setTimeout(endTrial, trial.feedback_duration);
}
};
// function to handle responses by the subject
const after_response = (info) => {
// only record first response
response = response.rt == null ? info : response;
// check if the response is correct
var responseLoc = [];
for (var i = 0; i < trial.choices.length; i++) {
for (var j = 0; j < trial.choices[i].length; j++) {
var t = trial.choices[i][j];
if (this.jsPsych.pluginAPI.compareKeys(info.key, t)) {
responseLoc = [i, j];
break;
}
}
}
response.responseLoc = responseLoc;
response.correct = JSON.stringify(responseLoc) == JSON.stringify(trial.target);
if (trial.response_ends_trial) {
if (trial.show_response_feedback) {
showFeedback();
}
else {
endTrial();
}
}
};
const showTarget = () => {
if (trial.fade_duration == null) {
display_element.querySelector("#jspsych-serial-reaction-time-stimulus-cell-" + trial.target[0] + "-" + trial.target[1]).style.backgroundColor = trial.target_color;
}
else {
display_element.querySelector("#jspsych-serial-reaction-time-stimulus-cell-" + trial.target[0] + "-" + trial.target[1]).style.transition = "background-color " + trial.fade_duration;
display_element.querySelector("#jspsych-serial-reaction-time-stimulus-cell-" + trial.target[0] + "-" + trial.target[1]).style.backgroundColor = trial.target_color;
}
keyboardListener = this.jsPsych.pluginAPI.getKeyboardResponse({
callback_function: after_response,
valid_responses: flat_choices,
allow_held_key: false,
});
if (trial.trial_duration !== null) {
this.jsPsych.pluginAPI.setTimeout(showFeedback, trial.trial_duration);
}
};
// display stimulus
var stimulus = this.stimulus(trial.grid, trial.grid_square_size);
display_element.innerHTML = stimulus;
if (trial.pre_target_duration <= 0) {
showTarget();
}
else {
this.jsPsych.pluginAPI.setTimeout(showTarget(), trial.pre_target_duration);
}
//show prompt if there is one
if (trial.prompt !== null) {
display_element.innerHTML += trial.prompt;
}
}
simulate(trial, simulation_mode, simulation_options, load_callback) {
if (simulation_mode == "data-only") {
load_callback();
this.simulate_data_only(trial, simulation_options);
}
if (simulation_mode == "visual") {
this.simulate_visual(trial, simulation_options, load_callback);
}
}
create_simulation_data(trial, simulation_options) {
let key;
if (this.jsPsych.randomization.sampleBernoulli(0.8) == 1) {
key = trial.choices[trial.target[0]][trial.target[1]];
}
else {
// @ts-ignore something wrong with trial.choices type here?
key = this.jsPsych.pluginAPI.getValidKey(trial.choices);
while (key == trial.choices[trial.target[0]][trial.target[1]]) {
// @ts-ignore something wrong with trial.choices type here?
key = this.jsPsych.pluginAPI.getValidKey(trial.choices);
}
}
const default_data = {
grid: trial.grid,
target: trial.target,
response: key,
rt: this.jsPsych.randomization.sampleExGaussian(500, 50, 1 / 150, true),
correct: key == trial.choices[trial.target[0]][trial.target[1]],
};
const data = this.jsPsych.pluginAPI.mergeSimulationData(default_data, simulation_options);
this.jsPsych.pluginAPI.ensureSimulationDataConsistency(trial, data);
return data;
}
simulate_data_only(trial, simulation_options) {
const data = this.create_simulation_data(trial, simulation_options);
this.jsPsych.finishTrial(data);
}
simulate_visual(trial, simulation_options, load_callback) {
const data = this.create_simulation_data(trial, simulation_options);
const display_element = this.jsPsych.getDisplayElement();
this.trial(display_element, trial);
load_callback();
if (data.rt !== null) {
this.jsPsych.pluginAPI.pressKey(data.response, data.rt);
}
}
}
SerialReactionTimePlugin.info = info;
return SerialReactionTimePlugin;
})(jsPsychModule);