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()));
The details