Skip to main content

@slopit/adapter-jspsych API Reference

The jsPsych adapter provides integration with jsPsych 8.x for behavioral capture during experiments.

Installation

pnpm add @slopit/adapter-jspsych jspsych@^8

jsPsych 8.x is a peer dependency and must be installed separately.

SlopitExtension

A jsPsych extension that adds behavioral capture to any jsPsych plugin. The extension attaches to DOM elements matching a CSS selector and records keystrokes, focus events, and paste operations.

Registration

Register the extension when initializing jsPsych:

import { initJsPsych } from "jspsych";
import { SlopitExtension } from "@slopit/adapter-jspsych";

const jsPsych = initJsPsych({
extensions: [{ type: SlopitExtension }],
});

Usage on Trials

Enable the extension on individual trials:

import jsPsychSurveyText from "@jspsych/plugin-survey-text";

const trial = {
type: jsPsychSurveyText,
questions: [{ prompt: "Describe your morning routine:", rows: 6 }],
extensions: [
{
type: SlopitExtension,
params: {
targetSelector: "textarea",
keystroke: { enabled: true },
paste: { enabled: true, prevent: false },
},
},
],
};

jsPsych.run([trial]);

SlopitExtensionParams

ParameterTypeDefaultDescription
targetSelectorstring(required)CSS selector for the target element(s) to capture
keystrokeKeystrokeConfig{ enabled: false }Keystroke capture configuration
focusFocusConfig{ enabled: false }Focus tracking configuration
pastePasteConfig{ enabled: false }Paste detection configuration

KeystrokeConfig

OptionTypeDefaultDescription
enabledbooleanfalseEnable keystroke capture
captureKeyUpbooleantrueInclude keyup events
includeModifiersbooleantrueInclude Shift/Ctrl/Alt/Meta states

FocusConfig

OptionTypeDefaultDescription
enabledbooleanfalseEnable focus tracking
useVisibilityAPIbooleantrueTrack tab visibility changes
useBlurFocusbooleantrueTrack window blur/focus

PasteConfig

OptionTypeDefaultDescription
enabledbooleanfalseEnable paste detection
preventbooleanfalseBlock paste operations
warnMessagestring""Message shown when paste is prevented
capturePreviewbooleantrueStore preview of pasted text
previewLengthnumber100Preview length in characters

Trial Data

The extension adds a slopit property to trial data:

PropertyTypeDescription
slopit.behavioralBehavioralDataRaw behavioral data
slopit.flagsCaptureFlag[]Generated flags

Example with All Options

const trial = {
type: jsPsychSurveyText,
questions: [
{
prompt: "<h3>Writing Task</h3><p>Describe your typical morning routine.</p>",
rows: 8,
},
],
extensions: [
{
type: SlopitExtension,
params: {
targetSelector: "textarea",
keystroke: {
enabled: true,
captureKeyUp: true,
includeModifiers: true,
},
focus: {
enabled: true,
useVisibilityAPI: true,
useBlurFocus: true,
},
paste: {
enabled: true,
prevent: true,
warnMessage: "Pasting is not allowed in this task.",
},
},
},
],
};

exportToSlopit()

Convert jsPsych data to SlopitSession format for server-side analysis.

Signature

function exportToSlopit(jsPsych: JsPsych, options?: ExportOptions): SlopitSession;

ExportOptions

interface ExportOptions {
participantId?: string;
studyId?: string;
trialFilter?: (trial: Record<string, unknown>) => boolean;
includePlatformData?: boolean; // Default: true
}
  • participantId: Participant ID (e.g., from Prolific PROLIFIC_PID)
  • studyId: Study identifier
  • trialFilter: Function to select which trials to include
  • includePlatformData: Whether to include raw jsPsych trial data

Basic Example

import { initJsPsych } from "jspsych";
import { SlopitExtension, exportToSlopit } from "@slopit/adapter-jspsych";

const jsPsych = initJsPsych({
extensions: [{ type: SlopitExtension }],
on_finish: () => {
const session = exportToSlopit(jsPsych, {
participantId: jsPsych.data.urlVariables().PROLIFIC_PID,
studyId: "my-study-001",
});

console.log(JSON.stringify(session));
},
});

