stop.html

Created Diff never expires
114 removals
Lines
Total
Removed
Words
Total
Removed
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
1044 lines
86 additions
Lines
Total
Added
Words
Total
Added
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
1017 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