stop.html
1044 lines
<!DOCTYPE html>
<!DOCTYPE html>
<html>
<html>
<!--
<!--
_ _ _
_ _ _
___| |_ ___ _ __ (_) |_
___| |_ ___ _ __ (_) |_
/ __| __/ _ \| '_ \ _____| | __|
/ __| __/ _ \| '_ \ _____| | __|
\__ \ || (_) | |_) |_____| | |_
\__ \ || (_) | |_) |_____| | |_
|___/\__\___/| .__/ |_|\__|
|___/\__\___/| .__/ |_|\__|
|_|
|_|
AUTHORS: Luc Vermeylen & Frederick Verbruggen (2019)
AUTHORS: Luc Vermeylen & Frederick Verbruggen (2019)
DESCRIPTION: This is the jsPsych version of the stop-signal task
DESCRIPTION: This is the jsPsych version of the stop-signal task
-->
-->
<head> <!-- import the jsPsych core library, specific plugins, jquery and some other scripts-->
<head> <!-- import the jsPsych core library, specific plugins, jquery and some other scripts-->
<title>Stop Signal Task</title> <!-- defines a title in the browser tab -->
<title>Stop Signal Task</title> <!-- defines a title in the browser tab -->
<script src="js/jspsych-6.0.5/jspsych.js"></script> <!-- jsPsych core library -->
<!-- Load libraries -->
<script src="../../static/lib/jquery-3.3.1/jquery.min.js"></script>
<script src="../../static/lib/jspsych-6.3.1/jspsych.js"></script>
<script src="js/jspsych-6.0.5/plugins/jspsych-instructions.js"></script> <!-- plugins define specific tasks, e.g., presenting instructions -->
<!-- Load NivTurk plug-ins -->
<script src="../../static/js/nivturk-plugins.js" type="text/javascript"></script>
<script src="js/jspsych-6.0.5/plugins/jspsych-html-keyboard-response.js"></script>
<!-- Load experiment -->
<script src="../../static/lib/jspsych-6.0.5/jspsych.js"></script> <!-- jsPsych core library -->
<script src="js/jspsych-6.0.5/plugins/jspsych-survey-text-beta-6.1.js"></script> <!-- beta 6.1 version has the 'input required' function for text fields -->
<script src="../../static/lib/jspsych-6.0.5/plugins/jspsych-instructions.js"></script> <!-- plugins define specific tasks, e.g., presenting instructions -->
<script src="../../static/lib/jspsych-6.0.5/plugins/jspsych-html-keyboard-response.js"></script>
<script src="js/jspsych-6.0.5/plugins/jspsych-survey-multi-choice.js"></script>
<script src="../../static/lib/jspsych-6.0.5/plugins/jspsych-survey-text-beta-6.1.js"></script> <!-- beta 6.1 version has the 'input required' function for text fields -->
<script src="../../static/lib/jspsych-6.0.5/plugins/jspsych-survey-multi-choice.js"></script>
<script src="js/jspsych-6.0.5/plugins/jspsych-call-function.js"></script>
<script src="../../static/lib/jspsych-6.0.5/plugins/jspsych-call-function.js"></script>
<script src="js/jspsych-6.0.5/plugins/jspsych-fullscreen.js"></script>
<script src="../../static/lib/jspsych-6.0.5/plugins/jspsych-fullscreen.js"></script>
<script src="../../static/js/stop/jquery-1.7.1.min.js"></script> <!-- the jquery library is used to communicate with the server (to store the data) through "AJAX" and PHP -->
<script src="js/jquery-1.7.1.min.js"></script> <!-- the jquery library is used to communicate with the server (to store the data) through "AJAX" and PHP -->
<script src="../../static/js/stop/bowser.js"></script> <!-- a browser and operating system detector -->
<script src="../../static/js/stop/sprintf.js"></script> <!-- format variables in a string, used for customizable feedback strings in which the variables are not yet declared -->
<script src="js/bowser.js"></script> <!-- a browser and operating system detector -->
<script src="../../static/js/stop/custom-stop-signal-plugin.js"></script> <!-- custom plugin for the main stop-signal trial based on the image-keyboard-response plugin -->
<script src="../../static/js/stop/jspsych-detect-held-down-keys.js"></script> <!-- custom plugin for detecting if a key is being held down -->
<script src="js/sprintf.js"></script> <!-- format variables in a string, used for customizable feedback strings in which the variables are not yet declared -->
<script src="../../static/js/stop/experiment_variables.js"></script> <!-- parameters to configure the experiment -->
<script src="js/custom-stop-signal-plugin.js"></script> <!-- custom plugin for the main stop-signal trial based on the image-keyboard-response plugin -->
<script src="../../static/js/stop/text_variables.js"></script> <!-- holds all the text variables for easy modification/translation -->
<script src="js/jspsych-detect-held-down-keys.js"></script> <!-- custom plugin for detecting if a key is being held down -->
<!-- Load CSS styles -->
<script src="configuration/experiment_variables.js"></script> <!-- parameters to configure the experiment -->
<link href="../../static/lib/jspsych-6.0.5/css/jspsych.css" rel="stylesheet" type="text/css"></link> <!-- standard jsPsych css stylesheet -->
<script src="configuration/text_variables.js"></script> <!-- holds all the text variables for easy modification/translation -->
<link href="js/jspsych-6.0.5/css/jspsych.css" rel="stylesheet" type="text/css"></link> <!-- standard jsPsych css stylesheet -->
</head>
</head>
<body></body>
<body></body>
<script>
<script>
/* #########################################################################
/* #########################################################################
Initialize variables
Initialize variables
######################################################################### */
######################################################################### */
// Initialize some important variables
// Initialize some important variables
var timeline = []; // this array stores the events we want to run in the experiment
var timeline = []; // this array stores the events we want to run in the experiment
var trial_ind = 1; // trial indexing variable starts at 1 for convenience
var trial_ind = 1; // trial indexing variable starts at 1 for convenience
var block_ind = 0; // block indexing variables: block 0 is considered to be the practice block
var block_ind = 0; // block indexing variables: block 0 is considered to be the practice block
var focus = 'focus'; // tracks if the current tab/window is the active tab/window, initially the current tab should be focused
var focus = 'focus'; // tracks if the current tab/window is the active tab/window, initially the current tab should be focused
var fullscr_ON = 'no'; // tracks fullscreen activity, initially not activated
var fullscr_ON = 'no'; // tracks fullscreen activity, initially not activated
var redirect_timeout = 1500; // set this so that data is saved before redirect!
var redirect_timeout = 1500; // set this so that data is saved before redirect!
// is the experiment running from a server or not? (this determines if data is saved on server or offline)
// is the experiment running from a server or not? (this determines if data is saved on server or offline)
if (document.location.host) { // returns your host or null
if (document.location.host) { // returns your host or null
online = true;
online = true;
} else {
} else {
online = false;
online = false;
};
};
// detect visitor variables with the bowser js library (/js/bowser.js)
// detect visitor variables with the bowser js library (/js/bowser.js)
jsPsych.data.addProperties({ // add these variables to all rows of the datafile
jsPsych.data.addProperties({ // add these variables to all rows of the datafile
battery_task: 'stop',
task_version: '1.0',
jspsych_version: '6.0.5',
browser_name: bowser.name, browser_version: bowser.version,
browser_name: bowser.name, browser_version: bowser.version,
os_name: bowser.osname, os_version: bowser.osversion,
os_name: bowser.osname, os_version: bowser.osversion,
tablet: String(bowser.tablet), mobile: String(bowser.mobile),
tablet: String(bowser.tablet), mobile: String(bowser.mobile),
// convert explicitly to string so that "undefined" (no response) does not lead to empty cells in the datafile
// convert explicitly to string so that "undefined" (no response) does not lead to empty cells in the datafile
screen_resolution: screen.width + ' x ' + screen.height,
screen_resolution: screen.width + ' x ' + screen.height,
window_resolution: window.innerWidth + ' x ' + window.innerHeight, // this will be updated throughout the experiment
window_resolution: window.innerWidth + ' x ' + window.innerHeight, // this will be updated throughout the experiment
});
});
// define the images to be loaded, the actual preloading occurs in the jsPsych.init function at the bottom
// define the images to be loaded, the actual preloading occurs in the jsPsych.init function at the bottom
var pre_load_stimuli = [fix_stim, go_stim1, go_stim2, stop_stim1, stop_stim2];
var pre_load_stimuli = [fix_stim, go_stim1, go_stim2, stop_stim1, stop_stim2];
/* #########################################################################
/* #########################################################################
Create the design based on the input from 'experiment_variables.js'
Create the design based on the input from 'experiment_variables.js'
######################################################################### */
######################################################################### */
// Since we have two stimuli, the number of trials of the basic design = 2 * nstim
// Since we have two stimuli, the number of trials of the basic design = 2 * nstim
// This design will later be repeated a few times for each block
// This design will later be repeated a few times for each block
// (number of repetitions is also defined in 'experiment_variables.js')
// (number of repetitions is also defined in 'experiment_variables.js')
var ngostop = 1/nprop // covert proportion to trial numbers. E.g. 1/5 = 1 stop signal and 4 go
var ngostop = 1/nprop // covert proportion to trial numbers. E.g. 1/5 = 1 stop signal and 4 go
var ntrials = ngostop * 2 // total number of trials in basic design (2 two choice stimuli x ngostop)
var ntrials = ngostop * 2 // total number of trials in basic design (2 two choice stimuli x ngostop)
var signalArray = Array(ngostop-1).fill('go'); // no-signal trials
var signalArray = Array(ngostop-1).fill('go'); // no-signal trials
signalArray[ngostop-1] = ('stop'); // stop-signal trials
signalArray[ngostop-1] = ('stop'); // stop-signal trials
// create factorial design from choices(2) and signal(nstim)
// create factorial design from choices(2) and signal(nstim)
var factors = {
var factors = {
stim: [choice_stim1, choice_stim2],
stim: [choice_stim1, choice_stim2],
signal: signalArray,
signal: signalArray,
};
};
var design = jsPsych.randomization.factorial(factors, 1);
var design = jsPsych.randomization.factorial(factors, 1);
// modify the design to make it compatible with the custom stop signal plugin
// modify the design to make it compatible with the custom stop signal plugin
// - set a first/second stimulus property.
// - set a first/second stimulus property.
// on no-signal trials, only one image will be used (i.e. the go image/stimulus)
// on no-signal trials, only one image will be used (i.e. the go image/stimulus)
// on stop-signal trials, two images will be used (i.e. the go and stop images/stimuli)
// on stop-signal trials, two images will be used (i.e. the go and stop images/stimuli)
// - set a data property with additional attributes for identifying the type of trial
// - set a data property with additional attributes for identifying the type of trial
for (var i = 0; i < design.length; i++) {
for (var i = 0; i < design.length; i++) {
design[i].data = {}
design[i].data = {}
if ((design[i].stim == choice_stim1) && (design[i].signal == 'go')) {
if ((design[i].stim == choice_stim1) && (design[i].signal == 'go')) {
design[i].fixation = fix_stim;
design[i].fixation = fix_stim;
design[i].first_stimulus = go_stim1;
design[i].first_stimulus = go_stim1;
design[i].second_stimulus = go_stim1;
design[i].second_stimulus = go_stim1;
design[i].data.stim = choice_stim1;
design[i].data.stim = choice_stim1;
design[i].data.correct_response = cresp_stim1;
design[i].data.correct_response = cresp_stim1;
design[i].data.signal = "no";
design[i].data.signal = "no";
} else if ((design[i].stim == choice_stim2) && (design[i].signal == 'go')) {
} else if ((design[i].stim == choice_stim2) && (design[i].signal == 'go')) {
design[i].fixation = fix_stim;
design[i].fixation = fix_stim;
design[i].first_stimulus = go_stim2;
design[i].first_stimulus = go_stim2;
design[i].second_stimulus = go_stim2;
design[i].second_stimulus = go_stim2;
design[i].data.stim = choice_stim2;
design[i].data.stim = choice_stim2;
design[i].data.correct_response = cresp_stim2;
design[i].data.correct_response = cresp_stim2;
design[i].data.signal = "no";
design[i].data.signal = "no";
} else if ((design[i].stim == choice_stim1) && (design[i].signal == 'stop')) {
} else if ((design[i].stim == choice_stim1) && (design[i].signal == 'stop')) {
design[i].fixation = fix_stim;
design[i].fixation = fix_stim;
design[i].first_stimulus = go_stim1;
design[i].first_stimulus = go_stim1;
design[i].second_stimulus = stop_stim1;
design[i].second_stimulus = stop_stim1;
design[i].data.stim = choice_stim1;
design[i].data.stim = choice_stim1;
design[i].data.correct_response = "undefined";
design[i].data.correct_response = "undefined";
design[i].data.signal = "yes";
design[i].data.signal = "yes";
} else if ((design[i].stim == choice_stim2) && (design[i].signal == 'stop')) {
} else if ((design[i].stim == choice_stim2) && (design[i].signal == 'stop')) {
design[i].fixation = fix_stim;
design[i].fixation = fix_stim;
design[i].first_stimulus = go_stim2;
design[i].first_stimulus = go_stim2;
design[i].second_stimulus = stop_stim2;
design[i].second_stimulus = stop_stim2;
design[i].data.stim = choice_stim2;
design[i].data.stim = choice_stim2;
design[i].data.correct_response = "undefined";
design[i].data.correct_response = "undefined";
design[i].data.signal = "yes";
design[i].data.signal = "yes";
}
}
delete design[i].signal; delete design[i].stim;
delete design[i].signal; delete design[i].stim;
};
};
//console.log(design); // uncomment to print the design in the browser's console
//console.log(design); // uncomment to print the design in the browser's console
/* #########################################################################
/* #########################################################################
Define the individual events/trials that make up the experiment
Define the individual events/trials that make up the experiment
######################################################################### */
######################################################################### */
// welcome message trial. Also: end the experiment if browser is not Chrome or Firefox
// welcome message trial. Also: end the experiment if browser is not Chrome or Firefox
var welcome = {
var welcome = {
type: "instructions",
type: "instructions",
pages: welcome_message,
pages: welcome_message,
show_clickable_nav: true,
show_clickable_nav: true,
allow_backward: false,
allow_backward: false,
button_label_next: label_next_button,
button_label_next: label_next_button,
on_start: function(trial){
on_start: function(trial){
if (bowser.name == 'Firefox' || bowser.name == 'Chrome'){
if (bowser.name == 'Firefox' || bowser.name == 'Chrome'){
trial.pages = welcome_message;
trial.pages = welcome_message;
} else {
} else {
trial.pages = not_supported_message;
trial.pages = not_supported_message;
setTimeout(function(){location.href="html/not_supported.html"}, 2000);
setTimeout(function(){location.href="html/not_supported.html"}, 2000);
}
}
}
}
};
};
// these events turn fullscreen mode on in the beginning and off at the end, if enabled (see experiment_variables.js)
// these events turn fullscreen mode on in the beginning and off at the end, if enabled (see experiment_variables.js)
var fullscr = {
var fullscr = {
type: 'fullscreen',
type: 'fullscreen',
fullscreen_mode: true,
fullscreen_mode: true,
message: full_screen_message,
message: full_screen_message,
button_label: label_next_button,
button_label: label_next_button,
};
};
var fullscr_off = {
var fullscr_off = {
type: 'fullscreen',
type: 'fullscreen',
fullscreen_mode: false,
fullscreen_mode: false,
button_label: label_next_button,
button_label: label_next_button,
};
};
// informed consent trial. The informed_consent_text variable comes from /configuration/text_variables.js
// informed consent trial. The informed_consent_text variable comes from /configuration/text_variables.js
// CONTE 2025-- Not used
var consent = {
var consent = {
type: "instructions",
type: "instructions",
pages: [informed_consent_text],
pages: [informed_consent_text],
show_clickable_nav: true,
show_clickable_nav: true,
button_label_next: label_consent_button,
button_label_next: label_consent_button,
allow_backward: false
allow_backward: false
};
};
// if enabled below, get participant's id from participant and add it to the datafile.
// if enabled below, get participant's id from participant and add it to the datafile.
// the prompt is declared in the configuration/text_variables.js file
// CONTE 2025-- Not used
// the prompt is declared in the configuration/text_variables.js file
var participant_id = {
var participant_id = {
type: 'survey-text',
type: 'survey-text',
questions: [{
questions: [{
prompt: subjID_instructions,
prompt: subjID_instructions,
required: true
required: true
}, ],
}, ],
button_label: label_next_button,
button_label: label_next_button,
on_finish: function(data) {
on_finish: function(data) {
var responses = JSON.parse(data.responses);
var responses = JSON.parse(data.responses);
var code = responses.Q0;
var code = responses.Q0;
jsPsych.data.addProperties({
jsPsych.data.addProperties({
participantID: code
workerId: code
});
});
}
}
};
};
// get participant's age and add it to the datafile
// get participant's age and add it to the datafile
// the prompt is declared in the configuration/text_variables.js file
// CONTE 2025-- Not used
// the prompt is declared in the configuration/text_variables.js file
var age = {
var age = {
type: 'survey-text',
type: 'survey-text',
questions: [{
questions: [{
prompt: age_instructions,
prompt: age_instructions,
required: true
required: true
}, ],
}, ],
button_label: label_next_button,
button_label: label_next_button,
on_finish: function(data) {
on_finish: function(data) {
var responses = JSON.parse(data.responses);
var responses = JSON.parse(data.responses);
var code = responses.Q0;
var code = responses.Q0;
jsPsych.data.addProperties({
jsPsych.data.addProperties({
age: code
age: code
});
});
}
}
};
};
// get participant's gender and add it to the datafile
// get participant's gender and add it to the datafile (the prompt and options are declared in the configuration/text_variables.js file)
// CONTE 2025-- Not used
// the prompt and options are declared in the configuration/text_variables.js file
var gender = {
var gender = {
type: 'survey-multi-choice',
type: 'survey-multi-choice',
questions: [{
questions: [{
prompt: gender_instructions,
prompt: gender_instructions,
options: gender_options,
options: gender_options,
required: true
required: true
}, ],
}, ],
button_label: label_next_button,
button_label: label_next_button,
on_finish: function(data) {
on_finish: function(data) {
var responses = JSON.parse(data.responses);
var responses = JSON.parse(data.responses);
var code = responses.Q0;
var code = responses.Q0;
jsPsych.data.addProperties({
jsPsych.data.addProperties({
gender: code
gender: code
});
});
}
}
};
};
// instruction trial
// instruction trial
// the instructions are declared in the configuration/text_variables.js file
// the instructions are declared in the configuration/text_variables.js file
var instructions = {
var instructions = {
type: "instructions",
type: "instructions",
pages: [page1, page2],
pages: [page1, page2],
show_clickable_nav: true
show_clickable_nav: true
,
,
button_label_previous: label_previous_button,
button_label_previous: label_previous_button,
button_label_next: label_next_button,
button_label_next: label_next_button,
};
};
// start of each block
// start of each block
// the start message is declared in the configuration/text_variables.js file
// the start message is declared in the configuration/text_variables.js file
var block_start = {
var block_start = {
type: 'html-keyboard-response',
type: 'html-keyboard-response',
stimulus: text_at_start_block,
stimulus: text_at_start_block,
choices: ['space']
choices: ['space']
};
};
// get ready for beginning of block
// get ready for beginning of block
// the get ready message is declared in the configuration/text_variables.js file
// the get ready message is declared in the configuration/text_variables.js file
var block_get_ready = {
var block_get_ready = {
type: 'html-keyboard-response',
type: 'html-keyboard-response',
stimulus: get_ready_message,
stimulus: get_ready_message,
choices: jsPsych.NO_KEYS,
choices: jsPsych.NO_KEYS,
trial_duration: 2000,
trial_duration: 2000,
};
};
// blank inter-trial interval
// blank inter-trial interval
var blank_ITI = {
var blank_ITI = {
type: 'jspsych-detect-held-down-keys',
type: 'jspsych-detect-held-down-keys',
// this enables the detection of held down keys
// this enables the detection of held down keys
stimulus: "",
stimulus: "",
// blank
// blank
trial_duration: ITI/2,
trial_duration: ITI/2,
response_ends_trial: false,
response_ends_trial: false,
};
};
// now put the trial in a node that loops (if response is registered)
// now put the trial in a node that loops (if response is registered)
var held_down_node = {
var held_down_node = {
timeline: [blank_ITI],
timeline: [blank_ITI],
loop_function: function(data){
loop_function: function(data){
if(data.values()[0].key_press != null){
if(data.values()[0].key_press != null){
return true; // keep looping when a response is registered
return true; // keep looping when a response is registered
} else {
} else {
return false; // break out of loop when no response is registered
return false; // break out of loop when no response is registered
}
}
}
}
};
};
// the main stimulus
// the main stimulus
// use custom-stop-signal-plugin.js to show three consecutive stimuli within one trial
// use custom-stop-signal-plugin.js to show three consecutive stimuli within one trial
// (fixation -> first stimulus -> second stimulus, with variable inter-stimuli-intervals)
// (fixation -> first stimulus -> second stimulus, with variable inter-stimuli-intervals)
var stimulus = {
var stimulus = {
type: 'custom-stop-signal-plugin',
type: 'custom-stop-signal-plugin',
fixation: jsPsych.timelineVariable('fixation'),
fixation: jsPsych.timelineVariable('fixation'),
fixation_duration: FIX,
fixation_duration: FIX,
stimulus1: jsPsych.timelineVariable('first_stimulus'),
stimulus1: jsPsych.timelineVariable('first_stimulus'),
stimulus2: jsPsych.timelineVariable('second_stimulus'),
stimulus2: jsPsych.timelineVariable('second_stimulus'),
trial_duration: MAXRT, // this is the max duration of the actual stimulus (excluding fixation time)
trial_duration: MAXRT, // this is the max duration of the actual stimulus (excluding fixation time)
// inter stimulus interval between first and second stimulus = stop signal delay (SSD)
// inter stimulus interval between first and second stimulus = stop signal delay (SSD)
ISI: function() {
ISI: function() {
var duration = SSD;
var duration = SSD;
return duration
return duration
},
},
response_ends_trial: true,
response_ends_trial: true,
choices: [cresp_stim1, cresp_stim2],
choices: [cresp_stim1, cresp_stim2],
data: jsPsych.timelineVariable('data'),
data: jsPsych.timelineVariable('data'),
// was the response correct? adapt SSD accordingly
// was the response correct? adapt SSD accordingly
on_finish: function(data) {
on_finish: function(data) {
// check if the response was correct
// check if the response was correct
data.response = jsPsych.pluginAPI.convertKeyCodeToKeyCharacter(data.key_press); // keys are stored in keycodes not in character, so convert for convenience
data.response = jsPsych.pluginAPI.convertKeyCodeToKeyCharacter(data.key_press); // keys are stored in keycodes not in character, so convert for convenience
data.response = String(data.response); // convert explicitly to string so that "undefined" (no response) does not lead to empty cells in the datafile
data.response = String(data.response); // convert explicitly to string so that "undefined" (no response) does not lead to empty cells in the datafile
data.correct = data.response == data.correct_response;
data.correct = data.response == data.correct_response;
// if no response was made, the reaction time should not be -250 but null
// if no response was made, the reaction time should not be -250 but null
if (data.rt == -250) {
if (data.rt == -250) {
data.rt = null
data.rt = null
};
};
// on go trials, reaction times on the fixation (below zero) are always wrong
// on go trials, reaction times on the fixation (below zero) are always wrong
if (data.signal == 'no' && data.rt < 0){
if (data.signal == 'no' && data.rt < 0){
data.correct = false;
data.correct = false;
};
};
// set and adapt stop signal delay (SSD)
// set and adapt stop signal delay (SSD)
data.SSD = SSD;
data.SSD = SSD;
data.trial_i = trial_ind;
data.trial_i = trial_ind;
data.block_i = block_ind;
data.block_i = block_ind;
trial_ind = trial_ind + 1;
trial_ind = trial_ind + 1;
if (data.signal == 'yes') {
if (data.signal == 'yes') {
if (data.correct) {
if (data.correct) {
SSD = SSD + SSDstep;
SSD = SSD + SSDstep;
if (SSD >= MAXRT) {
if (SSD >= MAXRT) {
SSD = MAXRT - SSDstep
SSD = MAXRT - SSDstep
};
};
} else {
} else {
SSD = SSD - SSDstep;
SSD = SSD - SSDstep;
if (SSD <= SSDstep) {
if (SSD <= SSDstep) {
SSD = SSDstep
SSD = SSDstep
};
};
}
}
}
}
}
}
};
};
// trial-by-trial feedback
// trial-by-trial feedback
// messages are defined in the configuration/text_variables.js file
// messages are defined in the configuration/text_variables.js file
var trial_feedback = {
var trial_feedback = {
type: 'html-keyboard-response',
type: 'html-keyboard-response',
choices: jsPsych.NO_KEYS,
choices: jsPsych.NO_KEYS,
trial_duration: iFBT,
trial_duration: iFBT,
stimulus: function() {
stimulus: function() {
var last_trial_data = jsPsych.data.get().last(1).values()[0];
var last_trial_data = jsPsych.data.get().last(1).values()[0];
if (last_trial_data['signal'] === 'no') {
if (last_trial_data['signal'] === 'no') {
// go trials
// go trials
if (last_trial_data['correct']) {
if (last_trial_data['correct']) {
return correct_msg
return correct_msg
} else {
} else {
if (last_trial_data['response'] === "undefined") {
if (last_trial_data['response'] === "undefined") {
// no response previous trial
// no response previous trial
return too_slow_msg
return too_slow_msg
} else {
} else {
if (last_trial_data['rt'] >= 0) {
if (last_trial_data['rt'] >= 0) {
return incorrect_msg
return incorrect_msg
} else {
} else {
return too_fast_msg
return too_fast_msg
}
}
}
}
}
}
} else {
} else {
// stop trials
// stop trials
if (last_trial_data['correct']) {
if (last_trial_data['correct']) {
return correct_stop_msg
return correct_stop_msg
} else {
} else {
if (last_trial_data['rt'] >= 0) {
if (last_trial_data['rt'] >= 0) {
return incorrect_stop_msg
return incorrect_stop_msg
} else {
} else {
return too_fast_msg
return too_fast_msg
}
}
}
}
}
}
}
}
};
};
// at the end of the block, give feedback on performance
// at the end of the block, give feedback on performance
var block_feedback = {
var block_feedback = {
type: 'html-keyboard-response',
type: 'html-keyboard-response',
trial_duration: bFBT,
trial_duration: bFBT,
choices: function() {
choices: function() {
if (block_ind == NexpBL){
if (block_ind == NexpBL){
return ['p','space']
return ['p','space']
} else {
} else {
return ['p'] // 'p' can be used to skip the feedback, useful for debugging
return ['p'] // 'p' can be used to skip the feedback, useful for debugging
}
}
},
},
stimulus: function() {
stimulus: function() {
// calculate performance measures
// calculate performance measures
var ns_trials = jsPsych.data.get().filter({
var ns_trials = jsPsych.data.get().filter({
trial_type: 'custom-stop-signal-plugin',
trial_type: 'custom-stop-signal-plugin',
block_i: block_ind,
block_i: block_ind,
signal: 'no'
signal: 'no'
});
});
var avg_nsRT = Math.round(ns_trials.select('rt').subset(function(x){ return x > 0; }).mean());
var avg_nsRT = Math.round(ns_trials.select('rt').subset(function(x){ return x > 0; }).mean());
var prop_ns_Correct = Math.round(ns_trials.filter({
var prop_ns_Correct = Math.round(ns_trials.filter({
correct: true
correct: true
}).count() / ns_trials.count() * 1000) / 1000; // unhandy multiplying and dividing by 1000 necessary to round to two decimals
}).count() / ns_trials.count() * 1000) / 1000; // unhandy multiplying and dividing by 1000 necessary to round to two decimals
var prop_ns_Missed = Math.round(ns_trials.filter({
var prop_ns_Missed = Math.round(ns_trials.filter({
key_press: null
key_press: null
}).count() / ns_trials.count() * 1000) / 1000;
}).count() / ns_trials.count() * 1000) / 1000;
var prop_ns_Incorrect = Math.round((1 - (prop_ns_Correct + prop_ns_Missed)) * 1000) / 1000;
var prop_ns_Incorrect = Math.round((1 - (prop_ns_Correct + prop_ns_Missed)) * 1000) / 1000;
var ss_trials = jsPsych.data.get().filter({
var ss_trials = jsPsych.data.get().filter({
trial_type: 'custom-stop-signal-plugin',
trial_type: 'custom-stop-signal-plugin',
block_i: block_ind,
block_i: block_ind,
signal: 'yes'
signal: 'yes'
});
});
var prop_ss_Correct = Math.round(ss_trials.filter({
var prop_ss_Correct = Math.round(ss_trials.filter({
correct: true
correct: true
}).count() / ss_trials.count() * 1000) / 1000;
}).count() / ss_trials.count() * 1000) / 1000;
// in the last block, we should not say that there will be a next block
// in the last block, we should not say that there will be a next block
if (block_ind == NexpBL){
if (block_ind == NexpBL){
var next_block_text = final_block_msg
var next_block_text = final_block_msg
} else { // make a countdown timer
} else { // make a countdown timer
var count=(bFBT/1000);
var count=(bFBT/1000);
var counter;
var counter;
clearInterval(counter);
clearInterval(counter);
counter=setInterval(timer, 1000); //1000 will run it every 1 second
counter=setInterval(timer, 1000); //1000 will run it every 1 second
function timer(){
function timer(){
count=count-1;
count=count-1;
if (count <= 0){
if (count <= 0){
clearInterval(counter);
clearInterval(counter);
}
}
document.getElementById("timer").innerHTML = count ;
document.getElementById("timer").innerHTML = count ;
}
}
var next_block_text = next_block_msg // insert countdown timer
var next_block_text = next_block_msg // insert countdown timer
}
}
// the final text to present. Can also show correct and incorrect proportions if requested.
// the final text to present. Can also show correct and incorrect proportions if requested.
return [
return [
no_signal_header +
no_signal_header +
sprintf(avg_rt_msg,avg_nsRT) +
sprintf(avg_rt_msg,avg_nsRT) +
sprintf(prop_miss_msg,prop_ns_Missed) +
sprintf(prop_miss_msg,prop_ns_Missed) +
stop_signal_header +
stop_signal_header +
sprintf(prop_corr_msg,prop_ss_Correct) +
sprintf(prop_corr_msg,prop_ss_Correct) +
next_block_text
next_block_text
]
]
},
},
on_finish: function() {
on_finish: function() {
trial_ind = 1; // reset trial counter
trial_ind = 1; // reset trial counter
block_ind = block_ind + 1; // next block
block_ind = block_ind + 1; // next block
}
}
};
};
var evaluate_end_if_practice = {
var evaluate_end_if_practice = {
type: 'call-function',
type: 'call-function',
func: function() {
func: function() {
if (block_ind == 0) { // this limits the amount of trials in the practice block
if (block_ind == 0) { // this limits the amount of trials in the practice block
if (trial_ind > NdesignReps_practice * ntrials) {
if (trial_ind > NdesignReps_practice * ntrials) {
jsPsych.endCurrentTimeline();
jsPsych.endCurrentTimeline();
}
}
}
}
}
}
};
};
// end trial and save the data
// end trial and save the data
var goodbye = {
var goodbye = {
type: "html-keyboard-response",
type: "html-keyboard-response",
stimulus: end_message,
stimulus: end_message,
on_start: function(data) {
on_start: function(data) {
var subjID = jsPsych.data.get().last(1).values()[0]['participantID'];
var subjID = jsPsych.data.get().last(1).values()[0]['workerId'];
var full_data = jsPsych.data.get();
var full_data = jsPsych.data.get();
var ignore_columns = ['raw_rt','trial_type','first_stimulus','second_stimulus','onset_of_first_stimulus',
var ignore_columns = ['raw_rt','trial_type','first_stimulus','second_stimulus','onset_of_first_stimulus',
'onset_of_second_stimulus','key_press','correct_response','trial_index','internal_node_id'];
'onset_of_second_stimulus','key_press','correct_response','trial_index','internal_node_id'];
var rows = {trial_type: 'custom-stop-signal-plugin'}; // we are only interested in our main stimulus, not fixation, feedback etc.
var rows = {trial_type: 'custom-stop-signal-plugin'}; // we are only interested in our main stimulus, not fixation, feedback etc.
var selected_data = jsPsych.data.get().filter(rows).ignore(ignore_columns);
var selected_data = jsPsych.data.get().filter(rows).ignore(ignore_columns);
// the next piece of codes orders the columns of the data file
// the next piece of codes orders the columns of the data file
var d = selected_data.values() // get the data values
var d = selected_data.values() // get the data values
// make an array that specifies the order of the object properties
// make an array that specifies the order of the object properties
var arr = ['participantID','age','gender','block_i','trial_i','stim','signal','SSD','response','rt','correct','focus','Fullscreen',
var arr = ['workerId','age','gender','block_i','trial_i','stim','signal','SSD','response','rt','correct','focus','Fullscreen',
'time_elapsed','browser_name','browser_version','os_name','os_version','tablet','mobile','screen_resolution','window_resolution'];
'time_elapsed','browser_name','browser_version','os_name','os_version','tablet','mobile','screen_resolution','window_resolution'];
new_arr = [] // we will fill this array with the ordered data
new_arr = [] // we will fill this array with the ordered data
function myFunction(item) { // this is function is called in the arr.forEach call below
function myFunction(item) { // this is function is called in the arr.forEach call below
new_obj[item] = obj[item]
new_obj[item] = obj[item]
return new_obj
return new_obj
}
}
// do it for the whole data array
// do it for the whole data array
for (i = 0; i < d.length; i++) {
for (i = 0; i < d.length; i++) {
obj = d[i]; // get one row of data
obj = d[i]; // get one row of data
new_obj = {};
new_obj = {};
arr.forEach(myFunction) // for each element in the array run my function
arr.forEach(myFunction) // for each element in the array run my function
selected_data.values()[i] = new_obj; // insert the ordered values back in the jsPsych.data object
selected_data.values()[i] = new_obj; // insert the ordered values back in the jsPsych.data object
}
}
if (!online) {
if (!online) {
selected_data.localSave('csv', 'SST_data_' + subjID + '.csv');
selected_data.localSave('csv', 'SST_data_' + subjID + '.csv');
}
}
}
}
};
};
/* #########################################################################
save and redirect function (instead of using the one from nivturk-plugins.js)
######################################################################### */
function on_success(experiment) {
const payload = {
experiment: experiment,
data: jsPsych.data.get().json()
};
fetch('/on_success', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
})
.then(response => {
if (!response.ok) throw new Error("Network response was not ok");
window.location.replace('/main');
})
.catch(error => {
console.error('Error submitting experiment data:', error);
window.location.replace('/main');
});
}
/* #########################################################################
/* #########################################################################
combine trials in procedures (create nested timeline)
combine trials in procedures (create nested timeline)
######################################################################### */
######################################################################### */
// only ask for participant id if 'id' = 'particpant' (experiment_variables.js)
// only ask for participant id if 'id' = 'particpant' (experiment_variables.js)
// if 'id' = 'url', get it from url; otherwise, generate random value
// if 'id' = 'url', get it from url; otherwise, generate random value
// only go into fullscreen mode if 'fullscreen' is true
// only go into fullscreen mode if 'fullscreen' is true
if (id == "participant"){
if (id == "participant"){
// CONTE: not used
if (fullscreen){
if (fullscreen){
// CONTE: if we wanted to use this, we'd take out: consent, participant_id, age, gender
var start_timeline = [welcome, consent, participant_id, age, gender, fullscr, instructions]
var start_timeline = [welcome, consent, participant_id, age, gender, fullscr, instructions]
} else {
} else {
// CONTE: if we wanted to use this, we'd take out: consent, participant_id, age, gender
var start_timeline = [welcome, consent, participant_id, age, gender, instructions]
var start_timeline = [welcome, consent, participant_id, age, gender, instructions]
}
}
} else {
} else {
if (id == 'prolific'){
// CONTE: CORRECT!
// Add participant info to all trials
jsPsych.data.addProperties({workerId: "{{ workerId }}"});
}
if (id == "url"){
if (id == "url"){
// CONTE: not used
var urlvar = jsPsych.data.urlVariables();
var urlvar = jsPsych.data.urlVariables();
var code = urlvar.subject; jsPsych.data.addProperties({participantID: code});
var code = urlvar.subject; jsPsych.data.addProperties({workerId: code});
} else {
} if (id == "random") {
// CONTE: not used
var code = jsPsych.randomization.randomID(); jsPsych.data.addProperties({participantID: code});
var code = jsPsych.randomization.randomID(); jsPsych.data.addProperties({workerId: code});
}
}
if (fullscreen) {
if (fullscreen) {
var start_timeline = [welcome, consent, age, gender, fullscr, instructions]
var start_timeline = [welcome, fullscr, instructions] // took out age and gender questions/pages
} else {
} else {
var start_timeline = [welcome, consent, age, gender, instructions]
var start_timeline = [welcome, instructions] // took out age and gender questions/pages
}
}
}
}
// start the experiment with the previously defined start_timeline trials
// start the experiment with the previously defined start_timeline trials
var start_procedure = {
var start_procedure = {
timeline: start_timeline,
timeline: start_timeline,
};
};
// put trial_feedback in its own timeline to make it conditional (only to be shown during the practice block)
// put trial_feedback in its own timeline to make it conditional (only to be shown during the practice block)
var feedback_node = {
var feedback_node = {
timeline: [trial_feedback],
timeline: [trial_feedback],
conditional_function: function() {
conditional_function: function() {
var last_trial_data = jsPsych.data.get().last(1).values()[0];
var last_trial_data = jsPsych.data.get().last(1).values()[0];
var current_block = block_ind;
var current_block = block_ind;
if (current_block == 0) {
if (current_block == 0) {
// this was previously set to provide feedback only on incorrect trials by adding: && last_trial_data['correct']==false
// this was previously set to provide feedback only on incorrect trials by adding: && last_trial_data['correct']==false
return true;
return true;
} else {
} else {
return false;
return false;
}
}
}
}
};
};
// timeline_variables determine the stimuli in the 'stimulus' trial
// timeline_variables determine the stimuli in the 'stimulus' trial
var trial_procedure = {
var trial_procedure = {
timeline: [blank_ITI, held_down_node, stimulus, feedback_node, evaluate_end_if_practice],
timeline: [blank_ITI, held_down_node, stimulus, feedback_node, evaluate_end_if_practice],
timeline_variables: design,
timeline_variables: design,
randomize_order: true,
randomize_order: true,
repetitions: NdesignReps_exp,
repetitions: NdesignReps_exp,
};
};
// again: combine the following screen in one timeline, which constitues of the procedure of one block
// again: combine the following screen in one timeline, which constitues of the procedure of one block
var block_procedure = {
var block_procedure = {
timeline: [block_start, block_get_ready, trial_procedure, block_feedback],
timeline: [block_start, block_get_ready, trial_procedure, block_feedback],
randomize_order: false,
randomize_order: false,
repetitions: NexpBL+1, // add one because the first block is the practice block
repetitions: NexpBL+1, // add one because the first block is the practice block
};
};
// end of the experiment
// end of the experiment
if (fullscreen){
if (fullscreen){
end_timeline = [fullscr_off, goodbye]
end_timeline = [fullscr_off, goodbye]
} else {
} else {
end_timeline = [goodbye]
end_timeline = [goodbye]
}
}
var end_procedure = {
var end_procedure = {
timeline: end_timeline, // here, you could add questionnaire trials etc...
timeline: end_timeline, // here, you could add questionnaire trials etc...
};
};
// finally, push all the procedures to the overall timeline
// finally, push all the procedures to the overall timeline
timeline.push(start_procedure, block_procedure, end_procedure)
timeline.push(start_procedure, block_procedure, end_procedure)
/* #########################################################################
/* #########################################################################
the functions that save the data and initiates the experiment
the functions that save the data and initiates the experiment
######################################################################### */
##########################################
// function that appends data to an existing file (or creates the file if it does not exist)
function appendData(filename, filedata) {
$.ajax({ // make sure jquery-1.7.1.min.js is loaded in the html header for this to work
type: 'post',
cache: false,
url: 'php/save_data_append.php', // IMPORTANT: change the php script to link to the directory of your server where you want to store the data!
data: {
filename: filename,
filedata: filedata
},
});
};
// run the experiment!
jsPsych.init({
timeline: timeline,
preload_images: pre_load_stimuli,
on_data_update: function(data) { // each time the data is updated:
// write the current window resolution to the data
data.window_resolution = window.innerWidth + ' x ' + window.innerHeight;
// is the experiment window the active window? (focus = yes, blur = no)
data.focus = focus; data.Fullscreen = fullscr_ON;
// append a subset of the data each time a go or stop stimulus is shown (the custom-stop-signal-plugin)
id_index = 2;
// point in experiment when particpant id is manually entered. see 'start_timeline'
if (online){
var subjID = jsPsych.data.get().last(1).values()[0]['participantID'];
if (data.trial_index == id_index){ // write header
data_row = "participantID,age,gender,block_i,trial_i,stim,signal,SSD,response,rt,correct," +
"focus,Fullscreen,time_elapsed,browser_name,browser_version,os_name,os_version," +
"tablet,mobile,screen_resolution,window_resolution\n"
appendData('SST_data_'+ subjID +'.csv',data_row)
} else if (data.trial_type == 'custom-stop-signal-plugin'){ // append data each stimulus
data_row = data.participantID + ',' + data.age + ',' + data.gender + ',' + data.block_i + ',' + data.trial_i + ',' +
data.stim + ',' + data.signal + ',' + da