18  Lab 7: Spatial Attention

18.1 Research in Brief: The Posner Cuing Paradigm

18.1.1 The Research Area

Attention refers to a family of cognitive mechanisms that combine to help us select, modulate, and sustain focus on information that might be most relevant for behavior. Our ability to process information is capacity-limited, meaning we can handle only small amounts of information at a time. Attention functions as an essential gatekeeper, allowing us to select and prioritize some information over other information when stimuli compete for our processing resources.

Attention research addresses fundamental questions about cognitive control and information processing. How do we decide what to focus on among competing stimuli? Can we prepare our attention for events before they occur? What mechanisms allow us to shift attention efficiently between different locations or objects? Understanding attention has important implications for performance in complex tasks, learning efficiency, and disorders affecting cognitive control.

The study of attention also connects to many practical applications in areas requiring sustained focus and rapid responses, such as air traffic control, driving, medical diagnosis, and educational settings. To investigate these attention mechanisms systematically, researchers have developed experimental paradigms that can isolate and measure different aspects of attentional processing under controlled conditions.

18.1.2 Types of Attention

Attention operates through several distinct but interconnected systems. Selective attention allows us to focus on relevant information while ignoring distractors, like following a conversation at a noisy party. Divided attention involves simultaneously processing multiple streams of information, such as listening to music while reading. Sustained attention refers to maintaining focus over extended periods, as required during long monitoring tasks.

Spatial attention represents a particularly important form of selective attention that involves directing cognitive resources to specific locations in visual space. Unlike other forms of attention that focus on particular objects or features, spatial attention operates like a mental spotlight that can be moved around the visual field to enhance processing at specific locations. This system allows us to prepare for events that might occur at particular spatial positions, even before any stimuli appear there.

Research distinguishes between two primary mechanisms for controlling spatial attention: endogenous attention that we consciously direct based on our goals and expectations, and exogenous attention that is automatically captured by sudden or salient stimuli in the environment. These systems have different temporal characteristics and serve complementary functions in attention control.

18.1.3 The Research Design

The spatial cuing paradigm developed by Posner (1980) uses a within-subjects experimental design to examine how different types of cues influence the allocation of spatial attention. Participants respond to target stimuli that appear at different locations following various types of spatial cues.

Stimulus Presentation: Participants fixate on a central point while viewing a display containing two or more potential target locations, typically boxes positioned to the left and right of fixation. Before the target appears, a cue indicates where the target is likely to appear.

Cue Types: The paradigm employs two distinct cuing conditions. Endogenous cues are symbolic indicators (such as central arrows) that point toward the likely target location. These cues require interpretation and voluntary attention shifts. Exogenous cues are sudden onset stimuli (such as brief flashes or brightening) that appear at the potential target locations themselves, automatically capturing attention.

Task Requirements: Participants must detect or identify target stimuli (such as asterisks or letters) that appear at cued or uncued locations. They respond as quickly as possible while maintaining accuracy, typically by pressing a key when they detect the target.

The design manipulates cue validity: valid trials where cues correctly predict target location (typically 75% of trials), invalid trials where cues incorrectly predict target location, and neutral trials where cues provide no spatial information.

The within-subjects design allows researchers to compare response times across different cuing conditions within the same participants. Each person experiences valid, invalid, and neutral trials for both endogenous and exogenous cues at various cue-target intervals. Each participant acts as their own ‘control’. This approach controls for individual differences in overall response speed and detection ability while isolating the specific effects of spatial cuing on attention allocation.

18.1.4 Key Findings

The within-subjects comparisons have revealed different temporal patterns for endogenous and exogenous spatial attention. Endogenous cuing showed benefits for valid trials (faster responses to targets at cued locations) and costs for invalid trials (slower responses to targets at uncued locations) that developed gradually over 300-500 milliseconds and remained stable for several seconds. This pattern reflects the voluntary, sustained nature of goal-directed attention.

Exogenous cuing produced rapid attention capture with benefits peaking around 150 milliseconds, followed by inhibition of return at longer intervals (typically after 300-500 milliseconds) where responses became slower to targets appearing at previously cued locations. This biphasic pattern reflects the automatic, transient nature of stimulus-driven attention. Inhibition of return may serve an adaptive function by preventing attention from repeatedly returning to the same location during visual search.

The magnitude of cuing effects typically ranges from 20-50 milliseconds for response time differences between valid and invalid trials, with endogenous cues showing larger and more sustained effects than exogenous cues at optimal intervals. These effects remain consistent across participants despite individual differences in overall response speed.

18.1.5 Implications

The distinct temporal profiles of endogenous and exogenous cuing provide behavioural evidence for separate attention control systems. This behavioural evidence is supported by neuroimaging and neurophysiology studies showing that endogenous attention involves dorsal frontoparietal networks associated with goal-directed control and requires time to implement but can be maintained strategically. Exogenous attention involves ventral networks associated with stimulus-driven reorienting, showing rapid onset followed by active suppression.

