1 <h1 id=
"export-to-filesystem">
2 <span class=
"h1-step">Step
6:
</span>
3 Export Todos to the Filesystem
7 <strong>Want to start fresh from here?
</strong>
8 Find the previous step's code in the
<a href=
"https://github.com/mangini/io13-codelab/archive/master.zip">reference code zip
</a> under
<strong><em>cheat_code
> solution_for_step5
</strong></em>.
11 <p>In this step, you will learn:
</p>
14 <li>How to get a reference to a file in the external filesystem.
</li>
15 <li>How to write to the filesystem.
</li>
19 <em>Estimated time to complete this step:
20 minutes.
</em>
21 To preview what you will complete in this step,
<a href=
"#launch">jump down to the bottom of this page
↓</a>.
24 <h2 id=
"export-todos">Export todos
</h2>
26 <p>This step adds an export button to the app.
27 When clicked, the current todo items are saved to a text file selected by the user.
28 If the file exists, it's replaced. Otherwise, a new file gets created.
</p>
30 <h3 id=
"update-permissions">Update permissions
</h3>
32 <p>File system permissions can be requested as a string for read-only access, or an Object with additional properties. For example:
</p>
36 "permissions": [
<b>"fileSystem"</b>]
39 "permissions": [
<b>{
"fileSystem": [
"write"]}
</b>]
41 // Read, write, autocomplate previous input, and select folder directories instead of files
42 "permissions": [
<b>{
"fileSystem": [
"write",
"retainEntries",
"directory"]}
</b>]
45 <p>You need read and write access.
46 In
<em>manifest.json
</em>, request the
<code>{fileSystem: [
"write" ] }
</code> permission:
</p>
48 <pre data-filename=
"manifest.json">
49 "permissions": [
"storage",
"alarms",
"notifications",
"webview",
50 "<all_urls>"<b>, {
"fileSystem": [
"write"] }
</b> ],
54 <h3 id=
"update-html-view">Update HTML view
</h3>
56 <p>In
<em>index.html
</em>, add an
<b>Export to disk
</b> button and
57 a
<code>div
</code> where the app shows a status message:
</p>
59 <pre data-filename=
"index.html">
60 <footer
id=
"info">
61 <button
id=
"toggleAlarm">Activate alarm
</button
>
62 <b><button
id=
"exportToDisk">Export to disk
</button
></b>
63 <b><div
id=
"status"></div
></b>
68 <p>Also in
<em>index.html
</em>, load the
<em>export.js
</em> script:
</p>
70 <pre data-filename=
"index.html">
72 <script
src=
"js/alarms.js"></script
>
73 <b><script
src=
"js/export.js"></script
></b>
76 <h3 id=
"create-js">Create export script
</h3>
78 <p>Create a new JavaScript file named
<em>export.js
</em> using the code below.
79 Save it in the
<em>js
</em> folder.
</p>
81 <pre data-filename=
"export.js">
84 var dbName = 'todos-vanillajs';
86 var savedFileEntry, fileDisplayPath;
88 function getTodosAsText(callback) {
91 function exportToFileEntry(fileEntry) {
94 function doExportToDisk() {
97 document.getElementById('exportToDisk').addEventListener('click', doExportToDisk);
102 <p>Right now,
<em>export.js
</em> only contains a click listener on the
<strong>Export to disk
</strong> button and stubs for
<code>getTodosAsText()
</code>,
<code>exportToFileEntry
</code>, and
<code>doExportToDisk()
</code>.
</p>
104 <h3 id=
"get-todos-as-text">Get todo items as text
</h3>
106 <p>Update
<code>getTodosAsText()
</code> so that it reads todos from
<code>chrome.storage.local
</code> and generates a textual representation of them:
</p>
108 <pre data-filename=
"export.js">
109 function getTodosAsText(callback) {
110 <b> chrome.storage.local.get(dbName, function(storedData) {
113 if ( storedData[dbName].todos ) {
114 storedData[dbName].todos.forEach(function(todo) {
116 if ( todo.completed ) {
130 <h3 id=
"choose-file">Choose a file
</h3>
132 <p>Update
<code>doExportToDisk()
</code> with
<code><a href=
"/apps/fileSystem#method-chooseEntry">chrome.fileSystem.chooseEntry()
</a></code> to allow the user to choose a file:
</p>
134 <pre data-filename=
"export.js">
135 function doExportToDisk() {
137 <b> if (savedFileEntry) {
139 exportToFileEntry(savedFileEntry);
143 chrome.fileSystem.chooseEntry( {
145 suggestedName: 'todos.txt',
146 accepts: [ { description: 'Text files (*.txt)',
147 extensions: ['txt']} ],
148 acceptsAllTypes: true
149 }, exportToFileEntry);
155 <p>The first parameter of
<code>chrome.fileSystem.chooseEntry()
</code> is an object of options. The second parameter is a callback method.
</p>
157 <p>If there's already a saved
<code>FileEntry
</code>,
158 use that instead when calling
<code>exportToFileEntry()
</code>.
159 File references exist for the lifetime of the object representing the
<code>FileEntry
</code>.
160 This example ties
<code>FileEntry
</code> to the app window so the JavaScript code can write
161 to the selected file without any user interaction as long as the app window remains open.
</p>
163 <h3 id=
"use-fileentry">Use FileEntry to write todos items to disk
</h3>
165 <p>Update
<code>exportToFileEntry()
</code> to save the todos as text via the
<code>FileEntry
</code> Web API:
</p>
167 <pre data-filename=
"export.js">
168 function exportToFileEntry(fileEntry) {
169 <b> savedFileEntry = fileEntry;
171 var status = document.getElementById('status');
173 // Use this to get a file path appropriate for displaying
174 chrome.fileSystem.getDisplayPath(fileEntry, function(path) {
175 fileDisplayPath = path;
176 status.innerText = 'Exporting to '+path;
179 getTodosAsText( function(contents) {
181 fileEntry.createWriter(function(fileWriter) {
183 var truncated = false;
184 var blob = new Blob([contents]);
186 fileWriter.onwriteend = function(e) {
189 // You need to explicitly set the file size to truncate
190 // any content that might have been there before
191 this.truncate(blob.size);
194 status.innerText = 'Export to '+fileDisplayPath+' completed';
197 fileWriter.onerror = function(e) {
198 status.innerText = 'Export failed: '+e.toString();
201 fileWriter.write(blob);
208 <p><code><a href=
"/apps/fileSystem#method-getDisplayPath">chrome.fileSystem.getDisplayPath()
</a></code> gets a displayable file path that outputs to the status
<code>div
</code>.
</p>
210 <p>Use
<code>fileEntry.createWriter()
</code> to create a
<code>FileWriter
</code> object.
<code>fileWriter.write()
</code> can then write a
<a href=
"https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob
</a> to the filesystem. Use
<code>fileWriter.onwriteend()
</code> and
<code>fileWriter.onerror()
</code> to update the status
<code>div
</code>.
</p>
212 <!-- <code>fileWriter.truncate()</code> -->
214 <p>For more information about
<code>FileEntry
</code>, read
<a href=
"http://www.html5rocks.com/en/tutorials/file/filesystem/"><em>Exploring the FileSystem APIs
</em></a> on HTML5Rocks, or refer to the
<code><a href=
"https://developer.mozilla.org/en-US/docs/Web/API/FileEntry">FileEntry docs
</a></code> on MDN.
</p>
216 <h3 id=
"persistance">Persist FileEntry objects
</h3>
218 <p><strong>Advanced
</strong>:
<code>FileEntry
</code> objects cannot be persisted indefinitely. Your app needs to ask the user to choose a file every time the app is launched.
219 If your app was forced to restart due to a runtime crash or update,
220 <a href=
"/apps/fileSystem#method-restoreEntry">restoreEntry()
</a>
221 is an option to restore a
<code>FileEntry
</code>.
</p>
223 <p>If you wish, experiment by saving the ID returned by
224 <a href=
"/apps/fileSystem.html#method-retainEntry">retainEntry()
</a>
225 and restoring it on app restart. (Hint: Add a listener to the
<code>onRestarted
</code> event
226 in the background page.)
</p>
228 <h2 id=
"launch">Launch your finished Todo app
</h2>
230 <p>You are done Step
6! Reload your app and add some todos. Click
<b>Export to disk
</b> to export your todos to a .txt file.
</p>
233 <img src=
"{{static}}/images/app_codelab/step6-completed.png" alt=
"The Todo app with exported todos"/>
236 <h2 id=
"recap">For more information
</h2>
238 <p>For more detailed information about some of the APIs introduced in this step, refer to:
</p>
242 <a href=
"/apps/app_storage#filesystem" title=
"Read 'Using the Chrome Filesystem API' in the Chrome developer docs">Using the Chrome Filesystem API
</a>
243 <a href=
"#export-todos" class=
"anchor-link-icon" title=
"This feature mentioned in 'Export todos'">↑</a>
246 <a href=
"/apps/declare_permissions" title=
"Read 'Declare Permissions' in the Chrome developer docs">Declare Permissions
</a>
247 <a href=
"#update-permissions" class=
"anchor-link-icon" title=
"This feature mentioned in 'Update permissions'">↑</a>
250 <a href=
"/apps/storage#method-StorageArea-get" title=
"Read 'chrome.storage.local.get()' in the Chrome developer docs">chrome.storage.local.get()
</a>
251 <a href=
"#get-todos-as-text" class=
"anchor-link-icon" title=
"This feature mentioned in 'Get todo items as text'">↑</a>
254 <a href=
"/apps/fileSystem#method-chooseEntry" title=
"Read 'chrome.fileSystem.chooseEntry()' in the Chrome developer docs">chrome.fileSystem.chooseEntry()
</a>
255 <a href=
"#choose-file" class=
"anchor-link-icon" title=
"This feature mentioned in 'Choose a file'">↑</a>
258 <a href=
"/apps/fileSystem#method-getDisplayPath" title=
"Read 'chrome.fileSystem.getDisplayPath()' in the Chrome developer docs">chrome.fileSystem.getDisplayPath()
</a>
259 <a href=
"#use-fileentry" class=
"anchor-link-icon" title=
"This feature mentioned in 'Use FileEntry to write todos items to disk'">↑</a>
262 <a href=
"/apps/fileSystem#method-restoreEntry" title=
"Read 'chrome.fileSystem.restoreEntry()' in the Chrome developer docs">chrome.fileSystem.restoreEntry()
</a>
263 <a href=
"#persistance" class=
"anchor-link-icon" title=
"This feature mentioned in 'Persist FileEntry objects'">↑</a>
266 <a href=
"/apps/fileSystem#method-retainEntry" title=
"Read 'chrome.fileSystem.retainEntry()' in the Chrome developer docs">chrome.fileSystem.retainEntry()
</a>
267 <a href=
"#persistance" class=
"anchor-link-icon" title=
"This feature mentioned in 'Persist FileEntry objects'">↑</a>
271 <p>Ready to continue onto the next step? Go to
<a href=
"app_codelab_publish.html">Step
7 - Publish your app
»</a></p>