AngularJSの学習--TodoMVCの分析

52423 ワード

最近AngularJSを見ていて、少しの時間にまとめてみました.
公式サイトアドレス:http://angularjs.org/
まずいくつかのチュートリアルをお勧めします
1.AngularJS入門チュートリアルは基礎を比較し、公式Tutorialの翻訳である.
2.七歩AngularJS菜鳥から専門家へも基礎を比較し、オンライン音楽再生サイトを作成した.
3.AngularJS開発ガイドこのチュートリアルは全面的ですが、翻訳が少し難解だと思います.
これらのチュートリアルを見て、AngularJSも少し分かったと思ったら、それを使って何かしたいと思って、AngularJSが書いたtodomvcを分析しましょう.
Todomvc公式サイトアドレス:http://todomvc.com/
プロジェクトのディレクトリは次のとおりです.
AngularJS的学习--TodoMVC的分析
bower_componentsには2つのフォルダがあり、angularフォルダはangularのように使用されています.jsファイルの、todomvc-commonフォルダには、すべてのtodoプロジェクトが統一されたcssjs(左側のコンテンツを生成するために使用されるだけで、プロジェクトに関係なく)と画像が入っています.
jsフォルダはヘッダで、対応するコントローラ(コントローラ)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を定義します
 
サービスの下の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のサービスメソッドを作成しました.このサービスメソッドの本質は、2つのメソッドgetとputを返し、どちらもJSON 2とHTML 5の特性を使用しています.getはtodosの内容をlocalStorageから取り出してJSONに解析し、putはtodosをJSON文字列に変換してlocalStorageに格納する.
 
directivesの下にある2つのコマンドファイルを見てみましょう.
 
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は要素のすべての属性、属性名などからなるオブジェクトである.
その中で2つのAngularJSの方法が使われています
$watch(watchExpression,listener,objectEquality)は、watchExpressionが変化するたびにリスニングコールバックが実行されるリスナーコールバックを登録します.
$timeout(fn[,delay],,invokeApply])timeoutの値が達するとfn関数が実行されます.
todoFocus.jsはtodoFocus命令を作成した.1つの要素が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()メソッドがトリガーされ、todosにtodoが追加されます.
<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は2つのclassをバインドし、todo.completed、editing:todo==editedTodo、todo.completedはtrueで、completed classを追加し、todo=editedTodoの場合、editing classを追加します.classはtoggleのcheckboxにtodoにバインドされます.completed.todoタイトルに表示されるlabelはダブルクリックイベントをバインドし、ダブルクリックしてeditTodo(todo)をトリガーし、editTodoはeditedTodoにtodoを割り当て、次の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をすべてクリアします.
 
分析はこれで终わります.间违いがあったり、分からないところがあったりしたら、伝言を残してください.