Skip to main content
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.
1

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.

2

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>
3

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