Recipe 10.6
Exporting API Data to a File
Compatibility Note: showSaveFilePicker
This feature may not be supported on all browsers yet. Please check the latest compatibility data before using in a production application.
Browser support for showSaveFilePickerDemo
The user data was exported.
First name | Last name | Department |
---|---|---|
Loading...
Loading Users
|
Code
JavaScript
const exportButton = document.querySelector('#export-button');
let userList;
/**
* Shows a save file picker and returns the selected file handle.
* @returns a file handle to the selected file, or null if the user clicked Cancel
*/
async function selectOutputFile() {
try {
return window.showSaveFilePicker({
// The default name for the output file
suggestedName: 'users.json',
// Limit the available file extensions
types: [
{ description: "JSON", accept: { "application/json": [".json"] } }
]
});
} catch (error) {
// If the user clicks Cancel, an exception is thrown. In this case,
// return null to indicate no file was selected.
return null;
}
}
async function exportData() {
const outputFile = await selectOutputFile();
// Only proceed if an output file was actually selected
if (outputFile) {
try {
// Prepare a writable stream, which is used to save the file
// to disk.
const stream = await outputFile.createWritable();
// Write the JSON, in a human readable format, to the stream.
await stream.write(JSON.stringify(userList, null, 2));
await stream.close();
// Show a success message.
document.querySelector('#export-success').classList.remove('d-none');
} catch (error) {
console.error(error);
}
}
}
exportButton.addEventListener('click', () => {
exportData();
});
function loadUsers() {
// Make the request
return fetch('/api/users')
// Parse the response body as an object
.then(response => response.json())
// Handle errors, including network and JSON parsing errors
.catch(error => console.error('Couldn\'t fetch:', error.message));
}
loadUsers().then(data => {
userList = data;
document.querySelector('#loader').remove();
if ('showSaveFilePicker' in window) {
exportButton.classList.remove('d-none');
}
renderUsers(userList);
});
/**
* Renders an array of users in the user table.
* @param userList the array of users
*/
function renderUsers(userList) {
const tableBody = document.querySelector('#users tbody');
userList.forEach(user => {
renderUser(user, tableBody);
});
}
/**
* Renders a user object as a row in the user table.
* @param user the user object to render
* @param tableBody the table body to append the row to
*/
function renderUser(user, tableBody) {
const row = document.createElement('tr');
const firstName = document.createElement('td');
firstName.textContent = user.firstName;
row.appendChild(firstName);
const lastName = document.createElement('td');
lastName.textContent = user.lastName;
row.appendChild(lastName);
const department = document.createElement('td');
department.textContent = user.department;
row.appendChild(department);
tableBody.appendChild(row);
}
HTML
<div class="alert alert-success d-none" id="export-success" role="alert">
The user data was exported.
</div>
<button class="btn btn-primary d-none" id="export-button">
<i class="bi bi-box-arrow-down"></i> Export
</button>
<table id="users" class="table">
<thead>
<tr>
<th class="w-25">First name</th>
<th class="w-25">Last name</th>
<th class="w-25">Department</th>
</tr>
</thead>
<tbody>
<tr id="loader">
<td colspan="3" class="text-center p-4">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<div>Loading Users</div>
</td>
</tr>
</tbody>
</table>