- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / common / extensions / docs / templates / articles / app_codelab5_data.html
1 <h1 id="lab_5_manage_data">Save and Fetch Data</h1>
2
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&#39;ve changed is lost.
7 In this section, we will save every change using
8 <a href="storage.html">chrome.storage.sync</a>.
9 </p>
10
11 <p>
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&#39;t have to handle online check and offline fallback in your application.
16 </p>
17
18 <h2 id="save_your_todos_in_the_cloud">Save your Todos in the cloud</h2>
19
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>
24
25 <h3 id="manifest">Update manifest</h3>
26
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>:
31 </p>
32
33 <pre data-filename="manifest.json">
34 {
35   ... ,
36   &quot;permissions&quot;: [&quot;storage&quot;]
37 }
38 </pre>
39
40 <h3 id="simple-controller">Update controller</h3>
41
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>.
46 </p>
47
48 <tabs data-group="source">
49
50   <header tabindex="0" data-value="angular">Angular</header>
51   <header tabindex="0" data-value="js">JavaScript</header>
52
53   <content>
54     <pre data-filename="controller.js">
55 // Notice that chrome.storage.sync.get is asynchronous
56 chrome.storage.sync.get(&#39;todolist&#39;, function(value) {
57   // The $apply is only necessary to execute the function inside Angular scope
58   $scope.$apply(function() {
59     $scope.load(value);
60   });
61 });
62
63 // If there is saved data in storage, use it. Otherwise, bootstrap with sample todos
64 $scope.load = function(value) {
65   if (value &amp;&amp; value.todolist) {
66     $scope.todos = value.todolist;
67   } else {
68     $scope.todos = [
69       {text:&#39;learn angular&#39;, done:true},
70       {text:&#39;build an angular app&#39;, done:false}];
71   }
72 }
73
74 $scope.save = function() {
75   chrome.storage.sync.set({&#39;todolist&#39;: $scope.todos});
76 };
77     </pre>
78   </content>
79   <content>
80     <pre data-filename="controller.js">
81   /**
82    * Listen to changes in the model and trigger the appropriate changes in the view
83    **/
84   model.addListener(function(model, changeType, param) {
85     if ( changeType === 'removed' || changeType === 'archived' || changeType === 'reset') {
86       redrawUI(model);
87     } else if ( changeType === 'added' ) {
88       drawTodo(model.todos[param], list);
89     } else if ( changeType === 'stateChanged') {
90       updateTodo(model.todos[param]);
91     }
92     storageSave();
93     updateCounters(model);
94   });
95
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);
101       } else {
102         model.addTodo('learn Chrome Apps', true);
103         model.addTodo('build a Chrome App', false);
104       }
105     });
106   }
107
108   var storageSave = function() {
109     chrome.storage.sync.set({'todolist': model.todos});
110   };
111     </pre>
112   </content>
113 </tabs>
114
115 <h3 id="simple-view">Update view</h3>
116
117 <p>In the view,
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.
121 In AngularJS,
122 we call <code>save()</code> explicitedly
123 but there are many ways of doing this.
124 For example,
125 you could also use <code>$watchers</code> on the scope.
126 </p>
127
128 <tabs data-group="source">
129
130   <header tabindex="0" data-value="angular">Angular</header>
131   <header tabindex="0" data-value="js">JavaScript</header>
132
133   <content>
134     <pre data-filename="index.html">
135 ...
136        [ &lt;a href=&quot;&quot; ng-click=&quot;archive() || save()&quot;&gt;archive&lt;/a&gt; ]
137 ...
138             &lt;input type=&quot;checkbox&quot; ng-model=&quot;todo.done&quot; ng-change=&quot;save()&quot;&gt;
139 ...
140        &lt;form ng-submit=&quot;addTodo() || save()&quot;&gt;
141 ...
142     </pre>
143   </content>
144   <content>
145     <pre data-filename="index.html">
146 &lt;form&gt;
147   &lt;input type="text" size="30"
148          placeholder="add new todo here"&gt;
149   &lt;input class="btn-primary" type="submit" value="add"&gt;
150 &lt;/form&gt;
151     </pre>
152   </content>
153
154 </tabs>
155
156 <h3 id="results1">Check the results</h3>
157
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.
162 </p>
163
164 <p>
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>.
170 </p>
171
172 <h2 id="handle_drag_and_dropped_files_and_urls">Handle drag-and-dropped files and URLs</h2>
173
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&#39;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>.
179 </p>
180
181 <h3 id="dd-controller">Update controller</h3>
182
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>.
187 </p>
188
189 <tabs data-group="source">
190
191   <header tabindex="0" data-value="angular">Angular</header>
192   <header tabindex="0" data-value="js">JavaScript</header>
193
194   <content>
195     <pre data-filename="controller.js">
196 var defaultDropText = &quot;Or drop files here...&quot;;
197 $scope.dropText = defaultDropText;
198
199 // on dragOver, we will change the style and text accordingly, depending on
200 // the data being transferred
201 var dragOver = function(e) {
202   e.stopPropagation();
203   e.preventDefault();
204   var valid = e.dataTransfer &amp;&amp; e.dataTransfer.types
205     &amp;&amp; ( e.dataTransfer.types.indexOf(&#39;Files&#39;) &gt;= 0
206       || e.dataTransfer.types.indexOf(&#39;text/uri-list&#39;) &gt;=0 )
207   $scope.$apply(function() {
208     $scope.dropText = valid ? &quot;Drop files and remote images and they will become Todos&quot;
209         : &quot;Can only drop files and remote images here&quot;;
210     $scope.dropClass = valid ? &quot;dragging&quot; : &quot;invalid-dragging&quot;;
211   });
212 }
213
214 // reset style and text to the default
215 var dragLeave = function(e) {
216   $scope.$apply(function() {
217     $scope.dropText = defaultDropText;
218     $scope.dropClass = &#39;&#39;;
219   });
220 }
221
222 // on drop, we create the appropriate TODOs using dropped data
223 var drop = function(e) {
224   e.preventDefault();
225   e.stopPropagation();
226
227 var newTodos=[];
228   if (e.dataTransfer.types.indexOf(&#39;Files&#39;) &gt;= 0) {
229     var files = e.dataTransfer.files;
230     for (var i = 0; i &lt; files.length; i++) {
231       var text = files[i].name+&#39;, &#39;+files[i].size+&#39; bytes&#39;;
232       newTodos.push({text:text, done:false, file: files[i]});
233     }
234   } else { // uris
235     var uri=e.dataTransfer.getData(&quot;text/uri-list&quot;);
236     newTodos.push({text:uri, done:false, uri: uri});
237   }
238
239 $scope.$apply(function() {
240     $scope.dropText = defaultDropText;
241     $scope.dropClass = &#39;&#39;;
242     for (var i = 0; i &lt; newTodos.length; i++) {
243       $scope.todos.push(newTodos[i]);
244     }
245     $scope.save();
246   });
247 }
248
249 document.body.addEventListener(&quot;dragover&quot;, dragOver, false);
250 document.body.addEventListener(&quot;dragleave&quot;, dragLeave, false);
251 document.body.addEventListener(&quot;drop&quot;, drop, false);
252     </pre>
253   </content>
254   <content>
255     <pre data-filename="controller.js">
256 var defaultDropText = "Or drop files here...";
257
258 var dropText = document.getElementById('dropText');
259 dropText.innerText = defaultDropText;
260
261 // on dragOver, we will change the style and text accordingly, depending on
262 // the data being transfered
263 var dragOver = function(e) {
264   e.stopPropagation();
265   e.preventDefault();
266   var valid = isValid(e.dataTransfer);
267   if (valid) {
268     dropText.innerText="Drop files and remote images and they will become Todos";
269     document.body.classList.add("dragging");
270   } else {
271     dropText.innerText="Can only drop files and remote images here";
272     document.body.classList.add("invalid-dragging");
273   }
274 }
275
276 var isValid = function(dataTransfer) {
277   return dataTransfer && dataTransfer.types
278     && ( dataTransfer.types.indexOf('Files') >= 0
279       || dataTransfer.types.indexOf('text/uri-list') >=0 )
280 }
281
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');
287 }
288
289 // on drop, we create the appropriate TODOs using dropped data
290 var drop = function(e, model) {
291   e.preventDefault();
292   e.stopPropagation();
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 &lt; files.length; i++) {
297         var text = files[i].name+', '+files[i].size+' bytes';
298         model.addTodo(text, false, {file: files[i]});
299       }
300     } else { // uris
301       var uri=e.dataTransfer.getData("text/uri-list");
302       model.addTodo(uri, false, {uri: uri});
303     }
304   }
305
306   dragLeave();
307 }
308
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) {
313       drop(e, model);
314     }, false);
315 }
316     </pre>
317   </content>
318
319 </tabs>
320
321 <h3 id="dd-view">Update view</h3>
322
323 <p>If using AngularJS,
324 let&#39;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&#39;s associate the body&#39;s CSS class with the Angular controller&#39;s class,
327 so we can change the class directly in the scope and have it automatically changed in the DOM:
328 </p>
329
330 <tabs data-group="source">
331
332   <header tabindex="0" data-value="angular">Angular</header>
333
334   <content>
335     <pre data-filename="index.html">
336 &lt;body ng-controller=&quot;TodoCtrl&quot; ng-class=&quot;dropClass&quot;&gt;
337 &lt;!-- remember to remove the ng-controller attribute from the div where it was before --&gt;
338     </pre>
339   </content>
340
341 </tabs>
342
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:
346 </p>
347
348 <tabs data-group="source">
349
350   <header tabindex="0" data-value="angular">Angular</header>
351   <header tabindex="0" data-value="js">JavaScript</header>
352   
353   <content>
354     <pre data-filename="index.html">
355 &lt;div&gt;
356  &#123;&#123;dropText&#125;&#125;
357 &lt;/div&gt;
358     </pre>
359   </content>
360   <content>
361     <pre data-filename="index.html">
362 &lt;div id=&quot;dropText&quot;&gt;
363 &lt;/div&gt;
364     </pre>
365   </content>
366
367 </tabs>
368
369 <h3 id="dd-css">Update stylesheet</h3>
370
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:
376 </p>
377
378 <pre data-filename="todo.css">
379 @-webkit-keyframes switch-green {
380   from { background-color: white;} to {background-color: rgb(163, 255, 163);}
381 }
382 @-webkit-keyframes switch-red {
383   from { background-color: white;} to {background-color: rgb(255, 203, 203);}
384 }
385 .dragging {
386   -webkit-animation: switch-green 0.5s ease-in-out 0 infinite alternate;
387 }
388
389 .invalid-dragging {
390   -webkit-animation: switch-red 0.5s ease-in-out 0 infinite alternate;
391 }
392 </pre>
393
394 <h3 id="results2">Check the results</h3>
395
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>
399
400 <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>.
406 </p>
407
408 <h2 id="challenge_">Challenge</h2>
409
410 <p>The current code only saves the file reference, but it doesn&#39;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 &quot;open&quot; 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>
411
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&#39;s <a href="https://github.com/ebidel/filer.js">filer.js</a>.</p>
413
414 <h2 id="takeaways_">Takeaways</h2>
415
416 <ul>
417 <li><p>Use <a href="storage.html">chrome.storage.sync</a> to save small data that you need to be sync&#39;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&#39;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>
423 </ul>
424
425 <h2 id="you_should_also_read">You should also read</h2>
426
427 <p><a href="app_storage.html">Manage Data</a> tutorial</p>
428
429 <h2 id="what_39_s_next_">What's next?</h2>
430
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>