1 <h1 id=
"lab_3_model_view_controller">Create MVC
</h1>
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>
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.
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>
21 <p class=
"note"><b>Note:
</b>
22 Chrome Apps don
't enforce any specific framework or programming style.
25 <h2 id=
"simple">Create a simple view
</h2>
27 <h3 id=
"basic-mvc">Add MVC basics
</h3>
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>
32 <a href=
"https://github.com/GoogleChrome/chrome-app-codelab/blob/master/lab3_mvc/angularjs/simpleview/angular.min.js">angular.min.js
</a>.
</p>
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>
38 <h3 id=
"update-view">Update view
</h3>
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:
44 <tabs data-group=
"source">
46 <header tabindex=
"0" data-value=
"angular">Angular
</header>
47 <header tabindex=
"0" data-value=
"js">JavaScript
</header>
50 <pre data-filename=
"index.html">
52 <html ng-app ng-csp
>
54 <script src=
"angular.min.js
"></script
>
55 <link
rel=
"stylesheet" href=
"todo.css">
58 <h2
>Todo
</h2
>
62 {{todoText
}}
65 <input type=
"text
" ng-model=
"todoText
" size=
"30"
66 placeholder=
"type your todo here
">
73 <pre data-filename=
"index.html">
77 <link rel=
"stylesheet
" href=
"todo.css
">
80 <h2
>Todo
</h2
>
83 <li id=
"todoText
">
86 <input type=
"text
" id=
"newTodo
" size=
"30"
87 placeholder=
"type your todo here
">
89 <script src=
"controller.js
"></script
>
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 when using Angular v1.1
.0+. We
've included it here so that the sample works regardless of the Angular version in use.
</p>
98 <h3 id=
"stylesheet">Add stylesheet
</h3>
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:
104 <pre data-filename=
"todo.css">
106 font-family:
"Helvetica Neue",Helvetica,Arial,sans-serif;
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);
122 text-decoration: line-through;
127 <h3 id=
"check1">Check the results
</h3>
130 Check the results by reloading the app: open the app, right-click and select Reload App.
</li>
133 <h2 id=
"real-todo">Create real Todo list
</h2>
136 The previous sample, although interesting, is not exactly useful.
137 Let
's transform it into a real Todo list, instead of a simple Todo item.
140 <h3 id=
"controller">Add controller
</h3>
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>.
149 <tabs data-group=
"source">
151 <header tabindex=
"0" data-value=
"angular">Angular
</header>
152 <header tabindex=
"0" data-value=
"js">JavaScript
</header>
155 <pre data-filename=
"controller.js">
156 function TodoCtrl($scope) {
158 {text:
'learn angular
', done:true},
159 {text:
'build an angular Chrome packaged app
', done:false}];
161 $scope.addTodo = function() {
162 $scope.todos.push({text:$scope.todoText, done:false});
163 $scope.todoText =
'';
166 $scope.remaining = function() {
168 angular.forEach($scope.todos, function(todo) {
169 count += todo.done ?
0 :
1;
174 $scope.archive = function() {
175 var oldTodos = $scope.todos;
177 angular.forEach(oldTodos, function(todo) {
178 if (!todo.done) $scope.todos.push(todo);
185 <pre data-filename=
"controller.js">
190 var TodoModel = function() {
195 TodoModel.prototype.clearTodos = function() {
197 this.notifyListeners('removed');
200 TodoModel.prototype.archiveDone = function() {
201 var oldTodos = this.todos;
203 for (var id in oldTodos) {
204 if ( ! oldTodos[id].isDone ) {
205 this.todos[id] = oldTodos[id];
208 this.notifyListeners('archived');
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);
218 TodoModel.prototype.addTodo = function(text, isDone) {
220 this.todos[id]={'id': id, 'text': text, 'isDone': isDone};
221 this.notifyListeners('added', id);
224 TodoModel.prototype.addListener = function(listener) {
225 this.listeners.push(listener);
228 TodoModel.prototype.notifyListeners = function(change, param) {
230 this.listeners.forEach(function(listener) {
231 listener(this_, change, param);
235 exports.TodoModel = TodoModel;
240 window.addEventListener('DOMContentLoaded', function() {
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"]');
248 form.addEventListener('submit', function(e) {
249 var textEl = e.target.querySelector('input[
type=
"text"]');
250 model.addTodo(textEl.value, false);
255 archive.addEventListener('click', function(e) {
260 model.addListener(function(model, changeType, param) {
261 if ( changeType === 'removed' || changeType === 'archived') {
263 } else if ( changeType === 'added' ) {
264 drawTodo(model.todos[param], list);
265 } else if ( changeType === 'stateChanged') {
266 updateTodo(model.todos[param]);
268 updateCounters(model);
271 var redrawUI = function(model) {
273 for (var id in model.todos) {
274 drawTodo(model.todos[id], list);
278 var drawTodo = function(todoObj, container) {
279 var el = todoTemplate.cloneNode(true);
280 el.setAttribute('data-id', todoObj.id);
281 container.appendChild(el);
283 var checkbox = el.querySelector('input[
type=
"checkbox"]');
284 checkbox.addEventListener('change', function(e) {
285 model.setTodoState(todoObj.id, e.target.checked);
289 var updateTodo = function(model) {
290 var todoElement = list.querySelector('li[
data-id=
"'+model.id+'"]');
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;
300 var updateCounters = function(model) {
303 for (var id in model.todos) {
305 if ( ! model.todos[id].isDone ) {
309 document.getElementById('remaining').innerText = notDone;
310 document.getElementById('length').innerText = count;
313 updateCounters(model);
320 <h3 id=
"index">Update view
</h3>
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>:
326 <tabs data-group=
"source">
328 <header tabindex=
"0" data-value=
"angular">Angular
</header>
329 <header tabindex=
"0" data-value=
"js">JavaScript
</header>
332 <pre data-filename=
"index.html">
333 <html ng-app ng-csp
>
335 <script src=
"angular.min.js
"></script
>
336 <script src=
"controller.js
"></script
>
337 <link rel=
"stylesheet
" href=
"todo.css
">
340 <h2
>Todo
</h2
>
341 <div ng-controller=
"TodoCtrl
">
342 <span
>{{remaining()
}} of
{{todos.length
}} remaining
</span
>
343 [
<a href=
"" ng-click=
"archive()
">archive
</a
> ]
345 <li ng-repeat=
"todo in todos
">
346 <input type=
"checkbox
" ng-model=
"todo.done
">
347 <span class=
"done-
{{todo.done
}}">{{todo.text
}}</span
>
350 <form ng-submit=
"addTodo()
">
351 <input type=
"text
" ng-model=
"todoText
" size=
"30"
352 placeholder=
"add new todo here
">
353 <input class=
"btn-primary
" type=
"submit
" value=
"add
">
361 <pre data-filename=
"index.html">
362 <!doctype html
>
365 <link rel=
"stylesheet
" href=
"todo.css
">
368 <h2
>Todo
</h2
>
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
">
375 <input type=
"text
" size=
"30"
376 placeholder=
"add new todo here
">
377 <input class=
"btn-primary
" type=
"submit
" value=
"add
">
381 <!-- poor man's template --
>
382 <div id=
"templates
" style=
"display: none;
">
383 <li data-name=
"list
">
384 <input type=
"checkbox
">
385 <span
></span
>
389 <script src=
"controller.js
"></script
>
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>
398 <h3 id=
"check2">Check the results
</h3>
401 Check the results by reloading the app: open the app, right-click and select Reload App.
</li>
404 <h2 id=
"takeaways_">Takeaways
</h2>
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>
418 <h2 id=
"you_should_also_read">You should also read
</h2>
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>
425 <h2 id=
"what_39_s_next_">What's next?
</h2>
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>