OLD | NEW |
1 <h1 id="import-existing-app"> | 1 <h1 id="import-existing-app"> |
2 <span class="h1-step">Step 2:</span> | 2 <span class="h1-step">Step 2:</span> |
3 Import an Existing Web App | 3 Import an Existing Web App |
4 </h1> | 4 </h1> |
5 | 5 |
6 <p class="note"> | 6 <p class="note"> |
7 <strong>Want to start fresh from here?</strong> | 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_step1</strong></em>. | 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_step1</strong></em>. |
9 </p> | 9 </p> |
10 | 10 |
11 <p>In this step, you will learn:</p> | 11 <p>In this step, you will learn:</p> |
12 | 12 |
13 <ul> | 13 <ul> |
14 <li>How to adapt an existing web application for the Chrome Apps platform.</li
> | 14 <li>How to adapt an existing web application for the Chrome Apps platform.</li
> |
15 <li>How to make your app scripts Content Security Policy (CSP) compliant.</li> | 15 <li>How to make your app scripts Content Security Policy (CSP) compliant.</li> |
16 <li>How to implement local storage using the <code>chrome.storage.local</code>
API.</li> | 16 <li>How to implement local storage using the <a href="/apps/storage" title="Re
ad 'chrome.storage.local' in the Chrome developer docs">chrome.storage.local</a>
.</li> |
17 </ul> | 17 </ul> |
18 | 18 |
19 <p> | 19 <p> |
20 <em>Estimated time to complete this step: 20 minutes.</em> | 20 <em>Estimated time to complete this step: 20 minutes.</em> |
21 <br> | 21 <br> |
22 To preview what you will complete in this step, <a href="#launch">jump down to
the bottom of this page ↓</a>. | 22 To preview what you will complete in this step, <a href="#launch">jump down to
the bottom of this page ↓</a>. |
23 </p> | 23 </p> |
24 | 24 |
25 <h2 id="todomvc">Import an existing Todo app</h2> | 25 <h2 id="todomvc">Import an existing Todo app</h2> |
26 | 26 |
27 <p>As a starting point, we will import the <a href="http://todomvc.com/vanilla-e
xamples/vanillajs/">vanilla | 27 <p>As a starting point, import the <a href="http://todomvc.com/vanilla-examples/
vanillajs/">vanilla |
28 JavaScript version</a> of <a href="http://todomvc.com/">TodoMVC</a>, a common be
nchmark app, into our project.</p> | 28 JavaScript version</a> of <a href="http://todomvc.com/">TodoMVC</a>, a common be
nchmark app, into your project.</p> |
29 | 29 |
30 <p>We've included a version of the TodoMVC app in the | 30 <p>We've included a version of the TodoMVC app in the |
31 <a href="https://github.com/mangini/io13-codelab/archive/master.zip">reference c
ode zip</a> in the <strong><em>todomvc</em></strong> folder. | 31 <a href="https://github.com/mangini/io13-codelab/archive/master.zip">reference c
ode zip</a> in the <strong><em>todomvc</em></strong> folder. |
32 Copy all files (including folders) from <em>todomvc</em> into your project folde
r.</p> | 32 Copy all files (including folders) from <em>todomvc</em> into your project folde
r.</p> |
33 | 33 |
34 <figure> | 34 <figure> |
35 <img src="{{static}}/images/app_codelab/copy-todomvc.png" alt="Copy todomvc fo
lder into codelab folder"> | 35 <img src="{{static}}/images/app_codelab/copy-todomvc.png" alt="Copy todomvc fo
lder into codelab folder"> |
36 </figure> | 36 </figure> |
37 | 37 |
38 <p>You will be asked to replace <em>index.html</em>. Go ahead and accept.</p> | 38 <p>You will be asked to replace <em>index.html</em>. Go ahead and accept.</p> |
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
100 <script src="js/store.js"></script> | 100 <script src="js/store.js"></script> |
101 </pre> | 101 </pre> |
102 | 102 |
103 <p>2. Create a file in the <strong><em>js</em></strong> folder named <strong><em
>bootstrap.js</em></strong>. Move the previously inline code to be in this file:
</p> | 103 <p>2. Create a file in the <strong><em>js</em></strong> folder named <strong><em
>bootstrap.js</em></strong>. Move the previously inline code to be in this file:
</p> |
104 | 104 |
105 <pre data-filename="bootstrap.js"> | 105 <pre data-filename="bootstrap.js"> |
106 // Bootstrap app data | 106 // Bootstrap app data |
107 window.app = {}; | 107 window.app = {}; |
108 </pre> | 108 </pre> |
109 | 109 |
110 <p>You'll still have a non-working Todo app if you reload the app now but we're
getting there.</p> | 110 <p>You'll still have a non-working Todo app if you reload the app now but you're
getting closer.</p> |
111 | 111 |
112 <h2 id="convert-storage">Convert localStorage to chrome.storage.local</h2> | 112 <h2 id="convert-storage">Convert localStorage to chrome.storage.local</h2> |
113 | 113 |
114 <p>If you open the DevTools Console now, the previous error should be gone. Howe
ver there is a new error about <code>window.localStorage</code> not being availa
ble:</p> | 114 <p>If you open the DevTools Console now, the previous error should be gone. Ther
e is a new error, however, about <code>window.localStorage</code> not being avai
lable:</p> |
115 | 115 |
116 <figure> | 116 <figure> |
117 <img src="{{static}}/images/app_codelab/localStorage-console-error.png" alt="T
odo app with localStorage console log error"> | 117 <img src="{{static}}/images/app_codelab/localStorage-console-error.png" alt="T
odo app with localStorage console log error"> |
118 <!-- | 118 <!-- |
119 <blockquote> | 119 <blockquote> |
120 > Uncaught window.localStorage is not available in packaged apps. Use <br> | 120 > Uncaught window.localStorage is not available in packaged apps. Use <br> |
121 > chrome.storage.local instead. store.js:21 | 121 > chrome.storage.local instead. store.js:21 |
122 </blockquote> | 122 </blockquote> |
123 --> | 123 --> |
124 </figure> | 124 </figure> |
125 | 125 |
126 <p><a href="http://dev.w3.org/html5/webstorage/#the-localstorage-attribute"><cod
e>LocalStorage</code></a> | 126 <p>Chrome Apps do not support |
127 is not supported in Chrome Apps because <code>LocalStorage</code> is | 127 <a href="http://dev.w3.org/html5/webstorage/#the-localstorage-attribute"><code>l
ocalStorage</code></a> |
128 synchronous. Synchronous access to blocking resources (I/O) in a single threaded
| 128 as <code>localStorage</code> is synchronous. |
129 runtime could make your app become unresponsive.</p> | 129 Synchronous access to blocking resources (I/O) in a single-threaded runtime |
| 130 could make your app unresponsive.</p> |
130 | 131 |
131 <p>Chrome Apps have an equivalent API that can store objects asynchronously. | 132 <p>Chrome Apps have an equivalent API that can store objects asynchronously. |
132 This will help avoid the sometimes costly object->string->object serialization p
rocess.</p> | 133 This will help avoid the sometimes costly object->string->object serialization p
rocess.</p> |
133 | 134 |
134 <p>To address the error message in our app, we will need to convert <code>LocalS
torage</code> to | 135 <p>To address the error message in our app, you need to convert <code>localStora
ge</code> to |
135 <code>chrome.storage.local</code>.</p> | 136 <a href="/apps/storage" title="Read 'chrome.storage.local' in the Chrome develop
er docs">chrome.storage.local</a>.</p> |
136 | 137 |
137 <h3 id="update-permissions">Update app permissions</h3> | 138 <h3 id="update-permissions">Update app permissions</h3> |
138 | 139 |
139 <p>In order to use <code>chrome.storage.local</code>, we need to request the <co
de>storage</code> permission. In <strong><em>manifest.json</em></strong>, add <c
ode>"storage"</code> to the <code>permissions</code> array:</p> | 140 <p>In order to use <code>chrome.storage.local</code>, you need to request the <c
ode>storage</code> permission. In <strong><em>manifest.json</em></strong>, add <
code>"storage"</code> to the <code>permissions</code> array:</p> |
140 | 141 |
141 <pre data-filename="manifest.json"> | 142 <pre data-filename="manifest.json"> |
142 "permissions": [<b>"storage"</b>], | 143 "permissions": [<b>"storage"</b>], |
143 </pre> | 144 </pre> |
144 | 145 |
145 <h3 id="get-and-set">Learn about local.storage.set() and local.storage.get()</h3
> | 146 <h3 id="get-and-set">Learn about local.storage.set() and local.storage.get()</h3
> |
146 | 147 |
147 <p>To save and retrieve todo items, we'll need to know a bit about the <code>set
()</code> and <code>get()</code> methods of the <code>chrome.storage</code> API.
</p> | 148 <p>To save and retrieve todo items, you need to know about the <code>set()</code
> and <code>get()</code> methods of the <code>chrome.storage</code> API.</p> |
148 | 149 |
149 <p>The <code>set()</code> method accepts an object of key-value pairs as its fir
st parameter. An optional callback function is the second parameter. For example
:</p> | 150 <p>The <a href="/apps/storage#method-StorageArea-set" title="Read 'chrome.storag
e.local.set()' in the Chrome developer docs">set()</a> |
| 151 method accepts an object of key-value pairs as its first parameter. An optional
callback function is the second parameter. For example:</p> |
150 | 152 |
151 <pre> | 153 <pre> |
152 chrome.storage.local.set({secretMessage:'Psst!',timeSet:Date.now()}, function()
{ | 154 chrome.storage.local.set({secretMessage:'Psst!',timeSet:Date.now()}, function()
{ |
153 console.log("Secret message saved"); | 155 console.log("Secret message saved"); |
154 }); | 156 }); |
155 </pre> | 157 </pre> |
156 | 158 |
157 <p>The <code>get()</code> method accepts an optional first parameter for the dat
astore keys you wish to retreive. A single key can be passed as a string; multip
le keys can be arranged into an array of strings or a dictionary object.</p> | 159 <p>The <a href="/apps/storage#method-StorageArea-get" title="Read 'chrome.storag
e.local.get()' in the Chrome developer docs">get()</a> method accepts an optiona
l first parameter for the datastore keys you wish to retreive. A single key can
be passed as a string; multiple keys can be arranged into an array of strings or
a dictionary object.</p> |
158 | 160 |
159 <p>The second parameter, which is required, is a callback function. In the retur
ned object, use the keys requested in the first parameter to access the stored v
alues. For example:</p> | 161 <p>The second parameter, which is required, is a callback function. In the retur
ned object, use the keys requested in the first parameter to access the stored v
alues. For example:</p> |
160 | 162 |
161 <pre> | 163 <pre> |
162 chrome.storage.local.get(['secretMessage','timeSet'], function(data) { | 164 chrome.storage.local.get(['secretMessage','timeSet'], function(data) { |
163 console.log("The secret message:", data.secretMessage, "saved at:", data.timeS
et); | 165 console.log("The secret message:", data.secretMessage, "saved at:", data.timeS
et); |
164 }); | 166 }); |
165 </pre> | 167 </pre> |
166 | 168 |
167 <p>If you want to <code>get()</code> everything that is currently in <code>chrom
e.storage.local</code>, | 169 <p>If you want to <code>get()</code> everything that is currently in <code>chrom
e.storage.local</code>, |
168 omit the first parameter:</p> | 170 omit the first parameter:</p> |
169 | 171 |
170 <pre> | 172 <pre> |
171 chrome.storage.local.get(function(data) { | 173 chrome.storage.local.get(function(data) { |
172 console.log(data); | 174 console.log(data); |
173 }); | 175 }); |
174 </pre> | 176 </pre> |
175 | 177 |
176 <p>Unlike <code>localStorage</code>, you won't be able to inspect locally stored
items using the DevTools Resources panel. However, you can interact with <code>
chrome.storage</code> from the JavaScript Console like so:</p> | 178 <p>Unlike <code>localStorage</code>, you won't be able to inspect locally stored
items using the DevTools Resources panel. You can, however, interact with <code
>chrome.storage</code> from the JavaScript Console like so:</p> |
177 | 179 |
178 <figure> | 180 <figure> |
179 <img src="{{static}}/images/app_codelab/get-set-in-console.png" alt="Use the C
onsole to debug chrome.storage"> | 181 <img src="{{static}}/images/app_codelab/get-set-in-console.png" alt="Use the C
onsole to debug chrome.storage"> |
180 </figure> | 182 </figure> |
181 | 183 |
182 <h3 id="preview-changes">Preview required API changes</h3> | 184 <h3 id="preview-changes">Preview required API changes</h3> |
183 | 185 |
184 <p>There are many remaining steps in converting the Todo app however they are al
l small changes to | 186 <p>Most of the remaining steps in converting the Todo app are small changes |
185 the API calls. Changing all the places where <code>localStorage</code> is curren
tly being used | 187 to the API calls. Changing all the places where <code>localStorage</code> |
186 will be time-consuming and error-prone — but required.</p> | 188 is currently being used, though time-consuming and error-prone, is required.</p> |
187 | 189 |
188 <p class="note"> | 190 <p class="note"> |
189 To maximize your fun with this codelab, it'll be best if you overwrite your | 191 To maximize your fun with this codelab, it'll be best if you overwrite your |
190 <strong><em>store.js</em></strong>, <strong><em>controller.js</em></strong>, a
nd <strong><em>model.js</em></strong> | 192 <strong><em>store.js</em></strong>, <strong><em>controller.js</em></strong>, a
nd <strong><em>model.js</em></strong> |
191 with the ones from <strong><em>cheat_code/solution_for_step_2</em></strong> in
the reference code zip. | 193 with the ones from <strong><em>cheat_code/solution_for_step_2</em></strong> in
the reference code zip. |
192 <br><br> | 194 <br><br> |
193 Once you've done that, continue reading as we'll go over each of the changes i
ndividually. | 195 Once you've done that, continue reading as we'll go over each of the changes i
ndividually. |
194 </p> | 196 </p> |
195 | 197 |
196 <p>The key differences between <code>localStorage</code> and <code>chrome.storag
e</code> come from the async nature of <code>chrome.storage</code>:</p> | 198 <p>The key differences between <code>localStorage</code> and <code>chrome.storag
e</code> come from the async nature of <code>chrome.storage</code>:</p> |
197 | 199 |
198 <ul> | 200 <ul> |
199 <li> | 201 <li> |
200 Instead of writing to <code>localStorage</code> using simple assignment, we
need to use <code>chrome.storage.local.set()</code> with optional callbacks. | 202 Instead of writing to <code>localStorage</code> using simple assignment, you
need to use <code>chrome.storage.local.set()</code> with optional callbacks. |
201 <pre> | 203 <pre> |
202 var data = { todos: [] }; | 204 var data = { todos: [] }; |
203 localStorage[dbName] = JSON.stringify(data); | 205 localStorage[dbName] = JSON.stringify(data); |
204 </pre> | 206 </pre> |
205 versus | 207 versus |
206 <pre> | 208 <pre> |
207 var storage = {}; | 209 var storage = {}; |
208 storage[dbName] = { todos: [] }; | 210 storage[dbName] = { todos: [] }; |
209 chrome.storage.local.set( storage, function() { | 211 chrome.storage.local.set( storage, function() { |
210 // optional callback | 212 // optional callback |
211 }); | 213 }); |
212 </pre> | 214 </pre> |
213 </li> | 215 </li> |
214 <li> | 216 <li> |
215 Instead of accessing <code>localStorage[myStorageName]</code> directly, we n
eed to use <code>chrome.storage.local.get(myStorageName,function(storage){...})<
/code> and then parse the returned <code>storage</code> object in the callback f
unction. | 217 Instead of accessing <code>localStorage[myStorageName]</code> directly, you
need to use <code>chrome.storage.local.get(myStorageName,function(storage){...})
</code> and then parse the returned <code>storage</code> object in the callback
function. |
216 <pre> | 218 <pre> |
217 var todos = JSON.parse(localStorage[dbName]).todos; | 219 var todos = JSON.parse(localStorage[dbName]).todos; |
218 </pre> | 220 </pre> |
219 versus | 221 versus |
220 <pre> | 222 <pre> |
221 chrome.storage.local.get(dbName, function(storage) { | 223 chrome.storage.local.get(dbName, function(storage) { |
222 var todos = storage[dbName].todos; | 224 var todos = storage[dbName].todos; |
223 }); | 225 }); |
224 </pre> | 226 </pre> |
225 </li> | 227 </li> |
226 <li> | 228 <li> |
227 The use of <code>.bind(this)</code> is being used on all callbacks to ensure
<code>this</code> refers to the <code>this</code> of the <code>Store</code> pro
totype. (More info on bound functions can be found on the MDN docs: <a href="htt
ps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Fu
nction/bind">Function.prototype.bind()</a>.) | 229 The function <code>.bind(this)</code> is used on all callbacks to ensure <co
de>this</code> refers to the <code>this</code> of the <code>Store</code> prototy
pe. (More info on bound functions can be found on the MDN docs: <a href="https:/
/developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Functi
on/bind">Function.prototype.bind()</a>.) |
228 <pre> | 230 <pre> |
229 function Store() { | 231 function Store() { |
230 this.scope = 'inside Store'; | 232 this.scope = 'inside Store'; |
231 chrome.storage.local.set( {}, function() { | 233 chrome.storage.local.set( {}, function() { |
232 console.log(this.scope); // outputs: 'undefined' | 234 console.log(this.scope); // outputs: 'undefined' |
233 }); | 235 }); |
234 } | 236 } |
235 new Store(); | 237 new Store(); |
236 </pre> | 238 </pre> |
237 versus | 239 versus |
238 <pre> | 240 <pre> |
239 function Store() { | 241 function Store() { |
240 this.scope = 'inside Store'; | 242 this.scope = 'inside Store'; |
241 chrome.storage.local.set( {}, function() { | 243 chrome.storage.local.set( {}, function() { |
242 console.log(this.scope); // outputs: 'inside Store' | 244 console.log(this.scope); // outputs: 'inside Store' |
243 }<b>.bind(this)</b>); | 245 }<b>.bind(this)</b>); |
244 } | 246 } |
245 new Store(); | 247 new Store(); |
246 </pre> | 248 </pre> |
247 </li> | 249 </li> |
248 </ul> | 250 </ul> |
249 | 251 |
250 <p>Keep these key differences in mind as we go over retrieving, saving, and remo
ving todo items in the following sections.</p> | 252 <p>Keep these key differences in mind as we cover retrieving, saving, and removi
ng todo items in the following sections.</p> |
251 | 253 |
252 <h3 id="retrieve-items">Retrieve todos items</h3> | 254 <h3 id="retrieve-items">Retrieve todo items</h3> |
253 | 255 |
254 Let's update the Todo app in order to retrieve todo items: | 256 Let's update the Todo app in order to retrieve todo items: |
255 | 257 |
256 <p>1. The <code>Store</code> constructor method takes care of initializing the T
odo app with all the existing todo items from the datastore. If this is the firs
t time the app has been loaded, the datastore might not exist so the method chec
ks if the datastore exists first. If it doesn't, it'll create an empty array of
<code>todos</code> and save it to the datastore so there are no runtime read err
ors.</p> | 258 <p>1. The <code>Store</code> constructor method takes care of initializing the T
odo app with all the existing todo items from the datastore. |
| 259 The method first checks if the datastore exists. |
| 260 If it doesn't, it'll create an empty array of <code>todos</code> and save it to
the datastore so there are no runtime read errors.</p> |
257 | 261 |
258 <p>In <strong><em>js/store.js</em></strong>, convert the use of <code>localStora
ge</code> in the constructor method to instead use | 262 <p>In <strong><em>js/store.js</em></strong>, convert the use of <code>localStora
ge</code> in the constructor method to instead use |
259 <code>chrome.storage.local</code>:</p> | 263 <code>chrome.storage.local</code>:</p> |
260 | 264 |
261 <pre data-filename="store.js"> | 265 <pre data-filename="store.js"> |
262 function Store(name, callback) { | 266 function Store(name, callback) { |
263 var data; | 267 var data; |
264 var dbName; | 268 var dbName; |
265 | 269 |
266 callback = callback || function () {}; | 270 callback = callback || function () {}; |
(...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
438 chrome.storage.local.set(storage, function() { | 442 chrome.storage.local.set(storage, function() { |
439 callback.call(this, [updateData]); | 443 callback.call(this, [updateData]); |
440 }.bind(this)); | 444 }.bind(this)); |
441 } | 445 } |
442 }.bind(this)); | 446 }.bind(this)); |
443 }; | 447 }; |
444 </pre> | 448 </pre> |
445 | 449 |
446 <h3 id="complete-items">Mark todo items as complete</h3> | 450 <h3 id="complete-items">Mark todo items as complete</h3> |
447 | 451 |
448 <p>Now that we are operating on arrays, we'll need to change how we handle a use
r clicking on the <b>Clear completed (#)</b> button:</p> | 452 <p>Now that app is operating on arrays, you need to change how the app handles a
user clicking on the <b>Clear completed (#)</b> button:</p> |
449 | 453 |
450 <p>1. In <strong><em>controller.js</em></strong>, update <code>toggleAll()</code
> to call <code>toggleComplete()</code> | 454 <p>1. In <strong><em>controller.js</em></strong>, update <code>toggleAll()</code
> to call <code>toggleComplete()</code> |
451 only once with an array of todos instead of marking a todo as completed | 455 only once with an array of todos instead of marking a todo as completed |
452 one by one. Also delete the call to <code>_filter()</code> since we'll be adjust
ing <code>toggleComplete</code>'s <code>_filter()</code>.</p> | 456 one by one. Also delete the call to <code>_filter()</code> since you'll be adjus
ting |
| 457 the <code>toggleComplete</code> <code>_filter()</code>.</p> |
453 | 458 |
454 <pre data-filename="controller.js"> | 459 <pre data-filename="controller.js"> |
455 Controller.prototype.toggleAll = function (e) { | 460 Controller.prototype.toggleAll = function (e) { |
456 var completed = e.target.checked ? 1 : 0; | 461 var completed = e.target.checked ? 1 : 0; |
457 var query = 0; | 462 var query = 0; |
458 if (completed === 0) { | 463 if (completed === 0) { |
459 query = 1; | 464 query = 1; |
460 } | 465 } |
461 this.model.read({ completed: query }, function (data) { | 466 this.model.read({ completed: query }, function (data) { |
462 <b>var ids = [];</b> | 467 <b>var ids = [];</b> |
463 data.forEach(function (item) { | 468 data.forEach(function (item) { |
464 <strike>this.toggleComplete(item.id, e.target, true);</strike> | 469 <strike>this.toggleComplete(item.id, e.target, true);</strike> |
465 <b>ids.push(item.id);</b> | 470 <b>ids.push(item.id);</b> |
466 }.bind(this)); | 471 }.bind(this)); |
467 <b>this.toggleComplete(ids, e.target, false);</b> | 472 <b>this.toggleComplete(ids, e.target, false);</b> |
468 }.bind(this)); | 473 }.bind(this)); |
469 | 474 |
470 <strike>this._filter();</strike> | 475 <strike>this._filter();</strike> |
471 }; | 476 }; |
472 </pre> | 477 </pre> |
473 | 478 |
474 <p>2. Now we need to update <code>toggleComplete()</code> to accept both a singl
e todo or an array of todos. This includes moving <code>filter()</code> to be in
side the <code>update()</code>, instead of outside.</p> | 479 <p>2. Now update <code>toggleComplete()</code> to accept both a single todo or a
n array of todos. This includes moving <code>filter()</code> to be inside the <c
ode>update()</code>, instead of outside.</p> |
475 | 480 |
476 <pre data-filename="controller.js"> | 481 <pre data-filename="controller.js"> |
477 Controller.prototype.toggleComplete = function (<strike>id</strike> <b>ids</b>,
checkbox, silent) { | 482 Controller.prototype.toggleComplete = function (<strike>id</strike> <b>ids</b>,
checkbox, silent) { |
478 var completed = checkbox.checked ? 1 : 0; | 483 var completed = checkbox.checked ? 1 : 0; |
479 this.model.update(<strike>id</strike> <b>ids</b>, { completed: completed }, fu
nction () { | 484 this.model.update(<strike>id</strike> <b>ids</b>, { completed: completed }, fu
nction () { |
480 <b>if ( ids.constructor != Array ) {</b> | 485 <b>if ( ids.constructor != Array ) {</b> |
481 <b> ids = [ ids ];</b> | 486 <b> ids = [ ids ];</b> |
482 <b>}</b> | 487 <b>}</b> |
483 <b>ids.forEach( function(id) {</b> | 488 <b>ids.forEach( function(id) {</b> |
484 var listItem = $$('[data-id="' + id + '"]'); | 489 var listItem = $$('[data-id="' + id + '"]'); |
(...skipping 15 matching lines...) Expand all Loading... |
500 }<b>.bind(this)</b>); | 505 }<b>.bind(this)</b>); |
501 | 506 |
502 <strike>if (!silent) {</strike> | 507 <strike>if (!silent) {</strike> |
503 <strike> this._filter();</strike> | 508 <strike> this._filter();</strike> |
504 <strike>}</strike> | 509 <strike>}</strike> |
505 }; | 510 }; |
506 </pre> | 511 </pre> |
507 | 512 |
508 <h3 id="count-items">Count todo items</h3> | 513 <h3 id="count-items">Count todo items</h3> |
509 | 514 |
510 <p>After switching to async storage, there is a minor bug that shows up when get
ting the number of todos. We'll need to wrap the count operation in a callback f
unction:</p> | 515 <p>After switching to async storage, there is a minor bug that shows up when get
ting the number of todos. You'll need to wrap the count operation in a callback
function:</p> |
511 | 516 |
512 <p>1. In <strong><em>model.js</em></strong>, update <code>getCount()</code> to a
ccept a callback:</p> | 517 <p>1. In <strong><em>model.js</em></strong>, update <code>getCount()</code> to a
ccept a callback:</p> |
513 | 518 |
514 <pre data-filename="model.js"> | 519 <pre data-filename="model.js"> |
515 Model.prototype.getCount = function (<b>callback</b>) { | 520 Model.prototype.getCount = function (<b>callback</b>) { |
516 var todos = { | 521 var todos = { |
517 active: 0, | 522 active: 0, |
518 completed: 0, | 523 completed: 0, |
519 total: 0 | 524 total: 0 |
520 }; | 525 }; |
(...skipping 25 matching lines...) Expand all Loading... |
546 <b> </b>this.$clearCompleted.style.display = todos.completed > 0 ? 'block' :
'none'; | 551 <b> </b>this.$clearCompleted.style.display = todos.completed > 0 ? 'block' :
'none'; |
547 <b> </b> | 552 <b> </b> |
548 <b> </b>this.$toggleAll.checked = todos.completed === todos.total; | 553 <b> </b>this.$toggleAll.checked = todos.completed === todos.total; |
549 <b> </b> | 554 <b> </b> |
550 <b> </b>this._toggleFrame(todos); | 555 <b> </b>this._toggleFrame(todos); |
551 <b>}.bind(this));</b> | 556 <b>}.bind(this));</b> |
552 | 557 |
553 }; | 558 }; |
554 </pre> | 559 </pre> |
555 | 560 |
556 <p>We are almost there! If you reload the app now, you will be able to insert ne
w | 561 <p>You are almost there! If you reload the app now, you will be able to insert n
ew |
557 todos without any console errors.</p> | 562 todos without any console errors.</p> |
558 | 563 |
559 <h3 id="remove-items">Remove todos items</h3> | 564 <h3 id="remove-items">Remove todos items</h3> |
560 | 565 |
561 <p>Now that we can save todo items, we're close to being done! | 566 <p>Now that the app can save todo items, you're close to being done! |
562 However we get errors when we attempt to <em>remove</em> our todo items:</p> | 567 You still get errors when you attempt to <em>remove</em> todo items:</p> |
563 | 568 |
564 <figure> | 569 <figure> |
565 <img src="{{static}}/images/app_codelab/remove-todo-console-error.png" alt="To
do app with localStorage console log error"> | 570 <img src="{{static}}/images/app_codelab/remove-todo-console-error.png" alt="To
do app with localStorage console log error"> |
566 </figure> | 571 </figure> |
567 | 572 |
568 <p>1. In <strong><em>store.js</em></strong>, we'll need to convert all the <code
>localStorage</code> instances to use <code>chrome.storage.local</code>:</p> | 573 <p>1. In <strong><em>store.js</em></strong>, convert all the <code>localStorage<
/code> instances to use <code>chrome.storage.local</code>:</p> |
569 | 574 |
570 <p>a) To start off, wrap everything already inside <code>remove()</code> with a
<code>get()</code> callback:</p> | 575 <p>a) To start off, wrap everything already inside <code>remove()</code> with a
<code>get()</code> callback:</p> |
571 | 576 |
572 <pre data-filename="store.js"> | 577 <pre data-filename="store.js"> |
573 Store.prototype.remove = function (id, callback) { | 578 Store.prototype.remove = function (id, callback) { |
574 <b>chrome.storage.local.get(this._dbName, function(storage) {</b> | 579 <b>chrome.storage.local.get(this._dbName, function(storage) {</b> |
575 <b> </b>var data = JSON.parse(localStorage[this._dbName]); | 580 <b> </b>var data = JSON.parse(localStorage[this._dbName]); |
576 <b> </b>var todos = data.todos; | 581 <b> </b>var todos = data.todos; |
577 <b> </b> | 582 <b> </b> |
578 <b> </b>for (var i = 0; i < todos.length; i++) { | 583 <b> </b>for (var i = 0; i < todos.length; i++) { |
(...skipping 28 matching lines...) Expand all Loading... |
607 <strike>localStorage[this._dbName] = JSON.stringify(data);</strike> | 612 <strike>localStorage[this._dbName] = JSON.stringify(data);</strike> |
608 <strike>callback.call(this, JSON.parse(localStorage[this._dbName]).todos);</
strike> | 613 <strike>callback.call(this, JSON.parse(localStorage[this._dbName]).todos);</
strike> |
609 <b>chrome.storage.local.set(storage, function() {</b> | 614 <b>chrome.storage.local.set(storage, function() {</b> |
610 <b> callback.call(this, todos);</b> | 615 <b> callback.call(this, todos);</b> |
611 <b>}.bind(this));</b> | 616 <b>}.bind(this));</b> |
612 }.bind(this)); | 617 }.bind(this)); |
613 }; | 618 }; |
614 </pre> | 619 </pre> |
615 | 620 |
616 <p>2. The same Read-After-Write data hazard issue previously present in the | 621 <p>2. The same Read-After-Write data hazard issue previously present in the |
617 <code>save()</code> method is also present when removing items so we'll need | 622 <code>save()</code> method is also present when removing items so you will need |
618 to update a few more places to allow for batch operations on a list of todo IDs.
</p> | 623 to update a few more places to allow for batch operations on a list of todo IDs.
</p> |
619 | 624 |
620 <p>a) Still in <em>store.js</em>, update <code>remove()</code>:</p> | 625 <p>a) Still in <em>store.js</em>, update <code>remove()</code>:</p> |
621 | 626 |
622 <pre data-filename="store.js"> | 627 <pre data-filename="store.js"> |
623 Store.prototype.remove = function (id, callback) { | 628 Store.prototype.remove = function (id, callback) { |
624 chrome.storage.local.get(this._dbName, function(storage) { | 629 chrome.storage.local.get(this._dbName, function(storage) { |
625 var data = storage[this._dbName]; | 630 var data = storage[this._dbName]; |
626 var todos = data.todos; | 631 var todos = data.todos; |
627 | 632 |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
698 <figure> | 703 <figure> |
699 <img src="{{static}}/images/app_codelab/step2-completed.gif" alt="The finished
Todo app after Step 2"> | 704 <img src="{{static}}/images/app_codelab/step2-completed.gif" alt="The finished
Todo app after Step 2"> |
700 </figure> | 705 </figure> |
701 | 706 |
702 <p class="note"> | 707 <p class="note"> |
703 <strong>Troubleshooting</strong> | 708 <strong>Troubleshooting</strong> |
704 <br> | 709 <br> |
705 Remember to always check the DevTools Console to see if there are any error me
ssages. | 710 Remember to always check the DevTools Console to see if there are any error me
ssages. |
706 </p> | 711 </p> |
707 | 712 |
708 <h2 id="recap">Recap APIs referenced in this step</h2> | 713 <h2 id="recap">For more information</h2> |
709 | 714 |
710 <p>For more detailed information about some of the APIs introduced in this step,
refer to:</p> | 715 <p>For more detailed information about some of the APIs introduced in this step,
refer to:</p> |
711 | 716 |
712 <ul> | 717 <ul> |
713 <li> | 718 <li> |
714 <a href="/apps/contentSecurityPolicy" title="Read 'Content Security Policy'
in the Chrome developer docs">Content Security Policy</a> | 719 <a href="/apps/contentSecurityPolicy" title="Read 'Content Security Policy'
in the Chrome developer docs">Content Security Policy</a> |
715 <a href="#csp-compliance" class="anchor-link-icon" title="This feature menti
oned in 'Make scripts Content Security Policy (CSP) compliant'">↑</a> | 720 <a href="#csp-compliance" class="anchor-link-icon" title="This feature menti
oned in 'Make scripts Content Security Policy (CSP) compliant'">↑</a> |
716 </li> | 721 </li> |
717 <li> | 722 <li> |
718 <a href="/apps/declare_permissions" title="Read 'Declare Permissions' in the
Chrome developer docs">Declare Permissions</a> | 723 <a href="/apps/declare_permissions" title="Read 'Declare Permissions' in the
Chrome developer docs">Declare Permissions</a> |
719 <a href="#update-permissions" class="anchor-link-icon" title="This feature m
entioned in 'Update app permissions'">↑</a> | 724 <a href="#update-permissions" class="anchor-link-icon" title="This feature m
entioned in 'Update app permissions'">↑</a> |
720 </li> | 725 </li> |
721 <li> | 726 <li> |
722 <a href="/apps/storage" title="Read 'chrome.storage' in the Chrome developer
docs">chrome.storage</a> | 727 <a href="/apps/storage" title="Read 'chrome.storage' in the Chrome developer
docs">chrome.storage</a> |
723 <a href="#get-and-set" class="anchor-link-icon" title="This feature mentione
d in 'Learn about local.storage.set() and local.storage.get()'">↑</a> | 728 <a href="#get-and-set" class="anchor-link-icon" title="This feature mentione
d in 'Learn about local.storage.set() and local.storage.get()'">↑</a> |
724 </li> | 729 </li> |
725 <li> | 730 <li> |
726 <a href="/apps/storage#method-StorageArea-get" title="Read 'chrome.storage.l
ocal.get()' in the Chrome developer docs">chrome.storage.local.get()</a> | 731 <a href="/apps/storage#method-StorageArea-get" title="Read 'chrome.storage.l
ocal.get()' in the Chrome developer docs">chrome.storage.local.get()</a> |
727 <a href="#get-and-set" class="anchor-link-icon" title="This feature mentione
d in 'Learn about local.storage.set() and local.storage.get()'">↑</a> | |
728 <a href="#retrieve-items" class="anchor-link-icon" title="This feature menti
oned in 'Retrieve todos items'">↑</a> | 732 <a href="#retrieve-items" class="anchor-link-icon" title="This feature menti
oned in 'Retrieve todos items'">↑</a> |
729 </li> | 733 </li> |
730 <li> | 734 <li> |
731 <a href="/apps/storage#method-StorageArea-set" title="Read 'chrome.storage.l
ocal.set()' in the Chrome developer docs">chrome.storage.local.set()</a> | 735 <a href="/apps/storage#method-StorageArea-set" title="Read 'chrome.storage.l
ocal.set()' in the Chrome developer docs">chrome.storage.local.set()</a> |
732 <a href="/apps/storage#method-StorageArea-get" title="Read 'chrome.storage.l
ocal.get()' in the Chrome developer docs">chrome.storage.local.get()</a> | |
733 <a href="#save-items" class="anchor-link-icon" title="This feature mentioned
in 'Save todos items'">↑</a> | 736 <a href="#save-items" class="anchor-link-icon" title="This feature mentioned
in 'Save todos items'">↑</a> |
734 </li> | 737 </li> |
735 <li> | 738 <li> |
736 <a href="/apps/storage#method-StorageArea-remove" title="Read 'chrome.storag
e.local.remove()' in the Chrome developer docs">chrome.storage.local.remove()</a
> | 739 <a href="/apps/storage#method-StorageArea-remove" title="Read 'chrome.storag
e.local.remove()' in the Chrome developer docs">chrome.storage.local.remove()</a
> |
737 <a href="#remove-items" class="anchor-link-icon" title="This feature mention
ed in 'Remove todos items'">↑</a> | 740 <a href="#remove-items" class="anchor-link-icon" title="This feature mention
ed in 'Remove todos items'">↑</a> |
738 </li> | 741 </li> |
739 <li> | 742 <li> |
740 <a href="/apps/storage#method-StorageArea-remove" title="Read 'chrome.storag
e.local.clear()' in the Chrome developer docs">chrome.storage.local.clear()</a> | 743 <a href="/apps/storage#method-StorageArea-remove" title="Read 'chrome.storag
e.local.clear()' in the Chrome developer docs">chrome.storage.local.clear()</a> |
741 <a href="#remove-items" class="anchor-link-icon" title="This feature mention
ed in 'Drop all todo items'">↑</a> | 744 <a href="#remove-items" class="anchor-link-icon" title="This feature mention
ed in 'Drop all todo items'">↑</a> |
742 </li> | 745 </li> |
743 </ul> | 746 </ul> |
744 | 747 |
745 <p>Ready to continue onto the next step? Go to <a href="app_codelab_alarms.html"
>Step 3 - Add alarms and notifications »</a></p> | 748 <p>Ready to continue onto the next step? Go to <a href="app_codelab_alarms.html"
>Step 3 - Add alarms and notifications »</a></p> |
OLD | NEW |