- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / common / extensions / docs / templates / articles / app_codelab3_mvc.html
1 <h1 id="lab_3_model_view_controller">Create MVC</h1>
2
3 <p>Whenever your application grows beyond a single script with a few dozen lines,
4 it gets harder and harder to manage without a good separation
5 of roles among app components.
6 One of the most common models for structuring a complex application,
7 no matter what language,
8 is the Model-View-Controller (MVC) and its variants,
9 like Model-View-Presentation (MVP).</p>
10
11 <p>There are several frameworks to help apply
12 <a href="app_frameworks.html">MVC concepts</a>
13 to a Javascript application, and most of them,
14 as long as they are CSP compliant, can be used in a Chrome App.
15 In this lab,
16 we'll add an MVC model using both pure JavaScript and
17 the <a href="http://angularjs.org/">AngularJS</a> framework.
18 Most of the AngularJS code from this section was copied,
19 with small changes, from the AngularJS Todo tutorial.</p>
20
21 <p class="note"><b>Note:</b>
22 Chrome Apps don&#39;t enforce any specific framework or programming style.
23 </p>
24
25 <h2 id="simple">Create a simple view</h2>
26
27 <h3 id="basic-mvc">Add MVC basics</h3>
28
29 <p>If using AngularJS, download the
30 <a href="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js">Angular script</a>
31 and save it as
32 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab3_mvc/angularjs/simpleview/angular.min.js">angular.min.js</a>.</p>
33
34 <p>If using JavaScript,
35 you will need to add a very simple controller with basic MVC functionalities:
36 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab3_mvc/javascript/simpleview/controller.js">JavaScript controller.js</a></p>
37
38 <h3 id="update-view">Update view</h3>
39
40 <p>Change the <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab3_mvc/angularjs/simpleview/index.html">AngularJS index.html</a> or
41 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab3_mvc/javascript/simpleview/index.html">JavaScript index.html</a> to use a simple sample:
42 </p>
43
44 <tabs data-group="source">
45
46   <header tabindex="0" data-value="angular">Angular</header>
47   <header tabindex="0" data-value="js">JavaScript</header>
48
49   <content>
50     <pre data-filename="index.html">
51 &lt;!doctype html&gt;
52 &lt;html ng-app ng-csp&gt;
53   &lt;head&gt;
54     &lt;script src=&quot;angular.min.js&quot;&gt;&lt;/script&gt;
55     &lt;link rel="stylesheet" href="todo.css"&gt;
56   &lt;/head&gt;
57   &lt;body&gt;
58     &lt;h2&gt;Todo&lt;/h2&gt;
59     &lt;div&gt;
60       &lt;ul&gt;
61         &lt;li&gt;
62           &#123;&#123;todoText&#125;&#125;
63         &lt;/li&gt;
64       &lt;/ul&gt;
65       &lt;input type=&quot;text&quot; ng-model=&quot;todoText&quot;  size="30"
66              placeholder=&quot;type your todo here&quot;&gt;
67     &lt;/div&gt;
68   &lt;/body&gt;
69 &lt;/html&gt;
70     </pre>
71   </content>
72   <content>
73     <pre data-filename="index.html">
74 &lt;!doctype html&gt;
75 &lt;html&gt;
76   &lt;head&gt;
77     &lt;link rel=&quot;stylesheet&quot; href=&quot;todo.css&quot;&gt;
78   &lt;/head&gt;
79   &lt;body&gt;
80     &lt;h2&gt;Todo&lt;/h2&gt;
81     &lt;div&gt;
82       &lt;ul&gt;
83         &lt;li id=&quot;todoText&quot;&gt;
84         &lt;/li&gt;
85       &lt;/ul&gt;
86       &lt;input type=&quot;text&quot; id=&quot;newTodo&quot; size=&quot;30&quot;
87              placeholder=&quot;type your todo here&quot;&gt;
88     &lt;/div&gt;
89     &lt;script src=&quot;controller.js&quot;&gt;&lt;/script&gt;
90   &lt;/body&gt;
91 &lt;/html&gt;
92     </pre>
93   </content>
94 </tabs>
95
96 <p class="note"><b>Note:</b>  The <code>ng-csp</code> directive tells Angular to run in a &quot;content security mode&quot;. You don&#39;t need this directive when using Angular v1.1.0+. We&#39;ve included it here so that the sample works regardless of the Angular version in use.</p>
97
98 <h3 id="stylesheet">Add stylesheet</h3>
99
100 <p><a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab3_mvc/angularjs/simpleview/todo.css">AngularJS todo.css</a> and
101 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab3_mvc/javascript/simpleview/todo.css">JavaScript todo.css</a> are the same:
102 </p>
103
104 <pre data-filename="todo.css">
105 body {
106   font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
107 }
108
109 ul {
110   list-style: none;
111 }
112
113 button, input[type=submit] {
114   background-color: #0074CC;
115   background-image: linear-gradient(top, #08C, #05C);
116   border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
117   text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
118   color: white;
119 }
120
121 .done-true {
122   text-decoration: line-through;
123   color: grey;
124 }
125 </pre>
126
127 <h3 id="check1">Check the results</h3>
128
129 <p>
130 Check the results by reloading the app: open the app, right-click and select Reload App.</li>
131 </p>
132
133 <h2 id="real-todo">Create real Todo list</h2>
134
135 <p>
136 The previous sample, although interesting, is not exactly useful.
137 Let&#39;s transform it into a real Todo list, instead of a simple Todo item.
138 </p>
139
140 <h3 id="controller">Add controller</h3>
141
142 <p>
143 Whether using pure JavaScript or AngularJS,
144 the controller manages the Todo list:
145 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab3_mvc/angularjs/withcontroller/controller.js">AngularJS controller.js</a> or
146 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab3_mvc/javascript/withcontroller/controller.js">JavaScript controller.js</a>.
147 </p>
148
149 <tabs data-group="source">
150
151   <header tabindex="0" data-value="angular">Angular</header>
152   <header tabindex="0" data-value="js">JavaScript</header>
153
154   <content>
155     <pre data-filename="controller.js">
156 function TodoCtrl($scope) {
157   $scope.todos = [
158     {text:&#39;learn angular&#39;, done:true},
159     {text:&#39;build an angular Chrome packaged app&#39;, done:false}];
160
161 $scope.addTodo = function() {
162     $scope.todos.push({text:$scope.todoText, done:false});
163     $scope.todoText = &#39;&#39;;
164   };
165
166 $scope.remaining = function() {
167     var count = 0;
168     angular.forEach($scope.todos, function(todo) {
169       count += todo.done ? 0 : 1;
170     });
171     return count;
172   };
173
174 $scope.archive = function() {
175     var oldTodos = $scope.todos;
176     $scope.todos = [];
177     angular.forEach(oldTodos, function(todo) {
178       if (!todo.done) $scope.todos.push(todo);
179     });
180   };
181 }
182     </pre>
183   </content>
184   <content>
185     <pre data-filename="controller.js">
186 (function(exports) {
187
188   var nextId = 1;
189
190   var TodoModel = function() {
191     this.todos = {};
192     this.listeners = [];
193   }
194
195   TodoModel.prototype.clearTodos = function() {
196     this.todos = {};
197     this.notifyListeners('removed');
198   }
199
200   TodoModel.prototype.archiveDone = function() {
201     var oldTodos = this.todos;
202     this.todos={};
203     for (var id in oldTodos) {
204       if ( ! oldTodos[id].isDone ) {
205         this.todos[id] = oldTodos[id];
206       }
207     }
208     this.notifyListeners('archived');
209   }
210
211   TodoModel.prototype.setTodoState = function(id, isDone) {
212     if ( this.todos[id].isDone != isDone ) {
213       this.todos[id].isDone = isDone;
214       this.notifyListeners('stateChanged', id);
215     }
216   }
217
218   TodoModel.prototype.addTodo = function(text, isDone) {
219     var id = nextId++;
220     this.todos[id]={'id': id, 'text': text, 'isDone': isDone};
221     this.notifyListeners('added', id);
222   }
223
224   TodoModel.prototype.addListener = function(listener) {
225     this.listeners.push(listener);
226   }
227
228   TodoModel.prototype.notifyListeners = function(change, param) {
229     var this_ = this;
230     this.listeners.forEach(function(listener) {
231       listener(this_, change, param);
232     });
233   }
234
235   exports.TodoModel = TodoModel;
236
237 })(window);
238
239
240 window.addEventListener('DOMContentLoaded', function() {
241
242   var model = new TodoModel();
243   var form = document.querySelector('form');
244   var archive = document.getElementById('archive');
245   var list = document.getElementById('list');
246   var todoTemplate = document.querySelector('#templates &gt; [data-name="list"]');
247
248   form.addEventListener('submit', function(e) {
249     var textEl = e.target.querySelector('input[type="text"]');
250     model.addTodo(textEl.value, false);
251     textEl.value=null;
252     e.preventDefault();
253   });
254
255   archive.addEventListener('click', function(e) {
256     model.archiveDone();
257     e.preventDefault();
258   });
259
260   model.addListener(function(model, changeType, param) {
261     if ( changeType === 'removed' || changeType === 'archived') {
262       redrawUI(model);
263     } else if ( changeType === 'added' ) {
264       drawTodo(model.todos[param], list);
265     } else if ( changeType === 'stateChanged') {
266       updateTodo(model.todos[param]);
267     }
268     updateCounters(model);
269   });
270
271   var redrawUI = function(model) {
272     list.innerHTML='';
273     for (var id in model.todos) {
274       drawTodo(model.todos[id], list);
275     }
276   };
277   
278   var drawTodo = function(todoObj, container) {
279     var el = todoTemplate.cloneNode(true);
280     el.setAttribute('data-id', todoObj.id);
281     container.appendChild(el);
282     updateTodo(todoObj);
283     var checkbox = el.querySelector('input[type="checkbox"]');
284     checkbox.addEventListener('change', function(e) {
285       model.setTodoState(todoObj.id, e.target.checked);
286     });
287   }
288
289   var updateTodo = function(model) {
290     var todoElement = list.querySelector('li[data-id="'+model.id+'"]');
291     if (todoElement) {
292       var checkbox = todoElement.querySelector('input[type="checkbox"]');
293       var desc = todoElement.querySelector('span');
294       checkbox.checked = model.isDone;
295       desc.innerText = model.text;
296       desc.className = "done-"+model.isDone;
297     }
298   }
299
300   var updateCounters = function(model) {
301     var count = 0;
302     var notDone = 0;
303     for (var id in model.todos) {
304       count++;
305       if ( ! model.todos[id].isDone ) {
306         notDone ++;
307       }
308     }
309     document.getElementById('remaining').innerText = notDone;
310     document.getElementById('length').innerText = count;
311   }
312
313   updateCounters(model);
314
315 });
316     </pre>
317   </content>
318 </tabs>
319
320 <h3 id="index">Update view</h3>
321
322 <p>Change the <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab3_mvc/angularjs/withcontroller/index.html">AngularJS index.html</a> or
323 <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab3_mvc/javascript/withcontroller/index.html">JavaScript index.html</a>:
324 </p>
325
326 <tabs data-group="source">
327
328   <header tabindex="0" data-value="angular">Angular</header>
329   <header tabindex="0" data-value="js">JavaScript</header>
330
331   <content>
332     <pre data-filename="index.html">
333 &lt;html ng-app ng-csp&gt;
334   &lt;head&gt;
335     &lt;script src=&quot;angular.min.js&quot;&gt;&lt;/script&gt;
336     &lt;script src=&quot;controller.js&quot;&gt;&lt;/script&gt;
337     &lt;link rel=&quot;stylesheet&quot; href=&quot;todo.css&quot;&gt;
338   &lt;/head&gt;
339   &lt;body&gt;
340     &lt;h2&gt;Todo&lt;/h2&gt;
341     &lt;div ng-controller=&quot;TodoCtrl&quot;&gt;
342       &lt;span&gt;&#123;&#123;remaining()&#125;&#125; of &#123;&#123;todos.length&#125;&#125; remaining&lt;/span&gt;
343       [ &lt;a href=&quot;&quot; ng-click=&quot;archive()&quot;&gt;archive&lt;/a&gt; ]
344       &lt;ul&gt;
345         &lt;li ng-repeat=&quot;todo in todos&quot;&gt;
346           &lt;input type=&quot;checkbox&quot; ng-model=&quot;todo.done&quot;&gt;
347           &lt;span class=&quot;done-&#123;&#123;todo.done&#125;&#125;&quot;&gt;&#123;&#123;todo.text&#125;&#125;&lt;/span&gt;
348         &lt;/li&gt;
349       &lt;/ul&gt;
350       &lt;form ng-submit=&quot;addTodo()&quot;&gt;
351         &lt;input type=&quot;text&quot; ng-model=&quot;todoText&quot; size=&quot;30&quot;
352                placeholder=&quot;add new todo here&quot;&gt;
353         &lt;input class=&quot;btn-primary&quot; type=&quot;submit&quot; value=&quot;add&quot;&gt;
354       &lt;/form&gt;
355     &lt;/div&gt;
356   &lt;/body&gt;
357 &lt;/html&gt;
358     </pre>
359   </content>
360   <content>
361     <pre data-filename="index.html">
362 &lt;!doctype html&gt;
363 &lt;html&gt;
364   &lt;head&gt;
365     &lt;link rel=&quot;stylesheet&quot; href=&quot;todo.css&quot;&gt;
366   &lt;/head&gt;
367   &lt;body&gt;
368     &lt;h2&gt;Todo&lt;/h2&gt;
369     &lt;div&gt;
370       &lt;span&gt;&lt;span id=&quot;remaining&quot;&gt;&lt;/span&gt; of &lt;span id=&quot;length&quot;&gt;&lt;/span&gt; remaining&lt;/span&gt;
371       [ &lt;a href=&quot;&quot; id=&quot;archive&quot;&gt;archive&lt;/a&gt; ]
372       &lt;ul class=&quot;unstyled&quot; id=&quot;list&quot;&gt;
373       &lt;/ul&gt;
374       &lt;form&gt;
375         &lt;input type=&quot;text&quot; size=&quot;30&quot;
376                placeholder=&quot;add new todo here&quot;&gt;
377         &lt;input class=&quot;btn-primary&quot; type=&quot;submit&quot; value=&quot;add&quot;&gt;
378       &lt;/form&gt;
379     &lt;/div&gt;
380
381     &lt;!-- poor man's template --&gt;
382     &lt;div id=&quot;templates&quot; style=&quot;display: none;&quot;&gt;
383       &lt;li data-name=&quot;list&quot;&gt;
384         &lt;input type=&quot;checkbox&quot;&gt;
385         &lt;span&gt;&lt;/span&gt;
386       &lt;/li&gt;
387     &lt;/div&gt;
388
389     &lt;script src=&quot;controller.js&quot;&gt;&lt;/script&gt;
390   &lt;/body&gt;
391 &lt;/html&gt;
392     </pre>
393   </content>
394 </tabs>
395
396 <p>Note how the data, stored in an array inside the controller, binds to the view and is automatically updated when it is changed by the controller.</p>
397
398 <h3 id="check2">Check the results</h3>
399
400 <p>
401 Check the results by reloading the app: open the app, right-click and select Reload App.</li>
402 </p>
403
404 <h2 id="takeaways_">Takeaways</h2>
405
406 <ul>
407 <li><p>Chrome Apps are
408 <a href="offline_apps.html">offline first</a>,
409 so the recommended way to include third-party scripts is to download
410 and package them inside your app.</p></li>
411 <li><p>You can use any framework you want,
412 as long as it complies with Content Security Policies
413 and other restrictions that Chrome Apps are enforced to follow.</p></li>
414 <li><p>MVC frameworks make your life easier.
415 Use them, specially if you want to build a non-trivial application.</p></li>
416 </ul>
417
418 <h2 id="you_should_also_read">You should also read</h2>
419
420 <ul>
421 <li><p><a href="angular_framework.html">Build Apps with AngularJS</a> tutorial</p></li>
422 <li><p><a href="http://angularjs.org/">AngularJS Todo</a> tutorial</p></li>
423 </ul>
424
425 <h2 id="what_39_s_next_">What's next?</h2>
426
427 <p>In <a href="app_codelab5_data.html">4 - Save and Fetch Data</a>,
428 you will modify your Todo list app so that Todo items are saved.</p>