📂 L08
├── 📄 index.html
├── 📄 exp.js
├── 📄 style.css
└── 📂 jspsych20 Lab 8: Cognitive Control
20.1 Research in Brief: The Flanker Paradigm
20.1.1 The Research Area
Cognitive control refers to the ability to orchestrate thought and action in accordance with internal goals. This system allows us to plan, decide, select behaviors, and coordinate multiple actions when needed. Because the mind has limited capacity for processing information, cognitive control functions as a resource manager, helping us prioritize competing demands from both our external environment and internal thoughts.
Research on cognitive control addresses questions about how we manage mental resources and regulate behavior. How do we maintain focus on relevant information while ignoring distractions? What mechanisms allow us to detect when we make errors and adjust our performance accordingly? How do we resolve conflicts between automatic responses and goal-directed actions? Understanding these processes has practical implications for performance in demanding tasks, educational outcomes, and clinical conditions affecting behavioral regulation.
Cognitive control operates through two distinct mechanisms: proactive control, where we anticipate challenges in advance, and reactive control, where we respond to challenges as they arise. These mechanisms recruit different brain regions and contribute to individual differences in the ability to guide behavior effectively under varying conditions.
20.1.2 Interference and Conflict Resolution
Cognitive control becomes necessary when task demands create interference between competing information or response options. Researchers have developed several laboratory tasks to measure different types of interference and cognitive control. In the Stroop task, participants name the ink color of printed words while ignoring the word meanings. When the word meaning conflicts with the ink color (e.g., the word BLUE printed in red ink), response times slow down because word reading is highly automatic. This demonstrates response conflict at the semantic level, where clashing word meanings compete for selection.
The Simon task requires participants to make a lateralized response (such as a left button press) to identify a non-spatial feature of a stimulus (such as its color) while ignoring where the stimulus appears on the screen. When a stimulus appears on the left side of the screen but requires a right-hand response, this spatial incompatibility slows reaction times, demonstrating interference between the target location and the natural inclination to respond with the hand on the same side as the stimulus.
The flanker task presents a central target stimulus surrounded by distractor stimuli that participants must ignore. Unlike the Stroop task, which produces semantic interference, or the Simon task, which produces spatial interference, the flanker task can reveal both perceptual interference (when distractors are difficult to filter out visually) and response interference (when distractors cue conflicting responses). This makes the flanker task particularly useful for studying how interference operates at multiple processing stages.
20.1.3 The Research Design
The flanker task developed by Eriksen and Eriksen (1974) uses a within-subjects experimental design to examine how distracting stimuli interfere with target processing and how cognitive control resolves this interference.
Stimulus Presentation: Participants view displays containing a central target stimulus flanked by distractor stimuli on either side. In a typical version, the target might be an arrow or letter that participants must identify, surrounded by additional arrows or letters serving as flankers.
Trial Types: The paradigm manipulates the relationship between target and flankers. On compatible trials, flankers match the target and cue the same response (e.g., < < < < <). On incompatible trials, flankers conflict with the target and cue a different response (e.g., < < > < <). Neutral trials present flankers that do not cue any response (e.g., - - > - -).
Task Requirements: Participants identify the central target as quickly and accurately as possible while ignoring the flanking distractors. They typically respond by pressing designated keys corresponding to the target identity. The task measures both response time and accuracy across different trial types.
The design manipulates spatial proximity between target and flankers to examine perceptual interference, or how physical spacing affects the ability to select the target. When flankers appear close to the target, perceptual selection becomes more difficult. When flankers are spaced farther away, the target becomes easier to isolate visually.
The within-subjects design allows comparison of performance across compatible, incompatible, and neutral trials, as well as across different spacing conditions, within the same participants. This approach controls for individual differences in baseline processing speed while isolating the specific effects of flanker interference and cognitive control.
20.1.4 Key Findings
The flanker task consistently produces a congruency effect: participants respond more slowly and less accurately on incompatible trials compared to compatible trials. This effect demonstrates that flanker information is processed automatically, even though it is task-irrelevant, creating response conflict that must be resolved through cognitive control. The magnitude of this interference typically ranges from 30-60 milliseconds for response times, with error rates increasing by 5-15% on incompatible trials.
Spatial proximity strongly influences the size of the congruency effect. When flankers appear closer to the target, perceptual interference increases because the visual system has more difficulty selecting the target from among nearby distractors. Conversely, when flankers are spaced farther from the target, the congruency effect becomes smaller because perceptual selection of the target becomes easier. This spatial proximity effect demonstrates that interference operates at multiple processing stages, with perceptual selection representing an early stage where cognitive control can filter out distracting information.
The anterior cingulate cortex shows greater activity when distractors are perceptually close to the target than when they are spaced away from it. This increased activity reflects the conflict monitoring system detecting higher levels of perceptual competition. The spatial proximity manipulation reveals how the visual attention system must work harder to focus processing resources on the target location when competing stimuli appear nearby, similar to trying to read a specific sign when surrounded by many other signs in close proximity.
20.1.5 Implications
The flanker paradigm demonstrates that cognitive control involves multiple mechanisms operating at different processing stages. The spatial proximity effect shows that control begins with selective attention, or the ability to focus processing on relevant spatial locations while filtering out nearby distractors. When task-irrelevant information appears close to targets, the visual system must work harder to select the relevant stimulus, revealing an early stage of interference that occurs during perceptual processing before response selection.
The spatial proximity findings have important implications for understanding how visual attention operates as a spotlight or zoom lens. When distractors are far from the target, attention can be narrowly focused on the target location, effectively excluding flanker information from processing. However, when distractors encroach on the target location, the attentional focus cannot be narrowed sufficiently to exclude them, resulting in automatic processing of the irrelevant information. This demonstrates fundamental limitations in the spatial resolution of selective attention.
These findings also reveal that interference operates at multiple processing stages. Both perceptual selection and response selection contribute to the overall conflict. Even when flankers are spatially separated from targets, incompatible flankers still produce interference at the response stage because their identity is processed and activates competing responses. This shows that cognitive control must operate at multiple points in the information processing stream to effectively manage interference.
20.1.6 Further Reading
Chun, M. M., & Most, S. B. (2021). Cognitive control and working memory. In Cognition.
Crump, Matthew JC, John V. McDonnell, and Todd M. Gureckis. “Evaluating Amazon’s Mechanical Turk as a tool for experimental behavioral research.” PloS one 8.3 (2013): e57410.
Gratton, G., Cooper, P., Fabiani, M., Carter, C. S., & Karayanidis, F. (2018). Dynamics of cognitive control: Theoretical bases, paradigms, and a view for the future. Psychophysiology, 55(3), e13016.
Monsell, S. (2003). Task switching. Trends in cognitive sciences, 7(3), 134-140.
20.2 Program a Flanker Task
In this tutorial we’re going to program the classic flanker task and manipulate the spatial proximity of the distractors.
However, we’re going to add a practice block that loops until participants reach a performance threshold to ensure they understand the task parameters. We’ll also add conditional feedback throughout the task such that participants receive “Incorrect!” or a “too slow!”, but is skipped if they respond correctly.
The initial folder contains the standard jsPsych boilerplate files with no additional components added yet.
20.2.1 Initiate JsPsych
Let’s begin by initializing jsPsych with the basic setup we typically need. In the HTML index page, all necessary files have been linked: the jsPsych core files, our exp.js file, our style.css file, and the two plugins required for this experiment (the instructions plugin and the HTML keyboard response plugin).
We will add an instructions trial that can be populated with task instructions later, and conclude the experiment with a save data trial to store participant responses.
Two notes about the code:
- I’ve added informative comment headers. As our experiments get more complicated, the code is going to become more complex. We’re going to start trying to keep our code organized and documented to make it easier to read and understand.
- I’ve already started adding data labels. These are important. We could wait until the end or add them as we go. Since I know we’re going to need data labels for feedback and data filtering, I’m just going to add them as we go.
<!DOCTYPE html>
<html>
<head>
<title>Lab 8: Cognitive Control</title>
<!-- jsPsych -->
<script src="jspsych/jspsych.js"></script>
<link href="jspsych/jspsych.css" rel="stylesheet" type="text/css" />
<!-- jPsych plugins -->
<script src="jspsych/plugin-instructions.js"></script>
<script src="jspsych/plugin-html-keyboard-response.js"></script>
<!-- custom CSS -->
<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<!-- custom JS -->
<script src="exp.js"></script>
</body>
</html>
// ============================================
// Initiate jsPsych
// ============================================
const jsPsych = initJsPsych();
// ============================================
// Instructions
// ============================================
const instructions = {
type: jsPsychInstructions,
pages: [
`<p>Welcome to the Experiment!</p>
<p>Use the buttons below to navigate through the instructions</p>`,
`<p>When you are ready to begin the first trial, press "Next"</p>`
],
show_clickable_nav: true,
data: {
phase: "instructions"
}
}
// ============================================
// Savd Data Trial
// ============================================
const saveData = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `
<div style="text-align: center;">
<p>Experiment complete!</p>
<p>Click the button below to save your data locally:</p>
<button id="save-btn" style="padding: 10px 20px; font-size: 16px; cursor: pointer;">
Click here to save the data locally
</button>
</div>
`,
choices: "NO_KEYS",
trial_duration: null,
on_load: function() {
document.getElementById("save-btn").addEventListener("click", function() {
jsPsych.data.get().localSave("csv", "flanker_data.csv");
});
},
data: {
phase: "save data trial"
}
};
// ============================================
// Run jsPsych
// ============================================
jsPsych.run([
instructions,
saveData
]); 20.2.2 Create a Practice Block
Our display really is just five letters in a row (“<<<<<”), but we’ll need to be able to adjust the spacing. I’ll use a function to manipulate the HTML display and wrap the target letter in a <span> tag. Span Tags don’t do anything on their own, but it allows me to adjust the CSS for just the letter inside it.
In this case, I’ll change the margin, using margin: 0 50. The first number is the top and bottom margin and the second number is the left and right margin. I can then insert the timeline variables to change each letter and to change the margin.
<!DOCTYPE html>
<html>
<head>
<title>Lab 8: Cognitive Control</title>
<!-- jsPsych -->
<script src="jspsych/jspsych.js"></script>
<link href="jspsych/jspsych.css" rel="stylesheet" type="text/css" />
<!-- jPsych plugins -->
<script src="jspsych/plugin-instructions.js"></script>
<script src="jspsych/plugin-html-keyboard-response.js"></script>
<!-- custom CSS -->
<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<!-- custom JS -->
<script src="exp.js"></script>
</body>
</html>
// ============================================
// Initiate jsPsych
// ============================================
const jsPsych = initJsPsych();
// ============================================
// Instructions
// ============================================
const instructions = {
type: jsPsychInstructions,
pages: [
`<p>Welcome to the Experiment!</p>
<p>Use the buttons below to navigate through the instructions</p>`,
`<p>When you are ready to begin the first trial, press "Next"</p>`
],
show_clickable_nav: true
}
// ============================================
// Flanker Practice Block
// ============================================
// fixation
let practice_fixation = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `<p style="font-size:48px">+</p>`,
choices: "NO_KEYS",
post_trial_gap: 250,
trial_duration: 1000,
data: {
trial_part: "fixation"
}
}
// stimulus
let practice_stimulus = {
type: jsPsychHtmlKeyboardResponse,
stimulus: function(){
let target = jsPsych.evaluateTimelineVariable("target");
let distractor = jsPsych.evaluateTimelineVariable("distractor");
let distance = jsPsych.evaluateTimelineVariable("distance");
// Create HTML with custom spacing
let output = `
<div style="font-size: 100px; font-family: monospace;">
${distractor}${distractor}<span style="margin: 0 ${distance}px;">${target}</span>${distractor}${distractor}
</div>`;
return output;
},
choices: ["a", "l"],
trial_duration: 1000,
post_trial_gap: 250,
data: {
trial_part: "stimulus"
}
}
// practice timeline
let practice_flanker = {
timeline: [
practice_fixation,
practice_stimulus
],
timeline_variables: [
{ target: "<", distractor: "<", distance: 0, congruency: "congruent", correct_response: "a" },
{ target: "<", distractor: "<", distance: 50, congruency: "congruent", correct_response: "a" },
{ target: "<", distractor: ">", distance: 0, congruency: "incongruent", correct_response: "a" },
{ target: "<", distractor: ">", distance: 50, congruency: "incongruent", correct_response: "a" },
{ target: ">", distractor: "<", distance: 0, congruency: "incongruent", correct_response: "l" },
{ target: ">", distractor: "<", distance: 50, congruency: "incongruent", correct_response: "l" },
{ target: ">", distractor: ">", distance: 0, congruency: "congruent", correct_response: "l" },
{ target: ">", distractor: ">", distance: 50, congruency: "congruent", correct_response: "l" }
],
randomize_order: true,
data: {
phase: "flanker practice",
target: jsPsych.timelineVariable("target"),
distractor: jsPsych.timelineVariable("distractor"),
congruency: jsPsych.timelineVariable("congruency"),
correct_response: jsPsych.timelineVariable("correct_response")
}
};
const end_practice = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `<p>You have completed the practice trials.</p>
<p>When you are ready to begin the experimental trials, press the space bar.</p>`,
choices: " ",
post_trial_gap: 250,
data: {
phase: "instructions"
}
}
// ============================================
// Savd Data Trial
// ============================================
const saveData = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `
<div style="text-align: center;">
<p>Experiment complete!</p>
<p>Click the button below to save your data locally:</p>
<button id="save-btn" style="padding: 10px 20px; font-size: 16px; cursor: pointer;">
Click here to save the data locally
</button>
</div>
`,
choices: "NO_KEYS",
trial_duration: null,
on_load: function() {
document.getElementById("save-btn").addEventListener("click", function() {
jsPsych.data.get().localSave("csv", "flanker_data.csv");
});
}
};
// ============================================
// Run jsPsych
// ============================================
jsPsych.run([
instructions,
practice_flanker,
end_practice,
saveData
]); 20.2.3 Add Feedback
For the practice block we’ll add feedback on every trial. We’ll store the accuracy on every trial, then display feedback based on accuracy.
<!DOCTYPE html>
<html>
<head>
<title>Lab 8: Cognitive Control</title>
<!-- jsPsych -->
<script src="jspsych/jspsych.js"></script>
<link href="jspsych/jspsych.css" rel="stylesheet" type="text/css" />
<!-- jPsych plugins -->
<script src="jspsych/plugin-instructions.js"></script>
<script src="jspsych/plugin-html-keyboard-response.js"></script>
<!-- custom CSS -->
<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<!-- custom JS -->
<script src="exp.js"></script>
</body>
</html>
// ============================================
// Initiate jsPsych
// ============================================
const jsPsych = initJsPsych();
// ============================================
// Instructions
// ============================================
const instructions = {
type: jsPsychInstructions,
pages: [
`<p>Welcome to the Experiment!</p>
<p>Use the buttons below to navigate through the instructions</p>`,
`<p>When you are ready to begin the first trial, press "Next"</p>`
],
show_clickable_nav: true
}
// ============================================
// Flanker Practice Block
// ============================================
// fixation
let practice_fixation = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `<p style="font-size:48px">+</p>`,
choices: "NO_KEYS",
post_trial_gap: 250,
trial_duration: 1000,
data: {
trial_part: "fixation"
}
}
// stimulus
let practice_stimulus = {
type: jsPsychHtmlKeyboardResponse,
stimulus: function(){
let target = jsPsych.evaluateTimelineVariable("target");
let distractor = jsPsych.evaluateTimelineVariable("distractor");
let distance = jsPsych.evaluateTimelineVariable("distance");
// Create HTML with custom spacing
let output = `
<div style="font-size: 100px; font-family: monospace;">
${distractor}${distractor}<span style="margin: 0 ${distance}px;">${target}</span>${distractor}${distractor}
</div>`;
return output;
},
choices: ["a", "l"],
trial_duration: 1000,
post_trial_gap: 250,
data: {
trial_part: "stimulus"
},
on_finish: function(data){
// store accuracy
data.correct = jsPsych.pluginAPI.compareKeys(data.response, data.correct_response)
}
}
// feedback
let practice_feedback = {
type: jsPsychHtmlKeyboardResponse,
stimulus: function(){
// get the last response
let last_trial = jsPsych.data.get().last(1).values()[0]
let output
if(last_trial.response === null){
// this will check for non-response first
output = `<p style="font-size:48px">Too Slow! Respond Faster!</p>`
} else if(last_trial.correct){
// else if correct
output = `<p style="font-size:48px">Correct!</p>`
} else {
// else incorrect
output = `<p style="font-size:48px">Incorrect!</p>`
}
return output;
},
choices: "NO_KEYS",
trial_duration: 1000,
post_trial_gap: 250,
data: {
trial_part: "feedback"
}
}
// practice timeline
let practice_flanker = {
timeline: [
practice_fixation,
practice_stimulus,
practice_feedback
],
timeline_variables: [
{ target: "<", distractor: "<", distance: 0, congruency: "congruent", correct_response: "a" },
{ target: "<", distractor: "<", distance: 50, congruency: "congruent", correct_response: "a" },
{ target: "<", distractor: ">", distance: 0, congruency: "incongruent", correct_response: "a" },
{ target: "<", distractor: ">", distance: 50, congruency: "incongruent", correct_response: "a" },
{ target: ">", distractor: "<", distance: 0, congruency: "incongruent", correct_response: "l" },
{ target: ">", distractor: "<", distance: 50, congruency: "incongruent", correct_response: "l" },
{ target: ">", distractor: ">", distance: 0, congruency: "congruent", correct_response: "l" },
{ target: ">", distractor: ">", distance: 50, congruency: "congruent", correct_response: "l" }
],
randomize_order: true,
data: {
phase: "flanker practice",
target: jsPsych.timelineVariable("target"),
distractor: jsPsych.timelineVariable("distractor"),
congruency: jsPsych.timelineVariable("congruency"),
correct_response: jsPsych.timelineVariable("correct_response")
}
};
const end_practice = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `<p>You have completed the practice trials.</p>
<p>When you are ready to begin the experimental trials, press the space bar.</p>`,
choices: " ",
post_trial_gap: 250,
data: {
phase: "instructions"
}
}
// ============================================
// Savd Data Trial
// ============================================
const saveData = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `
<div style="text-align: center;">
<p>Experiment complete!</p>
<p>Click the button below to save your data locally:</p>
<button id="save-btn" style="padding: 10px 20px; font-size: 16px; cursor: pointer;">
Click here to save the data locally
</button>
</div>
`,
choices: "NO_KEYS",
trial_duration: null,
on_load: function() {
document.getElementById("save-btn").addEventListener("click", function() {
jsPsych.data.get().localSave("csv", "flanker_data.csv");
});
}
};
// ============================================
// Run jsPsych
// ============================================
jsPsych.run([
instructions,
practice_flanker,
end_practice,
saveData
]); 20.2.4 Add a Loop Function
Let’s add a function that will loop the practice until they reach 75% accuracy.
<!DOCTYPE html>
<html>
<head>
<title>Lab 8: Cognitive Control</title>
<!-- jsPsych -->
<script src="jspsych/jspsych.js"></script>
<link href="jspsych/jspsych.css" rel="stylesheet" type="text/css" />
<!-- jPsych plugins -->
<script src="jspsych/plugin-instructions.js"></script>
<script src="jspsych/plugin-html-keyboard-response.js"></script>
<!-- custom CSS -->
<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<!-- custom JS -->
<script src="exp.js"></script>
</body>
</html>
// ============================================
// Initiate jsPsych
// ============================================
const jsPsych = initJsPsych();
// ============================================
// Instructions
// ============================================
const instructions = {
type: jsPsychInstructions,
pages: [
`<p>Welcome to the Experiment!</p>
<p>Use the buttons below to navigate through the instructions</p>`,
`<p>When you are ready to begin the first trial, press "Next"</p>`
],
show_clickable_nav: true
}
// ============================================
// Flanker Practice Block
// ============================================
// fixation
let practice_fixation = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `<p style="font-size:48px">+</p>`,
choices: "NO_KEYS",
post_trial_gap: 250,
trial_duration: 1000,
data: {
trial_part: "fixation"
}
}
// stimulus
let practice_stimulus = {
type: jsPsychHtmlKeyboardResponse,
stimulus: function(){
let target = jsPsych.evaluateTimelineVariable("target");
let distractor = jsPsych.evaluateTimelineVariable("distractor");
let distance = jsPsych.evaluateTimelineVariable("distance");
// Create HTML with custom spacing
let output = `
<div style="font-size: 100px; font-family: monospace;">
${distractor}${distractor}<span style="margin: 0 ${distance}px;">${target}</span>${distractor}${distractor}
</div>`;
return output;
},
choices: ["a", "l"],
trial_duration: 1000,
post_trial_gap: 250,
data: {
trial_part: "stimulus"
},
on_finish: function(data){
// store accuracy
data.correct = jsPsych.pluginAPI.compareKeys(data.response, data.correct_response)
}
}
// feedback
let practice_feedback = {
type: jsPsychHtmlKeyboardResponse,
stimulus: function(){
// get the last response
let last_trial = jsPsych.data.get().last(1).values()[0]
let output
if(last_trial.response === null){
// this will check for non-response first
output = `<p style="font-size:48px">Too Slow! Respond Faster!</p>`
} else if(last_trial.correct){
// else if correct
output = `<p style="font-size:48px">Correct!</p>`
} else {
// else incorrect
output = `<p style="font-size:48px">Incorrect!</p>`
}
return output;
},
choices: "NO_KEYS",
trial_duration: 1000,
post_trial_gap: 250,
data: {
trial_part: "feedback"
}
}
// practice timeline
let practice_flanker = {
timeline: [
practice_fixation,
practice_stimulus,
practice_feedback
],
timeline_variables: [
{ target: "<", distractor: "<", distance: 0, congruency: "congruent", correct_response: "a" },
{ target: "<", distractor: "<", distance: 50, congruency: "congruent", correct_response: "a" },
{ target: "<", distractor: ">", distance: 0, congruency: "incongruent", correct_response: "a" },
{ target: "<", distractor: ">", distance: 50, congruency: "incongruent", correct_response: "a" },
{ target: ">", distractor: "<", distance: 0, congruency: "incongruent", correct_response: "l" },
{ target: ">", distractor: "<", distance: 50, congruency: "incongruent", correct_response: "l" },
{ target: ">", distractor: ">", distance: 0, congruency: "congruent", correct_response: "l" },
{ target: ">", distractor: ">", distance: 50, congruency: "congruent", correct_response: "l" }
],
randomize_order: true,
data: {
phase: "flanker practice",
target: jsPsych.timelineVariable("target"),
distractor: jsPsych.timelineVariable("distractor"),
congruency: jsPsych.timelineVariable("congruency"),
correct_response: jsPsych.timelineVariable("correct_response")
},
loop_function: function(data) {
let trials = data.filter({trial_part: "stimulus"})
let correct_trials = trials.filter({correct: true})
let accuracy = correct_trials.count()/trials.count();
console.log(`Practice accuracy: ${accuracy}`);
// If accuracy is below 75%, repeat the block
if (accuracy < 0.75) {
return true; // Loop again
} else {
return false; // Move on
}
}
};
const end_practice = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `<p>You have completed the practice trials.</p>
<p>When you are ready to begin the experimental trials, press the space bar.</p>`,
choices: " ",
post_trial_gap: 250,
data: {
phase: "instructions"
}
}
// ============================================
// Savd Data Trial
// ============================================
const saveData = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `
<div style="text-align: center;">
<p>Experiment complete!</p>
<p>Click the button below to save your data locally:</p>
<button id="save-btn" style="padding: 10px 20px; font-size: 16px; cursor: pointer;">
Click here to save the data locally
</button>
</div>
`,
choices: "NO_KEYS",
trial_duration: null,
on_load: function() {
document.getElementById("save-btn").addEventListener("click", function() {
jsPsych.data.get().localSave("csv", "flanker_data.csv");
});
}
};
// ============================================
// Run jsPsych
// ============================================
jsPsych.run([
instructions,
practice_flanker,
end_practice,
saveData
]); 20.2.5 Add a Fallback for the Loop
Now we have our practice looping, it’s possible for our participants to continue looping forever!
Let’s add some logic that will quit the loop if they’ve failed too many times.
Note: To test whether the logic is working, you can change max_attempts to 1 and see if it continues after one loop with accuracy < 75%. Once you’ve tested it, change it back to 3.
<!DOCTYPE html>
<html>
<head>
<title>Lab 8: Cognitive Control</title>
<!-- jsPsych -->
<script src="jspsych/jspsych.js"></script>
<link href="jspsych/jspsych.css" rel="stylesheet" type="text/css" />
<!-- jPsych plugins -->
<script src="jspsych/plugin-instructions.js"></script>
<script src="jspsych/plugin-html-keyboard-response.js"></script>
<!-- custom CSS -->
<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<!-- custom JS -->
<script src="exp.js"></script>
</body>
</html>
// ============================================
// Initiate jsPsych
// ============================================
const jsPsych = initJsPsych();
// ============================================
// Instructions
// ============================================
const instructions = {
type: jsPsychInstructions,
pages: [
`<p>Welcome to the Experiment!</p>
<p>Use the buttons below to navigate through the instructions</p>`,
`<p>When you are ready to begin the first trial, press "Next"</p>`
],
show_clickable_nav: true
}
// ============================================
// Flanker Practice Block
// ============================================
// add a variable to track practice attempts
let attempts = 0
// this variable sets the maximum loops
let max_attempts = 3
// fixation
let practice_fixation = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `<p style="font-size:48px">+</p>`,
choices: "NO_KEYS",
post_trial_gap: 250,
trial_duration: 1000,
data: {
trial_part: "fixation"
}
}
// stimulus
let practice_stimulus = {
type: jsPsychHtmlKeyboardResponse,
stimulus: function(){
let target = jsPsych.evaluateTimelineVariable("target");
let distractor = jsPsych.evaluateTimelineVariable("distractor");
let distance = jsPsych.evaluateTimelineVariable("distance");
// Create HTML with custom spacing
let output = `
<div style="font-size: 100px; font-family: monospace;">
${distractor}${distractor}<span style="margin: 0 ${distance}px;">${target}</span>${distractor}${distractor}
</div>`;
return output;
},
choices: ["a", "l"],
trial_duration: 1000,
post_trial_gap: 250,
data: {
trial_part: "stimulus"
},
on_finish: function(data){
// store accuracy
data.correct = jsPsych.pluginAPI.compareKeys(data.response, data.correct_response)
}
}
// feedback
let practice_feedback = {
type: jsPsychHtmlKeyboardResponse,
stimulus: function(){
// get the last response
let last_trial = jsPsych.data.get().last(1).values()[0]
let output
if(last_trial.response === null){
// this will check for non-response first
output = `<p style="font-size:48px">Too Slow! Respond Faster!</p>`
} else if(last_trial.correct){
// else if correct
output = `<p style="font-size:48px">Correct!</p>`
} else {
// else incorrect
output = `<p style="font-size:48px">Incorrect!</p>`
}
return output;
},
choices: "NO_KEYS",
trial_duration: 1000,
post_trial_gap: 250,
data: {
trial_part: "feedback"
}
}
// practice timeline
let practice_flanker = {
timeline: [
practice_fixation,
practice_stimulus,
practice_feedback
],
timeline_variables: [
{ target: "<", distractor: "<", distance: 0, congruency: "congruent", correct_response: "a" },
{ target: "<", distractor: "<", distance: 50, congruency: "congruent", correct_response: "a" },
{ target: "<", distractor: ">", distance: 0, congruency: "incongruent", correct_response: "a" },
{ target: "<", distractor: ">", distance: 50, congruency: "incongruent", correct_response: "a" },
{ target: ">", distractor: "<", distance: 0, congruency: "incongruent", correct_response: "l" },
{ target: ">", distractor: "<", distance: 50, congruency: "incongruent", correct_response: "l" },
{ target: ">", distractor: ">", distance: 0, congruency: "congruent", correct_response: "l" },
{ target: ">", distractor: ">", distance: 50, congruency: "congruent", correct_response: "l" }
],
randomize_order: true,
data: {
phase: "flanker practice",
target: jsPsych.timelineVariable("target"),
distractor: jsPsych.timelineVariable("distractor"),
congruency: jsPsych.timelineVariable("congruency"),
correct_response: jsPsych.timelineVariable("correct_response")
},
loop_function: function(data) {
// add one to attempts
attempts++
let trials = data.filter({trial_part: "stimulus"})
let correct_trials = trials.filter({correct: true})
let accuracy = correct_trials.count()/trials.count();
console.log(`Practice accuracy: ${accuracy}`);
// add this before the accuracy check
// if attempts is greater than the max quit loop
if(attempts >= max_attempts){
return false
}
// If accuracy is below 75%, repeat the block
if (accuracy < 0.75) {
return true; // Loop again
} else {
return false; // Move on
}
}
};
const end_practice = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `<p>You have completed the practice trials.</p>
<p>When you are ready to begin the experimental trials, press the space bar.</p>`,
choices: " ",
post_trial_gap: 250,
data: {
phase: "instructions"
}
}
// ============================================
// Savd Data Trial
// ============================================
const saveData = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `
<div style="text-align: center;">
<p>Experiment complete!</p>
<p>Click the button below to save your data locally:</p>
<button id="save-btn" style="padding: 10px 20px; font-size: 16px; cursor: pointer;">
Click here to save the data locally
</button>
</div>
`,
choices: "NO_KEYS",
trial_duration: null,
on_load: function() {
document.getElementById("save-btn").addEventListener("click", function() {
jsPsych.data.get().localSave("csv", "flanker_data.csv");
});
}
};
// ============================================
// Run jsPsych
// ============================================
jsPsych.run([
instructions,
practice_flanker,
end_practice,
saveData
]); 20.2.6 Creat Experimental Block
We’ll create the experimental block of flanker trials. This is almost identical to the practice, but there is no loop function and no feedback, currently.
Important Note: You’ll notice our experiment is getting long and it’s taking more time to test it to make sure it’s working. One way around this, is to ‘comment out’ the sections inside jsPsych.run that we want to skip during testing.
For instance, in this part, we want to test the new experimental block and we already know our practice block works. We don’t want to sit through the practice phase each time we test it, so I can do this:
// ============================================
// Run jsPsych
// ============================================
jsPsych.run([
instructions,
// practice_flanker,
end_practice,
exp_flanker,
saveData
]);'I added the comment // on the line with practice_flanker which means that the browser will ignore that line. This allows me to skip the practice phase for now.
BUT, we have to remember to remove the comment tag when we are finished so we see the whole experiment at the end.
<!DOCTYPE html>
<html>
<head>
<title>Lab 8: Cognitive Control</title>
<!-- jsPsych -->
<script src="jspsych/jspsych.js"></script>
<link href="jspsych/jspsych.css" rel="stylesheet" type="text/css" />
<!-- jPsych plugins -->
<script src="jspsych/plugin-instructions.js"></script>
<script src="jspsych/plugin-html-keyboard-response.js"></script>
<!-- custom CSS -->
<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<!-- custom JS -->
<script src="exp.js"></script>
</body>
</html>
// ============================================
// Initiate jsPsych
// ============================================
const jsPsych = initJsPsych();
// ============================================
// Instructions
// ============================================
const instructions = {
type: jsPsychInstructions,
pages: [
`<p>Welcome to the Experiment!</p>
<p>Use the buttons below to navigate through the instructions</p>`,
`<p>When you are ready to begin the first trial, press "Next"</p>`
],
show_clickable_nav: true
}
// ============================================
// Flanker Practice Block
// ============================================
// add a variable to track practice attempts
let attempts = 0
// this variable sets the maximum loops
let max_attempts = 3
// fixation
let practice_fixation = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `<p style="font-size:48px">+</p>`,
choices: "NO_KEYS",
post_trial_gap: 250,
trial_duration: 1000,
data: {
trial_part: "fixation"
}
}
// stimulus
let practice_stimulus = {
type: jsPsychHtmlKeyboardResponse,
stimulus: function(){
let target = jsPsych.evaluateTimelineVariable("target");
let distractor = jsPsych.evaluateTimelineVariable("distractor");
let distance = jsPsych.evaluateTimelineVariable("distance");
// Create HTML with custom spacing
let output = `
<div style="font-size: 100px; font-family: monospace;">
${distractor}${distractor}<span style="margin: 0 ${distance}px;">${target}</span>${distractor}${distractor}
</div>`;
return output;
},
choices: ["a", "l"],
trial_duration: 1000,
post_trial_gap: 250,
data: {
trial_part: "stimulus"
},
on_finish: function(data){
// store accuracy
data.correct = jsPsych.pluginAPI.compareKeys(data.response, data.correct_response)
}
}
// feedback
let practice_feedback = {
type: jsPsychHtmlKeyboardResponse,
stimulus: function(){
// get the last response
let last_trial = jsPsych.data.get().last(1).values()[0]
let output
if(last_trial.response === null){
// this will check for non-response first
output = `<p style="font-size:48px">Too Slow! Respond Faster!</p>`
} else if(last_trial.correct){
// else if correct
output = `<p style="font-size:48px">Correct!</p>`
} else {
// else incorrect
output = `<p style="font-size:48px">Incorrect!</p>`
}
return output;
},
choices: "NO_KEYS",
trial_duration: 1000,
post_trial_gap: 250,
data: {
trial_part: "feedback"
}
}
// practice timeline
let practice_flanker = {
timeline: [
practice_fixation,
practice_stimulus,
practice_feedback
],
timeline_variables: [
{ target: "<", distractor: "<", distance: 0, congruency: "congruent", correct_response: "a" },
{ target: "<", distractor: "<", distance: 50, congruency: "congruent", correct_response: "a" },
{ target: "<", distractor: ">", distance: 0, congruency: "incongruent", correct_response: "a" },
{ target: "<", distractor: ">", distance: 50, congruency: "incongruent", correct_response: "a" },
{ target: ">", distractor: "<", distance: 0, congruency: "incongruent", correct_response: "l" },
{ target: ">", distractor: "<", distance: 50, congruency: "incongruent", correct_response: "l" },
{ target: ">", distractor: ">", distance: 0, congruency: "congruent", correct_response: "l" },
{ target: ">", distractor: ">", distance: 50, congruency: "congruent", correct_response: "l" }
],
randomize_order: true,
data: {
phase: "flanker practice",
target: jsPsych.timelineVariable("target"),
distractor: jsPsych.timelineVariable("distractor"),
congruency: jsPsych.timelineVariable("congruency"),
correct_response: jsPsych.timelineVariable("correct_response")
},
loop_function: function(data) {
// add one to attempts
attempts++
let trials = data.filter({trial_part: "stimulus"})
let correct_trials = trials.filter({correct: true})
let accuracy = correct_trials.count()/trials.count();
console.log(`Practice accuracy: ${accuracy}`);
// add this before the accuracy check
// if attempts is greater than the max quit loop
if(attempts >= max_attempts){
return false
}
// If accuracy is below 75%, repeat the block
if (accuracy < 0.75) {
return true; // Loop again
} else {
return false; // Move on
}
}
};
const end_practice = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `<p>You have completed the practice trials.</p>
<p>When you are ready to begin the experimental trials, press the space bar.</p>`,
choices: " ",
post_trial_gap: 250,
data: {
phase: "instructions"
}
}
// ============================================
// Flanker Experimental Block
// ============================================
// fixation
let exp_fixation = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `<p style="font-size:48px">+</p>`,
choices: "NO_KEYS",
post_trial_gap: 250,
trial_duration: 1000,
data: {
trial_part: "fixation"
}
}
// stimulus
let exp_stimulus = {
type: jsPsychHtmlKeyboardResponse,
stimulus: function(){
let target = jsPsych.evaluateTimelineVariable("target");
let distractor = jsPsych.evaluateTimelineVariable("distractor");
let distance = jsPsych.evaluateTimelineVariable("distance");
// Create HTML with custom spacing
let output = `
<div style="font-size: 100px; font-family: monospace;">
${distractor}${distractor}<span style="margin: 0 ${distance}px;">${target}</span>${distractor}${distractor}
</div>`;
return output;
},
choices: ["a", "l"],
trial_duration: 1000,
post_trial_gap: 250,
data: {
trial_part: "stimulus"
},
on_finish: function(data){
// store accuracy
data.correct = jsPsych.pluginAPI.compareKeys(data.response, data.correct_response)
}
}
// exp timeline
let exp_flanker = {
timeline: [
exp_fixation,
exp_stimulus
],
timeline_variables: [
{ target: "<", distractor: "<", distance: 0, congruency: "congruent", correct_response: "a" },
{ target: "<", distractor: "<", distance: 50, congruency: "congruent", correct_response: "a" },
{ target: "<", distractor: ">", distance: 0, congruency: "incongruent", correct_response: "a" },
{ target: "<", distractor: ">", distance: 50, congruency: "incongruent", correct_response: "a" },
{ target: ">", distractor: "<", distance: 0, congruency: "incongruent", correct_response: "l" },
{ target: ">", distractor: "<", distance: 50, congruency: "incongruent", correct_response: "l" },
{ target: ">", distractor: ">", distance: 0, congruency: "congruent", correct_response: "l" },
{ target: ">", distractor: ">", distance: 50, congruency: "congruent", correct_response: "l" }
],
randomize_order: true,
data: {
phase: "flanker",
target: jsPsych.timelineVariable("target"),
distractor: jsPsych.timelineVariable("distractor"),
congruency: jsPsych.timelineVariable("congruency"),
correct_response: jsPsych.timelineVariable("correct_response")
}
};
// ============================================
// Savd Data Trial
// ============================================
const saveData = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `
<div style="text-align: center;">
<p>Experiment complete!</p>
<p>Click the button below to save your data locally:</p>
<button id="save-btn" style="padding: 10px 20px; font-size: 16px; cursor: pointer;">
Click here to save the data locally
</button>
</div>
`,
choices: "NO_KEYS",
trial_duration: null,
on_load: function() {
document.getElementById("save-btn").addEventListener("click", function() {
jsPsych.data.get().localSave("csv", "flanker_data.csv");
});
}
};
// ============================================
// Run jsPsych
// ============================================
jsPsych.run([
instructions,
// practice_flanker,
end_practice,
exp_flanker,
saveData
]); 20.2.7 Add Conditional Feedback
For the experimental block of trials, we still want some kind of feedback, but it’s pretty disruptive to have the feedback on every trial.
So, we’ll add feedback that is conditional on making an error. That is, they only see feedback if they were too slow or responded incorrectly.
<!DOCTYPE html>
<html>
<head>
<title>Lab 8: Cognitive Control</title>
<!-- jsPsych -->
<script src="jspsych/jspsych.js"></script>
<link href="jspsych/jspsych.css" rel="stylesheet" type="text/css" />
<!-- jPsych plugins -->
<script src="jspsych/plugin-instructions.js"></script>
<script src="jspsych/plugin-html-keyboard-response.js"></script>
<!-- custom CSS -->
<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<!-- custom JS -->
<script src="exp.js"></script>
</body>
</html>
// ============================================
// Initiate jsPsych
// ============================================
const jsPsych = initJsPsych();
// ============================================
// Instructions
// ============================================
const instructions = {
type: jsPsychInstructions,
pages: [
`<p>Welcome to the Experiment!</p>
<p>Use the buttons below to navigate through the instructions</p>`,
`<p>When you are ready to begin the first trial, press "Next"</p>`
],
show_clickable_nav: true
}
// ============================================
// Flanker Practice Block
// ============================================
// add a variable to track practice attempts
let attempts = 0
// this variable sets the maximum loops
let max_attempts = 3
// fixation
let practice_fixation = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `<p style="font-size:48px">+</p>`,
choices: "NO_KEYS",
post_trial_gap: 250,
trial_duration: 1000,
data: {
trial_part: "fixation"
}
}
// stimulus
let practice_stimulus = {
type: jsPsychHtmlKeyboardResponse,
stimulus: function(){
let target = jsPsych.evaluateTimelineVariable("target");
let distractor = jsPsych.evaluateTimelineVariable("distractor");
let distance = jsPsych.evaluateTimelineVariable("distance");
// Create HTML with custom spacing
let output = `
<div style="font-size: 100px; font-family: monospace;">
${distractor}${distractor}<span style="margin: 0 ${distance}px;">${target}</span>${distractor}${distractor}
</div>`;
return output;
},
choices: ["a", "l"],
trial_duration: 1000,
post_trial_gap: 250,
data: {
trial_part: "stimulus"
},
on_finish: function(data){
// store accuracy
data.correct = jsPsych.pluginAPI.compareKeys(data.response, data.correct_response)
}
}
// feedback
let practice_feedback = {
type: jsPsychHtmlKeyboardResponse,
stimulus: function(){
// get the last response
let last_trial = jsPsych.data.get().last(1).values()[0]
let output
if(last_trial.response === null){
// this will check for non-response first
output = `<p style="font-size:48px">Too Slow! Respond Faster!</p>`
} else if(last_trial.correct){
// else if correct
output = `<p style="font-size:48px">Correct!</p>`
} else {
// else incorrect
output = `<p style="font-size:48px">Incorrect!</p>`
}
return output;
},
choices: "NO_KEYS",
trial_duration: 1000,
post_trial_gap: 250,
data: {
trial_part: "feedback"
}
}
// practice timeline
let practice_flanker = {
timeline: [
practice_fixation,
practice_stimulus,
practice_feedback
],
timeline_variables: [
{ target: "<", distractor: "<", distance: 0, congruency: "congruent", correct_response: "a" },
{ target: "<", distractor: "<", distance: 50, congruency: "congruent", correct_response: "a" },
{ target: "<", distractor: ">", distance: 0, congruency: "incongruent", correct_response: "a" },
{ target: "<", distractor: ">", distance: 50, congruency: "incongruent", correct_response: "a" },
{ target: ">", distractor: "<", distance: 0, congruency: "incongruent", correct_response: "l" },
{ target: ">", distractor: "<", distance: 50, congruency: "incongruent", correct_response: "l" },
{ target: ">", distractor: ">", distance: 0, congruency: "congruent", correct_response: "l" },
{ target: ">", distractor: ">", distance: 50, congruency: "congruent", correct_response: "l" }
],
randomize_order: true,
data: {
phase: "flanker practice",
target: jsPsych.timelineVariable("target"),
distractor: jsPsych.timelineVariable("distractor"),
congruency: jsPsych.timelineVariable("congruency"),
correct_response: jsPsych.timelineVariable("correct_response")
},
loop_function: function(data) {
// add one to attempts
attempts++
let trials = data.filter({trial_part: "stimulus"})
let correct_trials = trials.filter({correct: true})
let accuracy = correct_trials.count()/trials.count();
console.log(`Practice accuracy: ${accuracy}`);
// add this before the accuracy check
// if attempts is greater than the max quit loop
if(attempts >= max_attempts){
return false
}
// If accuracy is below 75%, repeat the block
if (accuracy < 0.75) {
return true; // Loop again
} else {
return false; // Move on
}
}
};
const end_practice = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `<p>You have completed the practice trials.</p>
<p>When you are ready to begin the experimental trials, press the space bar.</p>`,
choices: " ",
post_trial_gap: 250,
data: {
phase: "instructions"
}
}
// ============================================
// Flanker Experimental Block
// ============================================
// fixation
let exp_fixation = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `<p style="font-size:48px">+</p>`,
choices: "NO_KEYS",
post_trial_gap: 250,
trial_duration: 1000,
data: {
trial_part: "fixation"
}
}
// stimulus
let exp_stimulus = {
type: jsPsychHtmlKeyboardResponse,
stimulus: function(){
let target = jsPsych.evaluateTimelineVariable("target");
let distractor = jsPsych.evaluateTimelineVariable("distractor");
let distance = jsPsych.evaluateTimelineVariable("distance");
// Create HTML with custom spacing
let output = `
<div style="font-size: 100px; font-family: monospace;">
${distractor}${distractor}<span style="margin: 0 ${distance}px;">${target}</span>${distractor}${distractor}
</div>`;
return output;
},
choices: ["a", "l"],
trial_duration: 1000,
post_trial_gap: 250,
data: {
trial_part: "stimulus"
},
on_finish: function(data){
// store accuracy
data.correct = jsPsych.pluginAPI.compareKeys(data.response, data.correct_response)
}
}
// conditional feedback
let exp_feedback = {
timeline: [
{
type: jsPsychHtmlKeyboardResponse,
stimulus: function(){
// get the last response
let last_trial = jsPsych.data.get().last(1).values()[0]
let output
if(last_trial.response === null){
// this will check for non-response first
output = `<p style="font-size:48px">Too Slow! Respond Faster!</p>`
} else {
// else incorrect
output = `<p style="font-size:48px">Incorrect!</p>`
}
return output;
},
choices: "NO_KEYS",
trial_duration: 1000,
post_trial_gap: 250,
data: {
trial_part: "feedback"
}
}
],
conditional_function: function() {
// get the last response
let last_trial = jsPsych.data.get().last(1).values()[0]
if(last_trial.correct){
return false
} else {
return true
}
}
}
// exp timeline
let exp_flanker = {
timeline: [
exp_fixation,
exp_stimulus,
exp_feedback
],
timeline_variables: [
{ target: "<", distractor: "<", distance: 0, congruency: "congruent", correct_response: "a" },
{ target: "<", distractor: "<", distance: 50, congruency: "congruent", correct_response: "a" },
{ target: "<", distractor: ">", distance: 0, congruency: "incongruent", correct_response: "a" },
{ target: "<", distractor: ">", distance: 50, congruency: "incongruent", correct_response: "a" },
{ target: ">", distractor: "<", distance: 0, congruency: "incongruent", correct_response: "l" },
{ target: ">", distractor: "<", distance: 50, congruency: "incongruent", correct_response: "l" },
{ target: ">", distractor: ">", distance: 0, congruency: "congruent", correct_response: "l" },
{ target: ">", distractor: ">", distance: 50, congruency: "congruent", correct_response: "l" }
],
randomize_order: true,
data: {
phase: "flanker",
target: jsPsych.timelineVariable("target"),
distractor: jsPsych.timelineVariable("distractor"),
congruency: jsPsych.timelineVariable("congruency"),
correct_response: jsPsych.timelineVariable("correct_response")
}
};
// ============================================
// Savd Data Trial
// ============================================
const saveData = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `
<div style="text-align: center;">
<p>Experiment complete!</p>
<p>Click the button below to save your data locally:</p>
<button id="save-btn" style="padding: 10px 20px; font-size: 16px; cursor: pointer;">
Click here to save the data locally
</button>
</div>
`,
choices: "NO_KEYS",
trial_duration: null,
on_load: function() {
document.getElementById("save-btn").addEventListener("click", function() {
jsPsych.data.get().localSave("csv", "flanker_data.csv");
});
}
};
// ============================================
// Run jsPsych
// ============================================
jsPsych.run([
instructions,
// practice_flanker,
end_practice,
exp_flanker,
saveData
]); 20.2.8 Add Instructions
Finally, we can go ahead and fill out our instructions.
I’ll also remove the comments on the practice_flanker so that the whole experiment is displayed.
<!DOCTYPE html>
<html>
<head>
<title>Lab 8: Cognitive Control</title>
<!-- jsPsych -->
<script src="jspsych/jspsych.js"></script>
<link href="jspsych/jspsych.css" rel="stylesheet" type="text/css" />
<!-- jPsych plugins -->
<script src="jspsych/plugin-instructions.js"></script>
<script src="jspsych/plugin-html-keyboard-response.js"></script>
<!-- custom CSS -->
<link href="style.css" rel="stylesheet" type="text/css" />
</head>
<body>
<!-- custom JS -->
<script src="exp.js"></script>
</body>
</html>
// ============================================
// Initiate jsPsych
// ============================================
const jsPsych = initJsPsych();
// ============================================
// Instructions
// ============================================
const instructions = {
type: jsPsychInstructions,
pages: [
`<p>Welcome to the Experiment!</p>
<p>Use the buttons below to navigate through the instructions.</p>`,
`<p>In this experiment, you will complete what's known as a <strong>flanker task</strong>.</p>
<p>On each trial, you will see a row of arrows displayed on the screen.</p>`,
`<p>Your task is to respond to the <strong>direction of the center arrow</strong> only.</p>
<p>Ignore the arrows on either side (the "flankers").</p>`,
`<p><strong>Response Keys:</strong></p>
<p>Press <strong>A</strong> if the center arrow points LEFT</p>
<p>Press <strong>L</strong> if the center arrow points RIGHT</p>
<p>You will only have 1 second to respond to each trial. </p>
<p>Respond as quickly and accurately as possible.</p>`,
`<p>The experiment consists of two parts:</p>
<p>1. A <strong>practice block</strong> to familiarize yourself with the task</p>
<p>2. An <strong>experimental block</strong> with the main trials</p>`,
`<p>When you are ready to begin the practice trials, press "Next".</p>`
],
show_clickable_nav: true
}
// ============================================
// Flanker Practice Block
// ============================================
// add a variable to track practice attempts
let attempts = 0
// this variable sets the maximum loops
let max_attempts = 3
// fixation
let practice_fixation = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `<p style="font-size:48px">+</p>`,
choices: "NO_KEYS",
post_trial_gap: 250,
trial_duration: 1000,
data: {
trial_part: "fixation"
}
}
// stimulus
let practice_stimulus = {
type: jsPsychHtmlKeyboardResponse,
stimulus: function(){
let target = jsPsych.evaluateTimelineVariable("target");
let distractor = jsPsych.evaluateTimelineVariable("distractor");
let distance = jsPsych.evaluateTimelineVariable("distance");
// Create HTML with custom spacing
let output = `
<div style="font-size: 100px; font-family: monospace;">
${distractor}${distractor}<span style="margin: 0 ${distance}px;">${target}</span>${distractor}${distractor}
</div>`;
return output;
},
choices: ["a", "l"],
trial_duration: 1000,
post_trial_gap: 250,
data: {
trial_part: "stimulus"
},
on_finish: function(data){
// store accuracy
data.correct = jsPsych.pluginAPI.compareKeys(data.response, data.correct_response)
}
}
// feedback
let practice_feedback = {
type: jsPsychHtmlKeyboardResponse,
stimulus: function(){
// get the last response
let last_trial = jsPsych.data.get().last(1).values()[0]
let output
if(last_trial.response === null){
// this will check for non-response first
output = `<p style="font-size:48px">Too Slow! Respond Faster!</p>`
} else if(last_trial.correct){
// else if correct
output = `<p style="font-size:48px">Correct!</p>`
} else {
// else incorrect
output = `<p style="font-size:48px">Incorrect!</p>`
}
return output;
},
choices: "NO_KEYS",
trial_duration: 1000,
post_trial_gap: 250,
data: {
trial_part: "feedback"
}
}
// practice timeline
let practice_flanker = {
timeline: [
practice_fixation,
practice_stimulus,
practice_feedback
],
timeline_variables: [
{ target: "<", distractor: "<", distance: 0, congruency: "congruent", correct_response: "a" },
{ target: "<", distractor: "<", distance: 50, congruency: "congruent", correct_response: "a" },
{ target: "<", distractor: ">", distance: 0, congruency: "incongruent", correct_response: "a" },
{ target: "<", distractor: ">", distance: 50, congruency: "incongruent", correct_response: "a" },
{ target: ">", distractor: "<", distance: 0, congruency: "incongruent", correct_response: "l" },
{ target: ">", distractor: "<", distance: 50, congruency: "incongruent", correct_response: "l" },
{ target: ">", distractor: ">", distance: 0, congruency: "congruent", correct_response: "l" },
{ target: ">", distractor: ">", distance: 50, congruency: "congruent", correct_response: "l" }
],
randomize_order: true,
data: {
phase: "flanker practice",
target: jsPsych.timelineVariable("target"),
distractor: jsPsych.timelineVariable("distractor"),
congruency: jsPsych.timelineVariable("congruency"),
correct_response: jsPsych.timelineVariable("correct_response")
},
loop_function: function(data) {
// add one to attempts
attempts++
let trials = data.filter({trial_part: "stimulus"})
let correct_trials = trials.filter({correct: true})
let accuracy = correct_trials.count()/trials.count();
console.log(`Practice accuracy: ${accuracy}`);
// add this before the accuracy check
// if attempts is greater than the max quit loop
if(attempts >= max_attempts){
return false
}
// If accuracy is below 75%, repeat the block
if (accuracy < 0.75) {
return true; // Loop again
} else {
return false; // Move on
}
}
};
const end_practice = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `<p>You have completed the practice trials.</p>
<p>When you are ready to begin the experimental trials, press the space bar.</p>`,
choices: " ",
post_trial_gap: 250,
data: {
phase: "instructions"
}
}
// ============================================
// Flanker Experimental Block
// ============================================
// fixation
let exp_fixation = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `<p style="font-size:48px">+</p>`,
choices: "NO_KEYS",
post_trial_gap: 250,
trial_duration: 1000,
data: {
trial_part: "fixation"
}
}
// stimulus
let exp_stimulus = {
type: jsPsychHtmlKeyboardResponse,
stimulus: function(){
let target = jsPsych.evaluateTimelineVariable("target");
let distractor = jsPsych.evaluateTimelineVariable("distractor");
let distance = jsPsych.evaluateTimelineVariable("distance");
// Create HTML with custom spacing
let output = `
<div style="font-size: 100px; font-family: monospace;">
${distractor}${distractor}<span style="margin: 0 ${distance}px;">${target}</span>${distractor}${distractor}
</div>`;
return output;
},
choices: ["a", "l"],
trial_duration: 1000,
post_trial_gap: 250,
data: {
trial_part: "stimulus"
},
on_finish: function(data){
// store accuracy
data.correct = jsPsych.pluginAPI.compareKeys(data.response, data.correct_response)
}
}
// conditional feedback
let exp_feedback = {
timeline: [
{
type: jsPsychHtmlKeyboardResponse,
stimulus: function(){
// get the last response
let last_trial = jsPsych.data.get().last(1).values()[0]
let output
if(last_trial.response === null){
// this will check for non-response first
output = `<p style="font-size:48px">Too Slow! Respond Faster!</p>`
} else {
// else incorrect
output = `<p style="font-size:48px">Incorrect!</p>`
}
return output;
},
choices: "NO_KEYS",
trial_duration: 1000,
post_trial_gap: 250,
data: {
trial_part: "feedback"
}
}
],
conditional_function: function() {
// get the last response
let last_trial = jsPsych.data.get().last(1).values()[0]
if(last_trial.correct){
return false
} else {
return true
}
}
}
// exp timeline
let exp_flanker = {
timeline: [
exp_fixation,
exp_stimulus,
exp_feedback
],
timeline_variables: [
{ target: "<", distractor: "<", distance: 0, congruency: "congruent", correct_response: "a" },
{ target: "<", distractor: "<", distance: 50, congruency: "congruent", correct_response: "a" },
{ target: "<", distractor: ">", distance: 0, congruency: "incongruent", correct_response: "a" },
{ target: "<", distractor: ">", distance: 50, congruency: "incongruent", correct_response: "a" },
{ target: ">", distractor: "<", distance: 0, congruency: "incongruent", correct_response: "l" },
{ target: ">", distractor: "<", distance: 50, congruency: "incongruent", correct_response: "l" },
{ target: ">", distractor: ">", distance: 0, congruency: "congruent", correct_response: "l" },
{ target: ">", distractor: ">", distance: 50, congruency: "congruent", correct_response: "l" }
],
randomize_order: true,
data: {
phase: "flanker",
target: jsPsych.timelineVariable("target"),
distractor: jsPsych.timelineVariable("distractor"),
congruency: jsPsych.timelineVariable("congruency"),
correct_response: jsPsych.timelineVariable("correct_response")
}
};
// ============================================
// Savd Data Trial
// ============================================
const saveData = {
type: jsPsychHtmlKeyboardResponse,
stimulus: `
<div style="text-align: center;">
<p>Experiment complete!</p>
<p>Click the button below to save your data locally:</p>
<button id="save-btn" style="padding: 10px 20px; font-size: 16px; cursor: pointer;">
Click here to save the data locally
</button>
</div>
`,
choices: "NO_KEYS",
trial_duration: null,
on_load: function() {
document.getElementById("save-btn").addEventListener("click", function() {
jsPsych.data.get().localSave("csv", "flanker_data.csv");
});
}
};
// ============================================
// Run jsPsych
// ============================================
jsPsych.run([
instructions,
practice_flanker,
end_practice,
exp_flanker,
saveData
]); 20.3 Test and Save/Check Data
Now that it’s complete, run yourself through the whole experiment and save the data file (it’ll end up in your Downloads folder). Confirm that the data looks as expected and that everything is saving correctly.
20.4 Stretch Goals
20.4.1 End the experiment for participants who fail the practice
If participants fail the practice three times, let’s end the experiment early. This keeps poor participants from continuing.
This can be accomplished by adding conditional logic that will skip the exp_flanker if attempts is greater than max_attempts.
20.4.2 Add a display that pauses the experiment if they miss three responses in a row
Change the experimental block so that if participants fail to respond three trials in a row, the experiment stops with a screen that says “You have a missed three responses in a row. Please try to respond as quickly as possible. Press the space bar to continue”.
You’ll need a variable to track missed responses, adding one if they miss and resetting it to 0 when they don’t miss. Then you’ll need a new conditional trial that checks the miss count and displays the trial if misses are greater than 3.