OLD | NEW |
(Empty) | |
| 1 <h1 id="export-to-filesystem"> |
| 2 <span class="h1-step">Step 6:</span> |
| 3 Export Todos to the Filesystem |
| 4 </h1> |
| 5 |
| 6 <p class="note"> |
| 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>. |
| 9 </p> |
| 10 |
| 11 <p>In this step, you will learn:</p> |
| 12 |
| 13 <ul> |
| 14 <li>How to get a reference to a file in the external filesystem.</li> |
| 15 <li>How to write to the filesystem.</li> |
| 16 </ul> |
| 17 |
| 18 <p> |
| 19 <em>Estimated time to complete this step: 20 minutes.</em> |
| 20 <br> |
| 21 To preview what you will complete in this step, <a href="#launch">jump down to
the bottom of this page ↓</a>. |
| 22 </p> |
| 23 |
| 24 <h2 id="export-todos">Export todos</h2> |
| 25 |
| 26 <p>In this step, we will add an export button to the app. When clicked, the curr
ent |
| 27 todo items will be saved to a text file selected by the user. If the file |
| 28 exists, it will be replaced. Otherwise, a new file will be created.</p> |
| 29 |
| 30 <h3 id="update-permissions">Update permissions</h3> |
| 31 |
| 32 <p>File system permissions can be requested as a string for read-only access, or
an Object with additional properties. For example:</p> |
| 33 |
| 34 <pre> |
| 35 // Read only |
| 36 "permissions": [<b>"fileSystem"</b>] |
| 37 |
| 38 // Read and write |
| 39 "permissions": [<b>{"fileSystem": ["write"]}</b>] |
| 40 |
| 41 // Read, write, autocomplate previous input, and select folder directories inste
ad of files |
| 42 "permissions": [<b>{"fileSystem": ["write", "retainEntries", "directory"]}</b>] |
| 43 </pre> |
| 44 |
| 45 <p>For our purposes, we need read and write access. In <strong><em>manifest.json
</em></strong>, request the <code>{fileSystem: [ "write" ] }</code> permission:
</p> |
| 46 |
| 47 <pre data-filename="manifest.json"> |
| 48 "permissions": ["storage", "alarms", "notifications", "webview", |
| 49 "<all_urls>"<b>, { "fileSystem": ["write"] }</b> ], |
| 50 </pre> |
| 51 |
| 52 |
| 53 <h3 id="update-html-view">Update HTML view</h3> |
| 54 |
| 55 <p>In <strong><em>index.html</em></strong>, add an <b>Export to disk</b> button
and |
| 56 a <code>div</code> where we will show a status message:</p> |
| 57 |
| 58 <pre data-filename="index.html"> |
| 59 <footer id="info"> |
| 60 <button id="toggleAlarm">Activate alarm</button> |
| 61 <b><button id="exportToDisk">Export to disk</button></b> |
| 62 <b><div id="status"></div></b> |
| 63 ... |
| 64 </footer> |
| 65 </pre> |
| 66 |
| 67 <p>Also in <em>index.html</em>, load the <em>export.js</em> script we will creat
e next:</p> |
| 68 |
| 69 <pre data-filename="index.html"> |
| 70 ... |
| 71 <script src="js/alarms.js"></script> |
| 72 <b><script src="js/export.js"></script></b> |
| 73 </pre> |
| 74 |
| 75 <h3 id="create-js">Create export script</h3> |
| 76 |
| 77 <p>Create a new JavaScript file named <strong><em>export.js</em></strong> using
the code below. Save it in the <strong><em>js</em></strong> |
| 78 folder.</p> |
| 79 |
| 80 <pre data-filename="export.js"> |
| 81 (function() { |
| 82 |
| 83 var dbName = 'todos-vanillajs'; |
| 84 |
| 85 var savedFileEntry, fileDisplayPath; |
| 86 |
| 87 function getTodosAsText(callback) { |
| 88 } |
| 89 |
| 90 function exportToFileEntry(fileEntry) { |
| 91 } |
| 92 |
| 93 function doExportToDisk() { |
| 94 } |
| 95 |
| 96 document.getElementById('exportToDisk').addEventListener('click', doExportToDi
sk); |
| 97 |
| 98 })(); |
| 99 </pre> |
| 100 |
| 101 <p>Right now, <em>export.js</em> only contains a click listener on the <strong>E
xport to disk</strong> button and stubs for <code>getTodosAsText()</code>, <code
>exportToFileEntry</code>, and <code>doExportToDisk()</code>.</p> |
| 102 |
| 103 <h3 id="get-todos-as-text">Get todo items as text</h3> |
| 104 |
| 105 <p>Update <code>getTodosAsText()</code> so that it reads todos from <code>chrome
.storage.local</code> and generates a textual representation of them:</p> |
| 106 |
| 107 <pre data-filename="export.js"> |
| 108 function getTodosAsText(callback) { |
| 109 <b> chrome.storage.local.get(dbName, function(storedData) { |
| 110 var text = ''; |
| 111 |
| 112 if ( storedData[dbName].todos ) { |
| 113 storedData[dbName].todos.forEach(function(todo) { |
| 114 text += '- '; |
| 115 if ( todo.completed ) { |
| 116 text += '[DONE] '; |
| 117 } |
| 118 text += todo.title; |
| 119 text += '\n'; |
| 120 }, ''); |
| 121 } |
| 122 |
| 123 callback(text); |
| 124 |
| 125 }.bind(this));</b> |
| 126 } |
| 127 </pre> |
| 128 |
| 129 <h3 id="choose-file">Choose a file</h3> |
| 130 |
| 131 <p>Update <code>doExportToDisk()</code> with <code><a href="/apps/fileSystem#met
hod-chooseEntry">chrome.fileSystem.chooseEntry()</a></code> to allow the user to
choose a file:</p> |
| 132 |
| 133 <pre data-filename="export.js"> |
| 134 function doExportToDisk() { |
| 135 |
| 136 <b> if (savedFileEntry) { |
| 137 |
| 138 exportToFileEntry(savedFileEntry); |
| 139 |
| 140 } else { |
| 141 |
| 142 chrome.fileSystem.chooseEntry( { |
| 143 type: 'saveFile', |
| 144 suggestedName: 'todos.txt', |
| 145 accepts: [ { description: 'Text files (*.txt)', |
| 146 extensions: ['txt']} ], |
| 147 acceptsAllTypes: true |
| 148 }, exportToFileEntry); |
| 149 |
| 150 }</b> |
| 151 } |
| 152 </pre> |
| 153 |
| 154 <p>The first parameter of <code>chrome.fileSystem.chooseEntry()</code> is an obj
ect of options. The second parameter is a callback method.</p> |
| 155 |
| 156 <p>If we already have a saved <code>FileEntry</code>, we'll use that instead whe
n we call <code>exportToFileEntry()</code>. |
| 157 File references will exist for the lifetime of the object representing the <code
>FileEntry</code>. |
| 158 In our example, we tied <code>FileEntry</code> to the app window so the JavaScri
pt code can write to the |
| 159 selected file without any user interaction as long as the app window remains ope
n.</p> |
| 160 |
| 161 <h3 id="use-fileentry">Use FileEntry to write todos items to disk</h3> |
| 162 |
| 163 <p>Update <code>exportToFileEntry()</code> to save the todos as text via the <co
de>FileEntry</code> Web API:</p> |
| 164 |
| 165 <pre data-filename="export.js"> |
| 166 function exportToFileEntry(fileEntry) { |
| 167 <b> savedFileEntry = fileEntry; |
| 168 |
| 169 var status = document.getElementById('status'); |
| 170 |
| 171 // Use this to get a file path appropriate for displaying |
| 172 chrome.fileSystem.getDisplayPath(fileEntry, function(path) { |
| 173 fileDisplayPath = path; |
| 174 status.innerText = 'Exporting to '+path; |
| 175 }); |
| 176 |
| 177 getTodosAsText( function(contents) { |
| 178 |
| 179 fileEntry.createWriter(function(fileWriter) { |
| 180 |
| 181 var truncated = false; |
| 182 var blob = new Blob([contents]); |
| 183 |
| 184 fileWriter.onwriteend = function(e) { |
| 185 if (!truncated) { |
| 186 truncated = true; |
| 187 // You need to explicitly set the file size to truncate |
| 188 // any content that might have been there before |
| 189 this.truncate(blob.size); |
| 190 return; |
| 191 } |
| 192 status.innerText = 'Export to '+fileDisplayPath+' completed'; |
| 193 }; |
| 194 |
| 195 fileWriter.onerror = function(e) { |
| 196 status.innerText = 'Export failed: '+e.toString(); |
| 197 }; |
| 198 |
| 199 fileWriter.write(blob); |
| 200 |
| 201 }); |
| 202 });</b> |
| 203 } |
| 204 </pre> |
| 205 |
| 206 <p><code><a href="/apps/fileSystem#method-getDisplayPath">chrome.fileSystem.getD
isplayPath()</a></code> is used to get a displayable file path that we'll output
to the status <code>div</code>.</p> |
| 207 |
| 208 <p>Use <code>fileEntry.createWriter()</code> to create a <code>FileWriter</code>
object. <code>fileWriter.write()</code> can then write a <a href="https://devel
oper.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 t
he status <code>div</code>.</p> |
| 209 |
| 210 <!-- <code>fileWriter.truncate()</code> --> |
| 211 |
| 212 <p>For more information about <code>FileEntry</code>, read <a href="http://www.h
tml5rocks.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> |
| 213 |
| 214 <h3 id="persistance">Persist FileEntry objects</h3> |
| 215 |
| 216 <p><strong>Advanced</strong>: <code>FileEntry</code> objects cannot be persisted
indefinitely. This means |
| 217 that your app will need to ask the user to choose a file every time the app is |
| 218 launched. However, <a href="/apps/fileSystem#method-restoreEntry">restoreEntry()
</a> is an |
| 219 option to |
| 220 restore a <code>FileEntry</code> if your app was forced to restart due to a runt
ime crash or update.</p> |
| 221 |
| 222 <p>If you wish, experiment by saving the ID returned by |
| 223 <a href="/apps/fileSystem.html#method-retainEntry">retainEntry()</a> |
| 224 and restoring it on app restart. (Hint: Add a listener to the <code>onRestarted<
/code> event |
| 225 in the background page.)</p> |
| 226 |
| 227 <h2 id="launch">Launch your finished Todo app</h2> |
| 228 |
| 229 <p>You are done Step 6! Reload your app and add some todos. Click <b>Export to d
isk</b> to export your todos to a .txt file.</p> |
| 230 |
| 231 <figure> |
| 232 <img src="{{static}}/images/app_codelab/step6-completed.png" alt="The Todo app
with exported todos"/> |
| 233 </figure> |
| 234 |
| 235 <h2 id="recap">Recap APIs referenced in this step</h2> |
| 236 |
| 237 <p>For more detailed information about some of the APIs introduced in this step,
refer to:</p> |
| 238 |
| 239 <ul> |
| 240 <li> |
| 241 <a href="/apps/app_storage#filesystem" title="Read 'Using the Chrome Filesys
tem API' in the Chrome developer docs">Using the Chrome Filesystem API</a> |
| 242 <a href="#export-todos" class="anchor-link-icon" title="This feature mention
ed in 'Export todos'">↑</a> |
| 243 </li> |
| 244 <li> |
| 245 <a href="/apps/declare_permissions" title="Read 'Declare Permissions' in the
Chrome developer docs">Declare Permissions</a> |
| 246 <a href="#update-permissions" class="anchor-link-icon" title="This feature m
entioned in 'Update permissions'">↑</a> |
| 247 </li> |
| 248 <li> |
| 249 <a href="/apps/storage#method-StorageArea-get" title="Read 'chrome.storage.l
ocal.get()' in the Chrome developer docs">chrome.storage.local.get()</a> |
| 250 <a href="#get-todos-as-text" class="anchor-link-icon" title="This feature me
ntioned in 'Get todo items as text'">↑</a> |
| 251 </li> |
| 252 <li> |
| 253 <a href="/apps/fileSystem#method-chooseEntry" title="Read 'chrome.fileSystem
.chooseEntry()' in the Chrome developer docs">chrome.fileSystem.chooseEntry()</a
> |
| 254 <a href="#choose-file" class="anchor-link-icon" title="This feature mentione
d in 'Choose a file'">↑</a> |
| 255 </li> |
| 256 <li> |
| 257 <a href="/apps/fileSystem#method-getDisplayPath" title="Read 'chrome.fileSys
tem.getDisplayPath()' in the Chrome developer docs">chrome.fileSystem.getDisplay
Path()</a> |
| 258 <a href="#use-fileentry" class="anchor-link-icon" title="This feature mentio
ned in 'Use FileEntry to write todos items to disk'">↑</a> |
| 259 </li> |
| 260 <li> |
| 261 <a href="/apps/fileSystem#method-restoreEntry" title="Read 'chrome.fileSyste
m.restoreEntry()' in the Chrome developer docs">chrome.fileSystem.restoreEntry()
</a> |
| 262 <a href="#persistance" class="anchor-link-icon" title="This feature mentione
d in 'Persist FileEntry objects'">↑</a> |
| 263 </li> |
| 264 <li> |
| 265 <a href="/apps/fileSystem#method-retainEntry" title="Read 'chrome.fileSystem
.retainEntry()' in the Chrome developer docs">chrome.fileSystem.retainEntry()</a
> |
| 266 <a href="#persistance" class="anchor-link-icon" title="This feature mentione
d in 'Persist FileEntry objects'">↑</a> |
| 267 </li> |
| 268 </ul> |
| 269 |
| 270 <p>Ready to continue onto the next step? Go to <a href="app_codelab_publish.html
">Step 7 - Publish your app »</a></p> |
OLD | NEW |