ReDem only uses the last 300 MOUSE_MOVEMENT and last 300 MOUSE_CLICK events per BAS data point. Cap client-side tracking at the same limits — storing more only inflates browser session data without improving scoring.
Implementing a JavaScript Tracker for User Behavior Data
Copy the simple JavaScript tracker code (attached) into your survey platform’s frontend codebase or UI. This ensures that, whenever respondent complete surveys, it tracks input behavior data for open-ended questions directly from their individual browsers.
Integrate ReDem API into your workflow
We are using this script to persist this input_behavior in the browser until you are ready to call the API
<script>
document.addEventListener("DOMContentLoaded", function () {
const textarea = document.getElementById("input_behavior");
const submitButton = document.getElementById("submit_button");
// Constants
const INTERACTION_TYPES = {
KEYSTROKE: 'KEYSTROKE',
COPYPASTE: 'COPY_AND_PASTE',
MOUSE_MOVEMENT: "MOUSE_MOVEMENT",
MOUSE_CLICK: "MOUSE_CLICK",
};
// Question ID - This would typically come from your survey tool, you need to edit this
const QUESTION_ID = "<BAS_QuestionID>";
const MAX_MOUSE_MOVEMENT_EVENTS = 300;
const MAX_MOUSE_CLICK_EVENTS = 300;
const INTERACTION_STORAGE_KEY = QUESTION_ID + "_interaction_data";
const movementEvents = [];
const clickEvents = [];
const keystrokeEvents = [];
const createInteractionRecord = (value, interactionType) => ({
value,
interactionType,
timestamp: new Date().toISOString()
});
const applyCap = (buffer, maxEvents) => {
while (buffer.length > maxEvents) {
buffer.shift();
}
};
const pushToCappedBuffer = (buffer, record, maxEvents) => {
buffer.push(record);
applyCap(buffer, maxEvents);
};
const splitIntoInteractionBuffers = (interactionData) => {
interactionData.forEach((event) => {
if (event.interactionType === INTERACTION_TYPES.MOUSE_MOVEMENT) {
movementEvents.push(event);
return;
}
if (event.interactionType === INTERACTION_TYPES.MOUSE_CLICK) {
clickEvents.push(event);
return;
}
keystrokeEvents.push(event);
});
applyCap(movementEvents, MAX_MOUSE_MOVEMENT_EVENTS);
applyCap(clickEvents, MAX_MOUSE_CLICK_EVENTS);
};
const loadInteractionBuffers = () => {
const stored = sessionStorage.getItem(INTERACTION_STORAGE_KEY);
if (!stored) {
return;
}
splitIntoInteractionBuffers(JSON.parse(stored));
};
const mergeInteractionDataChronologically = () =>
[...movementEvents, ...clickEvents, ...keystrokeEvents].sort((a, b) =>
a.timestamp.localeCompare(b.timestamp)
);
const saveInteractionDataToSession = () => {
sessionStorage.setItem(
INTERACTION_STORAGE_KEY,
JSON.stringify(mergeInteractionDataChronologically())
);
};
loadInteractionBuffers();
// Handle mouse movement events
document.addEventListener("pointermove", (e) => {
pushToCappedBuffer(
movementEvents,
createInteractionRecord(
{ x: e.clientX, y: e.clientY },
INTERACTION_TYPES.MOUSE_MOVEMENT
),
MAX_MOUSE_MOVEMENT_EVENTS
);
});
// Handle mouse click events
document.addEventListener("click", (e) => {
const target = e.target instanceof Element ? e.target : null;
if (!target) return;
const rect = target.getBoundingClientRect();
pushToCappedBuffer(
clickEvents,
createInteractionRecord(
{ width: rect.width, height: rect.height, offsetX: e.clientX - rect.left, offsetY: e.clientY - rect.top },
INTERACTION_TYPES.MOUSE_CLICK
),
MAX_MOUSE_CLICK_EVENTS
);
});
// Handle Keystroke events
textarea.addEventListener("input", (e) => {
// Ignore paste events as they're handled separately
if (e.inputType === "insertFromPaste") return;
keystrokeEvents.push(createInteractionRecord(e.target.value, INTERACTION_TYPES.KEYSTROKE));
});
// Handle paste events
textarea.addEventListener("paste", (e) => {
const selectionStart = e.target.selectionStart;
const selectionEnd = e.target.selectionEnd;
const currentValue = e.target.value;
const pastedText = e.clipboardData?.getData("text") || "";
const newText = currentValue.slice(0, selectionStart) + pastedText + currentValue.slice(selectionEnd);
keystrokeEvents.push(createInteractionRecord(newText, INTERACTION_TYPES.COPYPASTE));
});
// Handle form submission
submitButton.addEventListener("click", function () {
saveBASDataPerQuestion();
});
window.addEventListener("pagehide", saveInteractionDataToSession);
// Function to save data in BAS format
const saveBASDataPerQuestion = () => {
const interactionData = mergeInteractionDataChronologically();
const basData = {
"qualityCheck": "BAS",
"dataPointId": QUESTION_ID,
"interactionData": interactionData
};
sessionStorage.setItem(QUESTION_ID, JSON.stringify(basData));
saveInteractionDataToSession();
};
});
</script>
Use API response to trigger your next step
When you are making the API call to ReDem use this structure from the session storage and make sure to connect QUESTION_ID during the integration process