These findings support dual-process theories of attention and the spotlight metaphor of spatial attention, demonstrating that attention can be directed to specific locations in advance of stimulus presentation. The consistent patterns across participants and laboratories suggest these represent fundamental properties of human attention rather than task-specific effects. Inhibition of return may serve an adaptive function by helping us avoid checking the same locations repeatedly when searching for objects, such as when trying to find a friend in a crowd.

The Posner cueing paradigm exemplifies how carefully designed experimental methods can reveal fundamental cognitive mechanisms. Its elegant simplicity allows researchers to isolate specific attention processes while maintaining experimental control, making it a model for how laboratory tasks can provide insights into complex mental operations. The paradigm continues to be widely used across laboratories, generating reliable findings that have built a cumulative knowledge base about spatial attention mechanisms.

18.1.6 Further Reading

Chun, M. M., & Most, S. B. (2021). External attention. In Cognition.

Jensen, M. S., Yao, R., Street, W. N., & Simons, D. J. (2011). Change blindness and inattentional blindness. Wiley Interdisciplinary Reviews: Cognitive Science, 2(5), 529-546.

Most, S. B., Chun, M. M., Widders, D. M., & Zald, D. H. (2005). Attentional rubbernecking: Cognitive control and personality in emotion-induced blindness. Psychonomic Bulletin & Review, 12, 654-661.

Posner, M. I. (1980). Orienting of attention. Quarterly Journal of Experimental Psychology, 32(1), 3-25.

Röer, J. P., & Cowan, N. (2021). A preregistered replication and extension of the cocktail party phenomenon: One’s name captures attention, unexpected words do not. Journal of Experimental Psychology: Learning, Memory, and Cognition, 47(2), 234.

18.2 Program a Posner Cuing Task

In this exercise, we will program the Posner Cuing task with two spatial locations: left and right sides of the screen. We will focus on creating the exogenous cue condition. On each trial, a cue will flash at either the right or left location, followed by a target X appearing at either the left or right location. The task is to indicate which location the target appears in.

The cue will be 75% valid, meaning that on 75% of trials, the cue indicates the correct location where the target will appear. We will manipulate the stimulus onset asynchrony (SOA), which refers to the time interval between the cue and the target. We will test three different SOAs designed to capture the temporal dynamics of exogenous attention: short (100ms), medium (300ms), and long (800ms).

These specific durations allow us to observe the complete profile of exogenous attention effects. At 100ms, we expect to see automatic attention capture with faster responses to validly cued locations. At 300ms, the initial facilitation effect diminishes.

The initial folder contains the standard jsPsych boilerplate files with no additional components added yet.

📂 L07
├──  📄 index.html
├──  📄 exp.js
├──  📄 style.css
└──  📂 jspsych

18.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.

 <!DOCTYPE html>
<html>
<head>
    <title>Lab 7: Attention</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>
 
 // 1. Initialize jsPsych
const jsPsych = initJsPsych();

 // 2. Define our trials
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
}

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", "attention_data.csv");
    });
  }
};

// 3. Run jsPsych with our trials
jsPsych.run([
  instructions,
  saveData
]); 
  
Live JsPsych Demo Click inside the demo to activate demo

18.2.2 Create an Outline

One of the ways I’ll get started on a new programming task is by creating an outline of what needs to happen on every trial.

For the Posner cuing paradigm, we need a (1) fixation (2) cue (3) target. In all three displays there should be two boxes, one on the left and one on the right. The fixation cross won’t have a box around it, but it will stay on screen the entire time.

Here’s how I’d start. Before creating all the complicated displays, I create a simple outline with placeholders. I also added comments inside the timeline to help keep it organized. Remember that the browser does not care about white space and comments are ignored. So I can keep my code organized by labeling the parts of the trial.

 <!DOCTYPE html>
<html>
<head>
    <title>Lab 7: Attention</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>
 
 // 1. Initialize jsPsych
const jsPsych = initJsPsych();

 // 2. Define our trials
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
}

const posnerExo = {
  timeline: [
    // 1. Fixation
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: "fixation",
      trial_duration: 1000,
      choices: "NO_KEYS"
    },

    // 2. Cue
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: "cue",
      trial_duration: 300,
      choices: "NO_KEYS"
    },

    // 3. Target
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: "target",
      choices: ["a", "l"]
    }
  ]

}

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", "attention_data.csv");
    });
  }
};

// 3. Run jsPsych with our trials
jsPsych.run([
  instructions,
  posnerExo,
  saveData
]); 
  
Live JsPsych Demo Click inside the demo to activate demo

18.2.3 Create an HTML display

Now, let’s create our display with three locations: left, center, and right. First, we’ve replaced our stimulus for the target display with a function. Remember that we’re going to need some if-else logic to change where the cue and target appear, so we’ll need a function to do that.

Next, we use flexbox at two levels to achieve the layout we need for the Posner cuing task. I highly recommend reviewing how flexbox works. It is a very convenient way to set to get complex layouts. You can read more about it here: https://css-tricks.com/snippets/css/a-guide-to-flexbox/

