AngularJS学习笔记之TodoMVC的分析
最近一段时间一直在看angularjs,趁着一点时间总结一下。
官网地址:
先推荐几个教程
1. angularjs入门教程 比较基础,是官方tutorial的翻译。
2. 七步从angularjs菜鸟到专家 也比较基础,制作了一个在线音乐播放网站。
3. angularjs开发指南 这个教程比较全面,但我感觉翻译的有些晦涩难懂。
看过这些教程后,觉得angularjs也懂一点了,就想用它干点事,就分析一下angularjs写的todomvc吧。
todomvc官网地址:
项目的目录如下:
bower_components里放了两个文件夹,其中angular文件夹是用来一如angular.js文件的,todomvc-common文件夹里的放入了所有todo项目统一的css\js(只是用来生成左侧内容的,与项目无关)和图片。
js文件夹是大头,里面放了相应的controller(控制器)\directive(指令)\service(服务)和app.js。
test文件夹里放的是测试用的代码,不分析。
index.html是项目的view页面。
先来看一下app.js
/*global angular */
/*jshint unused:false */
'use strict';
/**
* the main todomvc app module
*
* @type {angular.module}
*/
var todomvc = angular.module('todomvc', []);
就是定义了一个模块todomvc
再看一下services下的todostorage.js
/*global todomvc */
'use strict';
/**
* services that persists and retrieves todos from localstorage
*/
todomvc.factory('todostorage', function () {
// todos json字符串存储的唯一标识
var storage_id = 'todos-angularjs';
return {
// 从localstorage中取出todos,并解析成json对象
get: function () {
return json.parse(localstorage.getitem(storage_id) || '[]');
},
// 将todos对象转化成json字符串,并存入localstorage
put: function (todos) {
localstorage.setitem(storage_id, json.stringify(todos));
}
};
});
使用factory方法创建了todostorage的service方法,这个service方法的本质就是返回了两个方法get和put,两者都是用了json2和html5的特性。get将todos的内容从localstorage中取出,并解析成json,put将todos转化成json字符串,并存储到localstorage中。
再看一下directives下面的两个指令文件。
todofocus.js
/*global todomvc */
'use strict';
/**
* directive that places focus on the element it is applied to when the expression it binds to evaluates to true
*/
todomvc.directive('todofocus', function todofocus($timeout) {
return function (scope, elem, attrs) {
// 为todofocus属性的值添加监听
scope.$watch(attrs.todofocus, function (newval) {
if (newval) {
$timeout(function () {
elem[0].focus();
}, 0, false);
}
});
};
});
返回function的参数中,elem就是包含该指令的元素的数组,attrs是元素的所有属性、属性名等组成的对象。
其中用到了两个angularjs的方法
$watch(watchexpression, listener, objectequality) 注册一个侦听器回调,每当watchexpression变化时,监听回调将被执行。
$timeout(fn[, delay][, invokeapply]) 当timeout的值达到时,执行fn函数。
todofocus.js创建了todofocus指令。当一个元素拥有todofocus属性时,该指令会为该元素的todofocus属性的值添加监听,如果todofocus属性的值改变成true,就会执行$timeout(function () {elem[0].focus();}, 0, false);其中的延迟时间为0秒,所以会立即执行elem[0].focus()。
todoescape.js
/*global todomvc */
'use strict';
/**
* directive that executes an expression when the element it is applied to gets
* an `escape` keydown event.
*/
todomvc.directive('todoescape', function () {
var escape_key = 27;
return function (scope, elem, attrs) {
elem.bind('keydown', function (event) {
if (event.keycode === escape_key) {
scope.$apply(attrs.todoescape);
}
});
};
});
todoescape.js创建了todoescape指令。当按下escape键时,执行attrs.todoescape的表达式。
看一下大头,controllers文件夹中的todoctrl.js,这个文件略长,我就直接写注释了。
/*global todomvc, angular */
'use strict';
/**
* the main controller for the app. the controller:
* - retrieves and persists the model via the todostorage service
* - exposes the model to the template and provides event handlers
*/
todomvc.controller('todoctrl', function todoctrl($scope, $location, todostorage, filterfilter) {
// 从localstorage中获取todos
var todos = $scope.todos = todostorage.get();
// 记录新的todo
$scope.newtodo = '';
// 记录编辑过的todo
$scope.editedtodo = null;
// 当todos的值改变时执行其中的方法
$scope.$watch('todos', function (newvalue, oldvalue) {
// 获取未完成的todos的数目
$scope.remainingcount = filterfilter(todos, { completed: false }).length;
// 获取已完成的todos的数目
$scope.completedcount = todos.length - $scope.remainingcount;
// 当且仅当$scope.remainingcount为0时,$scope.allchecked为true
$scope.allchecked = !$scope.remainingcount;
// 当todos的新值和旧值不相等时,向localstorage中存入todos
if (newvalue !== oldvalue) { // this prevents unneeded calls to the local storage
todostorage.put(todos);
}
}, true);
if ($location.path() === '') {
// 如果$location.path()为空,就设置为/
$location.path('/');
}
$scope.location = $location;
// 当location.path()的值改变时执行其中的方法
$scope.$watch('location.path()', function (path) {
// 获取状态的过滤器
// 如果path为'/active',过滤器为{ completed: false }
// 如果path为'/completed',过滤器为{ completed: true }
// 否则,过滤器为null
$scope.statusfilter = (path === '/active') ?
{ completed: false } : (path === '/completed') ?
{ completed: true } : null;
});
// 添加一个新的todo
$scope.addtodo = function () {
var newtodo = $scope.newtodo.trim();
if (!newtodo.length) {
return;
}
// 向todos里添加一个todo,completed属性默认为false
todos.push({
title: newtodo,
completed: false
});
// 置空
$scope.newtodo = '';
};
// 编辑一个todo
$scope.edittodo = function (todo) {
$scope.editedtodo = todo;
// clone the original todo to restore it on demand.
// 保存编辑前的todo,为恢复编辑前做准备
$scope.originaltodo = angular.extend({}, todo);
};
// 编辑todo完成
$scope.doneediting = function (todo) {
// 置空
$scope.editedtodo = null;
todo.title = todo.title.trim();
if (!todo.title) {
// 如果todo的title为空,则移除该todo
$scope.removetodo(todo);
}
};
// 恢复编辑前的todo
$scope.revertediting = function (todo) {
todos[todos.indexof(todo)] = $scope.originaltodo;
$scope.doneediting($scope.originaltodo);
};
// 移除todo
$scope.removetodo = function (todo) {
todos.splice(todos.indexof(todo), 1);
};
// 清除已完成的todos
$scope.clearcompletedtodos = function () {
$scope.todos = todos = todos.filter(function (val) {
return !val.completed;
});
};
// 标记所有的todo的状态(true或false)
$scope.markall = function (completed) {
todos.foreach(function (todo) {
todo.completed = completed;
});
};
});
最后看一下index.html,这个文件我们一段一段的分析。
<!doctype html>
<html lang="en" ng-app="todomvc" data-framework="angularjs">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>angularjs • todomvc</title>
<link rel="stylesheet" href="bower_components/todomvc-common/base.css">
<style>[ng-cloak] { display: none; }</style>
</head>
<body>
<section id="todoapp" ng-controller="todoctrl">
<header id="header">
<h1>todos</h1>
<form id="todo-form" ng-submit="addtodo()">
<input id="new-todo" placeholder="what needs to be done?" ng-model="newtodo" autofocus>
</form>
</header>
<section id="main" ng-show="todos.length" ng-cloak>
<input id="toggle-all" type="checkbox" ng-model="allchecked" ng-click="markall(allchecked)">
<label for="toggle-all">mark all as complete</label>
<ul id="todo-list">
<li ng-repeat="todo in todos | filter:statusfilter track by $index" ng-class="{completed: todo.completed, editing: todo == editedtodo}">
<div class="view">
<input class="toggle" type="checkbox" ng-model="todo.completed">
<label ng-dblclick="edittodo(todo)">{{todo.title}}</label>
<button class="destroy" ng-click="removetodo(todo)"></button>
</div>
<form ng-submit="doneediting(todo)">
<input class="edit" ng-trim="false" ng-model="todo.title" todo-escape="revertediting(todo)" ng-blur="doneediting(todo)" todo-focus="todo == editedtodo">
</form>
</li>
</ul>
</section>
<footer id="footer" ng-show="todos.length" ng-cloak>
<span id="todo-count"><strong>{{remainingcount}}</strong>
<ng-pluralize count="remainingcount" when="{ one: 'item left', other: 'items left' }"></ng-pluralize>
</span>
<ul id="filters">
<li>
<a ng-class="{selected: location.path() == '/'} " href="#/">all</a>
</li>
<li>
<a ng-class="{selected: location.path() == '/active'}" href="#/active">active</a>
</li>
<li>
<a ng-class="{selected: location.path() == '/completed'}" href="#/completed">completed</a>
</li>
</ul>
<button id="clear-completed" ng-click="clearcompletedtodos()" ng-show="completedcount">clear completed ({{completedcount}})</button>
</footer>
</section>
<footer id="info">
<p>double-click to edit a todo</p>
<p>credits:
<a href="http://twitter.com/cburgdorf">christoph burgdorf</a>,
<a href="http://ericbidelman.com">eric bidelman</a>,
<a href="http://jacobmumm.com">jacob mumm</a> and
<a href="http://igorminar.com">igor minar</a>
</p>
<p>part of <a href="http://todomvc.com">todomvc</a></p>
</footer>
<script src="bower_components/todomvc-common/base.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers/todoctrl.js"></script>
<script src="js/services/todostorage.js"></script>
<script src="js/directives/todofocus.js"></script>
<script src="js/directives/todoescape.js"></script>
</body>
</html>
首先是在最下面,引入相应的js,这个就不多说了。
<script src="bower_components/todomvc-common/base.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers/todoctrl.js"></script>
<script src="js/services/todostorage.js"></script>
<script src="js/directives/todofocus.js"></script>
<script src="js/directives/todoescape.js"></script>
定义style[ng-cloak],含有ng-cloak属性则不可见。
<style>[ng-cloak] { display: none; }</style>
来看添加todo的html,绑定的model为newtodo,submit的方法是todoctrl.js中的addtodo(),会添加一条todo,点击enter,默认触发提交事件,就触发了addtodo()方法,添加了一条todo到todos中。
<form id="todo-form" ng-submit="addtodo()">
<input id="new-todo" placeholder="what needs to be done?" ng-model="newtodo" autofocus>
</form>
再看展示todos的html
<section id="main" ng-show="todos.length" ng-cloak>
<input id="toggle-all" type="checkbox" ng-model="allchecked" ng-click="markall(allchecked)">
<label for="toggle-all">mark all as complete</label>
<ul id="todo-list">
<li ng-repeat="todo in todos | filter:statusfilter track by $index" ng-class="{completed: todo.completed, editing: todo == editedtodo}">
<div class="view">
<input class="toggle" type="checkbox" ng-model="todo.completed">
<label ng-dblclick="edittodo(todo)">{{todo.title}}</label>
<button class="destroy" ng-click="removetodo(todo)"></button>
</div>
<form ng-submit="doneediting(todo)">
<input class="edit" ng-trim="false" ng-model="todo.title" todo-escape="revertediting(todo)" ng-blur="doneediting(todo)" todo-focus="todo == editedtodo">
</form>
</li>
</ul>
</section>
section使用ngshow方法根据todos的长度判断是否显示,加上ng-cloak属性是为了在刚开始时不要显示出angularjs未处理的页面。可以去掉刷新试一试。
其中id为toggle-all的checkbox绑定到allchecked model上,点击触发markall(allchecked),将allchecked的值传入,标记所有的todos。
使用ngrepeat循环产生li标签,todo in todos | filter:statusfilter track by $index,循环todos,用statusfilter过滤,用$index追踪。ngclass绑定了两个class,{completed: todo.completed, editing: todo == editedtodo},如果todo.completed为true,添加completed class,如果todo==editedtodo,则添加editing class。class为toggle的checkbox绑定到todo.completed。todo标题展示的label绑定了双击事件,双击触发edittodo(todo),edittodo会将todo赋给editedtodo,然后会触发下面form中的todofocus指令,这时候form中的input可见。按esc就触发revertediting(todo),恢复到编辑前,按enter或者失去焦点就触发doneediting(todo) ,保存编辑后的todo。class为destroy的button绑定了click事件,点击触发removetodo(todo),删除掉该条todo。
最后看todos的统计信息展示的html
<footer id="footer" ng-show="todos.length" ng-cloak>
<span id="todo-count"><strong>{{remainingcount}}</strong>
<ng-pluralize count="remainingcount" when="{ one: 'item left', other: 'items left' }"></ng-pluralize>
</span>
<ul id="filters">
<li>
<a ng-class="{selected: location.path() == '/'} " href="#/">all</a>
</li>
<li>
<a ng-class="{selected: location.path() == '/active'}" href="#/active">active</a>
</li>
<li>
<a ng-class="{selected: location.path() == '/completed'}" href="#/completed">completed</a>
</li>
</ul>
<button id="clear-completed" ng-click="clearcompletedtodos()" ng-show="completedcount">clear completed ({{completedcount}})</button>
</footer>
ng-pluralize标签实现了当remainingcount个数为1时,显示 item left,否则显示 items left。
id为filters的ul标签中根据location.path()的内容不同,标记不同的a标签被选中。
id为clear-completed的button添加了点击事件,触发clearcompletedtodos(),清除掉所有已完成的todo。
今天的笔记就先到这里吧,都是些个人心得,希望小伙伴们能够喜欢。