I was working on a project recently where I needed to create a file in the Javascript side of the application and save it to the filesystem. I was using Wails for the project because Golang is amazing! 😍 Wails supports a "SaveFileDialog" but at the time of writing this post, it does not support it on the frontend.
Now, the solution below is not recommended for large files. This is because we will be passing the file by encoding the Blob as a base64 string. For large files, this will consume a lot of memory.
To get started, we will create a new Wails project. I have named mine wails-save-blob
. We will be using the React template.
wails init -n wails-save-blob -t react-ts
Once the project code, open it in your favorite editor. I am using VSCode. Be sure to run wails dev
to make sure everything is working as expected.
Open the app.go
file and add the following function
func (a *App) SaveFile(title string, defaultFilename string, fileFilterDisplay string, fileFilterPattern string, base64Content string) string {
file, err := runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{
Title: title,
DefaultFilename: defaultFilename,
Filters: []runtime.FileFilter{{
DisplayName: fileFilterDisplay,
Pattern: fileFilterPattern,
}},
})
if err != nil {
return err.Error()
}
// Decode base64 content
bytes, err := base64.StdEncoding.DecodeString(base64Content)
if err != nil {
return err.Error()
}
// Write file
err = os.WriteFile(file, bytes, 0644)
if err != nil {
return err.Error()
}
return file
}
This function will take in the following parameters:
Parameter | Description |
---|---|
title | The title of the dialog |
defaultFilename | The default filename |
fileFilterDisplay | The display name of the file filter |
fileFilterPattern | The file filter pattern |
base64Content | The base64 encoded content |
The function will return the path of the file that was saved.
Let's do some cleanup in the template. First delete the assets folder and Delete App.css
and style.css
; Remove the style.css
import from main.tsx
. Finally, replace App.tsx with the following:
import { useState } from "react";
import { SaveFile } from "../wailsjs/go/main/App";
export default function App() {
const [isSaving, setIsSaving] = useState(false);
const createCsvString = () => {
const csvHeaders = "City, State";
const csvRows = [
["New York City", "New York"],
["Los Angeles", "California"],
["Chicago", "Illinois"],
];
const csvString = [csvHeaders, ...csvRows.map((row) => row.join(","))].join("\n");
return csvString;
};
const blobToBase64 = (blob: Blob) => {
const reader = new FileReader();
reader.readAsDataURL(blob);
return new Promise<string>((resolve, reject) => {
reader.onloadend = () => {
const base64data = reader.result;
if (typeof base64data === "string") {
resolve(base64data.split(",")[1]);
}
reject("Base64 data is not a string");
};
});
};
const handleSave = async () => {
setIsSaving(true);
const csvString = createCsvString();
const blob = new Blob([csvString], { type: "text/csv;charset=utf-8;" });
const base64 = await blobToBase64(blob);
const title = "Save cities.csv";
const filename = "cities.csv";
const fileFilderDisplay = "Csv File (*.csv)";
const fileExtension = "*.csv";
await SaveFile(title, filename, fileFilderDisplay, fileExtension, base64);
setIsSaving(false);
};
return (
<div id="App">
<button className="btn" onClick={handleSave}>
{isSaving ? "Saving... please wait" : "Save CSV File Blob"}
</button>
</div>
);
}
This React component, will create a CSV string and convert it to a Blob. Then it will convert the Blob to a base64 string. Finally, it will call the SaveFile
function that we created in the previous step.
Now that we have everything setup, let's test it out. Run wails dev
to start the application. Click on the button and you should see a dialog to save the file.
Let's open the file in Excel and make sure that everything is working as expected.
The source code used in this tutorial is available on GitHub.