0

I am using jsPsych to create an experiment and I am struggling to sample from two variables simultaneously. Specifically, in each trial, I would like to present a primeWord and a targetWord by randomly sampling each of them from its own variable.

I have looked into several resources—such as sampling without replacement, custom sampling and position indices—but to no avail. I'm a beginner at this, so it's possible that one of these resources was relevant (especially the last one, I think).

Could you please consider my code below? In addition to the parallel sampling, how could I save the same trial index in the data of both primeWord and targetWord?

Thank you so very much for your attention

* I've also posted on the jsPsych GitHub. If a solution is found, I will link to it on other question.

<!DOCTYPE html>
<html>

<head>
  
  <!-- jsPsych plugins -->
  <script src="../jspsych.js"></script>
  <script src="../plugins/jspsych-html-keyboard-response.js"></script>
  <script src="../plugins/jspsych-html-button-response.js"></script>

  <!-- CSS -->
  <link rel="stylesheet" href="../css/jspsych.css">
  
  <style>
    body.jspsych-display-element {
      color: #ececec;
      background-color: #2b2b2b;
    }

    #jspsych-html-keyboard-response-stimulus {
      font-size: 32px;
    }

    .fas, .far {
      color: #b6b6b6;
    }
  </style>
  
</head>


<!-- Beginning of the script containing the experiment -->
<script>

  /* Create empty timeline object, which will be sequentially filled in using timeline.push() */
  var timeline = [];

  var instructions = {
    type: 'html-button-response',
    stimulus: ["<p>Each screen will show a word in lower case, such as 'target'. Press <b>F</b> if the word is primarily abstract</p>" +
               '<p>or <b>J</b> if it is primarily concrete. Each word is presented for up to five seconds.</p>'],
    choices: ['Ready to start']
  }
  /* Add instructions to the timeline */
  timeline.push(instructions)


   /* Stimuli */
  
  var list_primeWords = [
    { primeWord: 'PRIME 1', position: 'prime' },
    { primeWord: 'PRIME 2', position: 'prime' },
    { primeWord: 'PRIME 3', position: 'prime' },
    { primeWord: 'PRIME 4', position: 'prime' },
    { primeWord: 'PRIME 5', position: 'prime' }
  ];
  
  var list_targetWords = [
    { targetWord: 'target 1', position: 'target', correct_response: 'abstract' },
    { targetWord: 'target 2', position: 'target', correct_response: 'concrete' },
    { targetWord: 'target 3', position: 'target', correct_response: 'abstract' },
    { targetWord: 'target 4', position: 'target', correct_response: 'concrete' },
    { targetWord: 'target 5', position: 'target', correct_response: 'abstract' }
  ];


  /* Procedure */
  
  /* Fixation cross */
  var fixation  = {
    type: 'html-keyboard-response',
    stimulus: '+',
    choices: jsPsych.NO_KEYS,
    trial_duration: function () {
      /* Set fixations with a varying duration to boost participants' attention */
      return jsPsych.randomization.sampleWithoutReplacement([300, 400, 450, 500, 550, 600, 700], 1)[0];
    },
    post_trial_gap: 0,
    css_classes: ['stimulus']
  };
  
  var primeWord = {
    type: 'html-keyboard-response',
    stimulus: jsPsych.timelineVariable('primeWord'),
    choices: jsPsych.NO_KEYS,
    trial_duration: 150,
    post_trial_gap: function () {
      /* Random interstimulus interval */
      return jsPsych.randomization.sampleWithoutReplacement([100, 200, 300, 400, 450, 500, 550, 600, 700, 800, 900, 1000, 1100, 1200], 1)[0];
    },
    css_classes: ['stimulus'],
    /* Computation run at the end of each trial */
    on_finish: function (data) {
      if (data.key_press !== null) {
        var primeWord_keypress = 'pressed';
      } else {
        var primeWord_keypress = 'unpressed';
      }
      data.primeWord_keypress = primeWord_keypress;
    },
    sample: {
      type: 'without-replacement', 
      size: 4
    }
  };
  
  var targetWord = {
    type: 'html-keyboard-response',
    stimulus: jsPsych.timelineVariable('targetWord'),
    choices: ['f', 'j'],
    trial_duration: 3000,
    post_trial_gap: 0,
    css_classes: ['stimulus'],
    data: {
      correct_response: jsPsych.timelineVariable('correct_response')
    },
    /* Computation run at the end of each trial */
    on_finish: function (data) {
      if (data.key_press !== null) {
        /* Label correct responses */
        if (data.correct_response == 'abstract' && data.key_press == jsPsych.pluginAPI.convertKeyCharacterToKeyCode('f') ||
          data.correct_response == 'concrete' && data.key_press == jsPsych.pluginAPI.convertKeyCharacterToKeyCode('j')) {
          var accuracy = 'correct';
          /* Label incorrect responses */
        } else if (data.correct_response == 'abstract' && data.key_press == jsPsych.pluginAPI.convertKeyCharacterToKeyCode('j') ||
          data.correct_response == 'concrete' && data.key_press == jsPsych.pluginAPI.convertKeyCharacterToKeyCode('f')) {
          var accuracy = 'incorrect';
        }
        /* Label unanswered trials */
      } else {
        var accuracy = 'unanswered';
      }
      data.accuracy = accuracy;
    },
    sample: {
      type: 'without-replacement', 
      size: 4
    }
  };
  
  feedback = {
    type: 'html-keyboard-response',
    stimulus: function () {
      var last_trial_accuracy = jsPsych.data.getLastTrialData().values()[0].accuracy;
      if (last_trial_accuracy == 'incorrect') {
        return '<p style="color:red; font-face:bold;">X</p>';
      } else if (last_trial_accuracy == 'unanswered') {
        return '<p style="color:red; font-face:bold;">0</p>'
      } else {
        return ''
      }
    },
    choices: jsPsych.NO_KEYS,
    trial_duration: function () {
      var last_trial_accuracy = jsPsych.data.getLastTrialData().values()[0].accuracy;
      if (last_trial_accuracy == 'correct') {
        return 0
      } else {
        return 800
      }
    }
  };
  
  var main_procedure = {
      timeline: [fixation, primeWord, targetWord, feedback],
      timeline_variables: [primeWord, targetWord]
  };
  timeline.push(main_procedure);
  

  var debrief = {
    type: 'html-keyboard-response',
    choices: ['c'],
    stimulus: function () {
      var total_correct = jsPsych.data.get().filter({ accuracy: 'correct' }).count();
      var total_incorrect = jsPsych.data.get().filter({ accuracy: 'incorrect' }).count();
      var accuracy_rate = Math.round(total_correct / (total_correct + total_incorrect) * 100) + "%";
      var total_unanswered = jsPsych.data.get().filter({ accuracy: 'unanswered' }).count();
      var message = "<div style='font-size:20px;'><p>All done!</p>" +
        "<p>Your accuracy rate was " + accuracy_rate + " (" + total_correct + " correct trials, " + total_incorrect +
        " incorrect and " + total_unanswered + " unanswered).</p>" +
        "<p>Press C to see the entire set of data generated by this experiment.</p></div>";
      return message;
    }
  }
  /* Add debrief to the timeline */
  timeline.push(debrief);


  /* Initialize experiment by incorporating the timeline and setting the data to be displayed at the end. */
  jsPsych.init({
    timeline: timeline,
    on_finish: function () {
      jsPsych.data.displayData();
    },
    default_iti: 250
  });

