| OLD | NEW |
| 1 <h1 id="lab_3_model_view_controller">Lab 3 - Model-View-Controller</h1> | 1 <h1 id="lab_3_model_view_controller">Create MVC</h1> |
| 2 | 2 |
| 3 <p>Whenever your application grows beyond a single script with a few dozen lines
, it gets | 3 <p>Whenever your application grows beyond a single script with a few dozen lines
, |
| 4 harder and harder to manage without a good separation of roles among app compone
nts. One of the most common | 4 it gets harder and harder to manage without a good separation |
| 5 models for structuring a complex application, no matter what language, is the Mo
del-View-Controller (MVC) and | 5 of roles among app components. |
| 6 its variants, like Model-View-Presentation (MVP).</p> | 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> |
| 7 | 10 |
| 8 <p>There are several frameworks to help apply <a href="http://developer.chrome.c
om/trunk/apps/app_frameworks.html">MVC concepts</a> to a Javascript application,
and most of them, | 11 <p>There are several frameworks to help apply |
| 9 as long as they are CSP compliant, can be used in a Chrome App. We will use the
<a href="http://angularjs.org/">AngularJS</a> framework in parts of this tutoria
l, with a special focus on the framework in this section.</p> | 12 <a href="http://developer.chrome.com/trunk/apps/app_frameworks.html">MVC concept
s</a> |
| 13 to a Javascript application, and most of them, |
| 14 as long as they are CSP compliant, can be used in a Chrome packaged 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> |
| 10 | 20 |
| 11 <h2 id="you_should_also_read">You should also read</h2> | 21 <p class="note"><b>Note:</b> |
| 22 Chrome packaged apps don't enforce any specific framework or programming sty
le. |
| 23 </p> |
| 12 | 24 |
| 13 <ul> | 25 <h2 id="simple">Create a simple view</h2> |
| 14 <li><p><a href="http://developer.chrome.com/trunk/apps/angular_framework.html">B
uild Apps with AngularJS</a> tutorial</p></li> | |
| 15 <li><p><a href="http://angularjs.org/">AngularJS Todo</a> tutorial</p></li> | |
| 16 </ul> | |
| 17 | 26 |
| 18 <p class="note"><b>Note:</b> Chrome apps don't enforce any specific framewo
rk or programming style. This section and additional parts of this tutorial use
the AngularJS framework. Most of the code from this section was copied, with sma
ll changes, from the AngularJS Todo tutorial. </p> | 27 <h3 id="basic-mvc">Add MVC basics</h3> |
| 19 | 28 |
| 20 <h2 id="create_a_simple_view_using_angularjs">Create a simple view using Angular
JS</h2> | 29 <p>If using AngularJS, download the |
| 30 <a href="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js">An
gular 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> |
| 21 | 33 |
| 22 <ol> | 34 <p>If using JavaScript, |
| 23 <li><p>Download the <a href="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.
2/angular.min.js">Angular script</a> and save it as <a href="https://github.com/
GoogleChrome/chrome-app-codelab/blob/master/lab3_mvc/angularjs/simpleview/angula
r.min.js">angular.min.js</a>.</p></li> | 35 you will need to add a very simple controller with basic MVC functionalities: |
| 24 <li><p>Change your <a href="https://github.com/GoogleChrome/chrome-app-codelab/b
lob/master/lab3_mvc/angularjs/simpleview/index.html">index.html</a> to use a sim
ple Angular sample:</p> | 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/m
aster/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 sam
ple: |
| 42 </p> |
| 25 | 43 |
| 26 <tabs data-group="source"> | 44 <tabs data-group="source"> |
| 27 | 45 |
| 46 <header tabindex="0" data-value="angular">Angular</header> |
| 28 <header tabindex="0" data-value="js">JavaScript</header> | 47 <header tabindex="0" data-value="js">JavaScript</header> |
| 29 <header tabindex="0" data-value="angular">Angular</header> | |
| 30 | 48 |
| 31 <content> | 49 <content> |
| 32 <pre> | 50 <pre> |
| 33 This is where the pure javascript code comes in | 51 <!doctype html> |
| 34 </pre> | |
| 35 </content> | |
| 36 <content> | |
| 37 <pre> | |
| 38 <html ng-app ng-csp> | 52 <html ng-app ng-csp> |
| 39 <head> | 53 <head> |
| 40 <script src="angular.min.js"></script> | 54 <script src="angular.min.js"></script> |
| 41 <link rel="stylesheet" href="todo.css"> | 55 <link rel="stylesheet" href="todo.css"> |
| 42 </head> | 56 </head> |
| 43 <body> | 57 <body> |
| 44 <h2>Todo</h2> | 58 <h2>Todo</h2> |
| 45 <div> | 59 <div> |
| 46 <ul> | 60 <ul> |
| 47 <li> | 61 <li> |
| 48 {{todoText}} | 62 {{todoText}} |
| 49 </li> | 63 </li> |
| 50 </ul> | 64 </ul> |
| 51 <input type="text" ng-model="todoText" size="
30" | 65 <input type="text" ng-model="todoText" size="30" |
| 52 placeholder="type your todo here"> | 66 placeholder="type your todo here"> |
| 53 </div> | 67 </div> |
| 54 </body> | 68 </body> |
| 55 </html> | 69 </html> |
| 56 </pre> | 70 </pre> |
| 57 </content> | 71 </content> |
| 58 | 72 <content> |
| 73 <pre> |
| 74 <!doctype html> |
| 75 <html> |
| 76 <head> |
| 77 <link rel="stylesheet" href="todo.css"> |
| 78 </head> |
| 79 <body> |
| 80 <h2>Todo</h2> |
| 81 <div> |
| 82 <ul> |
| 83 <li id="todoText"> |
| 84 </li> |
| 85 </ul> |
| 86 <input type="text" id="newTodo" size="30" |
| 87 placeholder="type your todo here"> |
| 88 </div> |
| 89 <script src="controller.js"></script> |
| 90 </body> |
| 91 </html> |
| 92 </pre> |
| 93 </content> |
| 59 </tabs> | 94 </tabs> |
| 60 | 95 |
| 61 </li> | 96 <p class="note"><b>Note:</b> The <code>ng-csp</code> directive tells Angular to
run in a "content security mode". You don't need this directive w
hen using Angular v1.1.0+. We've included it here so that the sample works r
egardless of the Angular version in use.</p> |
| 62 <li><p>Add a simple stylesheet: <a href="https://github.com/GoogleChrome/chrome-
app-codelab/blob/master/lab3_mvc/angularjs/simpleview/todo.css">todo.css</a> | 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 |
| 63 <pre> | 104 <pre> |
| 64 body { | 105 body { |
| 65 font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; | 106 font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; |
| 66 } | 107 } |
| 67 | 108 |
| 68 ul { | 109 ul { |
| 69 list-style: none; | 110 list-style: none; |
| 70 } | 111 } |
| 71 | 112 |
| 72 button, input[type=submit] { | 113 button, input[type=submit] { |
| 73 background-color: #0074CC; | 114 background-color: #0074CC; |
| 74 background-image: linear-gradient(top, #08C, #05C); | 115 background-image: linear-gradient(top, #08C, #05C); |
| 75 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); | 116 border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); |
| 76 text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); | 117 text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); |
| 77 color: white; | 118 color: white; |
| 78 } | 119 } |
| 79 | 120 |
| 80 .done-true { | 121 .done-true { |
| 81 text-decoration: line-through; | 122 text-decoration: line-through; |
| 82 color: grey; | 123 color: grey; |
| 83 } | 124 } |
| 84 </pre></li><li>Check the results by reloading the app: open the app, right-click
and select Reload App.</li> | 125 </pre> |
| 85 </ol> | |
| 86 | 126 |
| 87 <p class="note"><b>Note:</b> The ng-csp directive tells Angular to run in a &qu
ot;content security mode". You don't need this directive when using Ang
ular v1.1.0+. We've included it here so that the sample works regardless of
the Angular version in use.</p> | 127 <h3 id="check1">Check the results</h3> |
| 88 | 128 |
| 89 <h2 id="add_a_controller">Add a Controller</h2> | 129 <p> |
| 130 Check the results by reloading the app: open the app, right-click and select Rel
oad App.</li> |
| 131 </p> |
| 90 | 132 |
| 91 The previous sample, although interesting, is not exactly useful. Let's tran
sform it into a real Todo list, instead of a simple Todo item. We will create a
controller (controller.js) and make some small changes in the index.html: | 133 <h2 id="real-todo">Create real Todo list</h2> |
| 92 | 134 |
| 93 <ol> | 135 <p> |
| 94 <li>Add the <a href="https://github.com/GoogleChrome/chrome-app-codelab/blob/mas
ter/lab3_mvc/angularjs/withcontroller/controller.js">controller.js</a> file: | 136 The previous sample, although interesting, is not exactly useful. |
| 95 <pre> | 137 Let'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> |
| 96 function TodoCtrl($scope) { | 156 function TodoCtrl($scope) { |
| 97 $scope.todos = [ | 157 $scope.todos = [ |
| 98 {text:'learn angular', done:true}, | 158 {text:'learn angular', done:true}, |
| 99 {text:'build an angular Chrome packaged app', done:false}]; | 159 {text:'build an angular Chrome packaged app', done:false}]; |
| 100 | 160 |
| 101 $scope.addTodo = function() { | 161 $scope.addTodo = function() { |
| 102 $scope.todos.push({text:$scope.todoText, done:false}); | 162 $scope.todos.push({text:$scope.todoText, done:false}); |
| 103 $scope.todoText = ''; | 163 $scope.todoText = ''; |
| 104 }; | 164 }; |
| 105 | 165 |
| 106 $scope.remaining = function() { | 166 $scope.remaining = function() { |
| 107 var count = 0; | 167 var count = 0; |
| 108 angular.forEach($scope.todos, function(todo) { | 168 angular.forEach($scope.todos, function(todo) { |
| 109 count += todo.done ? 0 : 1; | 169 count += todo.done ? 0 : 1; |
| 110 }); | 170 }); |
| 111 return count; | 171 return count; |
| 112 }; | 172 }; |
| 113 | 173 |
| 114 $scope.archive = function() { | 174 $scope.archive = function() { |
| 115 var oldTodos = $scope.todos; | 175 var oldTodos = $scope.todos; |
| 116 $scope.todos = []; | 176 $scope.todos = []; |
| 117 angular.forEach(oldTodos, function(todo) { | 177 angular.forEach(oldTodos, function(todo) { |
| 118 if (!todo.done) $scope.todos.push(todo); | 178 if (!todo.done) $scope.todos.push(todo); |
| 119 }); | 179 }); |
| 120 }; | 180 }; |
| 121 } | 181 } |
| 122 </pre></p></li><li><p>Change <a href="https://github.com/GoogleChrome/chrome-app
-codelab/blob/master/lab3_mvc/angularjs/withcontroller/index.html">index.html</a
> file: | 182 </pre> |
| 123 <pre> | 183 </content> |
| 184 <content> |
| 185 <pre> |
| 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 > [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/m
aster/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> |
| 124 <html ng-app ng-csp> | 333 <html ng-app ng-csp> |
| 125 <head> | 334 <head> |
| 126 <script src="angular.min.js"></script> | 335 <script src="angular.min.js"></script> |
| 127 <script src="controller.js"></script> | 336 <script src="controller.js"></script> |
| 128 <link rel="stylesheet" href="todo.css"> | 337 <link rel="stylesheet" href="todo.css"> |
| 129 </head> | 338 </head> |
| 130 <body> | 339 <body> |
| 131 <h2>Todo</h2> | 340 <h2>Todo</h2> |
| 132 <div ng-controller="TodoCtrl"> | 341 <div ng-controller="TodoCtrl"> |
| 133 <span>{{remaining()}} of {{todos.lengt
h}} remaining</span> | 342 <span>{{remaining()}} of {{todos.lengt
h}} remaining</span> |
| 134 [ <a href="" ng-click="archive()">archive</a&
gt; ] | 343 [ <a href="" ng-click="archive()">archive</a&
gt; ] |
| 135 <ul> | 344 <ul> |
| 136 <li ng-repeat="todo in todos"> | 345 <li ng-repeat="todo in todos"> |
| 137 <input type="checkbox" ng-model="todo.done"> | 346 <input type="checkbox" ng-model="todo.done"> |
| 138 <span class="done-{{todo.done}}">&
#123;{todo.text}}</span> | 347 <span class="done-{{todo.done}}">&
#123;{todo.text}}</span> |
| 139 </li> | 348 </li> |
| 140 </ul> | 349 </ul> |
| 141 <form ng-submit="addTodo()"> | 350 <form ng-submit="addTodo()"> |
| 142 <input type="text" ng-model="todoText" size="
;30" | 351 <input type="text" ng-model="todoText" size="
;30" |
| 143 placeholder="add new todo here"> | 352 placeholder="add new todo here"> |
| 144 <input class="btn-primary" type="submit" value=&q
uot;add"> | 353 <input class="btn-primary" type="submit" value=&q
uot;add"> |
| 145 </form> | 354 </form> |
| 146 </div> | 355 </div> |
| 147 </body> | 356 </body> |
| 148 </html> | 357 </html> |
| 149 </pre></p></li> | 358 </pre> |
| 150 <li><p>Check the results by reloading the app: open the app, right-click and sel
ect Reload App.</p></li> | 359 </content> |
| 151 </ol> | 360 <content> |
| 361 <pre> |
| 362 <!doctype html> |
| 363 <html> |
| 364 <head> |
| 365 <link rel="stylesheet" href="todo.css"> |
| 366 </head> |
| 367 <body> |
| 368 <h2>Todo</h2> |
| 369 <div> |
| 370 <span><span id="remaining"></span> of <span
id="length"></span> remaining</span> |
| 371 [ <a href="" id="archive">archive</a> ] |
| 372 <ul class="unstyled" id="list"> |
| 373 </ul> |
| 374 <form> |
| 375 <input type="text" size="30" |
| 376 placeholder="add new todo here"> |
| 377 <input class="btn-primary" type="submit" value=&q
uot;add"> |
| 378 </form> |
| 379 </div> |
| 380 |
| 381 <!-- poor man's template --> |
| 382 <div id="templates" style="display: none;"> |
| 383 <li data-name="list"> |
| 384 <input type="checkbox"> |
| 385 <span></span> |
| 386 </li> |
| 387 </div> |
| 388 |
| 389 <script src="controller.js"></script> |
| 390 </body> |
| 391 </html> |
| 392 </pre> |
| 393 </content> |
| 394 </tabs> |
| 152 | 395 |
| 153 <p>Note how the data, stored in an array inside the controller, binds to the vie
w and is automatically updated when it is changed by the controller.</p> | 396 <p>Note how the data, stored in an array inside the controller, binds to the vie
w and is automatically updated when it is changed by the controller.</p> |
| 154 | 397 |
| 155 <h1 id="takeaways_">Takeaways:</h1> | 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 Rel
oad App.</li> |
| 402 </p> |
| 403 |
| 404 <h2 id="takeaways_">Takeaways</h2> |
| 156 | 405 |
| 157 <ul> | 406 <ul> |
| 158 <li><p>Chrome apps are <a href="http://developer.chrome.com/apps/offline_apps.ht
ml">offline first</a>, so the recommended way to include third-party scripts is
to download and package them inside your app.</p></li> | 407 <li><p>Chrome packaged apps are |
| 159 <li><p>You can use any framework you want, as long as it complies with Content S
ecurity Policies and other restrictions that Chrome apps are enforced to follow.
</p></li> | 408 <a href="http://developer.chrome.com/apps/offline_apps.html">offline first</a>, |
| 160 <li><p>MVC frameworks make your life easier. Use them, specially if you want to
build a non-trivial application.</p></li> | 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 packaged 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> |
| 161 </ul> | 416 </ul> |
| 162 | 417 |
| 163 <h1 id="what_39_s_next_">What's next?</h1> | 418 <h2 id="you_should_also_read">You should also read</h2> |
| 164 | 419 |
| 165 <p>Eventually in <a href="app_codelab4_testing.html">lab4_testing</a>, you will
test your app. | 420 <ul> |
| 166 Right now this lab is a work-in-progress. You can skip ahead to <a href="app_cod
elab5_data.html">lab5_data</a>.</p> | 421 <li><p><a href="http://developer.chrome.com/trunk/apps/angular_framework.html">B
uild 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> |
| OLD | NEW |