Skip to main content

@slopit/adapter-labjs API Reference

Integration adapter for lab.js experiments. Provides a mixin function to add behavioral capture to lab.js components and a session recorder for managing experiment data.

Installation

pnpm add @slopit/adapter-labjs

lab.js is a peer dependency (version 21.0.0 or higher). It is typically loaded as a global script in lab.js experiments rather than installed via npm.

When to Use This Package

Use @slopit/adapter-labjs when:

  • Running experiments built with the lab.js builder
  • Using lab.js as a library for custom experiment code
  • Deploying lab.js studies to any hosting platform

Quick Start

import { withSlopit, LabjsRecorder } from "@slopit/adapter-labjs";

// enhance a lab.js component class with behavioral capture
const SlopitScreen = withSlopit(lab.html.Screen);

// create a recording session
const recorder = new LabjsRecorder({
participantId: "P001",
studyId: "my-study",
});

// use the enhanced component
const trial = new SlopitScreen({
content: `<textarea id="response"></textarea>`,
slopit: {
targetSelector: "#response",
keystroke: { enabled: true },
paste: { enabled: true },
},
});

// run the trial and collect data

withSlopit(Component)

Mixin function that adds slopit behavioral capture to any lab.js component class. Returns an enhanced class that automatically attaches capture during the component's run lifecycle.

function withSlopit<T extends LabjsComponentConstructor>(Component: T): T

Usage

import { withSlopit } from "@slopit/adapter-labjs";

// assuming lab.js is loaded globally
declare const lab: {
html: { Screen: new (...args: unknown[]) => unknown };
};

// create enhanced component class
const SlopitScreen = withSlopit(lab.html.Screen as LabjsComponentConstructor);

// use the enhanced component with slopit configuration
const screen = new SlopitScreen({
content: `
<h2>Writing Task</h2>
<textarea id="response" rows="6"></textarea>
`,
slopit: {
targetSelector: "#response",
keystroke: { enabled: true },
paste: { enabled: true, prevent: false },
},
});

LabjsSlopitConfig

Configuration passed via the slopit property on component options.

PropertyTypeDefaultDescription
enabledbooleantrueEnable or disable capture
targetSelectorstring"textarea, input[type='text']"CSS selector for the target element
containerSelectorstring".lab-content"Container to search within
keystrokeKeystrokeConfig{ enabled: false }Keystroke capture settings
pastePasteConfig{ enabled: false }Paste detection settings
focusFocusConfig{ enabled: false }Focus tracking settings
mouseMouseConfig{ enabled: false }Mouse tracking settings
scrollScrollConfig{ enabled: false }Scroll tracking settings

Accessing Captured Data

The enhanced component stores captured data in the component's data property:

// after component ends
const slopitData = screen.data.slopit;
// { behavioral: BehavioralData, flags: CaptureFlag[] }

Or use the getSlopitData() method:

const data = screen.getSlopitData();
if (data !== null) {
console.log("Keystrokes:", data.behavioral.keystrokes.length);
console.log("Flags:", data.flags);
}

LabjsRecorder

Session recorder for lab.js experiments. Extends the base recorder with lab.js-specific platform detection.

Constructor

new LabjsRecorder(config?: RecorderConfig)

RecorderConfig

PropertyTypeDescription
participantIdstringParticipant identifier
studyIdstringStudy identifier
metadataRecord<string, unknown>Session-level metadata
behavioralBehavioralCaptureConfigDefault behavioral capture settings

Example

import { LabjsRecorder } from "@slopit/adapter-labjs";

const recorder = new LabjsRecorder({
participantId: "P001",
studyId: "my-study",
});

// start a trial
recorder.startTrial({
trialType: "text-entry",
targetElement: document.querySelector("textarea"),
});

// participant completes the trial...

// end the trial with response data
recorder.endTrial({
type: "text",
value: "participant response",
characterCount: 20,
wordCount: 2,
});

// export the session
const session = recorder.exportSession();

Methods

