zoukankan      html  css  js  c++  java
  • AngularJS的学习--TodoMVC的分析

    最近一段时间一直在看AngularJS,趁着一点时间总结一下。

    官网地址:http://angularjs.org/

    先推荐几个教程

    1. AngularJS入门教程 比较基础,是官方Tutorial的翻译。

    2. 七步从AngularJS菜鸟到专家 也比较基础,制作了一个在线音乐播放网站。

    3. AngularJS开发指南 这个教程比较全面,但我感觉翻译的有些晦涩难懂。

    看过这些教程后,觉得AngularJS也懂一点了,就想用它干点事,就分析一下AngularJS写的todomvc吧。

    Todomvc官网地址:http://todomvc.com/

    项目的目录如下:

    bower_components里放了两个文件夹,其中angular文件夹是用来一如angular.js文件的,todomvc-common文件夹里的放入了所有todo项目统一的cssjs(只是用来生成左侧内容的,与项目无关)和图片。

    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。

    分析到此结束,如有错误,或者有不明白的地方,请留言~~

     

  • 相关阅读:
    成功破解校园网锐捷客户端,实现笔记本无线网卡wifi
    献给正在郁闷的人们
    用友客户通,无法打开登录 'turbocrm' 中请求的数据库。登录失败。
    如何得到cxgrid的当前编辑值
    cxgrid当底层数据集为空时显示一条空记录
    使用nlite将SCSI RAID 阵列驱动整合到系统安装光盘内
    开始菜单的运行没有了
    Delphi代码获取网卡物理地址三种方法
    登录用友通模块时提示:运行时错误'430',类不支持自动化或不支持期望的接口 ...
    CentOS7下安装MySQL Mr
  • 原文地址:https://www.cnblogs.com/CraryPrimitiveMan/p/3499131.html
Copyright © 2011-2022 走看看