Here’s a breakdown of what we’ve done in this step.

The outer container uses display: flex with justify-content: space-between to distribute the three locations evenly across the 600px width. The space-between property pushes the left and right boxes to the edges while centering the middle location. We add margin: 0 auto to center the entire 600px container on the page.

Each of the three locations also uses display: flex with both align-items: center and justify-content: center. This creates perfect centering for any text content within each box. The align-items: center handles vertical centering, while justify-content: center handles horizontal centering.

This nested flexbox approach gives us precise control over both the spacing between locations and the positioning of content within each location, which is essential for the spatial precision required in attention experiments.

Two Notes: First, keep in mind that I have some intuition about how large the divs should be, but you may go through some trial-and-error before you settle on CSS that you’re happy with. That is perfectly normal.

Second, I decided to write the CSS in the external stylesheet because I know that I am going to have to repeat this code a few times. Therefore, it makes sense to write it once in the stylesheet and refer to the classes in the HTML. There are three classes, one for the container, one for the location boxes, and the third is for the border. I had to separate that out because not all the boxes have borders.

 <!DOCTYPE html>
<html>
<head>
    <title>Lab 7: Attention</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>
 
 // 1. Initialize jsPsych
const jsPsych = initJsPsych();

 // 2. Define our trials
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
}

const posnerExo = {
  timeline: [
    // 1. Fixation
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: "fixation",
      trial_duration: 1000,
      choices: "NO_KEYS"
    },

    // 2. Cue
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: "cue",
      trial_duration: 300,
      choices: "NO_KEYS"
    },

    // 3. Target
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
          let output 

          output = `<div class="posner-container">
                      <div class="posner-location posner-box">L</div>
                      <div class="posner-location">C</div>
                      <div class="posner-location posner-box">R</div>
                    </div>`

          return output
      },
      choices: ["a", "l"]
    }
  ]

}

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", "attention_data.csv");
    });
  }
};

// 3. Run jsPsych with our trials
jsPsych.run([
  instructions,
  posnerExo,
  saveData
]); 
 .posner-container {
  display: flex;
  justify-content: space-between;
  width: 600px;
  margin: 0 auto;
  font-size: 48px;
}

.posner-location {
  width: 100px;
  height: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.posner-box {
  border: 2px solid black;
} 
Live JsPsych Demo Click inside the demo to activate demo

18.2.4 Copy the HTML across trials

Now that we have a display we’re happy with, let’s copy that across the fixation and cue parts and see how it looks.

 <!DOCTYPE html>
<html>
<head>
    <title>Lab 7: Attention</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>
 
 // 1. Initialize jsPsych
const jsPsych = initJsPsych();

 // 2. Define our trials
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
}

const posnerExo = {
  timeline: [
    // 1. Fixation
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
          let output 

          output = `<div class="posner-container">
                      <div class="posner-location posner-box"></div>
                      <div class="posner-location">+</div>
                      <div class="posner-location posner-box"></div>
                    </div>`

          return output
      },
      trial_duration: 1000,
      choices: "NO_KEYS"
    },

    // 2. Cue
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
          let output 

          output = `<div class="posner-container">
                      <div class="posner-location posner-box">L</div>
                      <div class="posner-location">CUE</div>
                      <div class="posner-location posner-box">R</div>
                    </div>`

          return output
      },
      trial_duration: 300,
      choices: "NO_KEYS"
    },

    // 3. Target
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
          let output 

          output = `<div class="posner-container">
                      <div class="posner-location posner-box">L</div>
                      <div class="posner-location">C</div>
                      <div class="posner-location posner-box">R</div>
                    </div>`

          return output
      },
      choices: ["a", "l"]
    }
  ]

}

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", "attention_data.csv");
    });
  }
};

// 3. Run jsPsych with our trials
jsPsych.run([
  instructions,
  posnerExo,
  saveData
]); 
 .posner-container {
  display: flex;
  justify-content: space-between;
  width: 600px;
  margin: 0 auto;
  font-size: 48px;
}

.posner-location {
  width: 100px;
  height: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.posner-box {
  border: 2px solid black;
} 
Live JsPsych Demo Click inside the demo to activate demo

18.2.5 Add Timeline Variables

Before we can add the logic to update the display, we’re going to need our timeline_variables that are going to control the logic.

To control the cue location, we’ll need a timeline variable that indicates left or right. To control the target location, we’ll need a timeline variable that indicates whether it is left or right.

For now, we’re just going to add one for each possible left/right combination.

 <!DOCTYPE html>
<html>
<head>
    <title>Lab 7: Attention</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>
 
 // 1. Initialize jsPsych
const jsPsych = initJsPsych();

 // 2. Define our trials
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
}

