Save csv files locally and online simultaneously

Hello everyone,

We are using Pavlovia to run experiments on tablets/phones in schools. We are able to test 15+ persons at the same time, which is great.

In our setup, we pre-load all experiment sessions on the tablets/phones and then give them to the participants. The main issue is that - in this specific kind of non-laboratory setup - we sometimes encounter technical issues that make us lose the internet connection during the task (i.e., wifi disconnection). We experienced several participants who were able to do the task BUT at the end of the experiment, the data upload failed, and their data seemed to be forever lost.

My question is: is it possible to save simultaneously locally AND online? Alternatively, how can I make sure that the data is saved locally if the upload fails?

Best regards,
Mathieu

Hi Mathieu,

I know this is probably too late for you, but it might be helpful for others. We solved this by adding this code to our last routine.

Disclaimer: This code was built using AI.

// Make sure the final data row is included
if (psychoJS.experiment.isEntryEmpty()) {
    psychoJS.experiment.nextEntry();
}

// Retrieve accumulated experiment data
const rows = psychoJS.experiment._trialsData;

// Collect all column names
const columns = [...new Set(
    rows.flatMap(row => Object.keys(row))
)];

// Format values for CSV
function csvValue(value) {
    if (value === undefined || value === null) {
        return "";
    }

    if (typeof value === "object") {
        value = JSON.stringify(value);
    }

    value = String(value).replace(/"/g, '""');
    return `"${value}"`;
}

// Construct the CSV
const csvLines = [
    columns.map(csvValue).join(","),
    ...rows.map(row =>
        columns.map(column => csvValue(row[column])).join(",")
    )
];

const csvData = "\uFEFF" + csvLines.join("\r\n");

// Use information already stored in expInfo
const participantID = String(expInfo["participant"] || "unknown").replace(/[^a-zA-Z0-9_-]/g, "_");

const experimentName = String(expInfo["expName"] || expName || "experiment").replace(/[^a-zA-Z0-9_-]/g, "_");

const experimentDate = String(expInfo["date"] || "unknown_date").replace(/[^a-zA-Z0-9_.-]/g, "_");

const filename = `${participantID}_${experimentName}_${experimentDate}_localbackup.csv`;

// Download the local backup
util.offerDataForDownload(filename, csvData, "text/csv");