1 <h1 id=
"lab_5_manage_data">Save and Fetch Data
</h1>
3 <p>The
<a href=
"app_codelab3_mvc.html">sample from Lab
3</a>
4 uses a static array of Todos.
5 Every time your app restarts,
6 whatever you
've changed is lost.
7 In this section, we will save every change using
8 <a href=
"storage.html">chrome.storage.sync
</a>.
12 This lets you store
<em>small things
</em> that automatically sync to the cloud
13 if you are online and logged in to Chrome.
14 If you are offline or unlogged, it saves locally and transparently:
15 you don
't have to handle online check and offline fallback in your application.
18 <h2 id=
"save_your_todos_in_the_cloud">Save your Todos in the cloud
</h2>
20 <p class=
"note"><b>Note:
</b>
21 Chrome Sync Storage is not intended to be used as a generic database.
22 There are several restrictions on the amount of information you can save,
23 so it is more appropriate to save settings and other small chunks of data.
</p>
25 <h3 id=
"manifest">Update manifest
</h3>
27 <p>Request permission to use storage in your manifest.
28 Permissions are the same in the
29 <a href=
"https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab5_data/angularjs/1_storage_sync/manifest.json">AngularJS manifest.json
</a> and
30 <a href=
"https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab5_data/javascript/1_storage_sync/manifest.json">JavaScript manifest.json
</a>:
33 <pre data-filename=
"manifest.json">
36 "permissions
": [
"storage
"]
40 <h3 id=
"simple-controller">Update controller
</h3>
42 <p>Change your controller to get the Todo list
43 from syncable storage instead of a static list:
44 <a href=
"https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab5_data/angularjs/1_storage_sync/controller.js">AngularJS controller.js
</a> or
45 <a href=
"https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab5_data/javascript/1_storage_sync/controller.js">JavaScript controller.js
</a>.
48 <tabs data-group=
"source">
50 <header tabindex=
"0" data-value=
"angular">Angular
</header>
51 <header tabindex=
"0" data-value=
"js">JavaScript
</header>
54 <pre data-filename=
"controller.js">
55 // Notice that chrome.storage.sync.get is asynchronous
56 chrome.storage.sync.get(
'todolist
', function(value) {
57 // The $apply is only necessary to execute the function inside Angular scope
58 $scope.$apply(function() {
63 // If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
64 $scope.load = function(value) {
65 if (value
&& value.todolist) {
66 $scope.todos = value.todolist;
69 {text:
'learn angular
', done:true},
70 {text:
'build an angular app
', done:false}];
74 $scope.save = function() {
75 chrome.storage.sync.set({
'todolist
': $scope.todos});
80 <pre data-filename=
"controller.js">
82 * Listen to changes in the model and trigger the appropriate changes in the view
84 model.addListener(function(model, changeType, param) {
85 if ( changeType === 'removed' || changeType === 'archived' || changeType === 'reset') {
87 } else if ( changeType === 'added' ) {
88 drawTodo(model.todos[param], list);
89 } else if ( changeType === 'stateChanged') {
90 updateTodo(model.todos[param]);
93 updateCounters(model);
96 // If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
97 var storageLoad = function() {
98 chrome.storage.sync.get('todolist', function(value) {
99 if (value && value.todolist) {
100 model.setTodos(value.todolist);
102 model.addTodo('learn Chrome Apps', true);
103 model.addTodo('build a Chrome App', false);
108 var storageSave = function() {
109 chrome.storage.sync.set({'todolist': model.todos});
115 <h3 id=
"simple-view">Update view
</h3>
118 <a href=
"https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab5_data/angularjs/1_storage_sync/index.html">AngularJs index.hmtl
</a> or
119 <a href=
"https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab5_data/javascript/1_storage_sync/index.html">JavaScript index.html
</a>,
120 save the data whenever it changes.
122 we call
<code>save()
</code> explicitedly
123 but there are many ways of doing this.
125 you could also use
<code>$watchers
</code> on the scope.
128 <tabs data-group=
"source">
130 <header tabindex=
"0" data-value=
"angular">Angular
</header>
131 <header tabindex=
"0" data-value=
"js">JavaScript
</header>
134 <pre data-filename=
"index.html">
136 [
<a href=
"" ng-click=
"archive() || save()
">archive
</a
> ]
138 <input type=
"checkbox
" ng-model=
"todo.done
" ng-change=
"save()
">
140 <form ng-submit=
"addTodo() || save()
">
145 <pre data-filename=
"index.html">
147 <input
type=
"text" size=
"30"
148 placeholder=
"add new todo here">
149 <input
class=
"btn-primary" type=
"submit" value=
"add">
156 <h3 id=
"results1">Check the results
</h3>
158 <p>Check the results by reloading the app:
159 open the app, right-click and select Reload App.
160 You can now add Todo items, close the app,
161 and the new items will still be there when you reopen the app.
165 If you get stuck and want to see the app in action,
166 go to
<code>chrome://extensions
</code>,
167 load the unpacked app, and launch the app from a new tab:
168 <a href=
"https://github.com/GoogleChrome/chrome-app-codelab/tree/master/lab5_data/angularjs/1_storage_sync">Angular JS
1_storage_sync
</a> or
169 <a href=
"https://github.com/GoogleChrome/chrome-app-codelab/tree/master/lab5_data/javascript/1_storage_sync">JavaScript
1_storage_sync
</a>.
172 <h2 id=
"handle_drag_and_dropped_files_and_urls">Handle drag-and-dropped files and URLs
</h2>
174 <p>Suppose you want to create Todos associated with local files and/or URLs.
175 The natural way of doing this is to accept dropped items.
176 It
's simple enough to add drag-and-drop support
177 in a Chrome App using the standard
178 <a href=
"http://www.html5rocks.com/en/tutorials/dnd/basics/">HTML5 Drag-and-Drop API
</a>.
181 <h3 id=
"dd-controller">Update controller
</h3>
183 <p>In the controller,
184 add code to handle the events of dragover, dragleave, and drop:
185 <a href=
"https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab5_data/angularjs/2_drop_files/controller.js">AngularJS controller.js
</a> or
186 <a href=
"https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab5_data/javascript/2_drop_files/controller.js">JavaScript controller.js
</a>.
189 <tabs data-group=
"source">
191 <header tabindex=
"0" data-value=
"angular">Angular
</header>
192 <header tabindex=
"0" data-value=
"js">JavaScript
</header>
195 <pre data-filename=
"controller.js">
196 var defaultDropText =
"Or drop files here...
";
197 $scope.dropText = defaultDropText;
199 // on dragOver, we will change the style and text accordingly, depending on
200 // the data being transferred
201 var dragOver = function(e) {
204 var valid = e.dataTransfer
&& e.dataTransfer.types
205 && ( e.dataTransfer.types.indexOf(
'Files
')
>=
0
206 || e.dataTransfer.types.indexOf(
'text/uri-list
')
>=
0 )
207 $scope.$apply(function() {
208 $scope.dropText = valid ?
"Drop files and remote images and they will become Todos
"
209 :
"Can only drop files and remote images here
";
210 $scope.dropClass = valid ?
"dragging
" :
"invalid-dragging
";
214 // reset style and text to the default
215 var dragLeave = function(e) {
216 $scope.$apply(function() {
217 $scope.dropText = defaultDropText;
218 $scope.dropClass =
'';
222 // on drop, we create the appropriate TODOs using dropped data
223 var drop = function(e) {
228 if (e.dataTransfer.types.indexOf(
'Files
')
>=
0) {
229 var files = e.dataTransfer.files;
230 for (var i =
0; i
< files.length; i++) {
231 var text = files[i].name+
',
'+files[i].size+
' bytes
';
232 newTodos.push({text:text, done:false, file: files[i]});
235 var uri=e.dataTransfer.getData(
"text/uri-list
");
236 newTodos.push({text:uri, done:false, uri: uri});
239 $scope.$apply(function() {
240 $scope.dropText = defaultDropText;
241 $scope.dropClass =
'';
242 for (var i =
0; i
< newTodos.length; i++) {
243 $scope.todos.push(newTodos[i]);
249 document.body.addEventListener(
"dragover
", dragOver, false);
250 document.body.addEventListener(
"dragleave
", dragLeave, false);
251 document.body.addEventListener(
"drop
", drop, false);
255 <pre data-filename=
"controller.js">
256 var defaultDropText =
"Or drop files here...";
258 var dropText = document.getElementById('dropText');
259 dropText.innerText = defaultDropText;
261 // on dragOver, we will change the style and text accordingly, depending on
262 // the data being transfered
263 var dragOver = function(e) {
266 var valid = isValid(e.dataTransfer);
268 dropText.
innerText=
"Drop files and remote images and they will become Todos";
269 document.body.classList.add(
"dragging");
271 dropText.
innerText=
"Can only drop files and remote images here";
272 document.body.classList.add(
"invalid-dragging");
276 var isValid = function(dataTransfer) {
277 return dataTransfer && dataTransfer.types
278 && ( dataTransfer.types.indexOf('Files')
>=
0
279 || dataTransfer.types.indexOf('text/uri-list')
>=
0 )
282 // reset style and text to the default
283 var dragLeave = function(e) {
284 dropText.innerText=defaultDropText;
285 document.body.classList.remove('dragging');
286 document.body.classList.remove('invalid-dragging');
289 // on drop, we create the appropriate TODOs using dropped data
290 var drop = function(e, model) {
293 if (isValid(e.dataTransfer)) {
294 if (e.dataTransfer.types.indexOf('Files')
>=
0) {
295 var files = e.dataTransfer.files;
296 for (var i =
0; i
< files.length; i++) {
297 var text = files[i].name+', '+files[i].size+' bytes';
298 model.addTodo(text, false, {file: files[i]});
301 var uri=e.dataTransfer.getData(
"text/uri-list");
302 model.addTodo(uri, false, {uri: uri});
309 exports.setDragHandlers = function(model) {
310 document.body.addEventListener(
"dragover", dragOver, false);
311 document.body.addEventListener(
"dragleave", dragLeave, false);
312 document.body.addEventListener(
"drop", function(e) {
321 <h3 id=
"dd-view">Update view
</h3>
323 <p>If using AngularJS,
324 let
's move the Angular scope definition from the div to the body in the
325 <a href=
"https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab5_data/angularjs/2_drop_files/index.html">AngularJS index.html
</a> file to make all the area of the window accept the drop event and still work on the same scope.
326 Also, let
's associate the body
's CSS class with the Angular controller
's class,
327 so we can change the class directly in the scope and have it automatically changed in the DOM:
330 <tabs data-group=
"source">
332 <header tabindex=
"0" data-value=
"angular">Angular
</header>
335 <pre data-filename=
"index.html">
336 <body ng-controller=
"TodoCtrl
" ng-class=
"dropClass
">
337 <!-- remember to remove the ng-controller attribute from the div where it was before --
>
343 <p>Add a message placeholder
344 (in
<a href=
"https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab5_data/angularjs/2_drop_files/index.html">AngularJS index.html
</a> or
345 <a href=
"https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab5_data/javascript/2_drop_files/index.html">JavaScript index.html
</a>) to warn the user that some types of dragging are not allowed:
348 <tabs data-group=
"source">
350 <header tabindex=
"0" data-value=
"angular">Angular
</header>
351 <header tabindex=
"0" data-value=
"js">JavaScript
</header>
354 <pre data-filename=
"index.html">
356 {{dropText
}}
361 <pre data-filename=
"index.html">
362 <div id=
"dropText
">
369 <h3 id=
"dd-css">Update stylesheet
</h3>
371 <p>Add appropriate styling for the
<code>dragging
</code> and
372 <code>invalid-dragging
</code> CSS classes in the css.
373 <a href=
"https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab5_data/angularjs/2_drop_files/todo.css">AngularJS todo.css
</a> and
374 <a href=
"https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab5_data/javascript/2_drop_files/todo.css">JavaScript todo.css
</a> are the same.
375 Here we used a green or red background color animation:
378 <pre data-filename=
"todo.css">
379 @-webkit-keyframes switch-green {
380 from { background-color: white;} to {background-color: rgb(
163,
255,
163);}
382 @-webkit-keyframes switch-red {
383 from { background-color: white;} to {background-color: rgb(
255,
203,
203);}
386 -webkit-animation: switch-green
0.5s ease-in-out
0 infinite alternate;
390 -webkit-animation: switch-red
0.5s ease-in-out
0 infinite alternate;
394 <h3 id=
"results2">Check the results
</h3>
396 <p>Check the results by reloading the app:
397 open the app, right-click and select Reload App.
398 You can now drag files into the Todo list.
</p>
401 If you get stuck and want to see the app in action,
402 go to
<code>chrome://extensions
</code>, load the unpacked app,
403 and launch the app from a new tab:
404 <a href=
"https://github.com/GoogleChrome/chrome-app-codelab/tree/master/lab5_data/angularjs/2_drop_files">AngularJS
2_drop_files
</a>
405 or
<a href=
"https://github.com/GoogleChrome/chrome-app-codelab/tree/master/lab5_data/javascript/2_drop_files">JavaScript
2_drop_files
</a>.
408 <h2 id=
"challenge_">Challenge
</h2>
410 <p>The current code only saves the file reference, but it doesn
't open the file. Using the
<a href=
"http://www.html5rocks.com/en/tutorials/file/filesystem/">HTML5 Filesystem API
</a>, save the file contents in a sandboxed filesystem. When the Todo item is archived, remove the corresponding file from the sandboxed filesystem. Add an
"open
" link on each Todo that has an associated file. When the item is clicked and the file exists in the sandboxed filesystem, use the Chrome App
<a href=
"fileSystem.html">Filesystem API
</a> to request a writable FileEntry from the user. Save the file data from the sandboxed filesystem into that entry.
</p>
412 <p class=
"note"><b>Tip:
</b> managing file entries using the raw HTML5 Filesystem API is not trivial. You might want to use a wrapper library, like Eric Bidelman
's
<a href=
"https://github.com/ebidel/filer.js">filer.js
</a>.
</p>
414 <h2 id=
"takeaways_">Takeaways
</h2>
417 <li><p>Use
<a href=
"storage.html">chrome.storage.sync
</a> to save small data that you need to be sync
'ed among devices, like configuration options, application state, etc. The sync is automatic, as long as the same user is logged into Chrome on all devices.
</p></li>
418 <li><p>Chrome Apps support almost all HTML5 APIs, such as drag and drop.
419 HTML Filesystem API is also supported, with extra features from the Chrome App
's
420 <a href=
"fileSystem.html">Filesystem API
</a>,
421 like asking the user to pick files on her local disk for read and write.
422 The vanilla HTML5 Filesystem API only allows access to a sandboxed filesystem.
</p></li>
425 <h2 id=
"you_should_also_read">You should also read
</h2>
427 <p><a href=
"app_storage.html">Manage Data
</a> tutorial
</p>
429 <h2 id=
"what_39_s_next_">What's next?
</h2>
431 <p>In
<a href=
"app_codelab6_lifecycle.html">5 - Manage App Lifecycle
</a>,
432 you will learn the basics of the Chrome App lifecycle.
</p>