const posnerExo = {
  timeline: [
    // 1. Fixation
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
          let output 

          output = `<div class="posner-container">
                      <div class="posner-location posner-box"></div>
                      <div class="posner-location">+</div>
                      <div class="posner-location posner-box"></div>
                    </div>`

          return output
      },
      trial_duration: 1000,
      choices: "NO_KEYS"
    },

    // 2. Cue
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
          let output 

          output = `<div class="posner-container">
                      <div class="posner-location posner-box">L</div>
                      <div class="posner-location">CUE</div>
                      <div class="posner-location posner-box">R</div>
                    </div>`

          return output
      },
      trial_duration: 300,
      choices: "NO_KEYS"
    },

    // 3. Target
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
          let output 

          output = `<div class="posner-container">
                      <div class="posner-location posner-box">L</div>
                      <div class="posner-location">C</div>
                      <div class="posner-location posner-box">R</div>
                    </div>`

          return output
      },
      choices: ["a", "l"]
    }
  ],
  timeline_variables: [
    {cue_location: "left", target_location: "left"},
    {cue_location: "right", target_location: "right"},
    {cue_location: "left", target_location: "right"},
    {cue_location: "right", target_location: "left"}
  ],
  randomize_order: true
}

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", "attention_data.csv");
    });
  }
};

// 3. Run jsPsych with our trials
jsPsych.run([
  instructions,
  posnerExo,
  saveData
]); 
 .posner-container {
  display: flex;
  justify-content: space-between;
  width: 600px;
  margin: 0 auto;
  font-size: 48px;
}

.posner-location {
  width: 100px;
  height: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.posner-box {
  border: 2px solid black;
} 
Live JsPsych Demo Click inside the demo to activate demo

18.2.6 Add Cue and Target Logic

Alright, let’s add our logic! We’ll add some if-else statements inside our cue and target stimulus to change where the cue appears (we’ll use the background color as a cue) and where the target appears (we’ll use an X for the target).

 <!DOCTYPE html>
<html>
<head>
    <title>Lab 7: Attention</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>
 
 // 1. Initialize jsPsych
const jsPsych = initJsPsych();

 // 2. Define our trials
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
}

const posnerExo = {
  timeline: [
    // 1. Fixation
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
          let output 

          output = `<div class="posner-container">
                      <div class="posner-location posner-box"></div>
                      <div class="posner-location">+</div>
                      <div class="posner-location posner-box"></div>
                    </div>`

          return output
      },
      trial_duration: 1000,
      choices: "NO_KEYS"
    },

    // 2. Cue
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
          let output 
          let cue_location = jsPsych.evaluateTimelineVariable("cue_location")
          
          if(cue_location === "left"){
            output = `<div class="posner-container">
                        <div class="posner-location posner-box" style="background-color: yellow "></div>
                        <div class="posner-location">+</div>
                        <div class="posner-location posner-box"></div>
                      </div>`

          } else if(cue_location === "right"){
            output = `<div class="posner-container">
                        <div class="posner-location posner-box"></div>
                        <div class="posner-location">+</div>
                        <div class="posner-location posner-box" style="background-color: yellow "></div>
                      </div>`

          }

          return output
      },
      trial_duration: 300,
      choices: "NO_KEYS"
    },

    // 3. Target
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
         let output 
         let target_location = jsPsych.evaluateTimelineVariable("target_location")

         if(target_location === "left"){
            output = `<div class="posner-container">
                        <div class="posner-location posner-box">X</div>
                        <div class="posner-location">+</div>
                        <div class="posner-location posner-box"></div>
                      </div>`

          } else if(target_location === "right"){
            output = `<div class="posner-container">
                        <div class="posner-location posner-box"></div>
                        <div class="posner-location">+</div>
                        <div class="posner-location posner-box">X</div>
                      </div>`

          }

          return output
      },
      choices: ["a", "l"]
    }
  ],
  timeline_variables: [
    {cue_location: "left", target_location: "left"},
    {cue_location: "right", target_location: "right"},
    {cue_location: "left", target_location: "right"},
    {cue_location: "right", target_location: "left"}
  ],
  randomize_order: true
}

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", "attention_data.csv");
    });
  }
};

// 3. Run jsPsych with our trials
jsPsych.run([
  instructions,
  posnerExo,
  saveData
]); 
 .posner-container {
  display: flex;
  justify-content: space-between;
  width: 600px;
  margin: 0 auto;
  font-size: 48px;
}

.posner-location {
  width: 100px;
  height: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.posner-box {
  border: 2px solid black;
} 
Live JsPsych Demo Click inside the demo to activate demo

18.2.7 Change to 75% Valid Trials

Now that we have the basic setup, we need to add our key manipulations. First, we need to make 75% of the trials “valid”. Valid trials are when the cue is in the same location as the target.

I’m going to add some helpful labels to our timeline variables indicating valid/invalid. Then, I’m going to change the proportion of trials in the simplest way: I’ll make it so that there are 3 valid trials of each type and only 1 invalid trial of each type.

Remember that white space doesn’t matter anywhere, so I also added some comments and formatting to make it easier to read what is happening in the timeline_variables.

 <!DOCTYPE html>
<html>
<head>
    <title>Lab 7: Attention</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>
 
 // 1. Initialize jsPsych
const jsPsych = initJsPsych();

 // 2. Define our trials
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
}