The LabjsRecorder inherits all methods from BaseRecorder:

  • startTrial(config) - Start a new trial
  • endTrial(response?, platformData?) - End the current trial
  • attachCapture(element, startTime?) - Attach capture to an element
  • detachCapture() - Detach capture without ending trial
  • exportSession() - Export session data
  • getCurrentTrial() - Get current trial state
  • getTrials() - Get all completed trials
  • isTrialInProgress() - Check if trial is active
  • getSessionId() - Get session identifier
  • reset() - Reset for a new session

Complete Experiment Example

import { withSlopit, LabjsRecorder } from "@slopit/adapter-labjs";

// assuming lab.js is loaded globally
declare const lab: {
html: { Screen: LabjsComponentConstructor };
flow: { Sequence: LabjsComponentConstructor };
};

// create enhanced component
const SlopitScreen = withSlopit(lab.html.Screen);

// create recorder
const recorder = new LabjsRecorder({
participantId: new URL(location.href).searchParams.get("PROLIFIC_PID") ?? undefined,
studyId: "writing-study-001",
});

// instructions screen
const instructions = new lab.html.Screen({
content: `
<h2>Writing Task</h2>
<p>You will be asked to write about several topics.</p>
<p>Press <strong>Continue</strong> when ready.</p>
<button id="continue">Continue</button>
`,
responses: {
"click button#continue": "continue",
},
});

// writing trial with behavioral capture
const writingTrial = new SlopitScreen({
content: `
<h3>Please describe your ideal vacation destination.</h3>
<textarea id="response" rows="6" style="width: 100%;"></textarea>
<button id="submit">Submit</button>
`,
responses: {
"click button#submit": "submit",
},
slopit: {
targetSelector: "#response",
keystroke: { enabled: true },
paste: { enabled: true, prevent: true },
focus: { enabled: true },
},
});

// debrief screen
const debrief = new lab.html.Screen({
content: `
<h2>Thank You</h2>
<p>Your responses have been recorded.</p>
`,
});

// build experiment sequence
const experiment = new lab.flow.Sequence({
content: [instructions, writingTrial, debrief],
});

// handle experiment end
experiment.on("end", async () => {
// collect data from the writing trial
const slopitData = writingTrial.getSlopitData();

if (slopitData !== null) {
recorder.startTrial({
trialType: "writing",
stimulus: {
type: "text",
content: "Describe your ideal vacation destination.",
},
});

// get the response text
const responseText = writingTrial.data.response || "";

recorder.endTrial({
type: "text",
value: responseText,
});
}

// export and send session
const session = recorder.exportSession();

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

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

// run the experiment
experiment.run();

Using with the lab.js Builder

When using the lab.js Builder, you can add slopit capture to components via inline JavaScript:

  1. Export your study from the Builder
  2. Add the slopit adapter to your study's HTML or via a bundler
  3. Modify the generated code to wrap target components with withSlopit()
// in your study's main script
import { withSlopit } from "@slopit/adapter-labjs";

// wrap the target component class before it's used
const originalScreen = lab.html.Screen;
lab.html.Screen = withSlopit(originalScreen);

Types

LabjsComponentLike

Minimal interface for lab.js component lifecycle hooks.

interface LabjsComponentLike {
options: Record<string, unknown>;
data: Record<string, unknown>;
on(event: string, handler: (...args: unknown[]) => void): void;
}

LabjsComponentConstructor

Constructor type for lab.js components.

type LabjsComponentConstructor<T extends LabjsComponentLike = LabjsComponentLike> =
new (...args: unknown[]) => T;

LabjsTrialData

Trial data structure with optional slopit capture data.

interface LabjsTrialData {
slopit?: AdapterData;
[key: string]: unknown;
}

Platform Notes

  • lab.js version is detected from window.lab.version when available
  • The default container selector .lab-content works with standard lab.js layouts
  • Element discovery uses MutationObserver with a 5-second timeout
  • If the target element is not found, capture silently skips (no error thrown)