Overview
Terracotta provides a flexible LTI (Learning Tools Interoperability) integration that allows jsPsych experiments to be embedded within learning management system (LMS) assignments. This guide explains how to create a jsPsych experiment that can be seamlessly integrated with Terracotta into the LMS.
⚠️ Terracotta does not share students’ names or identities with custom web activities. Instead, Terracotta sends an anonymous identifier to jsPsych that can be used to connect jsPsych data to additional details about the student in Terracotta. If students will be asked to provide their name or any other personally identifiable information in the jsPsych experiment, make sure you have permission from your school.
Responsibilities of the jsPsych Experiment
When a jsPsych experiment is integrated in Terracotta as an LMS assignment, your activity is responsible for supporting student learning and reliably returning students’ scores back the LMS. The jsPsych experiment must comply with Terracotta’s Terms of Use, and the jsPsych activity is also responsible for presenting an interface that is accessible to diverse learners. Before integrating a jsPsych experiment, ensure the following:
The designer of the experiment should be familiar with web accessibility standards, and the jsPsych plugin should be screened for any accessibility issues (e.g., using the free WAVE Evaluation Tool).
When used for graded assessments, the correct answers should not be visible in the activity’s source code. If possible, utilize server-side scoring and validation.
If the custom web activity utilizes generative AI, honor the EDSAFE AI Alliance’s SAFE Benchmarks. In particular, provide learners with the means to flag inappropriate content, and create a process for handling any such situations.
All information collected by the jsPsych experiment is private and secure.
Configuration in Terracotta
To configure a jsPsych experiment in Terracotta, begin by creating a new experiment. Add a new assignment. For the treatment that will integrate with the jsPsych experiment, select External Integrations > Custom Web Activity, then you'll see the interface below:
In Box 1 (Launch to Custom Web Activity), provide the URL to the custom web activity. Once you provide the URL, you can preview your activity in a new tab. Indicate the maximum numeric score that students can earn in the web activity. Check the checkbox if the custom web activity is configured to allow students to view past submissions (this is an advanced feature; if you are unsure, leave this box unchecked). When Terracotta launches to your jsPsych experiment, it will append a set of launch parameters onto the URL you provided (listed below).
In Box 2 (Return to Terracotta), you are provided with a return URL. Your jsPsych experiment should redirect users to this return URL, and Terracotta expects that jsPsych will append two URL parameters: (1) launch_token, and (2) score. If “score” is omitted, Terracotta will assume that the submission will receive the maximum score.
Launch Parameters
The following variables are automatically provided to the custom web activity as URL parameters:
launch_token: A single-use token for launch validation and score return. The launch token is in UUIDv4 format.
anonymous_id: Anonymized student identifier. This ID matches Terracotta’s participant_id, however, depending on the configuration in Terracotta, students who access an custom web activity may not be consenting participants. Thus, we use the label anonymous_id to refer to students accessing custom web activities.
assignment_id: Unique identifier for the Canvas assignment
submission_id: Unique identifier for the current submission attempt
condition_name: Experimental condition name
experiment_id: Unique identifier for the experiment (specific to the course)
due_at: Assignment due date and time
remaining_attempts: Number of remaining submission attempts. If there are unlimited attempts available to the student, remaining_attempts = u.
Return to Terracotta
Terracotta expects that when a student completes the activity, they will be redirected to the URL:
https://app.terracotta.education/integrations
with two parameters appended to the URL: (1) launch_token, and (2) score. If “score” is omitted, Terracotta will assume that the submission will receive the maximum score.
Terracotta will not accept a score for a launch token that has already been used.
Best Practices
Record Granular Data: Your custom web activity will be responsible for a legitimate academic exercise, and it should keep records of users’ interactions, responses, and submissions. By doing so, your custom web activity will help ensure the integrity of students’ coursework.
Check if the Launch Token has Previously Been Used: When configuring a custom web activity in Terracotta, there is a checkbox: Tool allows students to view past submissions. If this box is checked, Terracotta will assume that the tool is able to display a past submission to a student. In this situation, if the external webpage receives a launch token that has previously been used, this indicates that a student is attempting to review their responses from a past submission. Check if the launch token has previously been used, and that the anonymous_id and assignment_id of the current launch matches the anonymous_id and assignment_id of the past submission associated with the launch_token. If so, the external page should display the data from the past submission associated with the provided launch token, with no interactivity.
NOTE: There is a reserved launch_token: "00000000-0000-4000-B000-000000000000". This launch token is issued by Terracotta when previewing a custom web activity.
Communicate When There are Limited Remaining Attempts: When the teacher has placed restrictions on the number of attempts that a student may submit in response to an assignment, it may be helpful for the custom web activity to communicate this to the student (e.g., if there is only 1 attempt remaining). If there are unlimited attempts available to the student, remaining_attempts = u. A custom web activity will never need to handle a situation where remaining_attempts = 0, because in this situation, Terracotta will not generate a launch_token and will not launch the student into an activity.
Respect Due Dates: Consider warning users when they are accessing the webpage after the due date. Submissions made after the due date will be delivered into Canvas, but will be flagged as late.
Score Calculation: The custom web activity is responsible for determining the student’s score on the activity, which should be returned to Terracotta as a URL parameter value with the name “score.”
A Minimal Example
Here is a minimal example of a jsPsych experiment that will integrate with Terracotta:
This example implements a (very short!) visual n-back task, and upon completion, displays the results to the user, with a final button to send credit back to the LMS gradebook.
In the <head>
of the HTML file, load the jsPsych library, the two plugins necessary for the experimental task (html-keyboard-response & html-button-response), and the jsPsych stylesheet.
<head> <script src="https://unpkg.com/jspsych@7.3.4"></script> <script src="https://unpkg.com/@jspsych/plugin-html-keyboard-response@1.1.3"></script> <script src="https://unpkg.com/@jspsych/plugin-html-button-response@1.2.0"></script> <link rel="stylesheet" href="https://unpkg.com/jspsych@7.3.4/css/jspsych.css" /> </head>
The <body>
of the HTML file should be empty, as is typical when using jsPsych.
<body> </body>
Then, contained within <script></script>
tags, collect the launch_token and the anonymous_id from URL parameters.
// Get URL parameters const queryString = window.location.search; const urlParams = new URLSearchParams(queryString); let url_launch_token = urlParams.get('launch_token'); // Single-use launch token const url_pid = urlParams.get('anonymous_id') // Participant ID // If there is no launch_token provided, assume that this is a preview if (url_launch_token == "" || url_launch_token == null || url_launch_token == undefined) { url_launch_token = "00000000-0000-4000-B000-000000000000"; // reserved preview token }
Initialize jsPsych. This also establishes that when the experiment is finished, jsPsych will redirect the user back to Terracotta, with the launch_token that was received in the URL parameters, and with score = 1.
var jsPsych = initJsPsych({ use_webaudio: false, on_finish: function () { window.location = "https://app.terracotta.education/integrations?launch_token=" + url_launch_token + "&score=1"; } });
Add the participant_id and the launch_token to the jsPsych data structure:
jsPsych.data.addProperties({ pid: url_pid, launch_token: url_launch_token });
Setup the study by setting session variables:
var timeline = []; var n_back_set = ['Z', 'X', 'C', 'V', 'B', 'N']; var sequence = []; var how_many_back = 2; var sequence_length = 12;
Push instructions into the jsPsych timeline:
/* Instructions #1 */ var instructions_1 = { type: jsPsychHtmlButtonResponse, stimulus: '<div style="width: 800px;">' + '<h2>Visual <i>N</i>-Back Task Demonstration</h2>' + '<p>This will test your ability to hold information in short-term, temporary memory. This is called working memory.</p>' + '</div>', choices: ["Continue"] } timeline.push(instructions_1); /* Instructions #2 */ var instructions_2 = { type: jsPsychHtmlButtonResponse, stimulus: '<div style="width: 800px;">' + '<p>You will see a sequence of letters presented one at a time. Your task is to determine if the letter on the screen matches ' + 'the letter that appeared two letters before.</p>' + '<h4>If the letters match, press the M key.</h4>' + '<p>For example, if you saw the sequence X, C, V, B, V, X you would press the M key when the second V appeared on the screen.</p>' + '<h4>Do not press any key when there is not a match.</h4>' + '</div>', choices: ["Continue"] } timeline.push(instructions_2); /* Instructions #3 */ var instructions_3 = { type: jsPsychHtmlKeyboardResponse, stimulus: '<div style="width: 800px;">' + '<p>The sequence will begin on the next screen.</p>' + '<p>Remember: press the M key if the letter on the screen matches the letter that appeared two letters ago.</p>' + '<p>When you are ready to begin, press the M key to begin</p>' + '</div>', choices: ["M"], post_trial_gap: 1000 } timeline.push(instructions_3);
Create the n-back trial, and populate the timeline with a sequence of n-back trials.
/* N Back sequence trials */ var n_back_trial = { type: jsPsychHtmlKeyboardResponse, /* Stimulus logic: - if the current length of the stimulus is less than 2, then just grab random letter - otherwise, - if timelinevariable says match==true then draw the stim from two steps back - else, draw two letters, and if the first matches, take the second */ stimulus: function () { if (sequence.length < how_many_back) { var letter = jsPsych.randomization.sampleWithoutReplacement(n_back_set, 1)[0] } else { if (jsPsych.timelineVariable('match', true) == true) { var letter = sequence[sequence.length - how_many_back]; } else { var possible_letters = jsPsych.randomization.sampleWithoutReplacement(n_back_set, 2); if (possible_letters[0] != sequence[sequence.length - how_many_back]) { var letter = possible_letters[0]; } else { var letter = possible_letters[1]; } } } sequence.push(letter); return '<span style="font-size: 96px; font-weight: bold;">' + letter + '</span>' }, choices: ['M'], trial_duration: 1500, response_ends_trial: false, // responding doesn't end the trial post_trial_gap: 500, on_finish: function (data) { if (jsPsych.timelineVariable('match', true)) { data.phase = "test"; data.match = true; data.correct = (data.response != null); } else { data.phase = "test"; data.match = false; data.correct = (data.response === null); } } } /* For any n_back_trial, match is either true or false */ var n_back_trials = [ { match: true }, { match: false } ] /* Make a sequence out of the n-back trials, randomly sampling whether it matches */ var n_back_sequence = { timeline: [n_back_trial], timeline_variables: n_back_trials, sample: { type: 'with-replacement', size: sequence_length, weights: [1, 2] // aims for twice as many non-matches as matches } } /* Push those n-back trials into the timeline */ timeline.push(n_back_sequence);
Create a final trial that will display feedback to the participant
/* Present feedback */ var feedback = { type: jsPsychHtmlButtonResponse, stimulus: function () { var test_trials = jsPsych.data.get().filter({ phase: 'test' }).last(sequence_length - 2); var n_match = test_trials.filter({ match: true }).count(); var n_nonmatch = test_trials.filter({ match: false }).count(); var n_correct = test_trials.filter({ match: true, correct: true }).count(); var false_alarms = test_trials.filter({ match: false, correct: false }).count(); var html = "<div style='width:800px;'>" + "<p>All done!</p>" + "<p>You correctly identified " + n_correct + " of the " + n_match + " matching items.</p>" + "<p>You incorrectly identified " + false_alarms + " of the " + n_nonmatch + " non-matching items as matches.</p>" + "<p>Click the button below to finish the activity, and have your credit sent to the gradebook.</p>" return html; }, choices: ["Continue"] } timeline.push(feedback);
Finally, run the timeline
jsPsych.run(timeline);
0 Comments