const posnerExo = {
  timeline: [
    // 1. Fixation
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
          let output 

          output = `<div class="posner-container">
                      <div class="posner-location posner-box"></div>
                      <div class="posner-location">+</div>
                      <div class="posner-location posner-box"></div>
                    </div>`

          return output
      },
      trial_duration: 1000,
      choices: "NO_KEYS"
    },

    // 2. Cue
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
          let output 
          let cue_location = jsPsych.evaluateTimelineVariable("cue_location")
          
          if(cue_location === "left"){
            output = `<div class="posner-container">
                        <div class="posner-location posner-box" style="background-color: yellow "></div>
                        <div class="posner-location">+</div>
                        <div class="posner-location posner-box"></div>
                      </div>`

          } else if(cue_location === "right"){
            output = `<div class="posner-container">
                        <div class="posner-location posner-box"></div>
                        <div class="posner-location">+</div>
                        <div class="posner-location posner-box" style="background-color: yellow "></div>
                      </div>`

          }

          return output
      },
      trial_duration: 300,
      choices: "NO_KEYS"
    },

    // 3. Target
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
         let output 
         let target_location = jsPsych.evaluateTimelineVariable("target_location")

         if(target_location === "left"){
            output = `<div class="posner-container">
                        <div class="posner-location posner-box">X</div>
                        <div class="posner-location">+</div>
                        <div class="posner-location posner-box"></div>
                      </div>`

          } else if(target_location === "right"){
            output = `<div class="posner-container">
                        <div class="posner-location posner-box"></div>
                        <div class="posner-location">+</div>
                        <div class="posner-location posner-box">X</div>
                      </div>`

          }

          return output
      },
      choices: ["a", "l"]
    }
  ],
  timeline_variables: [
    // valid trials 75%
    {cue_location: "left", target_location: "left", validity: "valid"},
    {cue_location: "right", target_location: "right", validity: "valid"},
    {cue_location: "left", target_location: "left", validity: "valid"},
    {cue_location: "right", target_location: "right", validity: "valid"},
    {cue_location: "left", target_location: "left", validity: "valid"},
    {cue_location: "right", target_location: "right", validity: "valid"},

    // invalid trials 25%
    {cue_location: "left", target_location: "right", validity: "invalid"},
    {cue_location: "right", target_location: "left", validity: "invalid"}
  ],
  randomize_order: true
}

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", "attention_data.csv");
    });
  }
};

// 3. Run jsPsych with our trials
jsPsych.run([
  instructions,
  posnerExo,
  saveData
]); 
 .posner-container {
  display: flex;
  justify-content: space-between;
  width: 600px;
  margin: 0 auto;
  font-size: 48px;
}

.posner-location {
  width: 100px;
  height: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.posner-box {
  border: 2px solid black;
} 
Live JsPsych Demo Click inside the demo to activate demo

18.2.8 Manipulate Cue Duration

The final thing we need to do is manipulate the cue duration. We’re going to have two different cue durations: 100 and 300 milliseconds.

We’ll need to add a new timeline variable (100 or 300) and we’ll double the trials to make sure we have one of each type.

Keep in mind, we don’t need a function to vary the trial_duration, we just need to make sure our timeline variable is in the correct format (numeric) and refer to it in the parameter using jsPsych.timelineVariable.

 <!DOCTYPE html>
<html>
<head>
    <title>Lab 7: Attention</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>
 
 // 1. Initialize jsPsych
const jsPsych = initJsPsych();

 // 2. Define our trials
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
}

