; PSY 1903
PSY 1903 Programming for Psychologists

Shared Assets

Shared CSS

If you have common CSS styles used across multiple experiments, you can put them in a global CSS file that can be shared amongst experiments. E.g. psy1903/docs/assets/shared-styles.css:

Commonly used styles:

.key {
    border: 1px solid black;
    border-radius: 2px;
    display: inline-block;
    padding: 5px 10px;
}

.jspsych-display-element input[type=text] {
    font-size: 36px;
    text-align: center;
    width: 100px;
}

.loader {
    width: 48px;
    height: 48px;
    border: 5px solid #000;
    border-bottom-color: transparent;
    border-radius: 50%;
    display: inline-block;
    box-sizing: border-box;
    animation: rotation 1s linear infinite;
}

@keyframes rotation {
    0% {
        transform: rotate(0deg);
    }

    100% {
        transform: rotate(360deg);
    }
}

Update your HTML files to include this stylesheet:

<link href='https://unpkg.com/jspsych/css/jspsych.css' rel='stylesheet' type='text/css'>
<link href='../../assets/shared-styles.css' rel='stylesheet' type='text/css'>
<link href='styles.css' rel='stylesheet' type='text/css'>

Observe the order of our style references which goes from “broad” to “specific”:

  • Broad external jsPsych styles
  • In-house shared styles
  • In-house experiment-specific styles

We follow this order because specific stylesheets often override or rely upon the broader stylesheets.

Shared JavaScript

Similar to above, we can also created a file of common JavaScript functions, psy1903/docs/assets/shared-functions.js:

/**
 * 
 */
async function saveResults(fileName, data, dataPipeExperimentId = '', forceOSFSave = false) {

    // Dynamically determine if the experiment is currently running locally or on production
    let isLocalHost = window.location.href.includes('localhost');

    let destination = '/save';
    if (!isLocalHost || forceOSFSave) {
        destination = 'https://pipe.jspsych.org/api/data/';
    }

    // Send the results to our saving end point
    let response = await fetch(destination, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            Accept: '*/*',
        },
        body: JSON.stringify({
            experimentID: dataPipeExperimentId,
            filename: fileName,
            data: data,
        }),
    }).then(response => {
        // Parse the response body text as JSON
        return response.json();
    }).then(responseBodyAsJson => {
        return responseBodyAsJson;
    });

    return response;
}

/**
 * 
 */
function getCurrentTimestamp() {
    return new Date().toISOString().replace(/T/, '-').replace(/\..+/, '').replace(/:/g, '-');
}

/**
 * 
 */
function objectArrayToCSV(data) {
    let headers = Object.keys(data[0]);
    let rows = data.map(obj => headers.map(header => obj[header]).join(","));
    return [headers.join(","), ...rows].join("\n");
}

/**
 * 
 */
function getRandomNumber(min, max) {
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

Update your HTML to include these shared functions.

Like CSS, we want to include our JavaScript files from “broad” to “specific”, with the idea being that specific JavaScript will often override or depend on the broader JavaScript files:

<script src='https://unpkg.com/jspsych'></script>
<script src='https://unpkg.com/@jspsych/plugin-html-keyboard-response'></script>
<script src='../../assets/shared-functions.js'></script> <!-- NEW -->
<script src='conditions.js'></script>
<script src='experiment.js'></script>

Update results trial to use saveResults function

Here’s an updated results trial that uses the saveResults function added above:

let resultsTrial = {
    type: jsPsychHtmlKeyboardResponse,
    choices: ['NO KEYS'],
    async: false,
    stimulus: `
        <h1>Please wait...</h1>
        <span class='loader'></span>
        <p>We are saving the results of your inputs.</p>
        `,
    on_start: function () {

        // Filter and retrieve results as CSV data
        let results = jsPsych.data
            .get()
            .filter({ collect: true })
            .ignore(['stimulus', 'trial_type', 'plugin_version', 'collect'])
            .csv();

        console.log(results);

        let prefix = 'lexical-decision';
        let dataPipeExperimentId = 'xGrIMXyGYhic';
        let forceOSFSave = false;
        let participantId = getCurrentTimestamp();
        let fileName = prefix + '-' + participantId + '.csv';

        saveResults(fileName, results, dataPipeExperimentId, forceOSFSave).then(response => {
            jsPsych.finishTrial();
        })

    }
}
timeline.push(resultsTrial);