</script>

</html>

Pablo Bernabeu
  • 402
  • 4
  • 23

2 Answers2

0

I have somehow advanced on this by using position indices. I provide the updated code below. However, this code still presents two problems:

  1. There is a bug. When the experiment is run, the parameters of the last trial are not identified. As a result, the word 'undefined' appears instead of the stimuli in the last trial.

  2. The current use of explicit position indices is fine for a few trials, as in this minimal example. However, in the real context of dozens of trials, I should use a for loop or a map function.

Thank you very much for your attention


<!DOCTYPE html>
<html>

<head>
  
  <!-- jsPsych plugins -->
  <script src="../jspsych.js"></script>
  <script src="../plugins/jspsych-html-keyboard-response.js"></script>
  <script src="../plugins/jspsych-html-button-response.js"></script>

  <!-- CSS -->
  <link rel="stylesheet" href="../css/jspsych.css">
  
  <style>
    body.jspsych-display-element {
      color: #ececec;
      background-color: #2b2b2b;
    }

    #jspsych-html-keyboard-response-stimulus {
      font-size: 32px;
    }

    .fas, .far {
      color: #b6b6b6;
    }
  </style>
  
</head>


<!-- Beginning of the script containing the experiment -->
<script>

  /* Create empty timeline object, which will be sequentially filled in using timeline.push() */
  var timeline = [];

  var instructions = {
    type: 'html-button-response',
    stimulus: ["<p>Each screen will show a word in lower case, such as 'target'. Press <b>F</b> if the word is primarily abstract</p>" +
               '<p>or <b>J</b> if it is primarily concrete. Each word is presented for up to five seconds.</p>'],
    choices: ['Ready to start']
  }
  /* Add instructions to the timeline */
  timeline.push(instructions)


  /* Stimuli */
   
  /* Begin with a general list of words, and randomly split the 
  list into a set of prime words and a set of target words. */
   
  var allWords = [
    'Word 1', 'Word 2', 'Word 3', 'Word 4', 'Word 5', 
    'Word 6', 'Word 7', 'Word 8', 'Word 9', 'Word 10'
  ]
  
  /* Shuffle all words */
  var shuffled_allWords = allWords.sort(function(){return 0.5 - Math.random()});
  
  /* Split up the list into two sets */
  var midpoint = Math.floor(shuffled_allWords.length / 2);
  
  /* Set number of trials per participant (must be smaller than half of all words) */
  var number_of_trials = 4;
  
  /* Create the set of prime words */
  var primeWords = shuffled_allWords.slice(0, number_of_trials);
  
  /* Make prime words uppercase, as in Hutchison et al.
  (2013; https://doi.org/10.3758/s13428-012-0304-z) */
  toUpper = function(x) { return x.toUpperCase() };
  var primeWords = primeWords.map(toUpper);
  
  /* Create the set of target words */
  var targetWords = shuffled_allWords.slice(midpoint, midpoint + number_of_trials);
  
  
  /* Next, create set of interstimulus intervals from a range between 60 and 1200 ms. 
  This range is split into as many integers as the number of trials. That is done 
  equally across participants. However, the list of interstimulus is shuffled within 
  participants. */
  
  function makeArr(startValue, stopValue, cardinality) {
    var arr = [];
    var step = (stopValue - startValue) / (cardinality - 1);
    for (var i = 0; i < cardinality; i++) {
      arr.push(startValue + (step * i));
    }
    return arr;
  };
  
  ordered_interstimulus_intervals = makeArr(60, 1200, number_of_trials);
  
  interstimulus_interval = 
    ordered_interstimulus_intervals.sort(function(){return 0.5 - Math.random()});
  
  
  /* Combine and save all trial information */
  var stimuli = [
    { primeWord: primeWords[1], targetWord: targetWords[1], correct_response: 'abstract', trial: 1, interstimulus_interval: interstimulus_interval[1] },
    { primeWord: primeWords[2], targetWord: targetWords[2], correct_response: 'abstract', trial: 2, interstimulus_interval: interstimulus_interval[2] },
    { primeWord: primeWords[3], targetWord: targetWords[3], correct_response: 'abstract', trial: 3, interstimulus_interval: interstimulus_interval[3] },
    { primeWord: primeWords[4], targetWord: targetWords[4], correct_response: 'abstract', trial: 4, interstimulus_interval: interstimulus_interval[4] }
  ];
  

  /* Trial content: fixation, primeWord (including interstimulus interval), 
  targetWord, feedback */
  
  /* Fixation cross */
  var fixation  = {
    type: 'html-keyboard-response',
    stimulus: '+',
    choices: jsPsych.NO_KEYS,
    trial_duration: function () {
      /* Set fixations with a varying duration to boost participants' attention */
      return jsPsych.randomization.sampleWithoutReplacement([400, 500, 600], 1)[0];
    },
    post_trial_gap: 0,
    css_classes: ['stimulus']
  };
  
  var primeWord = {
    type: 'html-keyboard-response',
    stimulus: jsPsych.timelineVariable('primeWord'),
    trial_duration: 150,
    post_trial_gap: interstimulus_interval,
    data: {
      position: 'prime',
      trial: jsPsych.timelineVariable('trial'),
      interstimulus_interval: jsPsych.timelineVariable('interstimulus_interval')
    },
    css_classes: ['stimulus'],
    /* Computation run at the end of each trial */
    on_finish: function (data) {
      if (data.key_press !== null) {
        var primeWord_keypress = 'pressed';
      } else {
        var primeWord_keypress = 'unpressed';
      }
      data.primeWord_keypress = primeWord_keypress;
    }
  };
  
  var targetWord = {
    type: 'html-keyboard-response',
    stimulus: jsPsych.timelineVariable('targetWord'),
    choices: ['f', 'j'],
    trial_duration: 3000,
    post_trial_gap: 0,
    css_classes: ['stimulus'],
    data: {
      position: 'target',
      trial: jsPsych.timelineVariable('trial'),
      correct_response: jsPsych.timelineVariable('correct_response')
    },
    /* Computation run at the end of each trial */
    on_finish: function (data) {
      if (data.key_press !== null) {
        /* Label correct responses */
        if (data.correct_response == 'abstract' && data.key_press == jsPsych.pluginAPI.convertKeyCharacterToKeyCode('f') ||
          data.correct_response == 'concrete' && data.key_press == jsPsych.pluginAPI.convertKeyCharacterToKeyCode('j')) {
          var accuracy = 'correct';
          /* Label incorrect responses */
        } else if (data.correct_response == 'abstract' && data.key_press == jsPsych.pluginAPI.convertKeyCharacterToKeyCode('j') ||
          data.correct_response == 'concrete' && data.key_press == jsPsych.pluginAPI.convertKeyCharacterToKeyCode('f')) {
          var accuracy = 'incorrect';
        }
        /* Label unanswered trials */
      } else {
        var accuracy = 'unanswered';
      }
      data.accuracy = accuracy;
    }
  };
  
  feedback = {
    type: 'html-keyboard-response',
    stimulus: function () {
      var last_trial_accuracy = jsPsych.data.getLastTrialData().values()[0].accuracy;
      if (last_trial_accuracy == 'incorrect') {
        return '<p style="color:red; font-face:bold;">X</p>';
      } else if (last_trial_accuracy == 'unanswered') {
        return '<p style="color:red; font-face:bold;">0</p>'
      } else {
        return ''
      }
    },
    choices: jsPsych.NO_KEYS,
    trial_duration: function () {
      var last_trial_accuracy = jsPsych.data.getLastTrialData().values()[0].accuracy;
      if (last_trial_accuracy == 'correct') {
        return 0
      } else {
        return 800
      }
    }
  };
  
  var main_procedure = {
      timeline: [fixation, primeWord, targetWord, feedback],
      timeline_variables: stimuli
  };
  timeline.push(main_procedure);
  

  var debrief = {
    type: 'html-keyboard-response',
    choices: ['c'],
    stimulus: function () {
      var total_correct = jsPsych.data.get().filter({ accuracy: 'correct' }).count();
      var total_incorrect = jsPsych.data.get().filter({ accuracy: 'incorrect' }).count();
      var total_unanswered = jsPsych.data.get().filter({ accuracy: 'unanswered' }).count();
      var accuracy_rate = Math.round(total_correct / (total_correct + total_incorrect + total_unanswered) * 100) + "%";
      var message = "<div style='font-size:20px;'><p>All done!</p>" +
        "<p>Your accuracy rate was " + accuracy_rate + " (" + total_correct + " correct trials, " + total_incorrect +
        " incorrect and " + total_unanswered + " unanswered).</p>" +
        "<p>Press C to see the entire set of data generated by this experiment.</p></div>";
      return message;
    }
  }
  /* Add debrief to the timeline */
  timeline.push(debrief);


  /* Initialize experiment by incorporating the timeline
  and setting the data to be displayed at the end. */
  jsPsych.init({
    timeline: timeline,
    on_finish: function () {
      jsPsych.data.displayData();
    },
    default_iti: function () {
      /* Use varying intertrial intervals to reduce habituation effects */
      return jsPsych.randomization.sampleWithoutReplacement([1300, 1400, 1500, 1600], 1)[0];
    }
  });