const posnerExo = {
  timeline: [
    // 1. Fixation
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
          let output 

          output = `<div class="posner-container">
                      <div class="posner-location posner-box"></div>
                      <div class="posner-location">+</div>
                      <div class="posner-location posner-box"></div>
                    </div>`

          return output
      },
      trial_duration: 1000,
      choices: "NO_KEYS"
    },

    // 2. Cue
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
          let output 
          let cue_location = jsPsych.evaluateTimelineVariable("cue_location")
          
          if(cue_location === "left"){
            output = `<div class="posner-container">
                        <div class="posner-location posner-box" style="background-color: yellow "></div>
                        <div class="posner-location">+</div>
                        <div class="posner-location posner-box"></div>
                      </div>`

          } else if(cue_location === "right"){
            output = `<div class="posner-container">
                        <div class="posner-location posner-box"></div>
                        <div class="posner-location">+</div>
                        <div class="posner-location posner-box" style="background-color: yellow "></div>
                      </div>`

          }

          return output
      },
      trial_duration: jsPsych.timelineVariable("cue_duration"),
      choices: "NO_KEYS"
    },

    // 3. Target
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
         let output 
         let target_location = jsPsych.evaluateTimelineVariable("target_location")

         if(target_location === "left"){
            output = `<div class="posner-container">
                        <div class="posner-location posner-box">X</div>
                        <div class="posner-location">+</div>
                        <div class="posner-location posner-box"></div>
                      </div>`

          } else if(target_location === "right"){
            output = `<div class="posner-container">
                        <div class="posner-location posner-box"></div>
                        <div class="posner-location">+</div>
                        <div class="posner-location posner-box">X</div>
                      </div>`

          }

          return output
      },
      choices: ["a", "l"]
    }
  ],
  timeline_variables: [
    // 100 ms SOA
    // valid trials 75%
    {cue_location: "left", target_location: "left", validity: "valid", cue_duration: 100},
    {cue_location: "right", target_location: "right", validity: "valid", cue_duration: 100},
    {cue_location: "left", target_location: "left", validity: "valid", cue_duration: 100},
    {cue_location: "right", target_location: "right", validity: "valid", cue_duration: 100},
    {cue_location: "left", target_location: "left", validity: "valid", cue_duration: 100},
    {cue_location: "right", target_location: "right", validity: "valid", cue_duration: 100},

    // invalid trials 25%
    {cue_location: "left", target_location: "right", validity: "invalid", cue_duration: 100},
    {cue_location: "right", target_location: "left", validity: "invalid", cue_duration: 100},

    // 300 ms SOA
    // valid trials 75%
    {cue_location: "left", target_location: "left", validity: "valid", cue_duration: 300},
    {cue_location: "right", target_location: "right", validity: "valid", cue_duration: 300},
    {cue_location: "left", target_location: "left", validity: "valid", cue_duration: 300},
    {cue_location: "right", target_location: "right", validity: "valid", cue_duration: 300},
    {cue_location: "left", target_location: "left", validity: "valid", cue_duration: 300},
    {cue_location: "right", target_location: "right", validity: "valid", cue_duration: 300},

    // invalid trials 25%
    {cue_location: "left", target_location: "right", validity: "invalid", cue_duration: 300},
    {cue_location: "right", target_location: "left", validity: "invalid", cue_duration: 300}
  ],
  randomize_order: true
}

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", "attention_data.csv");
    });
  }
};

// 3. Run jsPsych with our trials
jsPsych.run([
  instructions,
  posnerExo,
  saveData
]); 
 .posner-container {
  display: flex;
  justify-content: space-between;
  width: 600px;
  margin: 0 auto;
  font-size: 48px;
}

.posner-location {
  width: 100px;
  height: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.posner-box {
  border: 2px solid black;
} 
Live JsPsych Demo Click inside the demo to activate demo

18.2.9 Instructions & Styling

Let’s add some proper instructions and change the styling of the experiment to have a dark background with off-white text. Note that I had to change the border color to match the text. This was simple because I just needed to change it in my external stylesheet once.

 <!DOCTYPE html>
<html>
<head>
    <title>Lab 7: Attention</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>
 
 // 1. Initialize jsPsych
const jsPsych = initJsPsych();

 // 2. Define our trials
const instructions = {
  type: jsPsychInstructions,
  pages: [
    `<p>Welcome to the Experiment!</p>
     <p>Use the buttons below to navigate through the instructions</p>`,

    `<p>On every trial, you will see a fixation point and two boxes</p>
     <p>A target symbol  ("X") will be presented in either the left or right box.</p>`,

    `<p>Press the left key ("A") if the X appears in the left box.</p>
     <p>Press the right key ("L") if the X appears in the right box.</p>`,

    `<p>Every trial, you will also see a yellow box appear, which is a cue. </p>
     <p>Do not respond to the yellow box, just ignore it!</p>
     <p>Only respond to the X, using the left and right keys.</p>`,

    `<p>Try to respond as quickly and as accurately as possible.</p>
    <p>When you are ready to begin the first trial, press "Next".</p>` 
  ],
  show_clickable_nav: true
}

const posnerExo = {
  timeline: [
    // 1. Fixation
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
          let output 

          output = `<div class="posner-container">
                      <div class="posner-location posner-box"></div>
                      <div class="posner-location">+</div>
                      <div class="posner-location posner-box"></div>
                    </div>`

          return output
      },
      trial_duration: 1000,
      choices: "NO_KEYS"
    },

    // 2. Cue
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
          let output 
          let cue_location = jsPsych.evaluateTimelineVariable("cue_location")
          
          if(cue_location === "left"){
            output = `<div class="posner-container">
                        <div class="posner-location posner-box" style="background-color: yellow "></div>
                        <div class="posner-location">+</div>
                        <div class="posner-location posner-box"></div>
                      </div>`

          } else if(cue_location === "right"){
            output = `<div class="posner-container">
                        <div class="posner-location posner-box"></div>
                        <div class="posner-location">+</div>
                        <div class="posner-location posner-box" style="background-color: yellow "></div>
                      </div>`

          }

          return output
      },
      trial_duration: jsPsych.timelineVariable("cue_duration"),
      choices: "NO_KEYS"
    },

    // 3. Target
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
         let output 
         let target_location = jsPsych.evaluateTimelineVariable("target_location")

         if(target_location === "left"){
            output = `<div class="posner-container">
                        <div class="posner-location posner-box">X</div>
                        <div class="posner-location">+</div>
                        <div class="posner-location posner-box"></div>
                      </div>`

          } else if(target_location === "right"){
            output = `<div class="posner-container">
                        <div class="posner-location posner-box"></div>
                        <div class="posner-location">+</div>
                        <div class="posner-location posner-box">X</div>
                      </div>`

          }

          return output
      },
      choices: ["a", "l"]
    }
  ],
  timeline_variables: [
    // 100 ms SOA
    // valid trials 75%
    {cue_location: "left", target_location: "left", validity: "valid", cue_duration: 100},
    {cue_location: "right", target_location: "right", validity: "valid", cue_duration: 100},
    {cue_location: "left", target_location: "left", validity: "valid", cue_duration: 100},
    {cue_location: "right", target_location: "right", validity: "valid", cue_duration: 100},
    {cue_location: "left", target_location: "left", validity: "valid", cue_duration: 100},
    {cue_location: "right", target_location: "right", validity: "valid", cue_duration: 100},

    // invalid trials 25%
    {cue_location: "left", target_location: "right", validity: "invalid", cue_duration: 100},
    {cue_location: "right", target_location: "left", validity: "invalid", cue_duration: 100},

    // 300 ms SOA
    // valid trials 75%
    {cue_location: "left", target_location: "left", validity: "valid", cue_duration: 300},
    {cue_location: "right", target_location: "right", validity: "valid", cue_duration: 300},
    {cue_location: "left", target_location: "left", validity: "valid", cue_duration: 300},
    {cue_location: "right", target_location: "right", validity: "valid", cue_duration: 300},
    {cue_location: "left", target_location: "left", validity: "valid", cue_duration: 300},
    {cue_location: "right", target_location: "right", validity: "valid", cue_duration: 300},

    // invalid trials 25%
    {cue_location: "left", target_location: "right", validity: "invalid", cue_duration: 300},
    {cue_location: "right", target_location: "left", validity: "invalid", cue_duration: 300}
  ],
  randomize_order: true
}

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", "attention_data.csv");
    });
  }
};

