Step 6: Export Todos to the Filesystem

In this step, you will learn:

  • How to get a reference to a file in the external filesystem.
  • How to write to the filesystem.

Estimated time to complete this step: 20 minutes.
To preview what you will complete in this step, jump down to the bottom of this page ↓.

Export todos

This step adds an export button to the app. When clicked, the current todo items are saved to a text file selected by the user. If the file exists, it's replaced. Otherwise, a new file gets created.

Update permissions

File system permissions can be requested as a string for read-only access, or an Object with additional properties. For example:

// Read only
"permissions": ["fileSystem"]

// Read and write
"permissions": [{"fileSystem": ["write"]}]

// Read, write, autocomplate previous input, and select folder directories instead of files
"permissions": [{"fileSystem": ["write", "retainEntries", "directory"]}]

You need read and write access. In manifest.json, request the {fileSystem: [ "write" ] } permission:

"permissions": [
  "storage", 
  "alarms", 
  "notifications", 
  "webview",
  "<all_urls>", 
  { "fileSystem": ["write"] } 
],

Update HTML view

In index.html, add an Export to disk button and a div where the app shows a status message:

<footer id="info">
  <button id="toggleAlarm">Activate alarm</button>
  <button id="exportToDisk">Export to disk</button>
  <div id="status"></div>
  ...
</footer>

Also in index.html, load the export.js script:

...
<script src="js/alarms.js"></script>
<script src="js/export.js"></script>

Create export script

Create a new JavaScript file named export.js using the code below. Save it in the js folder.

(function() {

  var dbName = 'todos-vanillajs';

  var savedFileEntry, fileDisplayPath;

  function getTodosAsText(callback) {
  }

  function exportToFileEntry(fileEntry) {
  }

  function doExportToDisk() {
  }

  document.getElementById('exportToDisk').addEventListener('click', doExportToDisk);

})();

Right now, export.js only contains a click listener on the Export to disk button and stubs for getTodosAsText(), exportToFileEntry, and doExportToDisk().

Get todo items as text

Update getTodosAsText() so that it reads todos from chrome.storage.local and generates a textual representation of them:

function getTodosAsText(callback) {
  chrome.storage.local.get(dbName, function(storedData) {
    var text = '';

    if ( storedData[dbName].todos ) {
      storedData[dbName].todos.forEach(function(todo) {
          text += '- ';
          if ( todo.completed ) {
            text += '[DONE] ';
          }
          text += todo.title;
          text += '\n';
        }, '');
    }

    callback(text);

  }.bind(this));
}

Choose a file

Update doExportToDisk() with chrome.fileSystem.chooseEntry() to allow the user to choose a file:

function doExportToDisk() {

  if (savedFileEntry) {

    exportToFileEntry(savedFileEntry);

  } else {

    chrome.fileSystem.chooseEntry( {
      type: 'saveFile',
      suggestedName: 'todos.txt',
      accepts: [ { description: 'Text files (*.txt)',
                   extensions: ['txt']} ],
      acceptsAllTypes: true
    }, exportToFileEntry);

  }
}

The first parameter of chrome.fileSystem.chooseEntry() is an object of options. The second parameter is a callback method.

If there's already a saved FileEntry, use that instead when calling exportToFileEntry(). File references exist for the lifetime of the object representing the FileEntry. This example ties FileEntry to the app window so the JavaScript code can write to the selected file without any user interaction as long as the app window remains open.

Use FileEntry to write todos items to disk

Update exportToFileEntry() to save the todos as text via the FileEntry Web API:

function exportToFileEntry(fileEntry) {
  savedFileEntry = fileEntry;

  var status = document.getElementById('status');

  // Use this to get a file path appropriate for displaying
  chrome.fileSystem.getDisplayPath(fileEntry, function(path) {
    fileDisplayPath = path;
    status.innerText = 'Exporting to '+path;
  });

  getTodosAsText( function(contents) {

    fileEntry.createWriter(function(fileWriter) {

      var truncated = false;
      var blob = new Blob([contents]);

      fileWriter.onwriteend = function(e) {
        if (!truncated) {
          truncated = true;
          // You need to explicitly set the file size to truncate
          // any content that might have been there before
          this.truncate(blob.size);
          return;
        }
        status.innerText = 'Export to '+fileDisplayPath+' completed';
      };

      fileWriter.onerror = function(e) {
        status.innerText = 'Export failed: '+e.toString();
      };

      fileWriter.write(blob);

    });
  });
}

chrome.fileSystem.getDisplayPath() gets a displayable file path that outputs to the status div.

Use fileEntry.createWriter() to create a FileWriter object. fileWriter.write() can then write a Blob to the filesystem. Use fileWriter.onwriteend() and fileWriter.onerror() to update the status div.

For more information about FileEntry, read Exploring the FileSystem APIs on HTML5Rocks, or refer to the FileEntry docs on MDN.

Persist FileEntry objects

Advanced: FileEntry objects cannot be persisted indefinitely. Your app needs to ask the user to choose a file every time the app is launched. If your app was forced to restart due to a runtime crash or update, restoreEntry() is an option to restore a FileEntry.

If you wish, experiment by saving the ID returned by retainEntry() and restoring it on app restart. (Hint: Add a listener to the onRestarted event in the background page.)

Launch your finished Todo app

You are done Step 6! Reload your app and add some todos. Click Export to disk to export your todos to a .txt file.

The Todo app with exported todos

For more information

For more detailed information about some of the APIs introduced in this step, refer to:

Ready to continue onto the next step? Go to Step 7 - Publish your app »