</script>

</html>

Pablo Bernabeu
  • 402
  • 4
  • 23
0

Finally solved using position indices in a for-of loop (see below: 'For loop that creates trial information iteratively over trials (3 steps)').


<!DOCTYPE html>
<html>

  <head>

    <!-- jsPsych plugins -->
    <script src="../jspsych.js"></script>
    <script src="../plugins/jspsych-html-keyboard-response.js"></script>
    <script src="../plugins/jspsych-html-button-response.js"></script>

    <!-- CSS -->
    <link rel="stylesheet" href="../css/jspsych.css">

    <style>
      body.jspsych-display-element {
        color: #ececec;
        background-color: #2b2b2b;
      }

      #jspsych-html-keyboard-response-stimulus {
        font-size: 32px;
      }

      .fas,
      .far {
        color: #b6b6b6;
      }

    </style>

  </head>


  <!-- Beginning of the script that contains the core of the experiment -->
  <script>
    /* Create empty timeline object, which will be sequentially filled in using timeline.push() */
    var timeline = [];

    var instructions = {
      type: 'html-button-response',
      stimulus: ["<p>Each screen will show a word in lower case, such as 'target'. Press <b>F</b> if the word is primarily abstract</p>" +
        '<p>or <b>J</b> if it is primarily concrete. Each word is presented for up to five seconds.</p>'
      ],
      choices: ['Ready to start']
    }
    /* Add instructions to the timeline */
    timeline.push(instructions)


    /* Stimuli */

    /* Begin with a general list of words, and randomly split the 
    list into a set of prime words and a set of target words. */

    var allWords = [{
        word: 'word 1',
        correct_response: 'abstract'
      },
      {
        word: 'word 2',
        correct_response: 'abstract'
      },
      {
        word: 'word 3',
        correct_response: 'abstract'
      },
      {
        word: 'word 4',
        correct_response: 'abstract'
      },
      {
        word: 'word 5',
        correct_response: 'abstract'
      },
      {
        word: 'word 6',
        correct_response: 'concrete'
      },
      {
        word: 'word 7',
        correct_response: 'concrete'
      },
      {
        word: 'word 8',
        correct_response: 'concrete'
      },
      {
        word: 'word 9',
        correct_response: 'concrete'
      },
      {
        word: 'word 10',
        correct_response: 'concrete'
      }

    ]

    /* Shuffle all words */
    var shuffled_allWords = allWords.sort(function() {
      return 0.5 - Math.random()
    });

    /* Split up the list into two sets */
    var midpoint = Math.floor(shuffled_allWords.length / 2);

    /* Set number of trials per participant (must be smaller than half of all words) */
    var number_of_trials = 4;

    /* Create the set of prime words */
    var primeWords = shuffled_allWords.slice(0, number_of_trials);

    /* Make prime words uppercase, as in Hutchison et al.
    (2013; https://doi.org/10.3758/s13428-012-0304-z) */

    var primeWords = primeWords.map(item => ({
      ...item,
      word: item.word.toUpperCase()
    }))

    /* Create the set of target words */
    var targetWords = shuffled_allWords.slice(midpoint, midpoint + number_of_trials);


    /* Next, create set of interstimulus intervals from a range between 60 and 1200 ms. 
    First, the range is split into as many integers as the number of trials, equally 
    for all participants. Afterwards, the list is shuffled within participants. */

    function makeArr(startValue, stopValue, cardinality) {
      var arr = [];
      var step = (stopValue - startValue) / (cardinality - 1);
      for (var i = 0; i < cardinality; i++) {
        arr.push(startValue + (step * i));
      }
      return arr;
    }

    ordered_interstimulus_intervals = makeArr(60, 1200, number_of_trials);

    interstimulus_interval =
      ordered_interstimulus_intervals.sort(function() {
        return 0.5 - Math.random()
      });


    /* For loop that creates trial information iteratively over trials (3 steps) */

    /* 1. Enable function to create iterable range, to be used in the for loop below */
    const Range = (start, end) => ({
      *[Symbol.iterator]() {
        while (start < end)
          yield start++;
      }
    })

    /* 2. Initialise stimuli array */
    stimuli = [];

    /* 3. Run loop */
    for (const i of Range(0, number_of_trials)) {
      stimuli.push({
        primeWord: primeWords[i].word,
        targetWord: targetWords[i].word,
        interstimulus_interval: interstimulus_interval[i],
        correct_response: targetWords[i].correct_response,
        trial: i + 1 /* 1 is added because, otherwise, trials would else trials would start from 0 */
      })
    }


    /* Trial content: fixation, primeWord, interstimulus interval, targetWord, feedback.
    This constitutes a unique trial in the semantic priming paradigm. Yet, beware that 
    jsPsych provides a 'trial_index' value in the output of the task. That index is 
    assigned to each part of every trial. Thus, in the present experiment, there are 
    five trial_index values per trial--namely, one for each part listed above. */

    /* Fixation cross */
    var fixation = {
      type: 'html-keyboard-response',
      stimulus: '+',
      response_ends_trial: false,
      trial_duration: function() {
        /* Set fixations with a varying duration to boost participants' attention */
        return jsPsych.randomization.sampleWithoutReplacement([400, 450, 500, 550, 600], 1)[0];
      },
      post_trial_gap: 0,
      data: {
        trial: jsPsych.timelineVariable('trial')
      },
      css_classes: ['stimulus'],
      /* Computation run at the end of each trial */
      on_finish: function(data) {
        /* Log key presses, if any, by writing 1 into fixation_keypresses (else, write 0) */
        if (data.key_press == null) {
          var fixation_keypresses = 0;
        } else {
          var fixation_keypresses = 1;
        };
        data.fixation_keypresses = fixation_keypresses
      }
    };

    var primeWord = {
      type: 'html-keyboard-response',
      stimulus: jsPsych.timelineVariable('primeWord'),
      response_ends_trial: false,
      trial_duration: 150,
      post_trial_gap: 0,
      data: {
        position: 'prime',
        trial: jsPsych.timelineVariable('trial')
      },
      css_classes: ['stimulus'],
      /* Computation run at the end of each trial */
      on_finish: function(data) {
        /* Log key presses, if any, by writing 1 into primeWord_keypresses (else, write 0) */
        if (data.key_press == null) {
          var primeWord_keypresses = 0;
        } else {
          var primeWord_keypresses = 1;
        };
        data.primeWord_keypresses = primeWord_keypresses
      }
    };

    var interstimulus_interval = {
      type: 'html-keyboard-response',
      stimulus: ' ',
      response_ends_trial: false,
      trial_duration: jsPsych.timelineVariable('interstimulus_interval'),
      post_trial_gap: 0,
      data: {
        interstimulus_interval: jsPsych.timelineVariable('interstimulus_interval'),
        trial: jsPsych.timelineVariable('trial')
      },
      css_classes: ['stimulus'],
      /* Computation run at the end of each trial */
      on_finish: function(data) {
        /* Log key presses, if any, by writing 1 into interstimulus_interval_keypresses (else, write 0) */
        if (data.key_press == null) {
          var interstimulus_interval_keypresses = 0;
        } else {
          var interstimulus_interval_keypresses = 1;
        };
        data.interstimulus_interval_keypresses = interstimulus_interval_keypresses
      }
    };

    var targetWord = {
      type: 'html-keyboard-response',
      stimulus: jsPsych.timelineVariable('targetWord'),
      choices: ['f', 'j'],
      trial_duration: 3000,
      post_trial_gap: 0,
      css_classes: ['stimulus'],
      data: {
        position: 'target',
        trial: jsPsych.timelineVariable('trial'),
        correct_response: jsPsych.timelineVariable('correct_response')
      },
      /* Computation run at the end of each trial */
      on_finish: function(data) {
        if (data.key_press !== null) {
          /* Label correct responses */
          if (data.correct_response == 'abstract' && data.key_press == jsPsych.pluginAPI.convertKeyCharacterToKeyCode('f') ||
            data.correct_response == 'concrete' && data.key_press == jsPsych.pluginAPI.convertKeyCharacterToKeyCode('j')) {
            var accuracy = 'correct';
            /* Label incorrect responses */
          } else if (data.correct_response == 'abstract' && data.key_press == jsPsych.pluginAPI.convertKeyCharacterToKeyCode('j') ||
            data.correct_response == 'concrete' && data.key_press == jsPsych.pluginAPI.convertKeyCharacterToKeyCode('f')) {
            var accuracy = 'incorrect';
          }
          /* Label unanswered trials */
        } else {
          var accuracy = 'unanswered';
        };
        data.accuracy = accuracy;
        /* Count up premature responses per trial. The command 'last(4)' is used below
        to consider only the current part of the 'trial' (i.e., targetWord) and the 
        three previous parts (i.e., interstimulus_interval, primeWord and fixation). 
        Notice that the response entered in this part (targetWord) is not added into 
        the sum, as it is appropriate to respond to the target word. */
        data.premature_responses =
          jsPsych.data.get().last(4).filter('fixation_keypresses' == 1).select('fixation_keypresses').sum() +
          jsPsych.data.get().last(4).filter('primeWord_keypresses' == 1).select('primeWord_keypresses').sum() +
          jsPsych.data.get().last(4).filter('interstimulus_interval_keypresses' == 1).select('interstimulus_interval_keypresses').sum();
      }
    };

    feedback = {
      type: 'html-keyboard-response',
      stimulus: function() {
        var last_trial_accuracy = jsPsych.data.getLastTrialData().values()[0].accuracy;
        if (last_trial_accuracy == 'incorrect') {
          return '<p style="color:red; font-face:bold;">X</p>';
        } else if (last_trial_accuracy == 'unanswered') {
          return '<p style="color:red; font-face:bold;">0</p>'
        } else {
          return ''
        }
      },
      choices: jsPsych.NO_KEYS,
      trial_duration: function() {
        var last_trial_accuracy = jsPsych.data.getLastTrialData().values()[0].accuracy;
        if (last_trial_accuracy == 'correct') {
          return 0
        } else {
          return 800
        }
      }
    };

    var main_procedure = {
      timeline: [fixation, primeWord, interstimulus_interval, targetWord, feedback],
      timeline_variables: stimuli
    };
    timeline.push(main_procedure);


    var debrief = {
      type: 'html-keyboard-response',
      choices: ['c'],
      stimulus: function() {
        var total_correct = jsPsych.data.get().filter({
          accuracy: 'correct'
        }).count();
        var total_incorrect = jsPsych.data.get().filter({
          accuracy: 'incorrect'
        }).count();
        var total_unanswered = jsPsych.data.get().filter({
          accuracy: 'unanswered'
        }).count();
        var accuracy_rate = Math.round(total_correct / (total_correct + total_incorrect + total_unanswered) * 100) + "%";
        var message = "<div style='font-size:20px;'><p>All done!</p>" +
          "<p>Your accuracy rate was " + accuracy_rate + " (" + total_correct + " correct trials, " + total_incorrect +
          " incorrect and " + total_unanswered + " unanswered).</p>" +
          "<p>Press C to see the entire set of data generated by this experiment.</p></div>";
        return message;
      }
    }
    /* Add debrief to the timeline */
    timeline.push(debrief);


    /* Initialize experiment by incorporating the timeline
    and setting the data to be displayed at the end. */
    jsPsych.init({
      timeline: timeline,
      on_finish: function() {
        jsPsych.data.displayData();
      },
      default_iti: function() {
        /* Use varying intertrial intervals to reduce habituation effects */
        return jsPsych.randomization.sampleWithoutReplacement([1300, 1400, 1500, 1600, 1700], 1)[0];
      }
    });

  </script>

</html>

Pablo Bernabeu
  • 402
  • 4
  • 23