// 3. Run jsPsych with our trials
jsPsych.run([
  instructions,
  posnerExo,
  saveData
]); 
 body {
  background-color: #1a1a1a;
  color: #e0e0e0
}

.posner-container {
  display: flex;
  justify-content: space-between;
  width: 600px;
  margin: 0 auto;
  font-size: 48px;
}

.posner-location {
  width: 100px;
  height: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.posner-box {
  border: 2px solid #e0e0e0;
} 
Live JsPsych Demo Click inside the demo to activate demo

18.2.10 Adding Feedback

To add feedback text that says ‘correct!’ or ‘incorrect!’, we’ll need to add a few things. First, our timeline_variables is missing a label for the correct response. We’ll need that to determine accuracy.

Then, I added a new trial part after the target and, unlike what we’ve done before, I determine the accuracy in the stimulus function. Since it’s a function, we have the freedom to run our logic here instead.

Finally, I added if-else logic to change the output depending on the accuracy.

 <!DOCTYPE html>
<html>
<head>
    <title>Lab 7: Attention</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>
 
 // 1. Initialize jsPsych
const jsPsych = initJsPsych();

 // 2. Define our trials
const instructions = {
  type: jsPsychInstructions,
  pages: [
    `<p>Welcome to the Experiment!</p>
     <p>Use the buttons below to navigate through the instructions</p>`,

    `<p>On every trial, you will see a fixation point and two boxes</p>
     <p>A target symbol  ("X") will be presented in either the left or right box.</p>`,

    `<p>Press the left key ("A") if the X appears in the left box.</p>
     <p>Press the right key ("L") if the X appears in the right box.</p>`,

    `<p>Every trial, you will also see a yellow box appear, which is a cue. </p>
     <p>Do not respond to the yellow box, just ignore it!</p>
     <p>Only respond to the X, using the left and right keys.</p>`,

    `<p>Try to respond as quickly and as accurately as possible.</p>
    <p>When you are ready to begin the first trial, press "Next".</p>` 
  ],
  show_clickable_nav: true
}

const posnerExo = {
  timeline: [
    // 1. Fixation
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
          let output 

          output = `<div class="posner-container">
                      <div class="posner-location posner-box"></div>
                      <div class="posner-location">+</div>
                      <div class="posner-location posner-box"></div>
                    </div>`

          return output
      },
      trial_duration: 1000,
      choices: "NO_KEYS"
    },

    // 2. Cue
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
          let output 
          let cue_location = jsPsych.evaluateTimelineVariable("cue_location")
          
          if(cue_location === "left"){
            output = `<div class="posner-container">
                        <div class="posner-location posner-box" style="background-color: yellow "></div>
                        <div class="posner-location">+</div>
                        <div class="posner-location posner-box"></div>
                      </div>`

          } else if(cue_location === "right"){
            output = `<div class="posner-container">
                        <div class="posner-location posner-box"></div>
                        <div class="posner-location">+</div>
                        <div class="posner-location posner-box" style="background-color: yellow "></div>
                      </div>`

          }

          return output
      },
      trial_duration: jsPsych.timelineVariable("cue_duration"),
      choices: "NO_KEYS"
    },

    // 3. Target
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
         let output 
         let target_location = jsPsych.evaluateTimelineVariable("target_location")

         if(target_location === "left"){
            output = `<div class="posner-container">
                        <div class="posner-location posner-box">X</div>
                        <div class="posner-location">+</div>
                        <div class="posner-location posner-box"></div>
                      </div>`

          } else if(target_location === "right"){
            output = `<div class="posner-container">
                        <div class="posner-location posner-box"></div>
                        <div class="posner-location">+</div>
                        <div class="posner-location posner-box">X</div>
                      </div>`

          }

          return output
      },
      choices: ["a", "l"]
    },

    // 4. Feedback
    {
      type: jsPsychHtmlKeyboardResponse,
      stimulus: function(){
          // get the response from the last trial
          let last_trial = jsPsych.data.get().last(1).values()
          let last_response = last_trial[0].response

          // determine if the last response matches the correct response
          let correct_response = jsPsych.evaluateTimelineVariable("correct_response")
          let accuracy = jsPsych.pluginAPI.compareKeys(last_response, correct_response)
      
         // change output depending on accuracy
         if(accuracy){
            output = `<p style="color: ForestGreen; font-size: 48px;">Correct!</p>`

          } else {
            output = `<p style="color: LightCoral; font-size: 48px;">Incorrect!</p>`

          }

          return output
      },
      trial_duration: 1500,
      choices: "NO_KEYS"
    }
  ],
  timeline_variables: [
    // 100 ms SOA
    // valid trials 75%
    {cue_location: "left", target_location: "left", validity: "valid", cue_duration: 100, correct_response: "a"},
    {cue_location: "right", target_location: "right", validity: "valid", cue_duration: 100, correct_response: "l"},
    {cue_location: "left", target_location: "left", validity: "valid", cue_duration: 100, correct_response: "a"},
    {cue_location: "right", target_location: "right", validity: "valid", cue_duration: 100, correct_response: "l"},
    {cue_location: "left", target_location: "left", validity: "valid", cue_duration: 100, correct_response: "a"},
    {cue_location: "right", target_location: "right", validity: "valid", cue_duration: 100, correct_response: "l"},

    // invalid trials 25%
    {cue_location: "left", target_location: "right", validity: "invalid", cue_duration: 100, correct_response: "l"},
    {cue_location: "right", target_location: "left", validity: "invalid", cue_duration: 100, correct_response: "a"},

    // 300 ms SOA
    // valid trials 75%
    {cue_location: "left", target_location: "left", validity: "valid", cue_duration: 300, correct_response: "a"},
    {cue_location: "right", target_location: "right", validity: "valid", cue_duration: 300, correct_response: "l"},
    {cue_location: "left", target_location: "left", validity: "valid", cue_duration: 300, correct_response: "a"},
    {cue_location: "right", target_location: "right", validity: "valid", cue_duration: 300, correct_response: "l"},
    {cue_location: "left", target_location: "left", validity: "valid", cue_duration: 300, correct_response: "a"},
    {cue_location: "right", target_location: "right", validity: "valid", cue_duration: 300, correct_response: "l"},

    // invalid trials 25%
    {cue_location: "left", target_location: "right", validity: "invalid", cue_duration: 300, correct_response: "l"},
    {cue_location: "right", target_location: "left", validity: "invalid", cue_duration: 300, correct_response: "a"}
  ],
  randomize_order: true
}

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", "attention_data.csv");
    });
  }
};

