Reviewing The Filesystem Access API
The Filesystem Access API is a Chromium-only feature that's part of Project Fugu. It allows you to work opening and saving files to the local filesystem.
This post will discuss the feature and provide examples of how to open and save individual files.
This example will perform the following tasks
- Configure the file picker
- Write a function to open a file from the local file system
- Write a function to save the open file to disk
Before we start, I'll put the assumptions I make in the code here so you know what you're dealing with.
- This code will only work on Chromium browsers
- Since I don't work with any other Chromium browser (Brave, Vivaldi, etc) I have no way to know if other Chromium browsers enabled this feature
- Since this is a demo, I did not wrap the code in feature queries. That is left as an exercise for the reader
- We also know for certain that this feature is not available in Firefox and Safari and that it's unlikely they will implement it, so you'll have to work with a ponyfill or polyfill to make sure the feature works across browsers
Configuring the File Picker #
The pickerOpts
object provides the configuration for the file picker object.
This configuration provides retrictions on the file types it will open and whether we can open multiple files.
This section also creates a filehandle
placeholder variable that we'll use in later functions.
const pickerOpts = {
types: [
{
description: "Text Files",
accept: {
"text/*": [
".md",
".txt",
".js",
".css",
],
"text/html": [
".html",
".htm",
],
"text/xml": [
".xml",
],
}
},
{
description: "Images",
accept: {
"image/*": [
".png",
".gif",
".jpeg",
".jpg",
".webp",
".avif",
".heif",
]
}
},
],
excludeAcceptAllOption: true,
multiple: false
};
Opening a file #
The openFile
function handles opening a local file from the user's computer.
- Create an async function with no parameters. We'll use
try/catch
for this code - Inside the
try
block, runshowOpenFilePicker
with the options we defined earlier and assign the result to thefileHandle
variable - Get the file referenced in the previous step
- Assign the content of the file to a variable that we can use later
- In the
catch
block handle any errors - Outside the function, create a
click
event listener for theopenButton
button and assign the function as the second parameter.
// 1
async function openFile() {
try {
// 2
const [ fileHandle ] = await window.showOpenFilePicker(pickerOpts);
// 3
const file = await fileHandle.getFile();
// 4
const contents = await file.text();
} catch (err) { // 5
console.error("there was an error: ", err.message);
}
}
// 6
document
.getElementById("theButton")
.addEventListener("click", openFile);
Saving a file #
This implementation will save a file already open with the same name. It doesn't provide a way to rename the file.
- Create an async function
- If there is no value for the
fileHandle
variable then bail, theres's nothing to do - Use a
try/catch
block - Grab the value of the
fileHandle
- Create a writeable stream we'll use to write the content to disk
- Write the content of the file to disk
- Close the stream
- In the
catch
block handle errors - Set up a
click
event handler for thesaveButton
button element
// 1
async function saveFile() {
// 2
if (!fileHandle) {
alert("No file opened yet. Please open a file first.");
return;
}
// 3
try {
// Get the contents from the textarea
// 4
const contents = document.getElementById("content").value;
// Create a writable stream
// 5
const writable = await fileHandle.createWritable();
// 6
await writable.write(contents);
// Close the file
await writable.close();// 7
alert("File saved successfully!");
} catch (err) { // 8
console.error("Error saving file: ", err.message);
}
}
// 9
document
.getElementById("saveButton")
.addEventListener("click", saveFile);
Saving a file as #
The save as
functionality provides a different way save a file. This function provides a way to save the file under a different name.
It also provides a way to create new files. Since we capture the text from the text area, this function dones't rely on a file being open.
- Create an async function
- Create a
try/catch
block - Create a
saveFilePicker
- Set a variable with the content of the
textarea
element as its value - Create a writeable stream
- Write the content we set up in step 4 to the writeable stream
- Close the writeable stream
- Update the global handle
- In the
catch
block handle errors - Set up a
click
event listener and attach thesaveAs
function
// 1
async function saveAs() {
// 2
try {
// Show save file picker
// 3
const newFileHandle = await window.showSaveFilePicker(pickerOpts);
// Get the contents from the textarea
// 4
const contents = document.getElementById("content").value;
// Create a writable stream
// 5
const writable = await newFileHandle.createWritable();
// 6
await writable.write(contents);
// Close the file
// 7
await writable.close();
alert("File saved successfully!");
// Update the global file handle
// 8
fileHandle = newFileHandle;
} catch (err) { // 9
console.error("Error saving file as: ", err.message);
}
}
// 10
document
.getElementById("saveAsButton")
.addEventListener("click", saveAs);
These three functions provide the basic functionality of an editor. There are many possibilities to move from here.