The problem#
Users don't tend to have a lot of control over their data in web apps. Most often, the data is stored on a server the user does not control—or, if they do control it, we're talking about self-hosting which is much more involved then just navigating to a web app in a browser. Alternatively, the data may be stored locally, but using various browser-specific mechanisms which make it difficult for the user to share, backup, or otherwise reason about the data the web app manipulates.
While desktop apps can replicate these problems, usually they store data in files either explicitly chosen by the user or in well-known locations.
The solution#
Files are a flexible interface to let users do whatever they want with their data, so let's use them for web apps, too.
To save a file to the user's computer, modified from this example:
function saveFile(filename, data, mimeType) {
const element = document.createElement("a");
const url = URL.createObjectURL(new Blob([data],
{ type: mimeType }));
element.setAttribute("href", url);
element.setAttribute("download", filename);
element.click();
URL.revokeObjectURL(url);
}
// Save a JSON file:
saveFile("hello.json",
JSON.stringify({"Hello": "World!"}, null, 2),
"application/json");
(Consider using beforeunload
if the user has unsaved
changes to make sure they really do have their data in the file, and not
just in the browser.)
To load a file from the user's computer:
function loadFile() {
const element = document.createElement("input");
element.type = "file";
return new Promise((resolve, reject) => {
element.click();
element.addEventListener("change",
() => resolve(element.files[0]));
element.addEventListener("cancel",
() => reject("User canceled."));
});
}
// loadFile() must be called from a real user click.
myButton.addEventListener('click',
async (e) => myLoadFunc(await loadFile()));