// 3. Run jsPsych with our trials
jsPsych.run([
  instructions,
  posnerExo,
  saveData
]); 
 body {
  background-color: #1a1a1a;
  color: #e0e0e0
}

.posner-container {
  display: flex;
  justify-content: space-between;
  width: 600px;
  margin: 0 auto;
  font-size: 48px;
}

.posner-location {
  width: 100px;
  height: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.posner-box {
  border: 2px solid #e0e0e0;
} 
Live JsPsych Demo Click inside the demo to activate demo

18.3 Stretch Goals

18.3.1 Add proper data labels

Add data labels for:

  • all the timeline_variables
  • task_type (e.g., instructions, posner, saveData)
  • trial_part (e.g., fixation, cue, target)

18.3.2 Create a block of trials that uses an endogenous arrow cue

In the endogenous version of the task, there is not yellow box that flashes. Instead, an arrow at the center of the screen points left or right.

Create another block of trials called posnerEndo that is identical to the exogenous block, except that an arrow is used as a cue instead of the yellow box.

There are HTML codes for a variety of symbols, including arrows. To insert a right arrow like this , you can insert this HTML code &#10145;

For a left arrow, you can use &#11013;.

18.3.3 Add Additional Feedback Logic

Update the trial feedback to emphasize speed. If they were accurate, but took longer than 1000ms to respond, then the feedback should be “Too Slow! Respond Faster” in a “PaleVioletRed” color.