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>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 us
er. |
| 28 If the file exists, it's replaced. Otherwise, a new file gets 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>You need read and write access. |
| 46 In <em>manifest.json</em>, request the <code>{fileSystem: [ "write" ] }</code>
permission:</p> |
| 47 |
| 48 <pre data-filename="manifest.json"> |
| 49 "permissions": ["storage", "alarms", "notifications", "webview", |
| 50 "<all_urls>"<b>, { "fileSystem": ["write"] }</b> ], |
| 51 </pre> |
| 52 |
| 53 |
| 54 <h3 id="update-html-view">Update HTML view</h3> |
| 55 |
| 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> |
| 58 |
| 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> |
| 64 ... |
| 65 </footer> |
| 66 </pre> |
| 67 |
| 68 <p>Also in <em>index.html</em>, load the <em>export.js</em> script:</p> |
| 69 |
| 70 <pre data-filename="index.html"> |
| 71 ... |
| 72 <script src="js/alarms.js"></script> |
| 73 <b><script src="js/export.js"></script></b> |
| 74 </pre> |
| 75 |
| 76 <h3 id="create-js">Create export script</h3> |
| 77 |
| 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> |
| 80 |
| 81 <pre data-filename="export.js"> |
| 82 (function() { |
| 83 |
| 84 var dbName = 'todos-vanillajs'; |
| 85 |
| 86 var savedFileEntry, fileDisplayPath; |
| 87 |
| 88 function getTodosAsText(callback) { |
| 89 } |
| 90 |
| 91 function exportToFileEntry(fileEntry) { |
| 92 } |
| 93 |
| 94 function doExportToDisk() { |
| 95 } |
| 96 |
| 97 document.getElementById('exportToDisk').addEventListener('click', doExportToDi
sk); |
| 98 |
| 99 })(); |
| 100 </pre> |
| 101 |
| 102 <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> |
| 103 |
| 104 <h3 id="get-todos-as-text">Get todo items as text</h3> |
| 105 |
| 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> |
| 107 |
| 108 <pre data-filename="export.js"> |
| 109 function getTodosAsText(callback) { |
| 110 <b> chrome.storage.local.get(dbName, function(storedData) { |
| 111 var text = ''; |
| 112 |
| 113 if ( storedData[dbName].todos ) { |
| 114 storedData[dbName].todos.forEach(function(todo) { |
| 115 text += '- '; |
| 116 if ( todo.completed ) { |
| 117 text += '[DONE] '; |
| 118 } |
| 119 text += todo.title; |
| 120 text += '\n'; |
| 121 }, ''); |
| 122 } |
| 123 |
| 124 callback(text); |
| 125 |
| 126 }.bind(this));</b> |
| 127 } |
| 128 </pre> |
| 129 |
| 130 <h3 id="choose-file">Choose a file</h3> |
| 131 |
| 132 <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> |
| 133 |
| 134 <pre data-filename="export.js"> |
| 135 function doExportToDisk() { |
| 136 |
| 137 <b> if (savedFileEntry) { |
| 138 |
| 139 exportToFileEntry(savedFileEntry); |
| 140 |
| 141 } else { |
| 142 |
| 143 chrome.fileSystem.chooseEntry( { |
| 144 type: 'saveFile', |
| 145 suggestedName: 'todos.txt', |
| 146 accepts: [ { description: 'Text files (*.txt)', |
| 147 extensions: ['txt']} ], |
| 148 acceptsAllTypes: true |
| 149 }, exportToFileEntry); |
| 150 |
| 151 }</b> |
| 152 } |
| 153 </pre> |
| 154 |
| 155 <p>The first parameter of <code>chrome.fileSystem.chooseEntry()</code> is an obj
ect of options. The second parameter is a callback method.</p> |
| 156 |
| 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>File
Entry</code>. |
| 160 This example ties <code>FileEntry</code> to the app window so the JavaScript cod
e can write |
| 161 to the selected file without any user interaction as long as the app window rema
ins open.</p> |
| 162 |
| 163 <h3 id="use-fileentry">Use FileEntry to write todos items to disk</h3> |
| 164 |
| 165 <p>Update <code>exportToFileEntry()</code> to save the todos as text via the <co
de>FileEntry</code> Web API:</p> |
| 166 |
| 167 <pre data-filename="export.js"> |
| 168 function exportToFileEntry(fileEntry) { |
| 169 <b> savedFileEntry = fileEntry; |
| 170 |
| 171 var status = document.getElementById('status'); |
| 172 |
| 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; |
| 177 }); |
| 178 |
| 179 getTodosAsText( function(contents) { |
| 180 |
| 181 fileEntry.createWriter(function(fileWriter) { |
| 182 |
| 183 var truncated = false; |
| 184 var blob = new Blob([contents]); |
| 185 |
| 186 fileWriter.onwriteend = function(e) { |
| 187 if (!truncated) { |
| 188 truncated = true; |
| 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); |
| 192 return; |
| 193 } |
| 194 status.innerText = 'Export to '+fileDisplayPath+' completed'; |
| 195 }; |
| 196 |
| 197 fileWriter.onerror = function(e) { |
| 198 status.innerText = 'Export failed: '+e.toString(); |
| 199 }; |
| 200 |
| 201 fileWriter.write(blob); |
| 202 |
| 203 }); |
| 204 });</b> |
| 205 } |
| 206 </pre> |
| 207 |
| 208 <p><code><a href="/apps/fileSystem#method-getDisplayPath">chrome.fileSystem.getD
isplayPath()</a></code> gets a displayable file path that outputs to the status
<code>div</code>.</p> |
| 209 |
| 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://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> |
| 211 |
| 212 <!-- <code>fileWriter.truncate()</code> --> |
| 213 |
| 214 <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> |
| 215 |
| 216 <h3 id="persistance">Persist FileEntry objects</h3> |
| 217 |
| 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 ap
p 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> |
| 222 |
| 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> |
| 227 |
| 228 <h2 id="launch">Launch your finished Todo app</h2> |
| 229 |
| 230 <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> |
| 231 |
| 232 <figure> |
| 233 <img src="{{static}}/images/app_codelab/step6-completed.png" alt="The Todo app
with exported todos"/> |
| 234 </figure> |
| 235 |
| 236 <h2 id="recap">For more information</h2> |
| 237 |
| 238 <p>For more detailed information about some of the APIs introduced in this step,
refer to:</p> |
| 239 |
| 240 <ul> |
| 241 <li> |
| 242 <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> |
| 243 <a href="#export-todos" class="anchor-link-icon" title="This feature mention
ed in 'Export todos'">↑</a> |
| 244 </li> |
| 245 <li> |
| 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 m
entioned in 'Update permissions'">↑</a> |
| 248 </li> |
| 249 <li> |
| 250 <a href="/apps/storage#method-StorageArea-get" title="Read 'chrome.storage.l
ocal.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 me
ntioned in 'Get todo items as text'">↑</a> |
| 252 </li> |
| 253 <li> |
| 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 mentione
d in 'Choose a file'">↑</a> |
| 256 </li> |
| 257 <li> |
| 258 <a href="/apps/fileSystem#method-getDisplayPath" title="Read 'chrome.fileSys
tem.getDisplayPath()' in the Chrome developer docs">chrome.fileSystem.getDisplay
Path()</a> |
| 259 <a href="#use-fileentry" class="anchor-link-icon" title="This feature mentio
ned in 'Use FileEntry to write todos items to disk'">↑</a> |
| 260 </li> |
| 261 <li> |
| 262 <a href="/apps/fileSystem#method-restoreEntry" title="Read 'chrome.fileSyste
m.restoreEntry()' in the Chrome developer docs">chrome.fileSystem.restoreEntry()
</a> |
| 263 <a href="#persistance" class="anchor-link-icon" title="This feature mentione
d in 'Persist FileEntry objects'">↑</a> |
| 264 </li> |
| 265 <li> |
| 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 mentione
d in 'Persist FileEntry objects'">↑</a> |
| 268 </li> |
| 269 </ul> |
| 270 |
| 271 <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 |