With Trial Filtering

const session = exportToSlopit(jsPsych, {
participantId: participantId,
trialFilter: (trial) => {
// Only include trials that have slopit data
return trial["slopit"] !== undefined;
},
});

Sending to Server

const session = exportToSlopit(jsPsych, { participantId, studyId });

await fetch("/api/submit-session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(session),
});

getSlopitDataFromTrial()

Extract slopit data from a specific trial by index.

Signature

function getSlopitDataFromTrial(
jsPsych: JsPsych,
trialIndex: number
): { behavioral: unknown; flags: unknown[] } | undefined;

Example

import { getSlopitDataFromTrial } from "@slopit/adapter-jspsych";

// Get slopit data from trial 3
const slopitData = getSlopitDataFromTrial(jsPsych, 3);

if (slopitData) {
console.log("Behavioral data:", slopitData.behavioral);
console.log("Flags:", slopitData.flags);
}

Types

SlopitExtensionParams

Configuration type for the extension params.

interface SlopitExtensionParams {
targetSelector: string;
keystroke?: KeystrokeConfig;
focus?: FocusConfig;
paste?: PasteConfig;
}

Complete Experiment Example

import { initJsPsych } from "jspsych";
import htmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response";
import jsPsychSurveyText from "@jspsych/plugin-survey-text";
import { SlopitExtension, exportToSlopit } from "@slopit/adapter-jspsych";

const jsPsych = initJsPsych({
extensions: [{ type: SlopitExtension }],
on_finish: async () => {
const session = exportToSlopit(jsPsych, {
participantId: jsPsych.data.urlVariables().PROLIFIC_PID,
studyId: "writing-study-001",
});

await fetch("/api/sessions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(session),
});

// Redirect to Prolific completion
window.location.href = "https://app.prolific.co/submissions/complete?cc=ABC123";
},
});

const timeline = [
// Instructions
{
type: htmlKeyboardResponse,
stimulus: `
<h2>Writing Task</h2>
<p>You will be asked to write about several topics.</p>
<p>Press any key to continue.</p>
`,
},

// Writing trial 1
{
type: jsPsychSurveyText,
questions: [{ prompt: "Describe your ideal vacation destination.", rows: 6 }],
extensions: [
{
type: SlopitExtension,
params: {
targetSelector: "textarea",
keystroke: { enabled: true },
paste: { enabled: true, prevent: true },
},
},
],
},

// Writing trial 2
{
type: jsPsychSurveyText,
questions: [{ prompt: "Explain how to make your favorite meal.", rows: 6 }],
extensions: [
{
type: SlopitExtension,
params: {
targetSelector: "textarea",
keystroke: { enabled: true },
paste: { enabled: true, prevent: true },
},
},
],
},

// Debrief
{
type: htmlKeyboardResponse,
stimulus: `
<h2>Thank You</h2>
<p>Your responses have been recorded.</p>
<p>Press any key to complete the study.</p>
`,
},
];

jsPsych.run(timeline);

Working with Different Plugins

The SlopitExtension works with any jsPsych plugin that renders text inputs. Use the targetSelector to specify which element(s) to capture.

Survey Text

import jsPsychSurveyText from "@jspsych/plugin-survey-text";

{
type: jsPsychSurveyText,
questions: [{ prompt: "Your response:" }],
extensions: [
{
type: SlopitExtension,
params: { targetSelector: "textarea", keystroke: { enabled: true } },
},
],
}

Survey HTML Form

import jsPsychSurveyHtmlForm from "@jspsych/plugin-survey-html-form";

{
type: jsPsychSurveyHtmlForm,
html: `<textarea id="response" rows="6"></textarea>`,
extensions: [
{
type: SlopitExtension,
params: { targetSelector: "#response", keystroke: { enabled: true } },
},
],
}

HTML Button Response

import jsPsychHtmlButtonResponse from "@jspsych/plugin-html-button-response";

{
type: jsPsychHtmlButtonResponse,
stimulus: `<textarea id="answer"></textarea>`,
choices: ["Submit"],
extensions: [
{
type: SlopitExtension,
params: { targetSelector: "#answer", keystroke: { enabled: true } },
},
],
}