├── _template.html ├── .bowerrc ├── .gitignore ├── todo ├── step02 │ ├── app.js │ ├── style.css │ ├── article.md │ └── index.html ├── step03 │ ├── app.js │ ├── article.md │ └── index.html ├── step04 │ ├── app.js │ ├── article.md │ ├── index.html │ └── article.html ├── step00 │ ├── article.md │ └── article.html ├── step05 │ ├── app.js │ ├── article.md │ ├── article.html │ └── index.html ├── step08 │ ├── article.md │ ├── article.html │ ├── app.js │ └── index.html ├── step06 │ ├── app.js │ ├── article.md │ ├── index.html │ └── article.html ├── step01 │ ├── article.md │ └── article.html ├── step07 │ ├── app.js │ └── index.html └── step09 │ ├── app.js │ └── index.html ├── assets ├── fonts │ ├── mplus-1p-thin.ttf │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ ├── LICENSE_J │ ├── LICENSE_E │ ├── README_J │ └── README_E ├── js │ ├── location.js │ └── learn.js ├── css │ ├── example.css │ └── docs.css └── config.json ├── components ├── angular │ ├── bower.json │ ├── .bower.json │ └── README.md └── angular-route │ ├── bower.json │ ├── .bower.json │ ├── README.md │ └── angular-route.min.js ├── bower.json ├── README.md ├── package.json ├── guide ├── template │ ├── demo1.js │ ├── demo2.js │ ├── article.md │ ├── article.html │ ├── demo2.html │ └── demo1.html ├── controller │ ├── script.js │ ├── index.html │ ├── article.md │ └── article.html ├── scope │ ├── demo1.html │ ├── demo1.js │ ├── demo2.js │ ├── demo2.html │ └── article.md ├── filter │ ├── index.html │ ├── script.js │ ├── article.md │ └── article.html ├── service │ ├── script.js │ ├── article.md │ ├── index.html │ └── article.html ├── directive │ ├── index.html │ ├── script.js │ ├── article.md │ └── article.html ├── terminology │ ├── article.md │ └── article.html └── module │ ├── article.md │ └── article.html ├── account ├── step03 │ ├── app.js │ ├── article.md │ └── index.html ├── step07 │ ├── style.css │ ├── app.js │ └── article.md ├── step00 │ ├── article.md │ └── article.html ├── step04 │ ├── article.md │ └── article.html ├── step01 │ ├── article.md │ └── article.html ├── step05 │ ├── app.js │ ├── article.md │ └── index.html ├── step08 │ ├── article.md │ └── app.js ├── step06 │ ├── app.js │ ├── article.md │ └── index.html ├── step09 │ ├── app.js │ └── article.md └── step02 │ └── article.md ├── LICENSE └── Gruntfile.js /_template.html: -------------------------------------------------------------------------------- 1 | <%=content%> 2 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "components" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | .DS_Store 3 | 4 | node_modules 5 | -------------------------------------------------------------------------------- /todo/step02/app.js: -------------------------------------------------------------------------------- 1 | angular.module('App', ['LocationBar']); 2 | -------------------------------------------------------------------------------- /assets/fonts/mplus-1p-thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8th713/LearnAngularJS/HEAD/assets/fonts/mplus-1p-thin.ttf -------------------------------------------------------------------------------- /assets/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8th713/LearnAngularJS/HEAD/assets/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /assets/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8th713/LearnAngularJS/HEAD/assets/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /assets/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/8th713/LearnAngularJS/HEAD/assets/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /components/angular/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.2.0-rc.3", 4 | "main": "./angular.js", 5 | "dependencies": { 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "learn", 3 | "version": "0.0.1", 4 | "dependencies": { 5 | "angular": "~1.2.0", 6 | "angular-route": "~1.2.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /todo/step02/style.css: -------------------------------------------------------------------------------- 1 | /* example.css より一部抜粋 */ 2 | .todo-item .todo-title { 3 | color: #888; 4 | } 5 | 6 | .done .todo-title { 7 | text-decoration: line-through; 8 | } 9 | -------------------------------------------------------------------------------- /components/angular-route/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-route", 3 | "version": "1.2.0-rc.3", 4 | "main": "./angular-route.js", 5 | "dependencies": { 6 | "angular": "1.2.0-rc.3" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | すぐできる AngularJS 2 | ======================= 3 | 4 | AngularJS 学習用ドキュメントです。 5 | 6 | ## ライブラリ 7 | * AngularJS 8 | * Bootstrap 9 | * highlight.js 10 | 11 | ## フォント 12 | * M+ WEB FONTS 13 | 14 | ## Author 15 | * @8th713 ([twitter](https://twitter.com/8th_713)) 16 | -------------------------------------------------------------------------------- /todo/step03/app.js: -------------------------------------------------------------------------------- 1 | angular.module('App', ['LocationBar']) 2 | .controller('MainController', ['$scope', function ($scope) { 3 | $scope.todos = []; 4 | 5 | $scope.addTodo = function () { 6 | $scope.todos.push({ 7 | title: Math.random(), 8 | done: false 9 | }); 10 | }; 11 | }]); 12 | -------------------------------------------------------------------------------- /assets/fonts/LICENSE_J: -------------------------------------------------------------------------------- 1 | M+ FONTS Copyright (C) 2002-2013 M+ FONTS PROJECT 2 | 3 | - 4 | 5 | LICENSE_J 6 | 7 | 8 | 9 | 10 | これらのフォントはフリー(自由な)ソフトウエアです。 11 | あらゆる改変の有無に関わらず、また商業的な利用であっても、自由にご利用、 12 | 複製、再配布することができますが、全て無保証とさせていただきます。 13 | 14 | 15 | http://mplus-fonts.sourceforge.jp/ 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "learn-angular", 3 | "version": "0.0.1", 4 | "description": "learn AngualrJS", 5 | "author": "8th713", 6 | "license": "MIT", 7 | "devDependencies": { 8 | "grunt": "~0.4.1", 9 | "grunt-este-watch": "~0.1.10", 10 | "grunt-contrib-connect": "~0.5.0", 11 | "grunt-markdown": "~0.4.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /guide/template/demo1.js: -------------------------------------------------------------------------------- 1 | angular.module('Example', ['LocationBar']) 2 | .run(['$rootScope', function (scope) { 3 | scope.example = 'Parent'; 4 | 5 | scope.example2 = 'Parent Only'; 6 | 7 | }]) 8 | .controller('MainCtrl', ['$scope', function (scope) { 9 | scope.example = 'Child'; 10 | 11 | scope.square = function (n) { 12 | return n * n; 13 | }; 14 | }]); 15 | -------------------------------------------------------------------------------- /todo/step04/app.js: -------------------------------------------------------------------------------- 1 | angular.module('App', ['LocationBar']) 2 | .controller('MainController', ['$scope', function ($scope) { 3 | $scope.todos = []; 4 | 5 | $scope.newTitle = ''; 6 | 7 | $scope.addTodo = function () { 8 | $scope.todos.push({ 9 | title: $scope.newTitle, 10 | done: false 11 | }); 12 | 13 | $scope.newTitle = ''; 14 | }; 15 | }]); 16 | -------------------------------------------------------------------------------- /guide/template/demo2.js: -------------------------------------------------------------------------------- 1 | angular.module('Example', ['LocationBar']) 2 | .run(['$rootScope', function (scope) { 3 | scope.count = 0; 4 | scope.string = 'Hello world'; 5 | 6 | scope.countUp = function () { 7 | scope.count++; 8 | }; 9 | 10 | scope.delay = function () { 11 | setTimeout(function () { 12 | scope.count += 100; 13 | scope.$apply(); // 手動更新 14 | }, 2000); 15 | }; 16 | }]); 17 | -------------------------------------------------------------------------------- /account/step03/app.js: -------------------------------------------------------------------------------- 1 | angular.module('App', ['ngRoute', 'LocationBar']) 2 | .config(['$routeProvider', function ($routeProvider) { 3 | $routeProvider 4 | .when('/', { 5 | templateUrl: 'index-tmpl' 6 | }) 7 | .when('/new', { 8 | templateUrl: 'new-tmpl' 9 | }) 10 | .when('/sheet/:id', { 11 | templateUrl: 'sheet-tmpl' 12 | }) 13 | .otherwise({ 14 | redirectTo: '/' 15 | }); 16 | }]); 17 | -------------------------------------------------------------------------------- /account/step07/style.css: -------------------------------------------------------------------------------- 1 | /* example.css より一部抜粋 */ 2 | .ng-invalid { 3 | border-color: #b94a48; 4 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); 5 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); 6 | } 7 | 8 | .ng-invalid:focus { 9 | border-color: #953b39; 10 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #d59392; 11 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #d59392; 12 | } 13 | -------------------------------------------------------------------------------- /assets/fonts/LICENSE_E: -------------------------------------------------------------------------------- 1 | M+ FONTS Copyright (C) 2002-2013 M+ FONTS PROJECT 2 | 3 | - 4 | 5 | LICENSE_E 6 | 7 | 8 | 9 | 10 | These fonts are free software. 11 | Unlimited permission is granted to use, copy, and distribute them, with 12 | or without modification, either commercially or noncommercially. 13 | THESE FONTS ARE PROVIDED "AS IS" WITHOUT WARRANTY. 14 | 15 | 16 | http://mplus-fonts.sourceforge.jp/mplus-outline-fonts/ 17 | -------------------------------------------------------------------------------- /components/angular/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular", 3 | "version": "1.2.0-rc.3", 4 | "main": "./angular.js", 5 | "dependencies": {}, 6 | "homepage": "https://github.com/angular/bower-angular", 7 | "_release": "1.2.0-rc.3", 8 | "_resolution": { 9 | "type": "version", 10 | "tag": "v1.2.0-rc.3", 11 | "commit": "ffab5c44ee8fd4f46e13e8ebf96c19e67383bd7b" 12 | }, 13 | "_source": "git://github.com/angular/bower-angular.git", 14 | "_target": "~1.2.0", 15 | "_originalSource": "angular" 16 | } -------------------------------------------------------------------------------- /todo/step00/article.md: -------------------------------------------------------------------------------- 1 | このチュートリアルは AngularJS を使って帳票アプリをつくる過程を {[ group.articles.length - 1 ]} のステップにわけ、段階的に AngularJS の使い方を学習します。 2 | 3 | ## アプリケーションの概要 4 | 作成する ToDo アプリの概要は以下のとおりです。 5 | 6 | ### 扱うデータ 7 | * **ToDo** - 任意の要件と、完了もしくは未完了の状態を持つデータ 8 | * **ToDo リスト** - 複数のToDoを持つデータ 9 | 10 | ### アプリが行うこと 11 | * 新しい ToDo を作成しリストに追加 12 | * ToDo リストの表示 13 | * 作成済みの ToDo の要件を編集 14 | * 任意の状態の ToDo を絞り込み表示 15 | * 完了状態の ToDo を一括削除 16 | * リスト内の ToDo の状態を一括編集 17 | 18 | ### アプリが行わないこと 19 | * データの永続化 20 | 21 | 完成予定のアプリのイメージ 22 | -------------------------------------------------------------------------------- /components/angular-route/.bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-route", 3 | "version": "1.2.0-rc.3", 4 | "main": "./angular-route.js", 5 | "dependencies": { 6 | "angular": "1.2.0-rc.3" 7 | }, 8 | "homepage": "https://github.com/angular/bower-angular-route", 9 | "_release": "1.2.0-rc.3", 10 | "_resolution": { 11 | "type": "version", 12 | "tag": "v1.2.0-rc.3", 13 | "commit": "e548b5bf0b8d5113f675e0ee30cd3c4a001adb86" 14 | }, 15 | "_source": "git://github.com/angular/bower-angular-route.git", 16 | "_target": "~1.2.0", 17 | "_originalSource": "angular-route" 18 | } -------------------------------------------------------------------------------- /todo/step05/app.js: -------------------------------------------------------------------------------- 1 | angular.module('App', ['LocationBar']) 2 | .controller('MainController', ['$scope', function ($scope) { 3 | $scope.todos = []; 4 | 5 | $scope.newTitle = ''; 6 | 7 | $scope.addTodo = function () { 8 | $scope.todos.push({ 9 | title: $scope.newTitle, 10 | done: false 11 | }); 12 | 13 | $scope.newTitle = ''; 14 | }; 15 | 16 | $scope.filter = { 17 | done: { done: true }, 18 | remaining: { done: false } 19 | }; 20 | $scope.currentFilter = null; 21 | 22 | $scope.changeFilter = function (filter) { 23 | $scope.currentFilter = filter; 24 | }; 25 | }]); 26 | -------------------------------------------------------------------------------- /guide/controller/script.js: -------------------------------------------------------------------------------- 1 | angular.module('Example', ['LocationBar']) 2 | .controller('UserController', ['$scope', function ($scope) { 3 | // users model initial value 4 | $scope.users = [ 5 | {name: 'foo', email: 'bar@example.com'} 6 | ]; 7 | 8 | // add a new user, and then run the reset. 9 | $scope.addUser = function () { 10 | $scope.users.push({ 11 | name: $scope.name, 12 | email: $scope.email 13 | }); 14 | reset(); 15 | }; 16 | 17 | // Initialize name and email model. 18 | function reset() { 19 | $scope.name = null; 20 | $scope.email = null; 21 | } 22 | 23 | // Not required. 24 | reset(); 25 | }]); 26 | -------------------------------------------------------------------------------- /account/step00/article.md: -------------------------------------------------------------------------------- 1 | このチュートリアルは AngularJS を使って帳票アプリをつくる過程を {[ group.articles.length - 1 ]} のステップにわけ、段階的に AngularJS の使い方を学習します。 2 | 3 | ## アプリケーションの概要 4 | 作成する帳票アプリの概要は以下のとおりです。 5 | 6 | ### 扱うデータ 7 | * **注文明細行** - 任意の商品名、単価、注文個数を持つデータ 8 | * **帳票** - 通し番号、作成日時、注文明細行のコレクションを持つデータ 9 | * **帳票リスト** - 複数の帳票をもつコレクション 10 | 11 | ### アプリが行うこと 12 | * 帳票リスト内の帳票を一覧表示する 13 | * 任意の注文明細行を持った新しい帳票を作成し帳票リストに加える 14 | * 作成済みの帳票の詳細を表示する 15 | 16 | ### アプリが行わないこと 17 | * 帳票の編集 18 | * データの永続化 19 | 20 | ### 必要なビュー 21 | * **帳票一覧ビュー** - 帳票リストを表示するビュー 22 | * **帳票作成ビュー** - 新しい帳票を作成するビュー 23 | * **帳票詳細ビュー** - 作成済みの帳票を表示するビュー 24 | 25 | 完成予定のアプリのイメージ 26 | -------------------------------------------------------------------------------- /guide/template/article.md: -------------------------------------------------------------------------------- 1 | ## 役割 2 | ビューは以下の役割を果たすために使用されるものです。 3 | 4 | * ディレクティブを解析し DOM を構築する。 5 | * テンプレート構文(`{{ }}`)を解析し式を実行、結果を DOM に反映する。 6 | * モデルの変更を受け取り(必要なら)式を再評価します。 7 | 8 | ## 構文 9 | AngularJS のビューは `{{ }}` で括られた部分を式として評価します。 10 | 式はその要素自身の $scope のコンテキストで評価されます。 11 | 要素に $scope がない場合、親 $scope をたどり評価されます。 12 | もし $rootScope までたどってもプロパティが発見できなければ何も出力しません。 13 | 14 | テンプレート内では未定義のプロパティを参照したり未定義の関数を呼びだそうとしてもエラーを送出しません。 15 | この仕組みは `x || ''` や `y && y()` のような記述をしなくてよいという利便性をもたらします。 16 | 17 |
18 | 19 | ## オートバインディング 20 | テンプレート構文の式は $scope のプロパティが変更されるたび再評価されます。 21 | 開発者は基本的にモデルの変更を手動でビューに通知する必要はありません。 22 | 23 |
24 | -------------------------------------------------------------------------------- /todo/step00/article.html: -------------------------------------------------------------------------------- 1 |

このチュートリアルは AngularJS を使って帳票アプリをつくる過程を {[ group.articles.length - 1 ]} のステップにわけ、段階的に AngularJS の使い方を学習します。

2 |

アプリケーションの概要

3 |

作成する ToDo アプリの概要は以下のとおりです。

4 |

扱うデータ

5 | 9 |

アプリが行うこと

10 | 18 |

アプリが行わないこと

19 | 22 |

完成予定のアプリのイメージ

23 | 24 | -------------------------------------------------------------------------------- /guide/scope/demo1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 | Hello!! 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /guide/filter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |

{{ '1234567890abcdefghijk' | omit:10:'・・・・・・' }}

13 |

{{ [0,1,2,3,4,5,6,7,8,9] | odd }}

14 |

{{ [0,1,2,3,4,5,6,7,8,9] | sum }}

15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /guide/scope/demo1.js: -------------------------------------------------------------------------------- 1 | angular.module('Example', ['LocationBar']) 2 | .directive('countup', [function () { 3 | function up(scope) { 4 | scope.count++; 5 | if (scope.count === scope.max) { 6 | scope.count = 0; 7 | scope.$emit('complate'); 8 | } 9 | } 10 | 11 | return { 12 | template: 'click {{ count }}', 13 | scope: true, 14 | link: function (scope, $el, attrs) { 15 | scope.count = 0; 16 | scope.max = +attrs.countup; 17 | 18 | $el.on('click', function () { 19 | scope.$apply(up); 20 | }); 21 | } 22 | }; 23 | }]) 24 | .controller('Ctrl', ['$scope', function Ctrl($scope) { 25 | $scope.toggle = false; 26 | 27 | $scope.$on('complate', function () { 28 | $scope.toggle = !$scope.toggle; 29 | }); 30 | }]); 31 | -------------------------------------------------------------------------------- /guide/template/article.html: -------------------------------------------------------------------------------- 1 |

役割

2 |

ビューは以下の役割を果たすために使用されるものです。

3 | 8 |

構文

9 |

AngularJS のビューは {{ }} で括られた部分を式として評価します。 10 | 式はその要素自身の $scope のコンテキストで評価されます。 11 | 要素に $scope がない場合、親 $scope をたどり評価されます。 12 | もし $rootScope までたどってもプロパティが発見できなければ何も出力しません。

13 |

テンプレート内では未定義のプロパティを参照したり未定義の関数を呼びだそうとしてもエラーを送出しません。 14 | この仕組みは x || ''y && y() のような記述をしなくてよいという利便性をもたらします。

15 |
16 | 17 |

オートバインディング

18 |

テンプレート構文の式は $scope のプロパティが変更されるたび再評価されます。 19 | 開発者は基本的にモデルの変更を手動でビューに通知する必要はありません。

20 |
21 | 22 | -------------------------------------------------------------------------------- /todo/step08/article.md: -------------------------------------------------------------------------------- 1 | 残っているビジネスロジックは全て今まで使ってきた機能だけで実装できます。 2 | 3 | ```javascript 4 | // 全て完了/未了 5 | $scope.checkAll = function () { 6 | var state = !!$scope.remainingCount; // 未了にするのか完了にするのかの判定 7 | 8 | angular.forEach($scope.todos, function (todo) { 9 | todo.done = state; 10 | }); 11 | }; 12 | 13 | // 完了した ToDo を全て削除 14 | $scope.removeDoneTodo = function () { 15 | $scope.todos = where($scope.todos, $scope.filter.remaining); 16 | }; 17 | 18 | // 任意の ToDo を削除 19 | $scope.removeTodo = function (currentTodo) { 20 | $scope.todos = where($scope.todos, function (todo) { 21 | return currentTodo !== todo; 22 | }); 23 | }; 24 | ``` 25 | 26 | それぞれの関数を ngClick ディレクティブでボタンと結びつければ完了です。 27 | 28 | removeTodo メソッド内で where 関数に対して関数を渡している点に注意してください。 29 | これは単純に関数の戻り値が true の要素だけを抽出する処理です。 30 | 31 |
32 | -------------------------------------------------------------------------------- /guide/template/demo2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 |

{{ count }}

13 |

14 | 15 | 16 |

17 |
18 |

19 |

{{ string }}

20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /guide/filter/script.js: -------------------------------------------------------------------------------- 1 | angular.module('Example', ['LocationBar']) 2 | .filter('omit', [function () { 3 | var defaultLength = 100; 4 | var defaultChar = '…'; 5 | 6 | return function (arg, len, char) { 7 | len = len || defaultLength; 8 | 9 | if (angular.isString(arg) && arg.length > len) { 10 | return arg.slice(0, len) + (char || defaultChar); 11 | } 12 | return arg; 13 | }; 14 | }]) 15 | .filter('odd', [function () { 16 | return function (arg) { 17 | if (!angular.isArray(arg)) { 18 | return arg; 19 | } 20 | 21 | return arg.filter(function (n) { 22 | return n % 2; 23 | }); 24 | }; 25 | }]) 26 | .filter('sum', [function () { 27 | return function (arg) { 28 | if (!angular.isArray(arg)) { 29 | return arg; 30 | } 31 | 32 | return arg.reduce(function (sum, n) { 33 | n = +n; 34 | return sum + (!isNaN(n) ? n : 0); 35 | }, 0); 36 | }; 37 | }]); 38 | -------------------------------------------------------------------------------- /account/step00/article.html: -------------------------------------------------------------------------------- 1 |

このチュートリアルは AngularJS を使って帳票アプリをつくる過程を {[ group.articles.length - 1 ]} のステップにわけ、段階的に AngularJS の使い方を学習します。

2 |

アプリケーションの概要

3 |

作成する帳票アプリの概要は以下のとおりです。

4 |

扱うデータ

5 | 10 |

アプリが行うこと

11 | 16 |

アプリが行わないこと

17 | 21 |

必要なビュー

22 | 27 |

完成予定のアプリのイメージ

28 | 29 | -------------------------------------------------------------------------------- /todo/step06/app.js: -------------------------------------------------------------------------------- 1 | angular.module('App', ['LocationBar']) 2 | .controller('MainController', ['$scope', '$filter', function ($scope, $filter) { 3 | $scope.todos = []; 4 | 5 | $scope.newTitle = ''; 6 | 7 | $scope.addTodo = function () { 8 | $scope.todos.push({ 9 | title: $scope.newTitle, 10 | done: false 11 | }); 12 | 13 | $scope.newTitle = ''; 14 | }; 15 | 16 | $scope.filter = { 17 | done: { done: true }, 18 | remaining: { done: false } 19 | }; 20 | $scope.currentFilter = null; 21 | 22 | $scope.changeFilter = function (filter) { 23 | $scope.currentFilter = filter; 24 | }; 25 | 26 | var where = $filter('filter'); 27 | $scope.$watch('todos', function (todos) { 28 | var length = todos.length; 29 | 30 | $scope.allCount = length; 31 | $scope.doneCount = where(todos, $scope.filter.done).length; 32 | $scope.remainingCount = length - $scope.doneCount; 33 | }, true); 34 | }]); 35 | -------------------------------------------------------------------------------- /todo/step01/article.md: -------------------------------------------------------------------------------- 1 | アプリの作成にあたって最低限必要なものを準備します。 2 | 3 | まず index.html と app.js を用意します。 4 | 作業はこの2つのファイルを編集して行います。 5 | 6 | まず index.html を以下のように編集します。 7 | ```html 8 | 9 | 10 | 11 | 12 | Todo アプリ 13 | 14 | 15 | 16 | 17 | 18 | 19 | ``` 20 | 21 | ## フレームワークを読み込む 22 | 今回は安定版(v1.0.8)ではなく **RC 版(v1.2.0-rc.3)**を使用します。 23 | ソースマップに対応しているので圧縮版で開発しても問題はありません。 24 | 25 | 本来は古い Internet Explorer の為にもう少し色々書く必要がありますが今回は省きます。 26 | 詳しくは[公式サイト](http://docs.angularjs.org/guide/ie)を読みましょう。 27 | 28 | ## モジュールを読み込む 29 | body 要素に `ng-app="App"` を追加します。 30 | この記述で AngularJS はこのページに App モジュールをロードするようになります。 31 | しかしこの時点では App モジュールはまだ存在していないのでコンソールにエラーが出ます。 32 | 33 | app.js を編集し App モジュールを作成しましょう。 34 | 35 | ```javascript 36 | angular.module('App', []); 37 | ``` 38 | -------------------------------------------------------------------------------- /assets/js/location.js: -------------------------------------------------------------------------------- 1 | angular.module('LocationBar', []) 2 | .directive('locationBar', ['$location', function ($location) { 3 | return { 4 | restrict: 'C', 5 | replace: true, 6 | template: 7 | '
' + 8 | '' + 9 | '' + 10 | '' + 11 | '' + 12 | '' + 13 | '' + 14 | '' + 15 | '' + 16 | '' + 17 | '
', 18 | scope: {}, 19 | link: function (scope, $el) { 20 | if (!window.frameElement) { 21 | $el.css('display', 'none'); 22 | return; 23 | } 24 | 25 | scope.$watch(function () { 26 | return $location.absUrl(); 27 | }, function (url) { 28 | scope.url = url; 29 | }); 30 | } 31 | }; 32 | }]); 33 | -------------------------------------------------------------------------------- /account/step04/article.md: -------------------------------------------------------------------------------- 1 | それぞれのビューに対するコントローラを準備しましょう。 2 | 3 | ## ルートにコントローラをセットする 4 | テンプレートはまだ仮値を表示しているだけの状態です。 5 | テンプレートはモデルを参照しデータを表示しなければなりません。 6 | 7 | 各ルートにコントローラをセットし任意のルート訪問時にそのルートのみで扱うモデルを定義できるようにしましょう。 8 | 9 | ### コントローラを準備する 10 | ルート設定は3つなのでコントローラも3つ用意します。 11 | 12 | ```javascript 13 | angular.module('App', ['ngRoute']) 14 | .config([/* 省略 */]) 15 | .controller('SheetListController', [function SheetListController() {/* 一覧用 */}]) 16 | .controller('CreationController', [function CreationController() {/* 作成用 */}]) 17 | .controller('SheetController', [function SheetController() {/* 詳細用 */}]); 18 | ``` 19 | 20 | ### ルートにコントローラをセットする 21 | when メソッドに渡す設定オブジェクトを編集してルートで使用するコントローラを使用します。 22 | 23 | ```javascript 24 | $routeProvider 25 | .when('/', { 26 | templateUrl: 'index-tmpl', 27 | controller: 'SheetListController' 28 | }) 29 | .when('/new', { 30 | templateUrl: 'new-tmpl', 31 | controller: 'CreationController' 32 | }) 33 | .when('/sheet/:id', { 34 | templateUrl: 'sheet-tmpl', 35 | controller: 'SheetController' 36 | }) 37 | ``` 38 | 39 | 設定オブジェクトの controller プロパティにルートで使用するコントローラの名前をセットします。 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 8th713 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /assets/css/example.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 60px; 3 | } 4 | 5 | .location-bar { 6 | position: fixed; 7 | top: 0; 8 | left: 0; 9 | z-index: 10000; 10 | width: 100%; 11 | background-color: #fff; 12 | } 13 | 14 | .location-bar .input-group-addon, 15 | .location-bar .btn { 16 | border-radius: 0; 17 | } 18 | 19 | .location-bar .form-control { 20 | cursor: default; 21 | background-color: #fff; 22 | } 23 | 24 | .ng-invalid { 25 | border-color: #b94a48; 26 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); 27 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); 28 | } 29 | 30 | .ng-invalid:focus { 31 | border-color: #953b39; 32 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #d59392; 33 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #d59392; 34 | } 35 | 36 | /* ToDo app */ 37 | .todo-item .todo-title { 38 | color: #888; 39 | } 40 | 41 | .done .todo-title { 42 | text-decoration: line-through; 43 | } 44 | 45 | /* Account app */ 46 | .table tbody > tr > td { 47 | vertical-align: middle; 48 | } 49 | 50 | .col-md { 51 | width: 25%; 52 | } 53 | 54 | .col-sm { 55 | width: 20%; 56 | } 57 | -------------------------------------------------------------------------------- /guide/service/script.js: -------------------------------------------------------------------------------- 1 | // サービスはこんなことに使えますという説明のためのコードです。 2 | // 悪い実装なので真似しないでください。 3 | 4 | angular.module('Example', ['LocationBar']) 5 | .value('lines', []) 6 | // `getSubtotal` is common business logic. 7 | .factory('getSubtotal', [function () { 8 | return function (line) { 9 | return line.unitPrice * line.count; 10 | }; 11 | }]) 12 | .controller('MainCtrl', ['$scope', 'lines', 'getSubtotal', 13 | function (scope, lines, getSubtotal) { 14 | var line = this; 15 | 16 | function reset() { 17 | line.productName = null; 18 | line.unitPrice = 0; 19 | line.count = 0; 20 | } 21 | 22 | scope.add = function () { 23 | lines.push({ 24 | productName: line.productName, 25 | unitPrice: line.unitPrice, 26 | count: line.count 27 | }); 28 | reset(); 29 | }; 30 | 31 | scope.getSubtotal = getSubtotal; 32 | 33 | reset(); 34 | }]) 35 | .controller('SubCtrl', ['lines', 'getSubtotal', 36 | function (lines, getSubtotal) { 37 | this.lines = lines; 38 | 39 | this.getTotalAmount = function () { 40 | return this.lines.reduce(function (totalAmount, line) { 41 | return totalAmount + getSubtotal(line); 42 | }, 0); 43 | }; 44 | 45 | this.getSubtotal = getSubtotal; 46 | }]); 47 | -------------------------------------------------------------------------------- /guide/directive/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 |
14 |
15 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod 16 | tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, 17 | quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo 18 | consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse 19 | cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non 20 | proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 21 |
22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /assets/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "vars": { 3 | "@font-size-base": "16px", 4 | "@line-height-base": "1.6" 5 | }, 6 | "css": [ 7 | "print.less", 8 | "type.less", 9 | "code.less", 10 | "grid.less", 11 | "tables.less", 12 | "forms.less", 13 | "buttons.less", 14 | "glyphicons.less", 15 | "button-groups.less", 16 | "input-groups.less", 17 | "navs.less", 18 | "navbar.less", 19 | "breadcrumbs.less", 20 | "pagination.less", 21 | "pager.less", 22 | "labels.less", 23 | "badges.less", 24 | "jumbotron.less", 25 | "thumbnails.less", 26 | "alerts.less", 27 | "progress-bars.less", 28 | "media.less", 29 | "list-group.less", 30 | "panels.less", 31 | "wells.less", 32 | "close.less", 33 | "dropdowns.less", 34 | "tooltip.less", 35 | "popovers.less", 36 | "modals.less", 37 | "carousel.less", 38 | "utilities.less", 39 | "responsive-utilities.less", 40 | "component-animations.less" 41 | ], 42 | "js": [ 43 | "alert.js", 44 | "button.js", 45 | "carousel.js", 46 | "dropdown.js", 47 | "modal.js", 48 | "tooltip.js", 49 | "popover.js", 50 | "tab.js", 51 | "affix.js", 52 | "collapse.js", 53 | "scrollspy.js", 54 | "transition.js" 55 | ] 56 | } -------------------------------------------------------------------------------- /guide/filter/article.md: -------------------------------------------------------------------------------- 1 | ## 役割 2 | フィルタは以下の役割を果たすために使用されるものです。 3 | 4 | * ビューにバインドされたデータを加工し表示する。 5 | 6 | ## 使う 7 | ビューのテンプレート構文内で `| filterName` のようにして呼び出します。 8 | 9 | ```html 10 |

{{ name | uppercase }}

11 | ``` 12 | 13 | 変数 name は大文字に変換され出力されます。 14 | 出力が変換されるだけであり変数が書き換わるわけではありません。 15 | 16 | フィルタの中には引数を受け取るれるものも存在します。 17 | `:` に続く部分が引数です。 18 | 19 | ```html 20 |

{{ 0 | date:'yyyy-MM-dd' }}

21 | ``` 22 | 23 | 複数のフィルタを通すこともできます。 24 | 25 | ```html 26 |

{{ [1, 2, 3, 4, 5, 6, 7, 8, 9] | filter:odd | limitTo:2 }}

27 | ``` 28 | 29 | 30 | ## 作る 31 | module オブジェクトの filter メソッドでモジュールに新しいフィルタを定義できます。 32 | 33 | ```javascript 34 | module.filter('omit', [function () { 35 | var defaultLength = 100; 36 | var defaultChar = '…'; 37 | 38 | return function (arg, len, char) { 39 | len = len || defaultLength; 40 | 41 | if (angular.isString(arg) && arg.length > len) { 42 | return arg.slice(0, len) + (char || defaultChar); 43 | } 44 | return arg; 45 | }; 46 | }]); 47 | ``` 48 | 49 | 第一引数がフィルタの名前になります。 50 | 第二引数はアノテーション配列です。 51 | 52 | アノテーション配列の最後にはファクトリ関数を含めます。 53 | ファクトリ関数が返すべきものは第一引数にフィルタリングしたい値、第二引数以降に実行時に渡された引数を受け取り新しい値を返す関数です。 54 | 55 | ファクトリ関数はフィルタがはじめて呼び出された時に一度だけ実行されます。 56 | 2回目以降のフィルタ呼び出し時にはファクトリ関数は実行されません。 57 | 58 |
59 | -------------------------------------------------------------------------------- /guide/terminology/article.md: -------------------------------------------------------------------------------- 1 | ## モデル 2 | アプリケーションが扱うデータとビジネスロジック。 3 | 4 | AngularJS では JavaScript のオブジェクト(配列やプリミティブ値も含む)を $scope オブジェクトのプロパティに代入することでモデルを表現する。 5 | ビューへの通知は $scope オブジェクトが自動でやってくれる。 6 | 7 | ## ビュー 8 | モデルを取り出してユーザーに表示する部分。 9 | 10 | AngularJS では ngApp ディレクティブ以下の DOM がビューになる。 11 | HTML ファイルにテンプレート構文やディレクティブを書いてデータにアクセスする。 12 | 13 | ## コントローラ 14 | モデルを生成する部分。 15 | 16 | AngularJS では コントローラが初期化される時、新しい $scope オブジェクトが作成される。 17 | それを拡張してモデルを作成するのがコントローラの役目。 18 | 19 | サーバーサイド MVC のコントローラとは全くの別物。 20 | 21 | ## $scope 22 | ビューにモデルの変更を伝える機能を持ったオブジェクト。 23 | AngularJS では $scope オブジェクト拡張することでモデルを表現する。 24 | 25 | ## ディレクティブ 26 | HTML に新しい振る舞いを拡張する仕組み。 27 | プレゼンテーションロジック(DOM 操作)を担当する部分。 28 | 29 | AngularJS のコアモジュールには ngBind, ngClass, ngRepeat のような 30 | いろいろな種類のディレクティブが存在する。 31 | もちろん新たなディレクティブを自分で定義することもできる。 32 | 33 | ## フィルタ 34 | ビューにバインドされたデータモデルの表示をフォーマットする機能。 35 | 36 | ## サービス 37 | アプリケーション内で共通の機能を提供する仕組み。 38 | $http(XMLHttpRequest ラッパー)や $location(アプリケーション内のロケーションを取得、変更)など複数のコントローラやディレクティブで使用されるような機能が提供されている。 39 | 40 | 複数のコントローラで使用したいモデルの保存場所として使う場合もある。 41 | 42 | ## アノテーション配列 43 | あるコントローラやサービスが依存しているサービスの名前を列挙した配列。 44 | 配列の最後の要素には関数を含む。 45 | 基本的には module オブジェクトのメソッドに渡すもの。 46 | アノテーション配列を受け取り可能なメソッドは関数を直接受け取ることもできる。 47 | このサイト内での呼び方です。 48 | 49 | ## ファクトリ関数 50 | 何らかの生成物を返す関数。 51 | アノテーション配列の最後に含める関数はたいてい 52 | コンストラクタかファクトリ関数になる。 53 | -------------------------------------------------------------------------------- /account/step01/article.md: -------------------------------------------------------------------------------- 1 | アプリの作成にあたって最低限必要なものを準備します。 2 | 3 | まず index.html と app.js を用意します。 4 | 作業はこの2つのファイルを編集して行います。 5 | 6 | まず index.html を以下のように編集します。 7 | ```html 8 | 9 | 10 | 11 | 12 | 帳票アプリ 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ``` 21 | 22 | ## フレームワークを読み込む 23 | 今回は安定版(v1.0.8)ではなく **RC 版(v1.2.0-rc.3)**を使用します。 24 | ソースマップに対応しているので圧縮版で開発しても問題はありません。 25 | 26 | アプリケーションのルーティングに必要な [angular-route.js](http://docs.angularjs.org/api/ngRoute) も読み込みます。 27 | ルーティング機能は v1.0.8 ではコアモジュールに実装されていました。 28 | v1.2.0 では本体から分離され独立したモジュールになったため追加で読み込みます。 29 | 30 | 本来は古い Internet Explorer の為にもう少し色々書く必要がありますが今回は省きます。 31 | 詳しくは[公式サイト](http://docs.angularjs.org/guide/ie)を読みましょう。 32 | 33 | ## モジュールを読み込む 34 | body 要素に `ng-app="App"` を追加します。 35 | この記述で AngularJS はこのページに App モジュールをロードするようになります。 36 | しかしこの時点では App モジュールはまだ存在していないのでコンソールにエラーが出ます。 37 | 38 | app.js を編集し App モジュールを作成しましょう。 39 | 40 | ```javascript 41 | angular.module('App', ['ngRoute']); 42 | ``` 43 | 44 | App モジュール内でルーティング機能が使えるように [ngRoute](http://docs.angularjs.org/api/ngRoute) モジュールに依存することを明記します。 45 | -------------------------------------------------------------------------------- /guide/terminology/article.html: -------------------------------------------------------------------------------- 1 |

モデル

2 |

アプリケーションが扱うデータとビジネスロジック。

3 |

AngularJS では JavaScript のオブジェクト(配列やプリミティブ値も含む)を $scope オブジェクトのプロパティに代入することでモデルを表現する。 4 | ビューへの通知は $scope オブジェクトが自動でやってくれる。

5 |

ビュー

6 |

モデルを取り出してユーザーに表示する部分。

7 |

AngularJS では ngApp ディレクティブ以下の DOM がビューになる。 8 | HTML ファイルにテンプレート構文やディレクティブを書いてデータにアクセスする。

9 |

コントローラ

10 |

モデルを生成する部分。

11 |

AngularJS では コントローラが初期化される時、新しい $scope オブジェクトが作成される。 12 | それを拡張してモデルを作成するのがコントローラの役目。

13 |

サーバーサイド MVC のコントローラとは全くの別物。

14 |

$scope

15 |

ビューにモデルの変更を伝える機能を持ったオブジェクト。 16 | AngularJS では $scope オブジェクト拡張することでモデルを表現する。

17 |

ディレクティブ

18 |

HTML に新しい振る舞いを拡張する仕組み。 19 | プレゼンテーションロジック(DOM 操作)を担当する部分。

20 |

AngularJS のコアモジュールには ngBind, ngClass, ngRepeat のような 21 | いろいろな種類のディレクティブが存在する。 22 | もちろん新たなディレクティブを自分で定義することもできる。

23 |

フィルタ

24 |

ビューにバインドされたデータモデルの表示をフォーマットする機能。

25 |

サービス

26 |

アプリケーション内で共通の機能を提供する仕組み。 27 | $http(XMLHttpRequest ラッパー)や $location(アプリケーション内のロケーションを取得、変更)など複数のコントローラやディレクティブで使用されるような機能が提供されている。

28 |

複数のコントローラで使用したいモデルの保存場所として使う場合もある。

29 |

アノテーション配列

30 |

あるコントローラやサービスが依存しているサービスの名前を列挙した配列。 31 | 配列の最後の要素には関数を含む。 32 | 基本的には module オブジェクトのメソッドに渡すもの。 33 | アノテーション配列を受け取り可能なメソッドは関数を直接受け取ることもできる。 34 | このサイト内での呼び方です。

35 |

ファクトリ関数

36 |

何らかの生成物を返す関数。 37 | アノテーション配列の最後に含める関数はたいてい 38 | コンストラクタかファクトリ関数になる。

39 | 40 | -------------------------------------------------------------------------------- /todo/step08/article.html: -------------------------------------------------------------------------------- 1 |

残っているビジネスロジックは全て今まで使ってきた機能だけで実装できます。

2 |
// 全て完了/未了
 3 | $scope.checkAll = function () {
 4 |   var state = !!$scope.remainingCount; // 未了にするのか完了にするのかの判定
 5 | 
 6 |   angular.forEach($scope.todos, function (todo) {
 7 |     todo.done = state;
 8 |   });
 9 | };
10 | 
11 | // 完了した ToDo を全て削除
12 | $scope.removeDoneTodo = function () {
13 |   $scope.todos = where($scope.todos, $scope.filter.remaining);
14 | };
15 | 
16 | // 任意の ToDo を削除
17 | $scope.removeTodo = function (currentTodo) {
18 |   $scope.todos = where($scope.todos, function (todo) {
19 |     return currentTodo !== todo;
20 |   });
21 | };
22 |

それぞれの関数を ngClick ディレクティブでボタンと結びつければ完了です。

23 |

removeTodo メソッド内で where 関数に対して関数を渡している点に注意してください。 24 | これは単純に関数の戻り値が true の要素だけを抽出する処理です。

25 |
26 | 27 | -------------------------------------------------------------------------------- /todo/step07/app.js: -------------------------------------------------------------------------------- 1 | angular.module('App', ['LocationBar']) 2 | .controller('MainController', ['$scope', '$filter', function ($scope, $filter) { 3 | $scope.todos = []; 4 | 5 | $scope.newTitle = ''; 6 | 7 | $scope.addTodo = function () { 8 | $scope.todos.push({ 9 | title: $scope.newTitle, 10 | done: false 11 | }); 12 | 13 | $scope.newTitle = ''; 14 | }; 15 | 16 | $scope.filter = { 17 | done: { done: true }, 18 | remaining: { done: false } 19 | }; 20 | $scope.currentFilter = null; 21 | 22 | $scope.changeFilter = function (filter) { 23 | $scope.currentFilter = filter; 24 | }; 25 | 26 | var where = $filter('filter'); 27 | $scope.$watch('todos', function (todos) { 28 | var length = todos.length; 29 | 30 | $scope.allCount = length; 31 | $scope.doneCount = where(todos, $scope.filter.done).length; 32 | $scope.remainingCount = length - $scope.doneCount; 33 | }, true); 34 | 35 | var originalTitle; 36 | $scope.editing = null; 37 | 38 | $scope.editTodo = function (todo) { 39 | originalTitle = todo.title; 40 | $scope.editing = todo; 41 | }; 42 | 43 | $scope.doneEdit = function (todoForm) { 44 | if (todoForm.$invalid) { 45 | $scope.editing.title = originalTitle; 46 | } 47 | $scope.editing = originalTitle = null; 48 | }; 49 | }]) 50 | .directive('mySelect', [function () { 51 | return function (scope, $el, attrs) { 52 | scope.$watch(attrs.mySelect, function (val) { 53 | if (val) { 54 | $el[0].select(); 55 | } 56 | }); 57 | }; 58 | }]); 59 | -------------------------------------------------------------------------------- /guide/directive/script.js: -------------------------------------------------------------------------------- 1 | angular.module('Example', ['LocationBar']) 2 | .directive('myDialog', [function () { 3 | return function (scope, $el, attrs) { 4 | $el.on('click', function () { 5 | window.alert('Hello ' + attrs.myDialog); 6 | }); 7 | }; 8 | }]) 9 | .directive('myDetails', [function () { 10 | return { 11 | template: '
' + 12 | '
' + 13 | '

' + 14 | ' {{ title }}' + 15 | '

' + 16 | '
' + 17 | '
' + 18 | '
', 19 | transclude: true, 20 | scope: { 21 | title: '@myDetails' 22 | }, 23 | link: function (scope) { 24 | function open() { 25 | scope.hidden = false; 26 | scope.icon = 'glyphicon-chevron-down'; 27 | scope.borderWidth = { 28 | 'border-bottom-width' : 1 29 | }; 30 | } 31 | 32 | function close() { 33 | scope.hidden = true; 34 | scope.icon = 'glyphicon-chevron-right'; 35 | scope.borderWidth = { 36 | 'border-bottom-width' : 0 37 | }; 38 | } 39 | 40 | open(); 41 | scope.toggle = function () { 42 | if (scope.hidden) { 43 | open(); 44 | } else { 45 | close(); 46 | } 47 | }; 48 | } 49 | }; 50 | }]); 51 | -------------------------------------------------------------------------------- /todo/step05/article.md: -------------------------------------------------------------------------------- 1 | フィルタを使用しリストの表示を絞り込みましょう。 2 | 3 | ## 表示を加工する 4 | ToDo モデルの状態が変更可能になったので、指定した状態の ToDo だけを表示できるようにしましょう。 5 | 6 | データを変更せずに表示だけを加工する必要があるので li 要素に対してコアモジュールの [filter フィルタ](http://docs.angularjs.org/api/ng.filter:filter)を使用します。 7 | 8 | ```html 9 |
  • 12 | ``` 13 | 14 | filter フィルタはフィルタリング条件となる引数を受け取り、合致する要素のみの配列を返してくれます。 15 | フィルタリング条件引数は currentFilter モデルに代入することにしました。 16 | コントローラ内でフィルタリング条件を格納する $scope.filter モデルとフィルタリングの現在の状態を表す currentFilter モデル、フィルタリングの状態を変更する changeFilter メソッドを作成しましょう。 17 | 18 | ```javascript 19 | // フィルタリング条件モデル 20 | $scope.filter = { 21 | done: { done: true }, // 完了のみ 22 | remaining: { done: false } // 未了のみ 23 | }; 24 | 25 | // 現在のフィルタの状態モデル 26 | $scope.currentFilter = null; 27 | 28 | // フィルタリング条件を変更するメソッド 29 | $scope.changeFilter = function (filter) { 30 | $scope.currentFilter = filter; 31 | }; 32 | ``` 33 | 34 | 初期状態では絞り込みはしないので currentFilter の初期値は null にします。 35 | 36 | 続いてボタンクリックでフィルタリングが実行できるように html を編集しましょう。 37 | 38 | ```html 39 | 41 | 43 | 45 | ``` 46 | 47 | ngClick ディレクティブを使い使用したいフィルタリング条件モデルを指定して changeFilter を実行するようにします。 48 | またボタンはクリックされた時 `.active` になる必要があります。 49 | ngClass ディレクティブで currentFilter とフィルタリング条件モデルを比較するようにしましょう。 50 | 51 |
    52 | -------------------------------------------------------------------------------- /guide/controller/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
    11 |
    12 |
    13 |
    14 |
    15 |
    16 | 17 |
    18 | 19 |
    20 |
    21 |
    22 | 23 |
    24 | 25 |
    26 |
    27 |
    28 |
    29 |
    30 | 31 |
    32 |
    33 |
    34 |
    35 |
    36 |
      37 |
    • {{ user.name }} - {{ user.email }}
    • 38 |
    39 |
    40 |
    41 |
    42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /account/step05/app.js: -------------------------------------------------------------------------------- 1 | angular.module('App', ['ngRoute', 'LocationBar']) 2 | .config(['$routeProvider', function ($routeProvider) { 3 | $routeProvider 4 | .when('/', { 5 | templateUrl: 'index-tmpl', 6 | controller: 'SheetListController' 7 | }) 8 | .when('/new', { 9 | templateUrl: 'new-tmpl', 10 | controller: 'CreationController' 11 | }) 12 | .when('/sheet/:id', { 13 | templateUrl: 'sheet-tmpl', 14 | controller: 'SheetController' 15 | }) 16 | .otherwise({ 17 | redirectTo: '/' 18 | }); 19 | }]) 20 | .controller('SheetListController', [function SheetListController() {/* 一覧用 */}]) 21 | .controller('CreationController', ['$scope', function CreationController($scope) { 22 | function createOrderLine() { 23 | return { 24 | productName: '', 25 | unitPrice: 0, 26 | count: 0 27 | }; 28 | } 29 | 30 | $scope.initialize = function () { 31 | $scope.lines = [createOrderLine()]; 32 | }; 33 | 34 | $scope.addLine = function () { 35 | $scope.lines.push(createOrderLine()); 36 | }; 37 | 38 | $scope.removeLine = function (target) { 39 | var lines = $scope.lines; 40 | var index = lines.indexOf(target); 41 | 42 | if (index !== -1) { 43 | lines.splice(index, 1); 44 | } 45 | }; 46 | 47 | $scope.save = function () {}; 48 | 49 | $scope.getSubtotal = function (orderLine) { 50 | return orderLine.unitPrice * orderLine.count; 51 | }; 52 | 53 | $scope.getTotalAmount = function (lines) { 54 | var totalAmount = 0; 55 | 56 | angular.forEach(lines, function (orderLine) { 57 | totalAmount += $scope.getSubtotal(orderLine); 58 | }); 59 | 60 | return totalAmount; 61 | }; 62 | 63 | $scope.initialize(); 64 | }]) 65 | .controller('SheetController', [function SheetController() {/* 詳細用 */}]); 66 | -------------------------------------------------------------------------------- /components/angular/README.md: -------------------------------------------------------------------------------- 1 | # bower-angular 2 | 3 | This repo is for distribution on `bower`. The source for this module is in the 4 | [main AngularJS repo](https://github.com/angular/angular.js). 5 | Please file issues and pull requests against that repo. 6 | 7 | ## Install 8 | 9 | Install with `bower`: 10 | 11 | ```shell 12 | bower install angular 13 | ``` 14 | 15 | Add a ` 19 | ``` 20 | 21 | ## Documentation 22 | 23 | Documentation is available on the 24 | [AngularJS docs site](http://docs.angularjs.org/). 25 | 26 | ## License 27 | 28 | The MIT License 29 | 30 | Copyright (c) 2010-2012 Google, Inc. http://angularjs.org 31 | 32 | Permission is hereby granted, free of charge, to any person obtaining a copy 33 | of this software and associated documentation files (the "Software"), to deal 34 | in the Software without restriction, including without limitation the rights 35 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 36 | copies of the Software, and to permit persons to whom the Software is 37 | furnished to do so, subject to the following conditions: 38 | 39 | The above copyright notice and this permission notice shall be included in 40 | all copies or substantial portions of the Software. 41 | 42 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 43 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 44 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 45 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 46 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 47 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 48 | THE SOFTWARE. 49 | -------------------------------------------------------------------------------- /guide/template/demo1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
    11 |
    12 |
    13 |
    14 |
    15 |
    式評価
    16 |
    1 + 2 = {{ 1 + 2 }}
    17 |
    18 |
    19 |
    変数の参照
    20 |
    example = {{ example }}
    21 |
    22 |
    23 |
    属性値内での式評価
    24 |
    example
    25 |
    26 |
    27 |
    28 |
    29 |
    変数の参照(MainCtrl)
    30 |
    example = {{ example }}
    31 |
    32 |
    33 |
    継承された変数の参照
    34 |
    example2 = {{ example2 }}
    35 |
    36 |
    37 |
    メソッド呼び出し
    38 |
    square(10) = {{ square(10) }}
    39 |
    40 |
    41 |
    42 |
    43 |
    未定義変数の参照
    44 |
    x = {{ x }}
    45 |
    46 |
    47 |
    未定義関数の呼び出し
    48 |
    y() = {{ y() }}
    49 |
    50 |
    51 |
    未定義変数の関数の呼び出し
    52 |
    x.y() = {{ x.y() }}
    53 |
    54 |
    55 |
    56 |
    57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /guide/scope/demo2.js: -------------------------------------------------------------------------------- 1 | angular.module('Example', ['LocationBar']) 2 | .value('random', function (n) { 3 | n = n || 9; 4 | return Math.round(Math.random() * n); 5 | }) 6 | .controller('Ctrl1', ['$scope', 'random', function (scope, random) { 7 | scope.value = 0; 8 | scope.$watch('value', function (val, old) { 9 | scope.square = val * val; 10 | scope.old = old; 11 | }); 12 | scope.change = function () { 13 | scope.value = random(); 14 | }; 15 | }]) 16 | .controller('Ctrl2', ['$scope', 'random', function (scope, random) { 17 | var n = 0; 18 | scope.$watch(function() { return n; }, function (val, old) { 19 | scope.value = val; 20 | scope.square = val * val; 21 | scope.old = old; 22 | }); 23 | scope.change = function () { 24 | n = random(); 25 | }; 26 | }]) 27 | .controller('Ctrl3', ['$scope', 'random', function (scope, random) { 28 | scope.original = {}; 29 | scope.$watch('original', function (val) { 30 | scope.clone1 = angular.copy(val); 31 | }); 32 | scope.$watch('original', function (val) { 33 | scope.clone2 = angular.copy(val); 34 | }, true); 35 | scope.$watchCollection('original', function (val) { 36 | scope.clone3 = angular.copy(val); 37 | }); 38 | scope.change = function () { 39 | var num = random(); 40 | 41 | scope.original[random(2)] = num * num; 42 | }; 43 | }]) 44 | .controller('Ctrl4', ['$scope', 'random', function (scope, random) { 45 | scope.original = [ 46 | {value: random()} 47 | ]; 48 | scope.$watch('original', function (val) { 49 | scope.clone1 = angular.copy(val); 50 | }); 51 | scope.$watch('original', function (val) { 52 | scope.clone2 = angular.copy(val); 53 | }, true); 54 | scope.$watchCollection('original', function (val) { 55 | scope.clone3 = angular.copy(val); 56 | }); 57 | scope.change = function () { 58 | scope.original[0].value = random(); 59 | }; 60 | }]); 61 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | grunt.initConfig({ 5 | markdown: { 6 | article: { 7 | files: [ 8 | { 9 | expand: true, 10 | src: [ 11 | 'guide/**/*.md', 12 | 'account/**/*.md', 13 | 'todo/**/*.md' 14 | ], 15 | dest: './', 16 | ext: '.html' 17 | } 18 | ], 19 | options: { 20 | template: '_template.html', 21 | markdownOptions: { 22 | sanitize: false, 23 | highlight: 'manual' 24 | } 25 | } 26 | } 27 | }, 28 | esteWatch: { 29 | options: { 30 | dirs: [ 31 | './', 32 | 'assets/**/', 33 | 'guide/**/', 34 | 'account/**/', 35 | 'todo/**/', 36 | '!components/**/', 37 | '!node_modules/**/', 38 | ], 39 | ignoredFiles: [ 40 | 'README.md' 41 | ], 42 | livereload: { 43 | enabled: true, 44 | port: 35729, 45 | extensions: ['js', 'css', 'html'] 46 | } 47 | }, 48 | md: function (filepath) { 49 | var files = [{ 50 | expand: true, 51 | src: filepath, 52 | ext: '.html' 53 | }]; 54 | grunt.config.set('markdown.article.files', files); 55 | return 'markdown:article'; 56 | } 57 | }, 58 | connect: { 59 | app: { 60 | options: { 61 | port: 9000, 62 | livereload: true, 63 | open: 'http://localhost:9000/' 64 | } 65 | } 66 | } 67 | }); 68 | 69 | grunt.loadNpmTasks('grunt-markdown'); 70 | grunt.loadNpmTasks('grunt-contrib-connect'); 71 | grunt.loadNpmTasks('grunt-este-watch'); 72 | 73 | grunt.registerTask('default', ['connect', 'esteWatch']); 74 | }; 75 | -------------------------------------------------------------------------------- /account/step08/article.md: -------------------------------------------------------------------------------- 1 | パスパラメータから任意の帳票を選択し帳票の詳細を表示しましょう。 2 | 3 | ## パス内の変数を取得する 4 | 帳票詳細ページのパスは取得したい帳票の id を持っています。 5 | パスのパラメータは [$routeParams サービス](http://docs.angularjs.org/api/ngRoute.$routeParams) が保持しています。 6 | 7 | ルートの設定で `/sheet/:id` と設定したので条件に一致したパスに訪問した時 $routeParams サービスは `{id: 'パスの :id 部分の文字列'}` を保持しています。 8 | 9 | これを利用して sheets サービスの get メソッドを実行し任意の帳票を取得しましょう。 10 | 11 | ```javascript 12 | .controller('SheetController', ['$scope', '$routeParams', 'sheets', 13 | function SheetController($scope, $params, sheets) { 14 | var sheet = sheets.get($params.id); // 帳票を取得 15 | 16 | angular.extend($scope, sheet); // $scope オブジェクトを sheet で拡張 17 | 18 | $scope.getSubtotal = function (orderLine) {/* 省略 */}; 19 | 20 | $scope.getTotalAmount = function (lines) {/* 省略 */}; 21 | }]); 22 | ``` 23 | 24 | 詳細ページにも小計と合計金額を表示する必要があるので各ビジネスロジックも準備します。 25 | 26 | モデルの準備が完了したらテンプレートを編集します。 27 | 28 | ```html 29 |
    30 |

    帳票詳細 #{{ id }}

    31 |

    作成日時: {{ createdAt | date:'yyyy/MM/dd HH:mm' }}

    32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
    商品名単価個数小計
    {{ orderLine.productName }}{{ orderLine.unitPrice | number }}{{ orderLine.count | number }}{{ getSubtotal(orderLine) | number }}
    合計:{{ getTotalAmount(lines) | number }}
    56 |
    57 |
    58 | 存在しない帳票を参照しています。 新しい帳票を作る 59 |
    60 | ``` 61 | 62 | ngShow/ngHide ディレクティブを使用して帳票が正常に取得できなかった時用の表示も用意しましょう。 63 | 64 |
    65 | -------------------------------------------------------------------------------- /account/step04/article.html: -------------------------------------------------------------------------------- 1 |

    それぞれのビューに対するコントローラを準備しましょう。

    2 |

    ルートにコントローラをセットする

    3 |

    テンプレートはまだ仮値を表示しているだけの状態です。 4 | テンプレートはモデルを参照しデータを表示しなければなりません。

    5 |

    各ルートにコントローラをセットし任意のルート訪問時にそのルートのみで扱うモデルを定義できるようにしましょう。

    6 |

    コントローラを準備する

    7 |

    ルート設定は3つなのでコントローラも3つ用意します。

    8 |
    angular.module('App', ['ngRoute'])
     9 | .config([/* 省略 */])
    10 | .controller('SheetListController', [function SheetListController() {/* 一覧用 */}])
    11 | .controller('CreationController', [function CreationController() {/* 作成用 */}])
    12 | .controller('SheetController', [function SheetController() {/* 詳細用 */}]);
    13 |

    ルートにコントローラをセットする

    14 |

    when メソッドに渡す設定オブジェクトを編集してルートで使用するコントローラを使用します。

    15 |
    $routeProvider
    16 |   .when('/', {
    17 |     templateUrl: 'index-tmpl',
    18 |     controller: 'SheetListController'
    19 |   })
    20 |   .when('/new', {
    21 |     templateUrl: 'new-tmpl',
    22 |     controller: 'CreationController'
    23 |   })
    24 |   .when('/sheet/:id', {
    25 |     templateUrl: 'sheet-tmpl',
    26 |     controller: 'SheetController'
    27 |   })
    28 |

    設定オブジェクトの controller プロパティにルートで使用するコントローラの名前をセットします。

    29 | 30 | -------------------------------------------------------------------------------- /components/angular-route/README.md: -------------------------------------------------------------------------------- 1 | # bower-angular-route 2 | 3 | This repo is for distribution on `bower`. The source for this module is in the 4 | [main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngRoute). 5 | Please file issues and pull requests against that repo. 6 | 7 | ## Install 8 | 9 | Install with `bower`: 10 | 11 | ```shell 12 | bower install angular-route 13 | ``` 14 | 15 | Add a ` 19 | ``` 20 | 21 | And add `ngRoute` as a dependency for your app: 22 | 23 | ```javascript 24 | angular.module('myApp', ['ngRoute']); 25 | ``` 26 | 27 | ## Documentation 28 | 29 | Documentation is available on the 30 | [AngularJS docs site](http://docs.angularjs.org/api/ngRoute). 31 | 32 | ## License 33 | 34 | The MIT License 35 | 36 | Copyright (c) 2010-2012 Google, Inc. http://angularjs.org 37 | 38 | Permission is hereby granted, free of charge, to any person obtaining a copy 39 | of this software and associated documentation files (the "Software"), to deal 40 | in the Software without restriction, including without limitation the rights 41 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 42 | copies of the Software, and to permit persons to whom the Software is 43 | furnished to do so, subject to the following conditions: 44 | 45 | The above copyright notice and this permission notice shall be included in 46 | all copies or substantial portions of the Software. 47 | 48 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 49 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 50 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 51 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 52 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 53 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 54 | THE SOFTWARE. 55 | -------------------------------------------------------------------------------- /guide/controller/article.md: -------------------------------------------------------------------------------- 1 | ## 役割 2 | AngularJS のコントローラは以下の役割を果たすために使用されるものです。 3 | 4 | * $scope オブジェクトにプロパティを追加しモデルを作成する。 5 | * $scope オブジェクトにメソッドを追加しビューから参照できるようにする。 6 | 7 | コントローラはビューに一切依存せずにモデルの操作のみに集中する場所です。 8 | 9 | ## 作る 10 | module インスタンスの controller メソッドでモジュールに新しいコントローラを定義できます。 11 | 12 | ```javascript 13 | module.controller('UserController', ['$scope', function UserController($scope) { 14 | // users model initial value 15 | $scope.users = [ 16 | {name: 'foo', email: 'bar@example.com'} 17 | ]; 18 | 19 | // submit event listener 20 | $scope.addUser = function () { 21 | $scope.users.push({ 22 | name: $scope.name, 23 | email: $scope.email 24 | }); 25 | reset(); 26 | }; 27 | 28 | // reset name and email model 29 | function reset() { 30 | $scope.name = null; 31 | $scope.email = null; 32 | } 33 | 34 | // initialize name and email model 35 | reset(); 36 | }]); 37 | ``` 38 | 39 | 第一引数がコントローラの名前になります。 40 | 第二引数はアノテーション配列です。 41 | 42 | アノテーション配列の最後にはコンストラクタを含めます。 43 | 44 | AngularJS ではグローバルスコープに定義した関数も暗黙的にコントローラとして扱います。 45 | ただしこの方法は推奨されていません。 46 | 必ず module.controller メソッドを使うようにしてください。 47 | 48 | ## 使う 49 | `ng-controller` ディレクティブを使用することでコントローラとビューを関連付けます。 50 | 属性値にコントローラの名前を指定することでディレクティブが解決される時そのコントローラはインスタンス化されます。 51 | 52 | ```html 53 |
    54 |
    55 | 56 | 57 | 58 |
    59 | 62 |
    63 | ``` 64 | 65 |
    66 | **Tip:** 67 | 他にも `$route` サービスから初期化する方法もあります。 68 |
    69 | 70 |
    71 | **Tip:** 72 | v1.2.0 から `as propertyName` 構文がサポートされています。 73 | `as` の後に続くキーワードを $scope のプロパティとして作成しコンストラクタの `this` を代入します。 74 | この構文によりビューからコントローラのコンテキストに速やかにアクセスできます。 75 |
    76 | 77 |
    78 | -------------------------------------------------------------------------------- /todo/step08/app.js: -------------------------------------------------------------------------------- 1 | angular.module('App', ['LocationBar']) 2 | .controller('MainController', ['$scope', '$filter', function ($scope, $filter) { 3 | $scope.todos = []; 4 | 5 | $scope.newTitle = ''; 6 | 7 | $scope.addTodo = function () { 8 | $scope.todos.push({ 9 | title: $scope.newTitle, 10 | done: false 11 | }); 12 | 13 | $scope.newTitle = ''; 14 | }; 15 | 16 | $scope.filter = { 17 | done: { done: true }, 18 | remaining: { done: false } 19 | }; 20 | $scope.currentFilter = null; 21 | 22 | $scope.changeFilter = function (filter) { 23 | $scope.currentFilter = filter; 24 | }; 25 | 26 | var where = $filter('filter'); 27 | $scope.$watch('todos', function (todos) { 28 | var length = todos.length; 29 | 30 | $scope.allCount = length; 31 | $scope.doneCount = where(todos, $scope.filter.done).length; 32 | $scope.remainingCount = length - $scope.doneCount; 33 | }, true); 34 | 35 | var originalTitle; 36 | $scope.editing = null; 37 | 38 | $scope.editTodo = function (todo) { 39 | originalTitle = todo.title; 40 | $scope.editing = todo; 41 | }; 42 | 43 | $scope.doneEdit = function (todoForm) { 44 | if (todoForm.$invalid) { 45 | $scope.editing.title = originalTitle; 46 | } 47 | $scope.editing = originalTitle = null; 48 | }; 49 | 50 | $scope.checkAll = function () { 51 | var state = !!$scope.remainingCount; 52 | 53 | angular.forEach($scope.todos, function (todo) { 54 | todo.done = state; 55 | }); 56 | }; 57 | 58 | $scope.removeDoneTodo = function () { 59 | $scope.todos = where($scope.todos, $scope.filter.remaining); 60 | }; 61 | 62 | $scope.removeTodo = function (currentTodo) { 63 | $scope.todos = where($scope.todos, function (todo) { 64 | return currentTodo !== todo; 65 | }); 66 | }; 67 | }]) 68 | .directive('mySelect', [function () { 69 | return function (scope, $el, attrs) { 70 | scope.$watch(attrs.mySelect, function (val) { 71 | if (val) { 72 | $el[0].select(); 73 | } 74 | }); 75 | }; 76 | }]); 77 | -------------------------------------------------------------------------------- /todo/step02/article.md: -------------------------------------------------------------------------------- 1 | ユーザインタフェースのモックアップを作成しましょう。 2 | 3 | ## 新しい ToDo を作成するフォーム 4 | まずは新しい ToDo を作成するためのフォームが必要です。 5 | 6 | ```html 7 |
    8 | 9 | 10 |
    11 | ``` 12 | 13 | 要件を入力するテキストフィールドと追加を実行するボタンです。 14 | input 要素には required 属性を付けて要件が空の時はフォームを送信できないようにしておきましょう。 15 | 16 | 17 | ## ボタン類 18 | アプリケーションの機能を実行するためのボタンも必要ですね。 19 | 20 | ```html 21 |
    22 | 23 | 24 | 25 | 26 | 27 |
    28 | ``` 29 | 30 | 2 ~ 4 個目のボタンは ToDo の絞り込みボタンです。 31 | 選択中のボタンは active クラスがつくことにしましょう。 32 | span 要素内にはそれぞれの件数を表示しましょう。 33 | 34 | ## ToDo リスト 35 | ToDo を表示するリストも当然必要です。 36 | 未完了の ToDo、完了した ToDo、編集中の ToDo と3種類の状態のモックアップを作成しましょう。 37 | 38 | ### 未完了の ToDo 39 | ```html 40 |
  • 41 |
    42 | 43 | 未了の ToDo 44 | 45 |
    46 |
  • 47 | ``` 48 | 49 | 状態を示すチェックボックスと削除ボタンを持っています。 50 | 51 | 削除ボタンの type 属性は reset にしておきます。 52 | 用途にあっていませんが他のタイプにしてしまうと submit イベントが発生してしまうのでやむを得ない処置です。 53 | 54 | ### 完了した ToDo 55 | ```html 56 |
  • 57 |
    58 | 59 | 完了した ToDo 60 | 61 |
    62 |
  • 63 | ``` 64 | 65 | 完了した ToDo は `.todo-title` に打ち消し線を引きたいので li 要素に done クラスをつけることにしましょう。 66 | 67 | ### 編集中の ToDo 68 | ```html 69 |
  • 70 |
    71 | 72 | 73 |
    74 |
  • 75 | ``` 76 | 77 | 編集中の ToDo は editing クラスがつくことにします。 78 | span 要素と button 要素が削除され input 要素が追加されます。 79 | 80 |
    81 | 82 |
    83 | **Tip:** 84 | プレビューは見栄えのために Bootstrap を使用しています。 85 | その都合上マークアップが多少違います。 86 |
    87 | -------------------------------------------------------------------------------- /assets/js/learn.js: -------------------------------------------------------------------------------- 1 | angular.module('Learn', ['ngRoute', 'Learn.directives', 'Data']) 2 | .config(['$interpolateProvider', '$routeProvider', 3 | function ($interpolateProvider, $routeProvider) { 4 | $interpolateProvider 5 | .startSymbol('{[') 6 | .endSymbol(']}'); 7 | 8 | $routeProvider 9 | .when('/', { 10 | templateUrl: 'start-tmpl' 11 | }) 12 | .when('/:group', { 13 | templateUrl: 'index-tmpl', 14 | controller: 'ListController' 15 | }) 16 | .when('/:group/:id', { 17 | templateUrl: 'article-tmpl', 18 | controller: 'ArticleController' 19 | }) 20 | .otherwise({ 21 | redirectTo: '/' 22 | }); 23 | }]) 24 | .service('finder', ['$routeParams', 'models', 25 | function (params, models) { 26 | function find(arr, id) { 27 | var i = arr.length; 28 | var el; 29 | 30 | while (i--) { 31 | el = arr[i]; 32 | if (el.id === id) { 33 | return el; 34 | } 35 | } 36 | return; 37 | } 38 | 39 | function findGroup() { 40 | return find(models, params.group); 41 | } 42 | 43 | function findArticle() { 44 | var groupName = params.group; 45 | var id = params.id; 46 | var group = findGroup(); 47 | 48 | return { 49 | group: group, 50 | article: find(group.articles, params.id), 51 | url: groupName + '/' + id + '/article.html' 52 | }; 53 | } 54 | 55 | this.findGroup = findGroup; 56 | this.findArticle = findArticle; 57 | }]) 58 | .run(['$rootScope', '$location', 'models', 59 | function MainController(scope, $location, models) { 60 | scope.models = models; 61 | scope.currentGroup = null; 62 | scope.currentPath = $location.path(); 63 | 64 | scope.$on('$routeChangeStart', function (evt, route) { 65 | scope.currentGroup = route.params.group; 66 | scope.currentPath = $location.path(); 67 | }); 68 | }]) 69 | .controller('ListController', ['$scope', 'finder', 70 | function ListController(scope, finder) { 71 | angular.extend(scope, finder.findGroup()); 72 | }]) 73 | .controller('ArticleController', ['$scope', 'finder', 74 | function ArticleController(scope, finder) { 75 | angular.extend(scope, finder.findArticle()); 76 | }]); 77 | -------------------------------------------------------------------------------- /todo/step04/article.md: -------------------------------------------------------------------------------- 1 | 双方向バインディングを理解しユーザーが入力した値を受け取りましょう。 2 | 3 | ## input 要素の値を受け取る 4 | 先ほどのステップで ToDo を作成することが出来るようになりました。 5 | しかし肝心の要件が仮値のままです。 6 | 本来なら要件はフォームの input 要素から取得せねばなりません。 7 | 8 | input 要素の値を表すモデルが存在すれば addTodo メソッドはユーザーが入力した値を参照できそうです。 9 | まず input 要素の値を表すモデルを作成します。 10 | 11 | ```javascript 12 | $scope.newTitle = ''; 13 | ``` 14 | 15 | input 要素ははじめ値を持っている必要がないので初期値は空文字列です。 16 | newTitle モデルは input 要素の値と結びついてユーザーの入力に合わせて値を更新する必要があります。 17 | [ngModel ディレクティブ](http://docs.angularjs.org/api/ng.directive:ngModel)を使用してモデルと要素を結びつけましょう。 18 | 19 | ```html 20 |
    21 | 23 | 24 |
    25 | ``` 26 | 27 | input 要素に対して ngModel ディレクティブの使用を宣言します。 28 | ngModel ディレクティブは要素の値に紐付けたいモデルを受け取ります。 29 | これで input 要素の値が変化した時 newTitle モデルの値も合わせて更新されるようになります。 30 | 31 | 最後に addTodo メソッド内で newTitle モデルを参照し新しく作成する ToDo の要件に代入されるようにしましょう。 32 | 33 | ```javascript 34 | $scope.addTodo = function () { 35 | $scope.todos.push({ 36 | title: $scope.newTitle, 37 | done: false 38 | }); 39 | 40 | $scope.newTitle = ''; 41 | }; 42 | ``` 43 | 44 | 新しい Todo が作成されたら input 要素の値はもう必要ないので newTitle モデルは初期値に戻しておきましょう。 45 | newTitle モデルに代入を行えば input 要素の値もそれに追従します。 46 | 47 | ついでなのでリスト内の ToDo の状態にも ngModel ディレクティブを使用して ToDo モデルの done プロパティと結びつけておきましょう。 48 | 49 | ```html 50 |
  • 51 |
    52 | 53 | {{ todo.title }} 54 | 55 |
    56 |
  • 57 | ``` 58 | 59 | これでリスト内の ToDo モデルもチェックボックスの状態に合わせて更新されます。 60 | 61 | 完了状態の ToDo には done クラスがつくという仕様なので todo.done プロパティの値に合わせてクラスが付与されるようにしましょう。 62 | 63 | ```html 64 |
  • 66 | ``` 67 | 68 | li 要素に対して [ngClass ディレクティブ](http://docs.angularjs.org/api/ng.directive:ngClass)の使用を宣言します。 69 | ngClass ディレクティブは様々な形式の値を受けとれますが今回はマップオブジェクトを使用します。 70 | マップオブジェクトは `{'クラス名': クラスを付与する条件式}` の形式です。 71 | 上記の場合 todo.done プロパティが true なら done クラスが付与されるという意味になります。 72 | 73 |
    74 | -------------------------------------------------------------------------------- /guide/directive/article.md: -------------------------------------------------------------------------------- 1 | ## 役割 2 | ディレクティブは以下の役割を果たすために使用されるものです。 3 | 4 | * 宣言的な HTML の拡張(つまり HTML への機能追加) 5 | 6 | ディレクティブは HTML に新たな振る舞いを与えたり DOM を変更したりします。 7 | 8 | ## 使う 9 | HTML 内で普通にマークアップするだけです。 10 | ディレクティブはタグや属性、クラス、コメントなどの方法で呼び出します。 11 | ただし全てのディレクティブで全ての呼び出し方が使えるわけではありません。 12 | 実際には多くのディレクティブが属性としてしか呼び出せません。 13 | 14 | ```html 15 |
    16 |
    17 |

    18 | 19 | 20 |
    21 |
    22 | ``` 23 | 24 | `ng-` を含むタグ名、属性名、データ属性名、クラス名は全て AngularJS のコアモジュールが提供しているディレクティブです。 25 | 26 | 各ディレクティブがどんな働きを HTML に追加したのか軽く触れます。 27 | 28 | * ngApp 29 | * App モジュールを起動し子孫要素のテンプレートとディレクティブを解析します。 30 | * ngController 31 | * ビューに MainCtrl コントローラをアタッチし子孫要素の式のコンテキストを MainCtrl の $scope と結びつけます。 32 | * ngBind 33 | * $scope.title プロパティの値を自身の textContent に代入します。 34 | * ngClick 35 | * 自身がクリックされると $scope.say 関数が実行されます。 36 | * ngInclude 37 | * $scope.template プロパティの値の外部ファイルを読み込み自身の innerHTML に代入します。 38 | 39 | HTML バリデータをパスするようにしたい場合 `data-` 接頭辞を付けて汎用データ属性としてマークアップすることが可能です。 40 | 41 | ## 作る 42 | module インスタンスの directive メソッドでモジュールに新しいディレクティブを定義できます。 43 | 44 | ```javascript 45 | module.directive('myDialog', [function () { 46 | return function (scope, $el, attrs) { 47 | $el.on('click', function () { 48 | window.alert('Hello ' + attrs.myDialog); 49 | }); 50 | }; 51 | }]); 52 | ``` 53 | 54 | 第一引数がディレクティブの名前になります。 55 | 第二引数はアノテーション配列です。 56 | 57 | アノテーション配列の最後にはファクトリ関数を含めます。 58 | ファクトリ関数が返すべきものはディレクティブの振る舞いを定義した関数(リンク関数)です。 59 | 60 | ファクトリ関数はディレクティブがはじめて呼び出された時に一度だけ実行されます。 61 | 2回目以降のディレクティブ呼び出し時にはファクトリ関数は実行されません。 62 | 63 | ディレクティブの名前はキャメルケースで指定します。 64 | HTML 内ではハイフンケースで呼び出します。 65 | 上記の例では `

    Click me!!

    ` のように呼び出します。 66 | 要素をクリックすると属性値を受け取って アラートに 「Hallo world」と表示します。 67 | 68 | 上記は最も単純化された定義法でありより高度なディレクティブを作成する場合、ファクトリ関数はディレクティブ定義オブジェクトを返すようにします。 69 | 70 | ディレクティブ定義オブジェクトについては [AngularJS: Directives](http://docs.angularjs.org/guide/directive) や [JavaScript - AngularJSのdirectiveとは - Qiita [キータ]](http://qiita.com/grapswiz@github/items/878432cb7e04039b06fb) を参考してください。 71 | 72 |
    73 | -------------------------------------------------------------------------------- /todo/step01/article.html: -------------------------------------------------------------------------------- 1 |

    アプリの作成にあたって最低限必要なものを準備します。

    2 |

    まず index.html と app.js を用意します。 3 | 作業はこの2つのファイルを編集して行います。

    4 |

    まず index.html を以下のように編集します。

    5 |
    <!doctype html>
     6 | <html lang="ja">
     7 | <head>
     8 |   <meta charset="UTF-8">
     9 |   <title>Todo アプリ</title>
    10 | </head>
    11 | <body ng-app="App">
    12 |   <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.3/angular.min.js"></script>
    13 |   <script src="app.js"></script>
    14 | </body>
    15 | </html>
    16 |

    フレームワークを読み込む

    17 |

    今回は安定版(v1.0.8)ではなく RC 版(v1.2.0-rc.3)を使用します。 18 | ソースマップに対応しているので圧縮版で開発しても問題はありません。

    19 |

    本来は古い Internet Explorer の為にもう少し色々書く必要がありますが今回は省きます。 20 | 詳しくは公式サイトを読みましょう。

    21 |

    モジュールを読み込む

    22 |

    body 要素に ng-app="App" を追加します。 23 | この記述で AngularJS はこのページに App モジュールをロードするようになります。 24 | しかしこの時点では App モジュールはまだ存在していないのでコンソールにエラーが出ます。

    25 |

    app.js を編集し App モジュールを作成しましょう。

    26 |
    angular.module('App', []);
    27 | 28 | -------------------------------------------------------------------------------- /guide/filter/article.html: -------------------------------------------------------------------------------- 1 |

    役割

    2 |

    フィルタは以下の役割を果たすために使用されるものです。

    3 | 6 |

    使う

    7 |

    ビューのテンプレート構文内で | filterName のようにして呼び出します。

    8 |
    <p>{{ name | uppercase }}</p>
    9 |

    変数 name は大文字に変換され出力されます。 10 | 出力が変換されるだけであり変数が書き換わるわけではありません。

    11 |

    フィルタの中には引数を受け取るれるものも存在します。 12 | : に続く部分が引数です。

    13 |
    <p>{{ 0 | date:'yyyy-MM-dd' }}</p>
    14 |

    複数のフィルタを通すこともできます。

    15 |
    <p>{{ [1, 2, 3, 4, 5, 6, 7, 8, 9] | filter:odd | limitTo:2 }}</p>
    16 |

    作る

    17 |

    module オブジェクトの filter メソッドでモジュールに新しいフィルタを定義できます。

    18 |
    module.filter('omit', [function () {
    19 |   var defaultLength = 100;
    20 |   var defaultChar = '…';
    21 | 
    22 |   return function (arg, len, char) {
    23 |     len = len || defaultLength;
    24 | 
    25 |     if (angular.isString(arg) && arg.length > len) {
    26 |       return arg.slice(0, len) + (char || defaultChar);
    27 |     }
    28 |     return arg;
    29 |   };
    30 | }]);
    31 |

    第一引数がフィルタの名前になります。 32 | 第二引数はアノテーション配列です。

    33 |

    アノテーション配列の最後にはファクトリ関数を含めます。 34 | ファクトリ関数が返すべきものは第一引数にフィルタリングしたい値、第二引数以降に実行時に渡された引数を受け取り新しい値を返す関数です。

    35 |

    ファクトリ関数はフィルタがはじめて呼び出された時に一度だけ実行されます。 36 | 2回目以降のフィルタ呼び出し時にはファクトリ関数は実行されません。

    37 |
    38 | 39 | -------------------------------------------------------------------------------- /assets/fonts/README_J: -------------------------------------------------------------------------------- 1 | M+ FONTS Copyright (C) 2002-2013 M+ FONTS PROJECT 2 | 3 | - 4 | 5 | README_J 6 | 7 | 8 | 9 | 10 | M+ TESTFLIGHT 11 | 12 | M+ OUTLINE FONTS は 2 種類のかな文字を持つ固定幅和文フォントと 4 種類の 13 | プロポーショナル欧文フォント、3 種類の半角固定幅欧文フォントの組み合わせから 14 | 構成され、それぞれ Thin から Black まで 7 種類(半角固定幅フォントは Bold 15 | まで 5 種類)のウエイトバリエーションがあります。 16 | 17 | 18 | プロポーショナルフォント(和文は全角固定幅、欧文・数字はプロポーショナル) 19 | 20 | M+ 1P mplus-1p-thin.ttf 21 | mplus-1p-light.ttf 22 | mplus-1p-regular.ttf 23 | mplus-1p-medium.ttf 24 | mplus-1p-bold.ttf 25 | mplus-1p-heavy.ttf 26 | mplus-1p-black.ttf 27 | 28 | M+ 2P mplus-2p-thin.ttf 29 | mplus-2p-light.ttf 30 | mplus-2p-regular.ttf 31 | mplus-2p-medium.ttf 32 | mplus-2p-bold.ttf 33 | mplus-2p-heavy.ttf 34 | mplus-2p-black.ttf 35 | 36 | M+ 1C mplus-1c-thin.ttf 37 | mplus-1c-light.ttf 38 | mplus-1c-regular.ttf 39 | mplus-1c-medium.ttf 40 | mplus-1c-bold.ttf 41 | mplus-1c-heavy.ttf 42 | mplus-1c-black.ttf 43 | 44 | M+ 2C mplus-2c-thin.ttf 45 | mplus-2c-light.ttf 46 | mplus-2c-regular.ttf 47 | mplus-2c-medium.ttf 48 | mplus-2c-bold.ttf 49 | mplus-2c-heavy.ttf 50 | mplus-2c-black.ttf 51 | 52 | 53 | 固定幅フォント(和文は全角、欧文・数字は半角の固定幅) 54 | 55 | M+ 1M mplus-1m-thin.ttf 56 | mplus-1m-light.ttf 57 | mplus-1m-regular.ttf 58 | mplus-1m-medium.ttf 59 | mplus-1m-bold.ttf 60 | 61 | M+ 2M mplus-2m-thin.ttf 62 | mplus-2m-light.ttf 63 | mplus-2m-regular.ttf 64 | mplus-2m-medium.ttf 65 | mplus-2m-bold.ttf 66 | 67 | M+ 1MN mplus-1mn-thin.ttf 68 | mplus-1mn-light.ttf 69 | mplus-1mn-regular.ttf 70 | mplus-1mn-medium.ttf 71 | mplus-1mn-bold.ttf 72 | 73 | 74 | 75 | 76 | かな文字と常用漢字、基本的な欧文グリフが揃い、一般的な文章の大半の文字を表示 77 | できるようになりました。不足している文字は他のフォントからの自動補完、 78 | IPAG フォントとの合成などで補うことができます。 79 | M+ OUTLINE FONTS の詳細、お問い合わせなどは以下の URL からお願いします。 80 | 81 | - 82 | 83 | M+ OUTLINE FONTS 84 | http://mplus-fonts.sourceforge.jp/mplus-outline-fonts/ 85 | 86 | mplus-fonts-dev ML 87 | http://lists.sourceforge.jp/mailman/listinfo/mplus-fonts-dev 88 | 89 | M+ FONTS open forum 90 | http://sourceforge.jp/forum/forum.php?forum_id=3403 91 | -------------------------------------------------------------------------------- /assets/fonts/README_E: -------------------------------------------------------------------------------- 1 | M+ FONTS Copyright (C) 2002-2013 M+ FONTS PROJECT 2 | 3 | - 4 | 5 | README_E 6 | 7 | 8 | 9 | 10 | M+ TESTFLIGHT 11 | 12 | The M+ OUTLINE FONTS are distributed with proportional Latin (4 variations), fixed-halfwidth Latin (3 variations) and fixed-fullwidth Japanese (2 Kana variations) character set. 7 weights from Thin to Black are included, but fixed-halfwidth Latin with 5 weights from Thin to Bold. 13 | 14 | 15 | PROPORTIONAL FONTS (proportional Latin and fixed-fullwidth Japanese) 16 | 17 | M+ 1P mplus-1p-thin.ttf 18 | mplus-1p-light.ttf 19 | mplus-1p-regular.ttf 20 | mplus-1p-medium.ttf 21 | mplus-1p-bold.ttf 22 | mplus-1p-heavy.ttf 23 | mplus-1p-black.ttf 24 | 25 | M+ 2P mplus-2p-thin.ttf 26 | mplus-2p-light.ttf 27 | mplus-2p-regular.ttf 28 | mplus-2p-medium.ttf 29 | mplus-2p-bold.ttf 30 | mplus-2p-heavy.ttf 31 | mplus-2p-black.ttf 32 | 33 | M+ 1C mplus-1c-thin.ttf 34 | mplus-1c-light.ttf 35 | mplus-1c-regular.ttf 36 | mplus-1c-medium.ttf 37 | mplus-1c-bold.ttf 38 | mplus-1c-heavy.ttf 39 | mplus-1c-black.ttf 40 | 41 | M+ 2C mplus-2c-thin.ttf 42 | mplus-2c-light.ttf 43 | mplus-2c-regular.ttf 44 | mplus-2c-medium.ttf 45 | mplus-2c-bold.ttf 46 | mplus-2c-heavy.ttf 47 | mplus-2c-black.ttf 48 | 49 | 50 | FIXED-WIDTH FONTS (fixed-halfwidth Latin and fixed-fullwidth Japanese) 51 | 52 | M+ 1M mplus-1m-thin.ttf 53 | mplus-1m-light.ttf 54 | mplus-1m-regular.ttf 55 | mplus-1m-medium.ttf 56 | mplus-1m-bold.ttf 57 | 58 | M+ 2M mplus-2m-thin.ttf 59 | mplus-2m-light.ttf 60 | mplus-2m-regular.ttf 61 | mplus-2m-medium.ttf 62 | mplus-2m-bold.ttf 63 | 64 | M+ 1MN mplus-1mn-thin.ttf 65 | mplus-1mn-light.ttf 66 | mplus-1mn-regular.ttf 67 | mplus-1mn-medium.ttf 68 | mplus-1mn-bold.ttf 69 | 70 | 71 | 72 | 73 | - 74 | 75 | M+ OUTLINE FONTS 76 | http://mplus-fonts.sourceforge.jp/mplus-outline-fonts/ 77 | 78 | mplus-fonts-dev ML 79 | http://lists.sourceforge.jp/mailman/listinfo/mplus-fonts-dev 80 | 81 | M+ FONTS open forum 82 | http://sourceforge.jp/forum/forum.php?forum_id=3403 83 | -------------------------------------------------------------------------------- /guide/service/article.md: -------------------------------------------------------------------------------- 1 | ## 役割 2 | サービスは以下の役割を果たすために使用されるものです。 3 | 4 | * アプリケーション共通のオブジェクトを定義する。 5 | 6 | コントローラやディレクティブ、フィルタなどで共通して使われる値や関数を定義する場所です。 7 | 8 | ## 使う 9 | サービスを使用したいコントローラなどのアノテーション配列にサービス名を列挙します。 10 | 11 | ```javascript 12 | module.controller('Ctrl', ['$scope', '$http', '$timeout', function (scope, $http, $timeout) { 13 | $http.get('/data').success(function (data) { 14 | scope.data = data; 15 | }); 16 | 17 | $timeout(function () { 18 | scope.elapsed = true; 19 | }, 1000); 20 | }]); 21 | ``` 22 | 23 | アノテーション配列の最後の関数がサービスを受け取ります。 24 | 25 | ## 作る 26 | module インスタンスの factory, service, value などのメソッドで新しいサービスを定義できます。 27 | 28 | いずれのメソッドも第一引数にサービスの名前を受け取ります。 29 | factory と service は第二引数にアノテーション配列を受け取ります。 30 | 31 | factory メソッドのアノテーション配列の最後はファクトリ関数です。 32 | ファクトリ関数が返すべきものはサービスとなるオブジェクトです。 33 | ファクトリ関数は作成したサービスがはじめて依存注入された時に一度だけ実行されます。 34 | 2回目以降の依存注入では初回で作成されたサービスが渡されます。 35 | 36 | ```javascript 37 | module.factory('delayOneSecond', ['$timeout', function ($timeout) { 38 | return function (callback) { 39 | return $timeout(callbak, 1000); 40 | }; 41 | }]); 42 | ``` 43 | 44 | service メソッドのアノテーション配列の最後はコンストラクタです。 45 | コンストラクタは作成したサービスがはじめて依存注入された時に一度だけインスタンス化されます。 46 | 2回目以降の依存注入では初回で作成されたインスタンスが渡されます。 47 | 48 | ```javascript 49 | module.service('storage', [function () { 50 | var storage = {}; 51 | 52 | function getLange() { 53 | return Object.keys(storage).length; 54 | } 55 | 56 | this.length = 0; 57 | 58 | this.get = function (key) { 59 | return storage[key]; 60 | }; 61 | 62 | this.getAll = function () { 63 | return angular.copy(storage); 64 | }; 65 | 66 | this.set = function (key, value) { 67 | storage[key] = value; 68 | this.length = getLange(); 69 | }; 70 | 71 | this.remove = function (key) { 72 | delete storage[key]; 73 | this.length = getLange(); 74 | }; 75 | }]); 76 | ``` 77 | 78 | value メソッドの第二引数はサービスそのものを受け取ります。 79 | 80 | ```javascript 81 | module.value('data', [ 82 | {id: 1, name: 'Alice'}, 83 | {id: 2, name: 'Bob'}, 84 | {id: 3, name: 'Charlie'} 85 | ]); 86 | ``` 87 | 88 |
    89 | **Tip:** 90 | config ブロックで設定を変更できるような高度なサービスを作成する方法として provider メソッドも提供されています。 91 |
    92 | 93 |
    94 | **Tip:** 95 | アプリケーション共通の定数定義を作成する手段として constant も提供されています。 96 | constant で作成されたサービスは config ブロックでも取得できます。 97 |
    98 | 99 |
    100 | -------------------------------------------------------------------------------- /account/step06/app.js: -------------------------------------------------------------------------------- 1 | angular.module('App', ['ngRoute', 'LocationBar']) 2 | .config(['$routeProvider', function ($routeProvider) { 3 | $routeProvider 4 | .when('/', { 5 | templateUrl: 'index-tmpl', 6 | controller: 'SheetListController' 7 | }) 8 | .when('/new', { 9 | templateUrl: 'new-tmpl', 10 | controller: 'CreationController' 11 | }) 12 | .when('/sheet/:id', { 13 | templateUrl: 'sheet-tmpl', 14 | controller: 'SheetController' 15 | }) 16 | .otherwise({ 17 | redirectTo: '/' 18 | }); 19 | }]) 20 | .service('sheets', [function () { 21 | this.list = []; 22 | 23 | this.add = function (lines) { 24 | this.list.push({ 25 | id: String(this.list.length + 1), 26 | createdAt: Date.now(), 27 | lines: lines 28 | }); 29 | }; 30 | 31 | this.get = function (id) { 32 | var list = this.list; 33 | var index = list.length; 34 | var sheet; 35 | 36 | while (index--) { 37 | sheet = list[index]; 38 | if (sheet.id === id) { 39 | return sheet; 40 | } 41 | } 42 | return null; 43 | }; 44 | }]) 45 | .controller('SheetListController', ['$scope', 'sheets', 46 | function SheetListController($scope, sheets) { 47 | $scope.list = sheets.list; 48 | }]) 49 | .controller('CreationController', ['$scope', '$location', 'sheets', 50 | function CreationController($scope, $location, sheets) { 51 | function createOrderLine() { 52 | return { 53 | productName: '', 54 | unitPrice: 0, 55 | count: 0 56 | }; 57 | } 58 | 59 | $scope.initialize = function () { 60 | $scope.lines = [createOrderLine()]; 61 | }; 62 | 63 | $scope.addLine = function () { 64 | $scope.lines.push(createOrderLine()); 65 | }; 66 | 67 | $scope.removeLine = function (target) { 68 | var lines = $scope.lines; 69 | var index = lines.indexOf(target); 70 | 71 | if (index !== -1) { 72 | lines.splice(index, 1); 73 | } 74 | }; 75 | 76 | $scope.save = function () { 77 | sheets.add($scope.lines); 78 | $location.path('/'); 79 | }; 80 | 81 | $scope.getSubtotal = function (orderLine) { 82 | return orderLine.unitPrice * orderLine.count; 83 | }; 84 | 85 | $scope.getTotalAmount = function (lines) { 86 | var totalAmount = 0; 87 | 88 | angular.forEach(lines, function (orderLine) { 89 | totalAmount += $scope.getSubtotal(orderLine); 90 | }); 91 | 92 | return totalAmount; 93 | }; 94 | 95 | $scope.initialize(); 96 | }]) 97 | .controller('SheetController', [function SheetController() {/* 詳細用 */}]); 98 | -------------------------------------------------------------------------------- /account/step07/app.js: -------------------------------------------------------------------------------- 1 | angular.module('App', ['ngRoute', 'LocationBar']) 2 | .config(['$routeProvider', function ($routeProvider) { 3 | $routeProvider 4 | .when('/', { 5 | templateUrl: 'index-tmpl', 6 | controller: 'SheetListController' 7 | }) 8 | .when('/new', { 9 | templateUrl: 'new-tmpl', 10 | controller: 'CreationController' 11 | }) 12 | .when('/sheet/:id', { 13 | templateUrl: 'sheet-tmpl', 14 | controller: 'SheetController' 15 | }) 16 | .otherwise({ 17 | redirectTo: '/' 18 | }); 19 | }]) 20 | .service('sheets', [function () { 21 | this.list = []; 22 | 23 | this.add = function (lines) { 24 | this.list.push({ 25 | id: String(this.list.length + 1), 26 | createdAt: Date.now(), 27 | lines: lines 28 | }); 29 | }; 30 | 31 | this.get = function (id) { 32 | var list = this.list; 33 | var index = list.length; 34 | var sheet; 35 | 36 | while (index--) { 37 | sheet = list[index]; 38 | if (sheet.id === id) { 39 | return sheet; 40 | } 41 | } 42 | return null; 43 | }; 44 | }]) 45 | .controller('SheetListController', ['$scope', 'sheets', 46 | function SheetListController($scope, sheets) { 47 | $scope.list = sheets.list; 48 | }]) 49 | .controller('CreationController', ['$scope', '$location', 'sheets', 50 | function CreationController($scope, $location, sheets) { 51 | function createOrderLine() { 52 | return { 53 | productName: '', 54 | unitPrice: 0, 55 | count: 0 56 | }; 57 | } 58 | 59 | $scope.initialize = function () { 60 | $scope.lines = [createOrderLine()]; 61 | }; 62 | 63 | $scope.addLine = function () { 64 | $scope.lines.push(createOrderLine()); 65 | }; 66 | 67 | $scope.removeLine = function (target) { 68 | var lines = $scope.lines; 69 | var index = lines.indexOf(target); 70 | 71 | if (index !== -1) { 72 | lines.splice(index, 1); 73 | } 74 | }; 75 | 76 | $scope.save = function () { 77 | sheets.add($scope.lines); 78 | $location.path('/'); 79 | }; 80 | 81 | $scope.getSubtotal = function (orderLine) { 82 | return orderLine.unitPrice * orderLine.count; 83 | }; 84 | 85 | $scope.getTotalAmount = function (lines) { 86 | var totalAmount = 0; 87 | 88 | angular.forEach(lines, function (orderLine) { 89 | totalAmount += $scope.getSubtotal(orderLine); 90 | }); 91 | 92 | return totalAmount; 93 | }; 94 | 95 | $scope.initialize(); 96 | }]) 97 | .controller('SheetController', [function SheetController() {/* 詳細用 */}]); 98 | -------------------------------------------------------------------------------- /guide/scope/demo2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
    11 |
    12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
    #valueoldsquare
    Ctrl1 (string){{ value }}{{ old }}{{ square }}
    Ctrl2 (function){{ value }}{{ old }}{{ square }}
    39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 |
    #original$watch$watch (objectEquality)$watchCollection
    Ctrl3{{ original }}{{ clone1 }}{{ clone2 }}{{ clone3 }}
    Ctrl4{{ original }}{{ clone1 }}{{ clone2 }}{{ clone3 }}
    70 |
    71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /todo/step06/article.md: -------------------------------------------------------------------------------- 1 | $watch メソッドを使ってモデルの変更時の振る舞いを追加しましょう。 2 | 3 | ## ToDo リストモデルの変更を監視する 4 | 絞り込み表示ができるようになりましたがボタンに表示する件数が仮値のままです。 5 | 正しい件数を表示出来るようにしましょう。 6 | 7 | 未了、完了の件数はリスト内の Todo モデルの総数や状態の変更に合わせて更新する必要があります。 8 | [$scope.$watch](http://docs.angularjs.org/api/ng.$rootScope.Scope#$watch) メソッドを使ってリストが変更された時に正しい件数に更新されるモデルを作成しましょう。 9 | 10 | ```javascript 11 | $scope.$watch('todos', function (todos) { 12 | // todos が増減したり各要素のプロパティが変更された時に実行される 13 | }, true); 14 | ``` 15 | 16 | 配列の増減と配列内の ToDo の done プロパティを監視するため第三引数(objectEquality フラグ)を true に設定します。 17 | これを忘れると関数が実行されるのが todos に新しい値が代入された時だけになってしまいますので注意してください。 18 | 19 | ### コントローラからフィルタを使用する 20 | 未了、完了の件数を得るためにはリストから合致する要素だけを抽出する必要があります。 21 | この処理は先ほどのステップで使用したコアモジュールの filter フィルタが実現していたものと同じです。 22 | 23 | コントローラで特定のフィルタを使用したい場合 [$filter サービス](http://docs.angularjs.org/api/ng.$filter)を使ってフィルタを取得することができます。 24 | アノテーション配列を編集しコンストラクタが $filter サービスを受け取れるようにしましょう。 25 | 26 | ```javascript 27 | .controller('MainController', ['$scope', '$filter', function ($scope, $filter) { 28 | // 省略 29 | 30 | var where = $filter('filter'); // filter フィルタ関数の取得 31 | $scope.$watch('todos', function (todos) { 32 | var length = todos.length; 33 | 34 | $scope.allCount = length; // 総件数モデル 35 | $scope.doneCount = where(todos, $scope.filter.done).length; // 完了件数モデル 36 | $scope.remainingCount = length - $scope.doneCount; // 未了件数モデル 37 | }, true); 38 | }]); 39 | ``` 40 | 41 | filter 関数(変数 where)を利用して完了した ToDo だけの配列を取得しその長さを完了件数モデルとしました。 42 | さらに、総件数から完了件数を引いた値を利用して未了件数モデルに代入します。 43 | 44 | $scope.$watch に登録された関数はコントローラがインスタンス化された時初期化処理として一度実行されるので関数外で各モデルの初期値を定義しておく必要はありません。 45 | 46 | 最後にモックアップを編集して各モデルを表示しましょう。 47 | 48 | ```html 49 | 50 | 51 | 52 | ``` 53 | 54 |
    55 | 56 | ### $watch メソッドを使用しない実装方法 57 | 慣れないうちは $watch は少々扱いづらいので別のアプローチでの実装法も紹介します。 58 | 59 | ```javascript 60 | $scope.getDoneCount = function () { 61 | return where($scope.todos, $scope.filter.done).length; 62 | }; 63 | ``` 64 | 65 | ```html 66 | 67 | 68 | 69 | ``` 70 | 71 | ビューは $scope からの更新通知を受け取ると式を再評価するためこの実装は結果的に $watch メソッドを使った実装と 72 | 同じことになります。 73 | 74 | ただし $watch を使った実装ならば再評価時の動作は単純なプロパティ参照で済むのに対し、この実装は再評価時に必ず関数を実行するため多用するとパフォーマンスが低下する可能性があります。 75 | -------------------------------------------------------------------------------- /guide/module/article.md: -------------------------------------------------------------------------------- 1 | AngularJS でアプリケーションを構築する時最初に必要になるのがモジュールです。 2 | 3 | モジュールはアプリケーションが必要としているパーツの情報を持ったオブジェクトです。 4 | 5 | AngularJS ではアプリケーションに必要な機能は全てモジュールのメソッドを使って定義します。 6 | 7 | ## 作る 8 | モジュールを作成するには angular.module メソッドを使用します。 9 | 10 | ```javascript 11 | var app = angular.module('App', []); 12 | ``` 13 | 14 | 第一引数はアプリケーションの名前です。 15 | 第二引数の配列には作成するモジュールが依存している外部モジュールの名前を列挙します。 16 | 依存するモジュールがひとつも存在しない場合でも第二引数を省略することは出来ません。 17 | module メソッドは第二引数が省略されると作成済みモジュールを取得するゲッターとして機能します。 18 | 19 | ## 使う 20 | AngularJS は DOM の準備が終わる(DOMContentLoaded)と ng-app 属性を持った要素を探し出し属性値で指定されたモジュールをロードしてアプリケーションを開始します。 21 | 22 | ```html 23 | 24 | 25 | ``` 26 | 27 | ## モジュールの依存解決 28 | モジュールは別のモジュールに定義された機能を利用することが出来ます。 29 | 以下の例では App モジュールは自身に定義された機能以外に Sub モジュールに定義された機能も使用することが出来ます。 30 | 31 | ```javascript 32 | // sub.js 33 | angular.module('Sub', []); 34 | ``` 35 | ```javascript 36 | // app.js 37 | angular.module('App', ['Sub']); 38 | ``` 39 | 40 | 上記のコードではモジュールは別々のファイルに記述されています。 41 | この時 sub.js は app.js より先に読み込まれている**必要がありません**。 42 | どちらが先に読み込まれても正しく動作します。 43 | 44 | モジュールの依存解決はファイルがロードされた時ではなく、モジュールがロードされるときに行われます。 45 | 46 | ## モジュールの設定を変更する 47 | モジュールで使用するアプリケーション共通の機能(サービス)の設定を変更するためには config メソッドを使用します。 48 | 49 | ```javascript 50 | angular.module('App', []) 51 | .config(['$interpolateProvider', '$locationProvider', function ($interpolateProvider, $locationProvider) { 52 | // テンプレート構文を // ~ // に変更 53 | $interpolateProvider.startSymbol('//').endSymbol('//'); 54 | 55 | // $location サービスの振る舞いを History API を使用するものに変更 56 | $locationProvider.html5Mode(true); 57 | }]); 58 | ``` 59 | 60 | config メソッドはアノテーション配列を受け取ります。 61 | 設定関数はモジュールがロードされたときに実行されます。実行タイミングはサービスが初期化されるよりも前であることに注意してください。 62 | 63 |
    64 | **Tip:** 65 | アノテーション配列については依存関係の注釈付けで説明しています。 66 |
    67 | 68 | 69 | ## モジュールにスタートアップ処理を与える 70 | モジュールのロード完了直後に実行する必要がある処理を作成するには run メソッドを使用します。 71 | 72 | ```javascript 73 | angular.module('App', []) 74 | .run(['$rootScope', function (scope) { 75 | scope.$on('notification', function (evt, text) { 76 | // ... 77 | }); 78 | }]); 79 | ``` 80 | run メソッドはアノテーション配列を受け取ります。 81 | 関数はモジュールがロードされ設定処理が終わった直後に実行されます。 82 | config メソッドと違い関数はサービスを受け取ることができます。 83 | 84 | ## モジュールに機能を定義する 85 | モジュールにコントローラやサービスを定義する方法は各ガイドを参照してください。 86 | 87 | ## モジュールの作成からアプリケーション開始までの流れ 88 | 1. js ファイルがロードされます。 89 | 2. angular.module でモジュールが作成されます。 90 | 3. module オブジェクトのメソッドで機能が定義されます。 91 | この時点では引数として渡した関数は全て実行されません。 92 | 4. DOMContentLoaded のタイミングで フレームワークが ng-app 属性を持った要素を探します。 93 | 5. 要素が見つかったら指定モジュールの依存を解決します。 94 | 6. モジュールの設定関数が(あれば)実行されます。 95 | 7. モジュールのスタートアップ関数が(あれば)実行されます。 96 | 8. モジュールの情報を元にディレクティブが呼び出されます。 97 | -------------------------------------------------------------------------------- /guide/service/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
    11 |
    12 |
    13 |
    14 |
    15 |
    16 | 17 |
    18 | 19 |
    20 |
    21 |
    22 | 23 |
    24 | 25 |
    26 |
    27 |
    28 | 29 |
    30 | 31 |
    32 |
    33 |
    34 | 35 |
    36 |

    {{ getSubtotal(line) }}

    37 |
    38 |
    39 |
    40 |
    41 | 42 |
    43 |
    44 |
    45 |
    46 |
    47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 |
    商品名単価個数小計
    {{ line.productName }}{{ line.unitPrice | number}}{{ line.count | number }}{{ sheet.getSubtotal(line) | number }}
    65 |

    合計: {{ sheet.getTotalAmount() }}

    66 |
    67 |
    68 |
    69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /account/step01/article.html: -------------------------------------------------------------------------------- 1 |

    アプリの作成にあたって最低限必要なものを準備します。

    2 |

    まず index.html と app.js を用意します。 3 | 作業はこの2つのファイルを編集して行います。

    4 |

    まず index.html を以下のように編集します。

    5 |
    <!doctype html>
     6 | <html lang="ja">
     7 | <head>
     8 |   <meta charset="UTF-8">
     9 |   <title>帳票アプリ</title>
    10 | </head>
    11 | <body ng-app="App">
    12 |   <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.3/angular.min.js"></script>
    13 |   <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0-rc.3/angular-route.min.js"></script>
    14 |   <script src="app.js"></script>
    15 | </body>
    16 | </html>
    17 |

    フレームワークを読み込む

    18 |

    今回は安定版(v1.0.8)ではなく RC 版(v1.2.0-rc.3)を使用します。 19 | ソースマップに対応しているので圧縮版で開発しても問題はありません。

    20 |

    アプリケーションのルーティングに必要な angular-route.js も読み込みます。 21 | ルーティング機能は v1.0.8 ではコアモジュールに実装されていました。 22 | v1.2.0 では本体から分離され独立したモジュールになったため追加で読み込みます。

    23 |

    本来は古い Internet Explorer の為にもう少し色々書く必要がありますが今回は省きます。 24 | 詳しくは公式サイトを読みましょう。

    25 |

    モジュールを読み込む

    26 |

    body 要素に ng-app="App" を追加します。 27 | この記述で AngularJS はこのページに App モジュールをロードするようになります。 28 | しかしこの時点では App モジュールはまだ存在していないのでコンソールにエラーが出ます。

    29 |

    app.js を編集し App モジュールを作成しましょう。

    30 |
    angular.module('App', ['ngRoute']);
    31 |

    App モジュール内でルーティング機能が使えるように ngRoute モジュールに依存することを明記します。

    32 | 33 | -------------------------------------------------------------------------------- /todo/step04/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo アプリ 6 | 7 | 8 | 9 | 10 | 11 |
    12 | 13 |
    15 | 18 | 19 |
    21 | 26 | 27 | 29 | 30 |
    31 | 32 |
    33 | 34 |
    35 |
    36 | 37 |
    38 |
    39 | 43 | 47 | 51 |
    52 |
    53 | 54 |
    55 |
    56 | 57 |
    58 | 59 | 88 |
    89 | 90 | 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /account/step07/article.md: -------------------------------------------------------------------------------- 1 | 帳票を保存し一覧を表示できるようになりました。 2 | しかしこの保存機能には明細行に不正な入力がなされていた場合でも保存できてしまうという問題があります。 3 | 4 | 入力の検証を行い不正な帳票は保存できないようにしましょう。 5 | 6 | ## 適切な帳票の条件 7 | 検証機能を実装する前に適切な帳票の条件を決めます。 8 | 9 | * 明細行リストは必ず1つ以上の明細行を持たくてはならない。 10 | * 明細行の各項目はすべて必須項目である。 11 | * 明細行の単価、個数は n >= 0 でなければならない。 12 | * 明細行の単価、個数は 整数でなければならない。 13 | 14 | ## 削除ボタンを無効化する 15 | 条件のうち明細行の数については明細行が1つしか存在しない時は削除ボタンが機能しないようにすることで実装することにします。 16 | 17 | ```html 18 | 20 | ``` 21 | 22 | [ngDisabled ディレクティブ](http://docs.angularjs.org/api/ng.directive:ngDisabled) を使用し明細行リストの長さが2つ未満の時要素を無効化します。 23 | これでクリックイベントが発行されなくなるので明細行リストが空になることを防げます。 24 | 25 | ## 入力を検証する 26 | ユーザーの入力が適切かどうかを検証し不正な入力が存在していた時フォームの送信を禁止しましょう。 27 | 28 | まずフォームパーツに制約条件を加えます。 29 | 30 | AngularJS ではいくつかの制約条件については HTML5 で標準化された制約属性がそのまま使用できます。 31 | 使用可能な制約属性は required, min, max の三種類のみで step, pattern などは使用できません。 32 | 使用できない制約属性の代替として ngPattern, ngMaxlength などの独自の属性が用意されています。 33 | 34 | ```html 35 | 37 | 39 | 41 | ``` 42 | 43 | input 要素はすべて入力必須なので required 属性をセットします。 44 | input[number] 要素は 0 未満を許容しないので min 属性をセットします。 45 | pattern 属性は使用できなので ngPattern 属性を代わりに使用して入力を整数に限定します。 46 | 47 | これらの制約条件は ngModel ディレクティブが検証を行うために使用されます。 48 | 検証を行うのは ngModel ディレクティブなので標準化された検証機能が未実装の古いブラウザでも検証を行うことができます。 49 | 逆に検証機能を備えたモダンブラウザでは互いの検証機能が衝突してしまうため form 要素に novalidate 属性をセットしてブラウザサイドの検証機能を無効化する必要があります。 50 | 51 | 各制約は ngModel ディレクティブによってリアルタイムで検証されます。 52 | 検証結果は form 要素に紐付いた [FormController](http://docs.angularjs.org/api/ng.directive:form.FormController) オブジェクトに格納されています。 53 | 54 | 検証結果を参照する必要がある場合フォームパーツが所属している form 要素に name 属性をつけモデル化する必要があります。 55 | 56 | ```html 57 |
    58 | ``` 59 | 60 | これで現在のコンテキストに sheetForm モデルが作成されます。 61 | 62 | 検証結果を参照しフォームが不正な状態のとき送信ボタンが無効化されるようにしましょう。 63 | フォーム全体の検証結果は FormController の $invalid プロパティの状態を参照することで取得できます。 64 | 65 | ```html 66 | 67 | ``` 68 | 69 | これで検証に合格できないときは送信できないようになりました。 70 | しかし、このままではユーザーはなぜ送信できないのかがわからないので不親切です。 71 | 72 | 入力に不備があることをユーザーに通知する必要があるのでテンプレートに通知領域を加えましょう。 73 | 74 | ```html 75 |
    76 | 空欄が存在しています。 77 | 単価、個数の最小値は 0 です。 78 | 単価、個数は整数で指定してください。 79 |
    80 | ``` 81 | 82 | [ngShow ディレクティブ](http://docs.angularjs.org/api/ng.directive:ngShow) を使用して sheetForm.$invalid が true の時だけ表示される要素を準備します。 83 | 84 | 同様に通知すべきメッセージを3つ用意しそれぞれ必要な時だけ表示されるようにします。 85 | 表示条件は $error プロパティで管理します。 86 | 87 | また検証に合格できなかった要素は ng-invalid というクラスが付与されるので CSS でフォームパーツの見た目を変化させることにします。 88 | 89 |
    90 | -------------------------------------------------------------------------------- /account/step08/app.js: -------------------------------------------------------------------------------- 1 | angular.module('App', ['ngRoute', 'LocationBar']) 2 | .config(['$routeProvider', function ($routeProvider) { 3 | $routeProvider 4 | .when('/', { 5 | templateUrl: 'index-tmpl', 6 | controller: 'SheetListController' 7 | }) 8 | .when('/new', { 9 | templateUrl: 'new-tmpl', 10 | controller: 'CreationController' 11 | }) 12 | .when('/sheet/:id', { 13 | templateUrl: 'sheet-tmpl', 14 | controller: 'SheetController' 15 | }) 16 | .otherwise({ 17 | redirectTo: '/' 18 | }); 19 | }]) 20 | .service('sheets', [function () { 21 | this.list = []; 22 | 23 | this.add = function (lines) { 24 | this.list.push({ 25 | id: String(this.list.length + 1), 26 | createdAt: Date.now(), 27 | lines: lines 28 | }); 29 | }; 30 | 31 | this.get = function (id) { 32 | var list = this.list; 33 | var index = list.length; 34 | var sheet; 35 | 36 | while (index--) { 37 | sheet = list[index]; 38 | if (sheet.id === id) { 39 | return sheet; 40 | } 41 | } 42 | return null; 43 | }; 44 | }]) 45 | .controller('SheetListController', ['$scope', 'sheets', 46 | function SheetListController($scope, sheets) { 47 | $scope.list = sheets.list; 48 | }]) 49 | .controller('CreationController', ['$scope', '$location', 'sheets', 50 | function CreationController($scope, $location, sheets) { 51 | function createOrderLine() { 52 | return { 53 | productName: '', 54 | unitPrice: 0, 55 | count: 0 56 | }; 57 | } 58 | 59 | $scope.initialize = function () { 60 | $scope.lines = [createOrderLine()]; 61 | }; 62 | 63 | $scope.addLine = function () { 64 | $scope.lines.push(createOrderLine()); 65 | }; 66 | 67 | $scope.removeLine = function (target) { 68 | var lines = $scope.lines; 69 | var index = lines.indexOf(target); 70 | 71 | if (index !== -1) { 72 | lines.splice(index, 1); 73 | } 74 | }; 75 | 76 | $scope.save = function () { 77 | sheets.add($scope.lines); 78 | $location.path('/'); 79 | }; 80 | 81 | $scope.getSubtotal = function (orderLine) { 82 | return orderLine.unitPrice * orderLine.count; 83 | }; 84 | 85 | $scope.getTotalAmount = function (lines) { 86 | var totalAmount = 0; 87 | 88 | angular.forEach(lines, function (orderLine) { 89 | totalAmount += $scope.getSubtotal(orderLine); 90 | }); 91 | 92 | return totalAmount; 93 | }; 94 | 95 | $scope.initialize(); 96 | }]) 97 | .controller('SheetController', ['$scope', '$routeParams', 'sheets', 98 | function SheetController($scope, $params, sheets) { 99 | angular.extend($scope, sheets.get($params.id)); 100 | 101 | $scope.getSubtotal = function (orderLine) { 102 | return orderLine.unitPrice * orderLine.count; 103 | }; 104 | 105 | $scope.getTotalAmount = function (lines) { 106 | var totalAmount = 0; 107 | 108 | angular.forEach(lines, function (orderLine) { 109 | totalAmount += $scope.getSubtotal(orderLine); 110 | }); 111 | 112 | return totalAmount; 113 | }; 114 | }]); 115 | -------------------------------------------------------------------------------- /account/step09/app.js: -------------------------------------------------------------------------------- 1 | angular.module('App', ['ngRoute', 'LocationBar']) 2 | .config(['$routeProvider', function ($routeProvider) { 3 | $routeProvider 4 | .when('/', { 5 | templateUrl: 'index-tmpl', 6 | controller: 'SheetListController' 7 | }) 8 | .when('/new', { 9 | templateUrl: 'new-tmpl', 10 | controller: 'CreationController' 11 | }) 12 | .when('/sheet/:id', { 13 | templateUrl: 'sheet-tmpl', 14 | controller: 'SheetController' 15 | }) 16 | .otherwise({ 17 | redirectTo: '/' 18 | }); 19 | }]) 20 | .service('sheets', [function () { 21 | this.list = []; 22 | 23 | this.add = function (lines) { 24 | this.list.push({ 25 | id: String(this.list.length + 1), 26 | createdAt: Date.now(), 27 | lines: lines 28 | }); 29 | }; 30 | 31 | this.get = function (id) { 32 | var list = this.list; 33 | var index = list.length; 34 | var sheet; 35 | 36 | while (index--) { 37 | sheet = list[index]; 38 | if (sheet.id === id) { 39 | return sheet; 40 | } 41 | } 42 | return null; 43 | }; 44 | }]) 45 | .service('counting', function () { 46 | this.getSubtotal = function (orderLine) { 47 | return orderLine.unitPrice * orderLine.count; 48 | }; 49 | 50 | this.getTotalAmount = function (lines) { 51 | var totalAmount = 0; 52 | 53 | angular.forEach(lines, function (orderLine) { 54 | totalAmount += this.getSubtotal(orderLine); 55 | }, this); 56 | 57 | return totalAmount; 58 | }; 59 | }) 60 | .service('sheetAction', ['$location', 'sheets', 61 | function ($location, sheets) { 62 | function createOrderLine() { 63 | return { 64 | productName: '', 65 | unitPrice: 0, 66 | count: 0 67 | }; 68 | } 69 | 70 | this.initialize = function () { 71 | this.lines = [createOrderLine()]; 72 | }; 73 | 74 | this.addLine = function () { 75 | this.lines.push(createOrderLine()); 76 | }; 77 | 78 | this.removeLine = function (target) { 79 | var lines = this.lines; 80 | var index = lines.indexOf(target); 81 | 82 | if (index !== -1) { 83 | lines.splice(index, 1); 84 | } 85 | }; 86 | 87 | this.save = function () { 88 | sheets.add(this.lines); 89 | $location.path('/'); 90 | }; 91 | }]) 92 | .controller('SheetListController', ['$scope', 'sheets', 93 | function SheetListController($scope, sheets) { 94 | $scope.list = sheets.list; 95 | }]) 96 | .controller('CreationController', ['$scope', 'counting', 'sheetAction', 97 | function CreationController($scope, counting, sheetAction) { 98 | angular.extend($scope, sheetAction); 99 | angular.extend($scope, counting); 100 | 101 | $scope.integer = /^\d+$/; 102 | 103 | $scope.$watch('lines.length < 2', function (val) { 104 | $scope.disabledDelBtn = val; 105 | }); 106 | 107 | $scope.initialize(); 108 | }]) 109 | .controller('SheetController', ['$scope', '$routeParams', 'sheets', 'counting', 110 | function SheetController($scope, $params, sheets, counting) { 111 | angular.extend($scope, sheets.get($params.id)); 112 | angular.extend($scope, counting); 113 | }]); 114 | -------------------------------------------------------------------------------- /account/step06/article.md: -------------------------------------------------------------------------------- 1 | 前回のステップで後回しにした保存機能を実装しましょう。 2 | 3 | ## アプリケーション共通のデータを作成する 4 | 帳票の保存先である帳票リストは帳票一覧ページで必要なモデルです。 5 | しかし帳票を作成するには注文明細行のリストが必要であり明細行リストは帳票作成ページのモデルです。 6 | つまり、帳票リストは異なる2つのコントローラから参照できなければなりません。 7 | 8 | コントローラをまたぐデータをサービスで実装しましょう。 9 | 10 | ```javascript 11 | .service('sheets', [function () { 12 | this.list = []; // 帳票リスト 13 | 14 | // 明細行リストを受け取り新しい帳票を作成して帳票リストに加える 15 | this.add = function (lines) { 16 | this.list.push({ 17 | id: String(this.list.length + 1), 18 | createdAt: Date.now(), 19 | lines: lines 20 | }); 21 | }; 22 | 23 | // 任意の id を持った帳票を返す 24 | this.get = function (id) { 25 | var list = this.list; 26 | var index = list.length; 27 | var sheet; 28 | 29 | while (index--) { 30 | sheet = list[index]; 31 | if (sheet.id === id) { 32 | return sheet; 33 | } 34 | } 35 | return null; 36 | }; 37 | }]) 38 | ``` 39 | 40 | 帳票リストを保持する sheets サービスを実装しました。 41 | SheetListController は sheets サービスの list プロパティを参照しモデルを作成できます。 42 | CreationController は add メソッドを使用して明細行リストから帳票を作成し保存することができます。 43 | SheetController は get メソッドを使用して任意の帳票を取得できます。 44 | 45 | ## サービスを使用する 46 | 帳票の保存先が決まったので CreationController を編集し save メソッドを完成させましょう。 47 | 48 | save メソッドは現在の明細行リストから帳票を作成して保存しさらに、帳票一覧ページへ移動するように実装します。 49 | 50 | ```javascript 51 | .controller('CreationController', ['$scope', '$location', 'sheets', 52 | function CreationController($scope, $location, sheets) { 53 | // 省略 54 | 55 | $scope.save = function () { 56 | sheets.add($scope.lines); 57 | $location.path('/'); 58 | }; 59 | 60 | // 省略 61 | }]) 62 | ``` 63 | 64 | パスの変更は [$location サービス](http://docs.angularjs.org/api/ng.$location)を使用しますので $location サービスと sheets サービスが受け取れるように注釈をつけます。 65 | sheet.add メソッドを利用し帳票を作成、$location.path メソッドで帳票一覧ページのパスに移動します。 66 | 67 | さらに移動先の帳票一覧ページに作成した帳票が表示されるよう SheetListController と index-tmpl テンプレートを編集します。 68 | 69 | ```javascript 70 | .controller('SheetListController', ['$scope', 'sheets', 71 | function SheetListController($scope, sheets) { 72 | $scope.list = sheets.list; // 帳票リストモデル 73 | }]) 74 | ``` 75 | 76 | sheets.list を参照して帳票リストをモデル化します。 77 | 78 | ```html 79 | 86 |
    87 | 帳票が存在しません。 新しい帳票を作る 88 |
    89 | ``` 90 | 91 | [ngRereat ディレクティブ](http://docs.angularjs.org/api/ng.directive:ngRepeat)でリスト項目を反復します。 92 | 作成日時は読みやすいように [date フィルタ](http://docs.angularjs.org/api/ng.filter:date)で表示を加工しましょう。 93 | リンクの URL は直接 href 属性にセットせず [ngHref ディレクティブ](http://docs.angularjs.org/api/ng.directive:ngHref)を使用しましょう。 94 | href 属性にテンプレート構文を使用した文字列をセットしてしまうと構文がパースされるまで意図しないアンカーが作成されてしまうなどの問題が生じます。 95 | 96 | さらに帳票リストが空の時は帳票の作成を促すメッセージを表示するようにしましょう。 97 | [ngHide ディレクティブ](http://docs.angularjs.org/api/ng.directive:ngHide) を使用し帳票リストが存在していたら非表示になる要素を用意し、その中にメッセージと作成ページヘのリンクを表示しましょう。 98 | 99 | ついでなので [ngShow ディレクティブ](http://docs.angularjs.org/api/ng.directive:ngShow) ディレクティブも使用して リストが空の時 ul 要素が非表示になるようにしておきましょう。 100 | 101 |
    102 | -------------------------------------------------------------------------------- /todo/step09/app.js: -------------------------------------------------------------------------------- 1 | angular.module('App', ['LocationBar']) 2 | .service('todos', ['$rootScope', '$filter', function ($scope, $filter) { 3 | var list = []; 4 | 5 | $scope.$watch(function () { 6 | return list; 7 | }, function (value) { 8 | $scope.$broadcast('change:list', value); 9 | }, true); 10 | 11 | var where = $filter('filter'); 12 | 13 | var done = { done: true }; 14 | var remaining = { done: false }; 15 | 16 | this.filter = { 17 | done: done, 18 | remaining: remaining 19 | }; 20 | 21 | this.getDone = function () { 22 | return where(list, done); 23 | }; 24 | 25 | this.add = function (title) { 26 | list.push({ 27 | title: title, 28 | done: false 29 | }); 30 | }; 31 | 32 | this.remove = function (currentTodo) { 33 | list = where(list, function (todo) { 34 | return currentTodo !== todo; 35 | }); 36 | }; 37 | 38 | this.removeDone = function () { 39 | list = where(list, remaining); 40 | }; 41 | 42 | this.changeState = function (state) { 43 | angular.forEach(list, function (todo) { 44 | todo.done = state; 45 | }); 46 | }; 47 | }]) 48 | .controller('RegisterController', ['$scope', 'todos', function ($scope, todos) { 49 | $scope.newTitle = ''; 50 | 51 | $scope.addTodo = function () { 52 | todos.add($scope.newTitle); 53 | $scope.newTitle = ''; 54 | }; 55 | }]) 56 | .controller('ToolbarController', ['$scope', 'todos', function ($scope, todos) { 57 | $scope.filter = todos.filter; 58 | 59 | $scope.$on('change:list', function (evt, list) { 60 | var length = list.length; 61 | var doneCount = todos.getDone().length; 62 | 63 | $scope.allCount = length; 64 | $scope.doneCount = doneCount; 65 | $scope.remainingCount = length - doneCount; 66 | }); 67 | 68 | $scope.checkAll = function () { 69 | todos.changeState(!!$scope.remainingCount); 70 | }; 71 | 72 | $scope.changeFilter = function (filter) { 73 | $scope.$emit('change:filter', filter); 74 | }; 75 | 76 | $scope.removeDoneTodo = function () { 77 | todos.removeDone(); 78 | }; 79 | }]) 80 | .controller('TodoListController', ['$scope', 'todos', function ($scope, todos) { 81 | $scope.$on('change:list', function (evt, list) { 82 | $scope.todoList = list; 83 | }); 84 | 85 | var originalTitle; 86 | 87 | $scope.editing = null; 88 | 89 | $scope.editTodo = function (todo) { 90 | originalTitle = todo.title; 91 | $scope.editing = todo; 92 | }; 93 | 94 | $scope.doneEdit = function (todoForm) { 95 | if (todoForm.$invalid) { 96 | $scope.editing.title = originalTitle; 97 | } 98 | $scope.editing = originalTitle = null; 99 | }; 100 | 101 | $scope.removeTodo = function (todo) { 102 | todos.remove(todo); 103 | }; 104 | }]) 105 | .controller('MainController', ['$scope', function ($scope) { 106 | $scope.currentFilter = null; 107 | 108 | $scope.$on('change:filter', function (evt, filter) { 109 | $scope.currentFilter = filter; 110 | }); 111 | }]) 112 | .directive('mySelect', [function () { 113 | return function (scope, $el, attrs) { 114 | scope.$watch(attrs.mySelect, function (val) { 115 | if (val) { 116 | $el[0].select(); 117 | } 118 | }); 119 | }; 120 | }]); 121 | -------------------------------------------------------------------------------- /todo/step02/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo アプリ 6 | 7 | 8 | 9 | 10 | 11 |
    12 | 13 |
    14 | 17 | 18 | 19 | 23 | 24 | 26 | 27 | 28 | 29 |
    30 | 31 |
    32 |
    33 | 34 |
    35 |
    36 | 40 | 44 | 48 |
    49 |
    50 | 51 |
    52 |
    53 | 54 |
    55 | 56 |
      57 |
    • 58 |
      59 | 62 |

      未了の ToDo

      63 | 64 | 66 | 67 |
      68 |
    • 69 |
    • 70 |
      71 | 74 |

      完了した ToDo

      75 | 76 | 78 | 79 |
      80 |
    • 81 |
    • 82 |
      83 | 86 | 90 |
      91 |
    • 92 |
    93 |
    94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /todo/step05/article.html: -------------------------------------------------------------------------------- 1 |

    フィルタを使用しリストの表示を絞り込みましょう。

    2 |

    表示を加工する

    3 |

    ToDo モデルの状態が変更可能になったので、指定した状態の ToDo だけを表示できるようにしましょう。

    4 |

    データを変更せずに表示だけを加工する必要があるので li 要素に対してコアモジュールの filter フィルタを使用します。

    5 |
    <li class="todo-item"
     6 |     ng-repeat="todo in todos | filter:currentFilter"
     7 |     ng-class="{done: todo.done}">
    8 |

    filter フィルタはフィルタリング条件となる引数を受け取り、合致する要素のみの配列を返してくれます。 9 | フィルタリング条件引数は currentFilter モデルに代入することにしました。 10 | コントローラ内でフィルタリング条件を格納する $scope.filter モデルとフィルタリングの現在の状態を表す currentFilter モデル、フィルタリングの状態を変更する changeFilter メソッドを作成しましょう。

    11 |
    // フィルタリング条件モデル
    12 | $scope.filter = {
    13 |   done: { done: true },      // 完了のみ
    14 |   remaining: { done: false } // 未了のみ
    15 | };
    16 | 
    17 | // 現在のフィルタの状態モデル
    18 | $scope.currentFilter = null;
    19 | 
    20 | // フィルタリング条件を変更するメソッド
    21 | $scope.changeFilter = function (filter) {
    22 |   $scope.currentFilter = filter;
    23 | };
    24 |

    初期状態では絞り込みはしないので currentFilter の初期値は null にします。

    25 |

    続いてボタンクリックでフィルタリングが実行できるように html を編集しましょう。

    26 |
    <button ng-click="changeFilter()"
    27 |         ng-class="{active: !currentFilter}">全部 <span>{{ todos.length }}</span></button>
    28 | <button ng-click="changeFilter(filter.remaining)"
    29 |         ng-class="{active: currentFilter == filter.remaining}">未了 <span>n</span></button>
    30 | <button ng-click="changeFilter(filter.done)"
    31 |         ng-class="{active: currentFilter == filter.done}">完了 <span>n</span></button>
    32 |

    ngClick ディレクティブを使い使用したいフィルタリング条件モデルを指定して changeFilter を実行するようにします。 33 | またボタンはクリックされた時 .active になる必要があります。 34 | ngClass ディレクティブで currentFilter とフィルタリング条件モデルを比較するようにしましょう。

    35 |
    36 | 37 | -------------------------------------------------------------------------------- /todo/step03/article.md: -------------------------------------------------------------------------------- 1 | コントローラーを作成しモデルを定義しましょう。 2 | また、定義したモデルをビューから参照してみましょう。 3 | 4 | ## コントローラを作成する 5 | AngularJS でモデルを定義するには新しい $scope オブジェクトが必要になります。 6 | 新しい $scope オブジェクトを作成するにはコントローラが必要です。 7 | 8 | module オブジェクトの contoroller メソッドを使用してコントローラを作成しましょう。 9 | 10 | ```javascript 11 | angular.module('App', []) 12 | .controller('MainController', ['$scope', function ($scope) {}]); 13 | ``` 14 | 15 | コントローラのコンストラクタが $scope オブジェクトを受け取れるように依存の注釈をつけるのを忘れないでください。 16 | 17 | ## ビューが $scope を参照できるようにする 18 | AngularJS でのモデルは $scope オブジェクトのプロパティに代入されたオブジェクト(とプリミティブ)ですのでビューが $scope オブジェクトのプロパティを参照できるようにする必要があります。 19 | 20 | DOM に参照可能な $scope オブジェクトを知らせるために [ngController ディレクティブ](http://docs.angularjs.org/api/ng.directive:ngController)を使用します。 21 | 22 | ```html 23 |
    24 | 25 |
    26 | ``` 27 | 28 | 先ほど作ったモックアップを包む要素を作成し ngController ディレクティブの使用を宣言します。 29 | 属性値に参照したい $scope オブジェクトを持ったコントローラの名前を設定します。 30 | 31 |
    32 | **Tip:** 33 | デザイン上ワッパーが必要ないなら body 要素に対して ngController ディレクティブを使用しても構いません。 34 |
    35 | 36 | ## モデルを定義する 37 | 実装を始める前にアプリケーションが必要としているデータをおさらいします。 38 | 39 | * **ToDo** - 任意の要件と、完了もしくは未完了の状態を持つデータ 40 | * `todo = {title: '要件', done: '状態'}` とします。 41 | * **ToDo リスト** - 複数のToDoを持つデータ 42 | 43 | さらに今回は新しい ToDo を作成しリストに加えるビジネスロジックも必要です。 44 | 45 | まずは ToDo のリストを用意しましょう。 46 | 47 | ```javascript 48 | $scope.todos = []; 49 | ``` 50 | 51 | ビューから参照できるのは $scope オブジェクトのプロパティだけなので todos プロパティに空の配列を設定します。 52 | 53 | 次は新しい ToDo を作成しリストに加えるビジネスロジックを実装しましょう。 54 | 55 | ```javascript 56 | $scope.addTodo = function () { 57 | $scope.todos.push({ 58 | title: Math.random(), 59 | done: false 60 | }); 61 | }; 62 | ``` 63 | 64 | 新しい ToDo なので状態は未完了でいいでしょう。 65 | 要件は仮値としてランダムな数値が入るようにでもしておきましょう。 66 | 67 | ## ビューからモデルを参照する 68 | フォームが送信された時 addTodo が実行されるようにしましょう。 69 | 70 | ```html 71 |
    72 | 73 | 74 |
    75 | ``` 76 | 77 | form 要素に対して [ngSubmit ディレクティブ](http://docs.angularjs.org/api/ng.directive:ngSubmit)の使用を宣言します。 78 | ngSubmit ディレクティブはサブミットイベント発生時に属性値の式を直近の $scope オブジェクトで評価してくれます。 79 | 80 | これでフォームが送信された時 addTodo が実行され新しい ToDo がリストに追加されます。 81 | 82 | 続いて ToDo リストを表示してみましょう。 83 | とりあえず未完了状態の ToDo のモックアップを編集して表示に使いましょう。 84 | 85 | ```html 86 |
  • 87 |
    88 | 89 | {{ todo.title }} 90 | 91 |
    92 |
  • 93 | ``` 94 | 95 | li 要素に対して [ngRepeat ディレクティブ](http://docs.angularjs.org/api/ng.directive:ngRepeat)の使用を宣言します。 96 | ngRepeat ディレクティブは JavaScript の for...in のような、コレクションを列挙する特別な構文を受け取り DOM を複製するディレクティブです。 また、このディレクティブは複製された DOM に新たな $scope オブジェクトを結びつけます。 97 | 98 | $scope オブジェクトはキーワードとなった名前のプロパティ(上記例では todo)を持ちそこには配列の各要素が代入されます。 99 | つまり li 要素の以下の要素は個々の ToDo モデルにアクセス可能ということです。 100 | 101 | 本当に ToDo モデルにアクセス可能か確認するためテンプレート構文 `{{ }}` を使って ToDo モデルの要件を出力してみましょう。 102 | 103 | ついでなので ToDo の総数も表示できるよう該当箇所を編集しましょう。 104 | 105 | ```html 106 | 107 | ``` 108 | 109 | プレビューで実際に動く様子を確認してみましょう。 110 | 111 |
    112 | 113 |
    114 | **Tip:** 115 | プレビューで使用している LocationBar モジュールは iframe 内でもルーティングの状態が見えるようにアドレスバーを表示するためのモジュールです。 116 | 本チュートリアルとは直接関係しないものです。 117 |
    118 | -------------------------------------------------------------------------------- /todo/step05/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo アプリ 6 | 7 | 8 | 9 | 10 | 11 |
    12 | 13 |
    15 | 18 | 19 |
    21 | 26 | 27 | 29 | 30 |
    31 | 32 |
    33 | 34 |
    35 |
    36 | 37 |
    38 |
    39 | 45 | 51 | 57 |
    58 |
    59 | 60 |
    61 |
    62 | 63 |
    64 | 65 | 94 |
    95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /todo/step03/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo アプリ 6 | 7 | 8 | 9 | 10 | 11 |
    12 | 13 |
    15 | 18 | 19 |
    21 | 25 | 26 | 28 | 29 |
    30 | 31 |
    32 | 33 |
    34 |
    35 | 36 |
    37 |
    38 | 42 | 46 | 50 |
    51 |
    52 | 53 |
    54 |
    55 | 56 |
    57 | 58 | 98 |
    99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /todo/step06/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo アプリ 6 | 7 | 8 | 9 | 10 | 11 |
    12 | 13 |
    15 | 18 | 19 |
    21 | 26 | 27 | 29 | 30 |
    31 | 32 |
    33 | 34 |
    35 |
    36 | 37 |
    38 |
    39 | 45 | 51 | 57 |
    58 |
    59 | 60 |
    61 |
    62 | 63 |
    64 | 65 | 94 |
    95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /todo/step07/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo アプリ 6 | 7 | 8 | 9 | 10 | 11 |
    12 | 13 |
    15 | 18 | 19 |
    21 | 26 | 27 | 29 | 30 |
    31 | 32 |
    33 | 34 |
    35 |
    36 | 37 |
    38 |
    39 | 45 | 51 | 57 |
    58 |
    59 | 60 |
    61 |
    62 | 63 |
    64 | 65 | 93 |
    94 | 95 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /components/angular-route/angular-route.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.2.0-rc.3 3 | (c) 2010-2012 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(u,c,A){'use strict';function w(c,s,g,b,d){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",compile:function(l,m,y){return function(p,l,m){function k(){h&&(h.$destroy(),h=null);q&&(d.leave(q),q=null)}function x(){var a=c.current&&c.current.locals,e=a&&a.$template;if(e){var r=p.$new();y(r,function(v){k();v.html(e);d.enter(v,null,l);var f=g(v.contents()),n=c.current;h=n.scope=r;q=v;if(n.controller){a.$scope=h;var p=b(n.controller,a);n.controllerAs&&(h[n.controllerAs]=p); 7 | v.data("$ngControllerController",p);v.children().data("$ngControllerController",p)}f(h);h.$emit("$viewContentLoaded");h.$eval(t);s()})}else k()}var h,q,t=m.onload||"";p.$on("$routeChangeSuccess",x);x()}}}}u=c.module("ngRoute",["ng"]).provider("$route",function(){function u(b,d){return c.extend(new (c.extend(function(){},{prototype:b})),d)}function s(b,c){var l=c.caseInsensitiveMatch,m={originalPath:b,regexp:b},g=m.keys=[];b=b.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?|\*])?/g,function(b, 8 | c,d,k){b="?"===k?k:null;k="*"===k?k:null;g.push({name:d,optional:!!b});c=c||"";return""+(b?"":c)+"(?:"+(b?c:"")+(k&&"(.+?)"||"([^/]+)")+(b||"")+")"+(b||"")}).replace(/([\/$\*])/g,"\\$1");m.regexp=RegExp("^"+b+"$",l?"i":"");return m}var g={};this.when=function(b,d){g[b]=c.extend({reloadOnSearch:!0},d,b&&s(b,d));if(b){var l="/"==b[b.length-1]?b.substr(0,b.length-1):b+"/";g[l]=c.extend({redirectTo:b},s(l,d))}return this};this.otherwise=function(b){this.when(null,b);return this};this.$get=["$rootScope", 9 | "$location","$routeParams","$q","$injector","$http","$templateCache","$sce",function(b,d,l,m,s,p,w,z){function k(){var a=x(),e=t.current;if(a&&e&&a.$$route===e.$$route&&c.equals(a.pathParams,e.pathParams)&&!a.reloadOnSearch&&!q)e.params=a.params,c.copy(e.params,l),b.$broadcast("$routeUpdate",e);else if(a||e)q=!1,b.$broadcast("$routeChangeStart",a,e),(t.current=a)&&a.redirectTo&&(c.isString(a.redirectTo)?d.path(h(a.redirectTo,a.params)).search(a.params).replace():d.url(a.redirectTo(a.pathParams,d.path(), 10 | d.search())).replace()),m.when(a).then(function(){if(a){var b=c.extend({},a.resolve),e,f;c.forEach(b,function(a,e){b[e]=c.isString(a)?s.get(a):s.invoke(a)});c.isDefined(e=a.template)?c.isFunction(e)&&(e=e(a.params)):c.isDefined(f=a.templateUrl)&&(c.isFunction(f)&&(f=f(a.params)),f=z.getTrustedResourceUrl(f),c.isDefined(f)&&(a.loadedTemplateUrl=f,e=p.get(f,{cache:w}).then(function(a){return a.data})));c.isDefined(e)&&(b.$template=e);return m.all(b)}}).then(function(d){a==t.current&&(a&&(a.locals=d, 11 | c.copy(a.params,l)),b.$broadcast("$routeChangeSuccess",a,e))},function(c){a==t.current&&b.$broadcast("$routeChangeError",a,e,c)})}function x(){var a,b;c.forEach(g,function(r,k){var f;if(f=!b){var n=d.path();f=r.keys;var h={};if(r.regexp)if(n=r.regexp.exec(n)){for(var g=1,l=n.length;g役割 2 |

    ディレクティブは以下の役割を果たすために使用されるものです。

    3 | 6 |

    ディレクティブは HTML に新たな振る舞いを与えたり DOM を変更したりします。

    7 |

    使う

    8 |

    HTML 内で普通にマークアップするだけです。 9 | ディレクティブはタグや属性、クラス、コメントなどの方法で呼び出します。 10 | ただし全てのディレクティブで全ての呼び出し方が使えるわけではありません。 11 | 実際には多くのディレクティブが属性としてしか呼び出せません。

    12 |
    <div ng-app="App">
    13 |   <div ng-controller="MainCtrl">
    14 |     <h1 class="ng-bind: title"></h1>
    15 |     <button data-ng-click="say()">Click me!!</button>
    16 |     <ng-include="template"></ng-include>
    17 |   </div>
    18 | </div>
    19 |

    ng- を含むタグ名、属性名、データ属性名、クラス名は全て AngularJS のコアモジュールが提供しているディレクティブです。

    20 |

    各ディレクティブがどんな働きを HTML に追加したのか軽く触れます。

    21 | 43 |

    HTML バリデータをパスするようにしたい場合 data- 接頭辞を付けて汎用データ属性としてマークアップすることが可能です。

    44 |

    作る

    45 |

    module インスタンスの directive メソッドでモジュールに新しいディレクティブを定義できます。

    46 |
    module.directive('myDialog', [function () {
    47 |   return function (scope, $el, attrs) {
    48 |     $el.on('click', function () {
    49 |       window.alert('Hello ' + attrs.myDialog);
    50 |     });
    51 |   };
    52 | }]);
    53 |

    第一引数がディレクティブの名前になります。 54 | 第二引数はアノテーション配列です。

    55 |

    アノテーション配列の最後にはファクトリ関数を含めます。 56 | ファクトリ関数が返すべきものはディレクティブの振る舞いを定義した関数(リンク関数)です。

    57 |

    ファクトリ関数はディレクティブがはじめて呼び出された時に一度だけ実行されます。 58 | 2回目以降のディレクティブ呼び出し時にはファクトリ関数は実行されません。

    59 |

    ディレクティブの名前はキャメルケースで指定します。 60 | HTML 内ではハイフンケースで呼び出します。 61 | 上記の例では <p my-dialog="world">Click me!!</p> のように呼び出します。 62 | 要素をクリックすると属性値を受け取って アラートに 「Hallo world」と表示します。

    63 |

    上記は最も単純化された定義法でありより高度なディレクティブを作成する場合、ファクトリ関数はディレクティブ定義オブジェクトを返すようにします。

    64 |

    ディレクティブ定義オブジェクトについては AngularJS: DirectivesJavaScript - AngularJSのdirectiveとは - Qiita [キータ] を参考してください。

    65 |
    66 | 67 | -------------------------------------------------------------------------------- /account/step03/article.md: -------------------------------------------------------------------------------- 1 | 特定のパスを訪問した時、先ほど作成したテンプレートが表示されるようにしましょう。 2 | 3 | ## AngularJS でのルーティングの基礎 4 | AngularJS のデフォルトのルーティングはハッシュフラグメントでビューを指し示します。 5 | $location サービスの設定を変更することで History API を使用するモードに変更することも可能です。 6 | 7 | 今回はサーバーサイドの実装がありませんのでデフォルトのハッシュフラグメント方式を採用します。 8 | アプリのページ(`./index.html`)のフラグメント部分(`#` 以降の部分)が App モジュールが管理すべきパスになります。 9 | 10 | ## ルートにテンプレートを割り当てる 11 | App モジュールはルーティングに関する設定をまだ持っていません。 12 | config メソッドを使ってアプリケーションの設定を行いましょう。 13 | 14 | ```javascript 15 | angular.module('App', ['ngRoute']) 16 | .config(['$routeProvider', function ($routeProvider) {}]); 17 | ``` 18 | 19 | アプリケーションにルーティングの設定を加えるには ngRoute モジュールが提供する [$routeProvider](http://docs.angularjs.org/api/ngRoute.$routeProvider) が必要になります。 20 | コールバックが $routeProvider を受けとれるように依存の注釈を行います。 21 | 22 | ルートを設定するには $routeProvider.when メソッドを使用します。 23 | 24 | ```javascript 25 | $routeProvider 26 | .when('/', { 27 | templateUrl: 'index-tmpl' 28 | }) 29 | .when('/new', { 30 | templateUrl: 'new-tmpl' 31 | }) 32 | .when('/sheet/:id', { 33 | templateUrl: 'sheet-tmpl' 34 | }); 35 | ``` 36 | 37 | when メソッドは第一引数にルートを割り当てたいパスを、第二引数にルートの設定オブジェクトを受け取ります。 38 | 39 | ### 帳票一覧ビュー 40 | 手始めに `/` に index-tmpl テンプレートを割り当てます。 41 | 42 | オブジェクトの templateUrl プロパティに index-tmpl を設定します。 43 | templateUrl は本来外部ファイルの URL を受け取りますが前回のステップでも説明したとおりテンプレート化した script 要素の id 属性は URL として扱われるので属性値を与えてやれば OK です。 44 | 45 | これで `index.html#/` にアクセスすれば index-tmpl テンプレートが使用されるようになりました。 46 | 47 |
    48 | **Tip:** 49 | フラグメント無しでページにアクセスした時、`/` にルート設定が存在していたら自動的に `#/` が付与されルート設定が適用されます。 50 |
    51 | 52 | ### 帳票作成ビュー 53 | 同様の手順で `/new` に new-tmpl テンプレートを割り当てます。 54 | `index.html#/new` のとき new-tmpl テンプレートが使用されます。 55 | 56 |
    57 | **Tip:** 58 | `/new/` に別の割り当てが無い限り `index/html#/new/` にアクセスしても new-tmpl テンプレートが使用されます(`index/html#/new` にリダイレクトされます)。 59 |
    60 | 61 | ### 帳票詳細ビュー 62 | 帳票詳細ビューは `/sheet/帳票の ID` で表示するようにしましょう。 63 | 帳票の ID 部分は不定の値になります。 64 | 65 | パスに不定の値を含む場合は `:` キーワードを使用します。 66 | `:` キーワードの後に続く語句は $routeParams サービスオブジェクトのプロパティの名前として使用されます。 67 | 68 | 受け取りたい値は帳票の ID なのでそのまま `/sheet/:id` とし sheet-tmpl テンプレートを設定しましょう。 69 | 70 | ## ルートが設定されていないパスにアクセスした時の振る舞いを設定する 71 | 3つのルート設定が完了しました。 72 | しかし、このままではルートが設定されていないパスにアクセスがあった時どのテンプレートも表示されず白紙のページになってしまいます。 73 | 74 | ルートが設定されていないパスにアクセスした時、帳票一覧にリダイレクトされるようにしましょう。 75 | 76 | ```javascript 77 | $routeProvider 78 | .otherwise({ 79 | redirectTo: '/' 80 | }); 81 | ``` 82 | 83 | 個別ルート外のパスにルート設定を行うには otherwise メソッドを使用します。 84 | otherwise メソッドは when メソッドと同じ形式の設定オブジェクトを受け取ります。 85 | 86 | 今回はリダイレクト先を指し示す redirectTo プロパティを設定して `index.html#/` にリダイレクトされるようにします。 87 | 88 | ## テンプレートが注入される要素を用意する 89 | ルート設定が完了したのでモジュールはどのパスの時どのテンプレートを使用すればいいのかがわかるようになりました。 90 | しかし、モジュールは DOM のどの部分にテンプレートを流し込めばいいのかを知りません。 91 | 92 | ngView ディレクティブを使ってテンプレートが注入されるべき要素を指定しましょう。 93 | 94 | ```html 95 |
    96 | ``` 97 | 98 | 入れ物となる要素を作成し `ng-view` 属性を追加します。 99 | ルート設定されたパスにユーザーがアクセスするとこの要素の中身が自動的に指定されたテンプレートに置き換えられます。 100 | 101 | ## グローバルナビゲーションを用意する 102 | 実際にパスが変更された時テンプレートが注入されるかを簡単に確認するためグローバルナビゲーションもマークアップしておきましょう。 103 | 104 | ```javascript 105 | 109 | ``` 110 | 111 | 帳票一覧と帳票作成へのリンクを持っていれば場所やマークアップの仕方はお好みで構いません。 112 | 帳票詳細へのリンクは帳票一覧にすでに含まれているので必要ありません。 113 | 114 |
    115 | 116 |
    117 | **Tip:** 118 | プレビューは見栄えのために Bootstrap を使用しています。 119 | その都合上マークアップが多少違います。 120 |
    121 | 122 |
    123 | **Tip:** 124 | プレビューで使用している LocationBar モジュールは iframe 内でもルーティングの状態が見えるようにアドレスバーを表示するためのモジュールです。 125 | 本チュートリアルとは直接関係しないものです。 126 |
    127 | -------------------------------------------------------------------------------- /todo/step08/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo アプリ 6 | 7 | 8 | 9 | 10 | 11 |
    12 | 13 |
    15 | 18 | 19 |
    21 | 26 | 27 | 29 | 30 |
    31 | 32 |
    33 | 34 |
    35 |
    36 | 38 |
    39 |
    40 | 46 | 52 | 58 |
    59 |
    60 | 62 |
    63 |
    64 | 65 |
    66 | 67 | 96 |
    97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /account/step02/article.md: -------------------------------------------------------------------------------- 1 | 今回作成するのは3つのビューを持ったアプリケーションです。 2 | それぞれのビューで使用するテンプレートを準備しましょう。 3 | 4 | ## テンプレートの基礎 5 | テンプレートは専用の html ファイルを用意するか script 要素内に記述します。 6 | 7 | script 要素をテンプレートとして使用する場合 type 属性に `text/ng-template` を指定します。 8 | 適切な属性を付与することでその要素内の記述を、外部ファイルのように扱ってくれます。 9 | 10 | 1つの script 要素が外部ファイルの1つ分に相当するため script 要素には外部ファイルの url に相当する情報も持たせる必要があります。 11 | URL に相当する情報は要素の `id` 要素に指定します。 12 | 13 |
    14 | **Warning:** 15 | テンプレートとして使用される `script` 要素の `id` は URL に見立てられるものなので `/templates/index.html` のような URL 風の値にしても構いません(公式リファレンスはそういう使い方をしています)。 16 | ただし HTML 4.01 で `id` 要素の値として使用できる記号は `-` `_` `:` `.` だけですので注意が必要です。 17 | HTML 5 では空白文字を禁止する制限以外に言及がないので `/` も使えるはずです。 18 | 互換性などを考慮する場合 `-` `_` 以外の記号を使うのはやめたほうがいいかもしれません。 19 |
    20 | 21 | ## 帳票一覧のテンプレート 22 | id 要素の値は `index-tmpl` にしました。 23 | 24 | アプリケーションはまだデータを持っていないのでデータ部分は仮の値を入れておくことにします。 25 | 26 | リスト項目は詳細画面へのリンクを持っています。 27 | 詳細画面に割り当てる予定の URL に設定しておきましょう。 28 | 29 | ```html 30 | 38 | ``` 39 | 40 | ## 帳票作成のテンプレート 41 | id 要素の値は `new-tmpl` にしました。 42 | 43 | 仮値として注文明細行を2つ持っていることにしておきます。 44 | 45 | 注文明細行は商品名、単価、個数を入力できるようにします。 46 | さらに小計の項目と明細行を削除するボタンも持っていることにしましょう。 47 | 48 | 合計金額を表示する場所も確保しておきましょう。 49 | 50 | そのほかにも「明細行を追加」などいくつかのボタンとリンクがあります。 51 | 52 | ```html 53 | 96 | ``` 97 | 98 | ## 帳票詳細のテンプレート 99 | id 要素の値は `sheet-tmpl` にしました。 100 | 101 | ここでも仮値として注文明細行を2つ持っていることにしておきます。 102 | 表示する項目は作成画面とほぼ同じです。 103 | 104 | このページは作成済みの帳票を閲覧するだけなのでインタラクティブな要素は帳票一覧へ戻るリンクのみです。 105 | 106 | ```html 107 | 141 | ``` 142 | -------------------------------------------------------------------------------- /guide/module/article.html: -------------------------------------------------------------------------------- 1 |

    AngularJS でアプリケーションを構築する時最初に必要になるのがモジュールです。

    2 |

    モジュールはアプリケーションが必要としているパーツの情報を持ったオブジェクトです。

    3 |

    AngularJS ではアプリケーションに必要な機能は全てモジュールのメソッドを使って定義します。

    4 |

    作る

    5 |

    モジュールを作成するには angular.module メソッドを使用します。

    6 |
    var app = angular.module('App', []);
    7 |

    第一引数はアプリケーションの名前です。 8 | 第二引数の配列には作成するモジュールが依存している外部モジュールの名前を列挙します。 9 | 依存するモジュールがひとつも存在しない場合でも第二引数を省略することは出来ません。 10 | module メソッドは第二引数が省略されると作成済みモジュールを取得するゲッターとして機能します。

    11 |

    使う

    12 |

    AngularJS は DOM の準備が終わる(DOMContentLoaded)と ng-app 属性を持った要素を探し出し属性値で指定されたモジュールをロードしてアプリケーションを開始します。

    13 |
    <body ng-app="App">
    14 | </body>
    15 |

    モジュールの依存解決

    16 |

    モジュールは別のモジュールに定義された機能を利用することが出来ます。 17 | 以下の例では App モジュールは自身に定義された機能以外に Sub モジュールに定義された機能も使用することが出来ます。

    18 |
    // sub.js
    19 | angular.module('Sub', []);
    20 |
    // app.js
    21 | angular.module('App', ['Sub']);
    22 |

    上記のコードではモジュールは別々のファイルに記述されています。 23 | この時 sub.js は app.js より先に読み込まれている必要がありません。 24 | どちらが先に読み込まれても正しく動作します。

    25 |

    モジュールの依存解決はファイルがロードされた時ではなく、モジュールがロードされるときに行われます。

    26 |

    モジュールの設定を変更する

    27 |

    モジュールで使用するアプリケーション共通の機能(サービス)の設定を変更するためには config メソッドを使用します。

    28 |
    angular.module('App', [])
    29 | .config(['$interpolateProvider', '$locationProvider', function ($interpolateProvider, $locationProvider) {
    30 |   // テンプレート構文を // ~ // に変更
    31 |   $interpolateProvider.startSymbol('//').endSymbol('//');
    32 | 
    33 |   // $location サービスの振る舞いを History API を使用するものに変更
    34 |   $locationProvider.html5Mode(true);
    35 | }]);
    36 |

    config メソッドはアノテーション配列を受け取ります。 37 | 設定関数はモジュールがロードされたときに実行されます。実行タイミングはサービスが初期化されるよりも前であることに注意してください。

    38 |
    39 | Tip: 40 | アノテーション配列については依存関係の注釈付けで説明しています。 41 |
    42 | 43 | 44 |

    モジュールにスタートアップ処理を与える

    45 |

    モジュールのロード完了直後に実行する必要がある処理を作成するには run メソッドを使用します。

    46 |
    angular.module('App', [])
    47 | .run(['$rootScope', function (scope) {
    48 |   scope.$on('notification', function (evt, text) {
    49 |     // ...
    50 |   });
    51 | }]);
    52 |

    run メソッドはアノテーション配列を受け取ります。 53 | 関数はモジュールがロードされ設定処理が終わった直後に実行されます。 54 | config メソッドと違い関数はサービスを受け取ることができます。

    55 |

    モジュールに機能を定義する

    56 |

    モジュールにコントローラやサービスを定義する方法は各ガイドを参照してください。

    57 |

    モジュールの作成からアプリケーション開始までの流れ

    58 |
      59 |
    1. js ファイルがロードされます。
    2. 60 |
    3. angular.module でモジュールが作成されます。
    4. 61 |
    5. module オブジェクトのメソッドで機能が定義されます。 62 | この時点では引数として渡した関数は全て実行されません。
    6. 63 |
    7. DOMContentLoaded のタイミングで フレームワークが ng-app 属性を持った要素を探します。
    8. 64 |
    9. 要素が見つかったら指定モジュールの依存を解決します。
    10. 65 |
    11. モジュールの設定関数が(あれば)実行されます。
    12. 66 |
    13. モジュールのスタートアップ関数が(あれば)実行されます。
    14. 67 |
    15. モジュールの情報を元にディレクティブが呼び出されます。
    16. 68 |
    69 | 70 | -------------------------------------------------------------------------------- /account/step05/article.md: -------------------------------------------------------------------------------- 1 | 前回のステップまででルート訪問時に使用されるテンプレートとコントローラの準備が整いました。 2 | 3 | 次は CreationController を編集して帳票作成ページで必要なモデルを定義していきましょう。 4 | 5 | ## 帳票作成ページで必要なものをおさらいする 6 | 実装を始める前に必要なものを再確認しましょう。 7 | 8 | * データ 9 | * 注文明細行のリスト 10 | * 個別の注文明細行 - リストの要素 11 | * ビジネスロジック 12 | * 任意の明細行から小計を計算する機能 13 | * リストモデル内の明細行の合計金額を計算する機能 14 | * リストモデルに新しい明細行を追加する機能 15 | * 任意の明細行をリストモデルから取り除く機能 16 | * リストモデルを初期化する機能 17 | * リストモデルから帳票モデルを作成して保存する機能 18 | 19 | ## データを準備する 20 | まずデータの準備から始めましょう。 21 | 22 | ```javascript 23 | .controller('CreationController', ['$scope', function CreationController($scope) { 24 | // 新しい明細行を作成する 25 | function createOrderLine() { 26 | return { 27 | productName: '', 28 | unitPrice: 0, 29 | count: 0 30 | }; 31 | } 32 | 33 | $scope.lines = [createOrderLine()]; // 明細行リスト 34 | }]) 35 | ``` 36 | 37 | モデルは $scope オブジェクトのプロパティとして定義する必要があるので依存の注釈付けを行いコンストラクタが $scope オブジェクトを受け取れるようにします。 38 | 39 | 明細行は行を追加する処理などで作成される必要があるので createOrderLine 関数を準備しておきます。 40 | 新しい明細行が必要なときはこの関数を実行して取得することにします。 41 | 42 | 明細行リストは初期状態で明細行を一つ持っているようにしたいので createOrderLine 関数を使って配列にひとつ要素を加えておきます。 43 | 44 | ## データを表示する 45 | テンプレートを編集して lines モデルが表示されるようにしましょう。 46 | 47 | ```html 48 | 49 | 50 | 51 | 52 | n,nnn 53 | 54 | 55 | ``` 56 | 57 | [ngRepeat ディレクティブ](http://docs.angularjs.org/api/ng.directive:ngRepeat)を使い tr 要素を lines モデルの長さ分だけ反復させます。 58 | 各 input 要素と明細行のプロパティが双方向に結びつくよう [ngModel ディレクティブ](http://docs.angularjs.org/api/ng.directive:ngModel)を使用します。 59 | 60 | ## 振る舞いを加える 61 | リストに行を追加する機能や任意の行を削除する機能などの振る舞いを実装しビューから呼び出してみましょう。 62 | 63 | ```javascript 64 | // リストモデルに新しい明細行を追加する 65 | $scope.addLine = function () { 66 | $scope.lines.push(createOrderLine()); 67 | }; 68 | 69 | // リストモデルを初期化する 70 | $scope.initialize = function () { 71 | $scope.lines = [createOrderLine()]; 72 | }; 73 | 74 | // リストモデルから帳票モデルを作成して保存 75 | $scope.save = function () {}; 76 | 77 | // 任意の明細行をリストモデルから取り除く 78 | $scope.removeLine = function (target) { 79 | var lines = $scope.lines; 80 | var index = lines.indexOf(target); 81 | 82 | if (index !== -1) { 83 | lines.splice(index, 1); 84 | } 85 | }; 86 | ``` 87 | 88 | save メソッドについてはまだ帳票の保存先を用意していないため、後回しにしておきます。 89 | 90 | テンプレートも合わせて編集しましょう。 91 | 92 | ```html 93 | 94 | 95 | ``` 96 | ```html 97 | 98 | ``` 99 | 100 | button 要素に対して [ngClick ディレクティブ](http://docs.angularjs.org/api/ng.directive:ngClick)を使い振る舞いを実行するようにします。 101 | 102 | ```html 103 |
    104 | ``` 105 | 106 | form 要素には [ngSubmit ディレクティブ](http://docs.angularjs.org/api/ng.directive:ngSubmit)を使用します。 107 | 108 | ## 小計、合計を表示する 109 | 小計と合計を算出し出力されるようにしましょう。 110 | 111 | ```javascript 112 | // 引数から小計を計算して返す 113 | $scope.getSubtotal = function (orderLine) { 114 | return orderLine.unitPrice * orderLine.count; 115 | }; 116 | 117 | // リストから合計金額を計算して返す 118 | $scope.getTotalAmount = function (lines) { 119 | var totalAmount = 0; 120 | 121 | angular.forEach(lines, function (orderLine) { 122 | totalAmount += $scope.getSubtotal(orderLine); 123 | }); 124 | 125 | return totalAmount; 126 | }; 127 | ``` 128 | 129 | テンプレートは以下のとおりです。 130 | 131 | ```html 132 | {{ getSubtotal(orderLine) | number }} 133 | ``` 134 | 135 | ```html 136 | {{ getTotalAmount(lines) | number }} 137 | ``` 138 | 139 | [number フィルタ](http://docs.angularjs.org/api/ng.filter:number)を使用して桁区切りで表示されるようにします。 140 | 141 |
    142 | -------------------------------------------------------------------------------- /guide/controller/article.html: -------------------------------------------------------------------------------- 1 |

    役割

    2 |

    AngularJS のコントローラは以下の役割を果たすために使用されるものです。

    3 | 7 |

    コントローラはビューに一切依存せずにモデルの操作のみに集中する場所です。

    8 |

    作る

    9 |

    module インスタンスの controller メソッドでモジュールに新しいコントローラを定義できます。

    10 |
    module.controller('UserController', ['$scope', function UserController($scope) {
    11 |   // users model initial value
    12 |   $scope.users = [
    13 |     {name: 'foo', email: 'bar@example.com'}
    14 |   ];
    15 | 
    16 |   // submit event listener
    17 |   $scope.addUser = function () {
    18 |     $scope.users.push({
    19 |       name: $scope.name,
    20 |       email: $scope.email
    21 |     });
    22 |     reset();
    23 |   };
    24 | 
    25 |   // reset name and email model
    26 |   function reset() {
    27 |     $scope.name = null;
    28 |     $scope.email = null;
    29 |   }
    30 | 
    31 |   // initialize name and email model
    32 |   reset();
    33 | }]);
    34 |

    第一引数がコントローラの名前になります。 35 | 第二引数はアノテーション配列です。

    36 |

    アノテーション配列の最後にはコンストラクタを含めます。

    37 |

    AngularJS ではグローバルスコープに定義した関数も暗黙的にコントローラとして扱います。 38 | ただしこの方法は推奨されていません。 39 | 必ず module.controller メソッドを使うようにしてください。

    40 |

    使う

    41 |

    ng-controller ディレクティブを使用することでコントローラとビューを関連付けます。 42 | 属性値にコントローラの名前を指定することでディレクティブが解決される時そのコントローラはインスタンス化されます。

    43 |
    <div ng-controller="UserController">
    44 |   <form ng-submit="addUser()">
    45 |     <input type="text" ng-model="name">
    46 |     <input type="email" ng-model="email">
    47 |     <input type="submit" valuse="追加">
    48 |   </form>
    49 |   <ul>
    50 |     <li ng-repeat="user in users">{{ user.name }} - {{ user.email }}</li>
    51 |   </ul>
    52 | </div>
    53 |
    54 | Tip: 55 | 他にも $route サービスから初期化する方法もあります。 56 |
    57 | 58 |
    59 | Tip: 60 | v1.2.0 から as propertyName 構文がサポートされています。 61 | as の後に続くキーワードを $scope のプロパティとして作成しコンストラクタの this を代入します。 62 | この構文によりビューからコントローラのコンテキストに速やかにアクセスできます。 63 |
    64 | 65 |
    66 | 67 | -------------------------------------------------------------------------------- /todo/step09/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Todo アプリ 6 | 7 | 8 | 9 | 10 | 11 |
    12 | 13 |
    15 | 18 | 19 | 22 | 27 | 28 | 30 | 31 | 32 | 33 |
    34 | 35 |
    37 |
    38 | 40 |
    41 |
    42 | 48 | 54 | 60 |
    61 |
    62 | 64 |
    65 |
    66 | 67 |
    68 | 69 |
      71 |
    • 74 |
      77 | 80 |

      {{ todo.title }}

      83 | 90 | 92 | 95 | 96 |
      97 |
    • 98 |
    99 |
    100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /todo/step04/article.html: -------------------------------------------------------------------------------- 1 |

    双方向バインディングを理解しユーザーが入力した値を受け取りましょう。

    2 |

    input 要素の値を受け取る

    3 |

    先ほどのステップで ToDo を作成することが出来るようになりました。 4 | しかし肝心の要件が仮値のままです。 5 | 本来なら要件はフォームの input 要素から取得せねばなりません。

    6 |

    input 要素の値を表すモデルが存在すれば addTodo メソッドはユーザーが入力した値を参照できそうです。 7 | まず input 要素の値を表すモデルを作成します。

    8 |
    $scope.newTitle = '';
    9 |

    input 要素ははじめ値を持っている必要がないので初期値は空文字列です。 10 | newTitle モデルは input 要素の値と結びついてユーザーの入力に合わせて値を更新する必要があります。 11 | ngModel ディレクティブを使用してモデルと要素を結びつけましょう。

    12 |
    <form ng-submit="addTodo()">
    13 |   <input type="text" required placeholder="新しい要件を入力".
    14 |          ng-model="newTitle">
    15 |   <button type="submit">追加</button>
    16 | </form>
    17 |

    input 要素に対して ngModel ディレクティブの使用を宣言します。 18 | ngModel ディレクティブは要素の値に紐付けたいモデルを受け取ります。 19 | これで input 要素の値が変化した時 newTitle モデルの値も合わせて更新されるようになります。

    20 |

    最後に addTodo メソッド内で newTitle モデルを参照し新しく作成する ToDo の要件に代入されるようにしましょう。

    21 |
    $scope.addTodo = function () {
    22 |   $scope.todos.push({
    23 |     title: $scope.newTitle,
    24 |     done: false
    25 |   });
    26 | 
    27 |   $scope.newTitle = '';
    28 | };
    29 |

    新しい Todo が作成されたら input 要素の値はもう必要ないので newTitle モデルは初期値に戻しておきましょう。 30 | newTitle モデルに代入を行えば input 要素の値もそれに追従します。

    31 |

    ついでなのでリスト内の ToDo の状態にも ngModel ディレクティブを使用して ToDo モデルの done プロパティと結びつけておきましょう。

    32 |
    <li class="todo-item" ng-repeat="todo in todos">
    33 |   <form>
    34 |     <input type="checkbox" ng-model="todo.done">
    35 |     <span class="todo-title">{{ todo.title }}</span>
    36 |     <button type="reset">削除</button>
    37 |   </form>
    38 | </li>
    39 |

    これでリスト内の ToDo モデルもチェックボックスの状態に合わせて更新されます。

    40 |

    完了状態の ToDo には done クラスがつくという仕様なので todo.done プロパティの値に合わせてクラスが付与されるようにしましょう。

    41 |
    <li class="todo-item" ng-repeat="todo in todos"
    42 |     ng-class="{done: todo.done}">
    43 |

    li 要素に対して ngClass ディレクティブの使用を宣言します。 44 | ngClass ディレクティブは様々な形式の値を受けとれますが今回はマップオブジェクトを使用します。 45 | マップオブジェクトは {'クラス名': クラスを付与する条件式} の形式です。 46 | 上記の場合 todo.done プロパティが true なら done クラスが付与されるという意味になります。

    47 |
    48 | 49 | -------------------------------------------------------------------------------- /account/step03/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 帳票アプリ 6 | 7 | 8 | 9 | 10 | 11 |
    12 | 13 | 14 |
    15 | 26 |
    27 | 28 | 29 |
    30 | 31 | 32 | 40 | 41 | 87 | 88 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /guide/service/article.html: -------------------------------------------------------------------------------- 1 |

    役割

    2 |

    サービスは以下の役割を果たすために使用されるものです。

    3 | 6 |

    コントローラやディレクティブ、フィルタなどで共通して使われる値や関数を定義する場所です。

    7 |

    使う

    8 |

    サービスを使用したいコントローラなどのアノテーション配列にサービス名を列挙します。

    9 |
    module.controller('Ctrl', ['$scope', '$http', '$timeout', function (scope, $http, $timeout) {
    10 |   $http.get('/data').success(function (data) {
    11 |     scope.data = data;
    12 |   });
    13 | 
    14 |   $timeout(function () {
    15 |     scope.elapsed = true;
    16 |   }, 1000);
    17 | }]);
    18 |

    アノテーション配列の最後の関数がサービスを受け取ります。

    19 |

    作る

    20 |

    module インスタンスの factory, service, value などのメソッドで新しいサービスを定義できます。

    21 |

    いずれのメソッドも第一引数にサービスの名前を受け取ります。 22 | factory と service は第二引数にアノテーション配列を受け取ります。

    23 |

    factory メソッドのアノテーション配列の最後はファクトリ関数です。 24 | ファクトリ関数が返すべきものはサービスとなるオブジェクトです。 25 | ファクトリ関数は作成したサービスがはじめて依存注入された時に一度だけ実行されます。 26 | 2回目以降の依存注入では初回で作成されたサービスが渡されます。

    27 |
    module.factory('delayOneSecond', ['$timeout', function ($timeout) {
    28 |   return function (callback) {
    29 |     return $timeout(callbak, 1000);
    30 |   };
    31 | }]);
    32 |

    service メソッドのアノテーション配列の最後はコンストラクタです。 33 | コンストラクタは作成したサービスがはじめて依存注入された時に一度だけインスタンス化されます。 34 | 2回目以降の依存注入では初回で作成されたインスタンスが渡されます。

    35 |
    module.service('storage', [function () {
    36 |   var storage = {};
    37 | 
    38 |   function getLange() {
    39 |     return Object.keys(storage).length;
    40 |   }
    41 | 
    42 |   this.length = 0;
    43 | 
    44 |   this.get = function (key) {
    45 |     return storage[key];
    46 |   };
    47 | 
    48 |   this.getAll = function () {
    49 |     return angular.copy(storage);
    50 |   };
    51 | 
    52 |   this.set = function (key, value) {
    53 |     storage[key] = value;
    54 |     this.length = getLange();
    55 |   };
    56 | 
    57 |   this.remove = function (key) {
    58 |     delete storage[key];
    59 |     this.length = getLange();
    60 |   };
    61 | }]);
    62 |

    value メソッドの第二引数はサービスそのものを受け取ります。

    63 |
    module.value('data', [
    64 |   {id: 1, name: 'Alice'},
    65 |   {id: 2, name: 'Bob'},
    66 |   {id: 3, name: 'Charlie'}
    67 | ]);
    68 |
    69 | Tip: 70 | config ブロックで設定を変更できるような高度なサービスを作成する方法として provider メソッドも提供されています。 71 |
    72 | 73 |
    74 | Tip: 75 | アプリケーション共通の定数定義を作成する手段として constant も提供されています。 76 | constant で作成されたサービスは config ブロックでも取得できます。 77 |
    78 | 79 |
    80 | 81 | -------------------------------------------------------------------------------- /account/step05/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 帳票アプリ 6 | 7 | 8 | 9 | 10 | 11 |
    12 | 13 | 14 |
    15 | 26 |
    27 | 28 | 29 |
    30 | 31 | 32 | 40 | 41 | 90 | 91 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /guide/scope/article.md: -------------------------------------------------------------------------------- 1 | ## 役割 2 | $scope オブジェクトは以下の役割を果たすために使用されるものです。 3 | 4 | * ビューにバインドしたいデータモデルを定義する場所。 5 | * 自身のプロパティ(つまりデータモデル)に変更があった時ビューにそれを伝える。 6 | * ビューが式を評価する際のコンテキストの提供(つまりビューのグローバルスコープ)。 7 | * イベントの発行とイベントリスナーの登録 8 | 9 | ## コントローラやビューとの関係 10 | $scope はコントローラやディレクティブとビューの橋渡しを行います。 11 | ビューは $scope を参照しデータをバインドしていきます。 12 | コントローラやディレクティブがデータモデルを変更した時 $scope はその変更をビューに伝えてくれます。 13 | 変更を受け取ったビューは自動的に表示を更新します。 14 | 15 | この仕組のためコントローラーはビューを全く意識せずにビジネスロジックのみに集中できます。 16 | 17 | ## ビューへの変更通知 18 | AngularJS ではほとんどの場合においてビューへの変更通知を手動で行う必要がありません。 19 | 手動で変更通知を行わなければいけない場合の代表例は以下のとおりです。 20 | 21 | * ディレクティブを通さずにアタッチした DOM イベントのリスナー内 22 | * タイマーや XMLHttpRequest などの非同期処理のコールバック内 23 | 24 | 手動で更新通知を行うための手段として $scope.$apply メソッドが提供されています。 25 | 26 | ```javascript 27 | $scope.foo = false; 28 | 29 | setTimeout(function () { 30 | $scope.foo = true; 31 | $scope.$apply(); 32 | }, 1000); 33 | // or 34 | setTimeout(function () { 35 | // こちらのほうがベター 36 | $scope.$apply(function () { 37 | $scope.foo = true; 38 | }); 39 | }, 1000); 40 | ``` 41 | 42 | タイマーは $timeout サービスを、 XMLHttpRequest は $http サービスを使うことで更新を自動化できます。 43 | 44 |
    45 | **Tip:** 46 | デバッガや console API でスタックトレースを調べて $scope.$apply が呼び出されているかどうかを確認することで手動更新が必要かどうかを判別することができます。 47 |
    48 | 49 | ## 階層構造と継承 50 | $scope は親子関係を持っており小スコープは親スコープのプロパティをプロトタイプ継承します。 51 | 52 | アプリケーションははじめ $rootScope だけを持っています。 53 | いくつかのディレクティブは 新規に $scope を作成するのでアプリケーションは複数の $scope を持つことになります。 54 | 新規の $scope は親 $scope の子となり DOM の構造に従ったツリー構造になります。 55 | 56 | ビューが式を評価する時、直近の $scope のプロパティに値が発見できなかったら $rootScope に到達するまで親スコープのプロパティをたどります。 57 | 58 | ## イベント 59 | $scope はイベントの発行とイベントリスナーの管理も行います。 60 | イベントはいくつかのディレクティブやサービスが $scope を使って発行したりします。 61 | もちろん自分でカスタムイベントを発行することもできます。 62 | 63 | ```javascript 64 | // イベントリスナーの登録 65 | $scope.$on('eventName', eventListener); 66 | 67 | // イベント発行 68 | // 親スコープに伝搬する 69 | $scope.$emit('eventName', passingData, ...); 70 | 71 | // 小スコープに伝搬する 72 | $scope.$broadcast('eventName', passingData, ...); 73 | ``` 74 | 75 |
    76 | 77 | ## 変更時にコールバックを実行する 78 | ビューに変更が通知される時、特定のプロパティの値が変わっていたら追加の処理を行ないたい場合があります。 79 | 80 | $scope.$watch メソッドを使うと値が変化していた時に任意の関数を実行する事ができます。 81 | 82 | ```javascript 83 | $scope.name = 'Alice'; 84 | $scope.$watch('name', function (newValue, oldValue, scope) { 85 | console.log(scope === $scope); 86 | console.log('name は %s から %s に変更されました', oldValue, newValue); 87 | }); 88 | 89 | $scope.name = 'Bob'; 90 | $scope.$apply(); // 更新を通知 91 | // => true 92 | // => name は Alice から Bob に変更されました 93 | ``` 94 | 95 | 第一引数に文字列を渡すと $scope のそのプロパティの変更を監視します。 96 | コールバックが実行されるのはビューに変更が通知される直前で監視中のプロパティの値が変更されていたときのみです。 97 | 98 | 第一引数に関数を渡すことで $scope オブジェクトのプロパティ以外の変更も監視できます。 99 | 100 | ```javascript 101 | var bool; 102 | $scope.$watch(function() { 103 | return bool; 104 | }, function (newValue, oldValue) { 105 | $scope.value = newValue; 106 | }); 107 | 108 | bool = true; 109 | $scope.$apply(); 110 | ``` 111 | 112 | 注意点として変更とはプロパティへの代入だということです。 113 | 以下の例は期待通りに動きません。 114 | 115 | ```javascript 116 | $scope.arr = [1, 2, 3]; 117 | $scope.$watch('arr', function (newValue, oldValue) {}); 118 | $scope.arr.push(4); 119 | 120 | $scope.obj = {value1: 1}; 121 | $scope.$watch('obj', function (newValue, oldValue) {}); 122 | $scope.obj.value2 = 2; 123 | 124 | $scope.$apply(); 125 | ``` 126 | 127 | 上記例ではコールバックが受け取る newValue と oldValue は参照が同じなので `newValue === oldValue` が成り立ちます。 128 | つまりプロパティは変更されていないということになります。 129 | 130 | この変更を監視したい場合は $watchCollection メソッドを使います。 131 | 132 | ```javascript 133 | $scope.arr = [1, 2, 3]; 134 | $scope.$watchCollection('arr', function (newValue, oldValue) { 135 | console.log(newValue); 136 | // => [1, 2, 3, 4] 137 | }); 138 | $scope.arr.push(4); 139 | 140 | $scope.obj = {value1: 1}; 141 | $scope.$watchCollection('obj', function (newValue, oldValue) { 142 | console.log(newValue); 143 | // => {value1: 1, value2: 2} 144 | }); 145 | $scope.obj.value2 = 2; 146 | 147 | $scope.$apply(); 148 | ``` 149 | 150 | 配列の要素やオブジェクトのプロパティの値に変更があった場合を監視したい時は以下のようにします。 151 | 152 | ```javascript 153 | $scope.list = [ 154 | {value: false} 155 | ]; 156 | 157 | $scope.$watch('list', function (newValue, oldValue) { 158 | // 実行されない 159 | }); 160 | 161 | $scope.$watch('list', function (newValue, oldValue) { 162 | // 実行される 163 | }, true); // 第三引数に true を渡す 164 | 165 | $scope.$watchCollection('list', function (newValue, oldValue) { 166 | // 実行されない 167 | }); 168 | 169 | $scope.list[0].value = true; 170 | $scope.$apply(); 171 | ``` 172 | 173 | 第三引数を設定することで比較の仕方を参照の比較から各値を個別に比較するようになります。 174 | 175 |
    176 | -------------------------------------------------------------------------------- /account/step06/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 帳票アプリ 6 | 7 | 8 | 9 | 10 | 11 |
    12 | 13 | 14 |
    15 | 26 |
    27 | 28 | 29 |
    30 | 31 | 32 | 47 | 48 | 97 | 98 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /account/step09/article.md: -------------------------------------------------------------------------------- 1 | 前回のステップまでで帳票アプリは完成です。 2 | しかし改めて完成したコードを眺めると無駄な箇所があることがわかります。 3 | 4 | ## 複数のコントローラが全く同じ機能を別々に実装している 5 | getSubtotal メソッドと getTotalAmount メソッドが CreationController と SheetController の2箇所に存在しています。 6 | コードの重複は無駄ですし処理に変更があった際の修正箇所も増えてしまいます。 7 | 8 | これらのメソッドはサービスとして一箇所にまとめて実装しましょう。 9 | 10 | ```javascript 11 | .service('counting', function () { 12 | this.getSubtotal = function (orderLine) {/* 省略 */}; 13 | this.getTotalAmount = function (lines) {/* 省略 */}; 14 | }) 15 | .controller('CreationController', ['$scope', 'sheets', 'counting', 16 | function CreationController($scope, sheets, counting) { 17 | // 省略 18 | 19 | angular.extend($scope, counting); // $scope オブジェクトに counting サービスメソッドをミックスイン 20 | 21 | $scope.initialize(); 22 | }]) 23 | .controller('SheetController', ['$scope', '$routeParams', 'sheets', 'counting', 24 | function SheetController($scope, $params, sheets, counting) { 25 | angular.extend($scope, sheets.get($params.id)); 26 | angular.extend($scope, counting); // counting サービスで $scope オブジェクトを拡張 27 | }]); 28 | ``` 29 | 30 | コントローラはサービスを受け取り $scope オブジェクトにミックスインしてビューから参照できるようにします。 31 | 32 | 副次的なメリットとしてコンスタクタがインスタンス化されるときに関数を作成するコストが少なくなりました。 33 | 34 | ## コントローラのインスタンス化のコストを減らす 35 | コントローラ関数はコンストラクタです。 36 | マルチビューアプリケーションではコントローラはルートが変わるたびにインスタンス化されます。 37 | 38 | コンストラクタ内で関数定義を行うとインスタンス化のたびに新しく関数を定義することになりインスタンス化の際にかかるコストが高くなります。 39 | 40 | 一方サービスは常にシングルトンであり、サービス内の関数定義はアプリケーション全体を通して一度だけしか行われません。 41 | 上記を踏まえた上で改めてコードを見れば新たに無駄な場所を発見できると思います。 42 | 43 | CreationController 内で定義されたビジネスロジックの定義です。 44 | これらもすべてサービスにカプセル化しコントローラ内ではミックスインするだけにしましょう。 45 | 46 | ```javascript 47 | .service('sheetAction', ['$location', 'sheets', 48 | function ($location, sheets) { 49 | function createOrderLine() {/* 省略 */} 50 | 51 | this.initialize = function () {/* 省略 */}; 52 | this.addLine = function () {/* 省略 */}; 53 | this.removeLine = function (target) {/* 省略 */}; 54 | this.save = function () {/* 省略 */}; 55 | }]) 56 | .controller('CreationController', ['$scope', 'counting', 'sheetAction', 57 | function CreationController($scope, counting, sheetAction) { 58 | angular.extend($scope, sheetAction); // $scope オブジェクトに sheetAction サービスメソッドをミックスイン 59 | angular.extend($scope, counting); 60 | 61 | $scope.initialize(); 62 | }]) 63 | ``` 64 | 65 | ## テンプレート内のわかりにくい部分をコントローラに移管する 66 | テンプレート内の ngPattern ディレクティブに渡す正規表現や削除ボタンの ngDisabled ディレクティブに渡す条件式などは HTML 上では少し把握しづらいものです。 67 | とりわけこのようなものはコメントを残しておかないと、あとで困った事になるケースも有り得ます(今回のケースでは単純な条件なのでまだ大丈夫だとは思いますが)。 68 | 69 | わかりにくい部分はモデル化してビューにはモデルの参照だけさせるようにしましょう。 70 | 71 | ```javascript 72 | $scope.integer = /^\d+$/; // 整数にマッチ 73 | ``` 74 | ```html 75 | 76 | ``` 77 | 78 | このほうがわかりやすいですね。 79 | 80 | ngDisabled ディレクティブに渡す条件はリストが更新されるたびに評価される必要があります。 81 | もっとも単純な例は下記のやり方です。 82 | 83 | ```javascript 84 | $scope.isDisabled = function () { 85 | retrun $scope.lines.length < 2; 86 | }; 87 | ``` 88 | ```html 89 | 90 | ``` 91 | 92 | しかしこのやり方は実はあまりいい実装ではありません。 93 | 例えば100個の注文明細行をもった帳票だった場合、個別の実行結果が変わらないにもかかわらず isDisabled メソッドは100回コールされることになります。 94 | これは恐ろしく無駄ですので他の方法で実装しましょう。 95 | 96 | ```javascript 97 | $scope.$watch('lines.length < 2', function (val) { 98 | $scope.disabledDelBtn = val; 99 | }); 100 | ``` 101 | ```html 102 | 103 | ``` 104 | 105 | [$watch](http://docs.angularjs.org/api/ng.$rootScope.Scope#$watch) メソッドを使いボタンを無効化する条件を監視しその結果をモデル化します。 106 | ディレクティブは関数実行ではなくモデルを参照するだけなので要素数が増えてもパフォーマンスに与える影響が少なくてすみます。 107 | 108 |
    109 | **Warning:** 110 | 最初の状態と比べた場合、パフォーマンスが低下する可能性があるのでメンテナンス性とパフォーマンスを秤にかける必要があります。 111 |
    112 | 113 | lines モデルや lines モデルの length プロパティを監視してもいいのですがその場合コールバック内で条件評価を行うことになります。 114 | それは結果的に disabledDelBtn モデルの値に変化がない場合でもコールバックが実行されるケースがあることを意味するので無駄が発生してしまいます。 115 | 116 | ```javascript 117 | $scope.$watch('lines.length', function (length) { 118 | // この関数は lenes.length が 4 から 5 に変化した時も呼ばれます 119 | // その条件では length < 2 の結果には変化がありません。 120 | $scope.disabledDelBtn = length < 2; 121 | }); 122 | ``` 123 | ```javascript 124 | $scope.$watch('lines.length < 2', function (val) { 125 | // この関数は lines.length < 2 の結果が変わった時だけ呼ばれます 126 | $scope.disabledDelBtn = val; 127 | }); 128 | ``` 129 | 130 | $watch を使う場合コールバック内の処理は簡潔なものにとどめましょう。 131 | 監視対象によっては開発者が思う以上にコールバックが呼ばれたり最悪の場合無限ループに陥るケースもありえます。 132 | 133 |
    134 | **Tip:** 135 | AngularJS ではこのような無限ループ(digest ループ)はデフォルトで 10回繰り返した段階で強制的にループから抜けます。 136 |
    137 | 138 |
    139 | 140 | ## 完成 141 | 以上でチュートリアルは終了です。 142 | -------------------------------------------------------------------------------- /assets/css/docs.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 70px; 3 | } 4 | 5 | hr { 6 | border-color: #ddd; 7 | } 8 | 9 | pre { 10 | font-size: 14px; 11 | } 12 | 13 | .main-top { 14 | margin: -20px 0 0; 15 | } 16 | 17 | .jumbotron { 18 | margin: 0; 19 | color: #fff; 20 | font-size: 16px; 21 | } 22 | 23 | .jumbotron h1, 24 | .jumbotron h2, 25 | .jumbotron h3 { 26 | line-height: 1.1; 27 | } 28 | 29 | .jumbotron p { 30 | color: #eee; 31 | } 32 | 33 | .jumbotron section { 34 | margin-top: 32px; 35 | } 36 | 37 | @media screen and (min-width: 768px) { 38 | @font-face { 39 | font-family: mplus; 40 | font-weight: 100; 41 | src: local('M+ 1p'), 42 | url('../fonts/mplus-1p-thin.ttf') format('truetype'); 43 | } 44 | 45 | .jumbotron { 46 | font-family: mplus, 'Helvetica Neue', Helvetica, Arial, sans-serif; 47 | font-size: 21px; 48 | } 49 | 50 | .jumbotron h1, 51 | .jumbotron h2, 52 | .jumbotron h3 { 53 | font-family: mplus, 'Helvetica Neue', Helvetica, Arial, sans-serif; 54 | line-height: 1.1; 55 | } 56 | 57 | .jumbotron h2 { 58 | font-size: 49px; 59 | } 60 | 61 | .jumbotron h3 { 62 | font-size: 32px; 63 | } 64 | } 65 | 66 | .jumbotron-blue { 67 | background-color: #2980b9; 68 | } 69 | 70 | .jumbotron-sky { 71 | background-color: #3498db; 72 | } 73 | 74 | .alert { 75 | font-size: 14px; 76 | } 77 | 78 | a.list-group-item:hover, 79 | a.list-group-item:focus { 80 | z-index: 2; 81 | color: #ffffff; 82 | background-color: #428bca; 83 | border-color: #428bca; 84 | } 85 | 86 | .tab-content-code { 87 | margin-top: 18px; 88 | } 89 | 90 | .aside { 91 | margin-top: 40px; 92 | } 93 | 94 | .pager li > a { 95 | border-radius: 3px; 96 | } 97 | 98 | .pager .next > a { 99 | color: #fff; 100 | background-color: #428bca; 101 | border-color: #357ebd; 102 | } 103 | 104 | .pager .next > a:hover, 105 | .pager .next > a:focus, 106 | .pager .next > a:active { 107 | color: #fff; 108 | background-color: #3276b1; 109 | border-color: #285e8e; 110 | } 111 | 112 | .preview { 113 | width: 100%; 114 | min-height: 500px; 115 | border: 1px solid #ccc; 116 | border-radius: 4px; 117 | resize: vertical; 118 | } 119 | 120 | .footer { 121 | border-top: 1px solid #e5e5e5; 122 | padding: 40px 0; 123 | color: #999; 124 | } 125 | 126 | .footer-link { 127 | padding: 0; 128 | text-align: center; 129 | } 130 | 131 | .footer-link li { 132 | display: inline; 133 | } 134 | 135 | /*! Highlight.js theme */ 136 | /*! github.com style (c) Vasily Polovnyov */ 137 | pre .comment, 138 | pre .template_comment, 139 | pre .diff .header, 140 | pre .javadoc { 141 | color: #998; 142 | /* fixed by 8th713 */ 143 | /* disabled the font-style because Italics is hard to read in Japanese. */ 144 | /* font-style: italic */ 145 | } 146 | 147 | pre .keyword, 148 | pre .css .rule .keyword, 149 | pre .winutils, 150 | pre .javascript .title, 151 | pre .nginx .title, 152 | pre .subst, 153 | pre .request, 154 | pre .status { 155 | color: #333; 156 | font-weight: bold 157 | } 158 | 159 | pre .number, 160 | pre .hexcolor, 161 | pre .ruby .constant { 162 | color: #099; 163 | } 164 | 165 | pre .string, 166 | pre .tag .value, 167 | pre .phpdoc, 168 | pre .tex .formula { 169 | color: #d14 170 | } 171 | 172 | pre .title, 173 | pre .id { 174 | color: #900; 175 | font-weight: bold 176 | } 177 | 178 | pre .javascript .title, 179 | pre .lisp .title, 180 | pre .clojure .title, 181 | pre .subst { 182 | font-weight: normal 183 | } 184 | 185 | pre .class .title, 186 | pre .haskell .type, 187 | pre .vhdl .literal, 188 | pre .tex .command { 189 | color: #458; 190 | font-weight: bold 191 | } 192 | 193 | pre .tag, 194 | pre .tag .title, 195 | pre .rules .property, 196 | pre .django .tag .keyword { 197 | color: #000080; 198 | font-weight: normal 199 | } 200 | 201 | pre .attribute, 202 | pre .variable, 203 | pre .lisp .body { 204 | color: #008080 205 | } 206 | 207 | pre .regexp { 208 | color: #009926 209 | } 210 | 211 | pre .class { 212 | color: #458; 213 | font-weight: bold 214 | } 215 | 216 | pre .symbol, 217 | pre .ruby .symbol .string, 218 | pre .lisp .keyword, 219 | pre .tex .special, 220 | pre .prompt { 221 | color: #990073 222 | } 223 | 224 | pre .built_in, 225 | pre .lisp .title, 226 | pre .clojure .built_in { 227 | color: #0086b3 228 | } 229 | 230 | pre .preprocessor, 231 | pre .pi, 232 | pre .doctype, 233 | pre .shebang, 234 | pre .cdata { 235 | color: #999; 236 | font-weight: bold 237 | } 238 | 239 | pre .deletion { 240 | background: #fdd 241 | } 242 | 243 | pre .addition { 244 | background: #dfd 245 | } 246 | 247 | pre .diff .change { 248 | background: #0086b3 249 | } 250 | 251 | pre .chunk { 252 | color: #aaa 253 | } 254 | -------------------------------------------------------------------------------- /todo/step06/article.html: -------------------------------------------------------------------------------- 1 |

    $watch メソッドを使ってモデルの変更時の振る舞いを追加しましょう。

    2 |

    ToDo リストモデルの変更を監視する

    3 |

    絞り込み表示ができるようになりましたがボタンに表示する件数が仮値のままです。 4 | 正しい件数を表示出来るようにしましょう。

    5 |

    未了、完了の件数はリスト内の Todo モデルの総数や状態の変更に合わせて更新する必要があります。 6 | $scope.$watch メソッドを使ってリストが変更された時に正しい件数に更新されるモデルを作成しましょう。

    7 |
    $scope.$watch('todos', function (todos) {
     8 |   // todos が増減したり各要素のプロパティが変更された時に実行される
     9 | }, true);
    10 |

    配列の増減と配列内の ToDo の done プロパティを監視するため第三引数(objectEquality フラグ)を true に設定します。 11 | これを忘れると関数が実行されるのが todos に新しい値が代入された時だけになってしまいますので注意してください。

    12 |

    コントローラからフィルタを使用する

    13 |

    未了、完了の件数を得るためにはリストから合致する要素だけを抽出する必要があります。 14 | この処理は先ほどのステップで使用したコアモジュールの filter フィルタが実現していたものと同じです。

    15 |

    コントローラで特定のフィルタを使用したい場合 $filter サービスを使ってフィルタを取得することができます。 16 | アノテーション配列を編集しコンストラクタが $filter サービスを受け取れるようにしましょう。

    17 |
    .controller('MainController', ['$scope', '$filter', function ($scope, $filter) {
    18 |   // 省略
    19 | 
    20 |   var where = $filter('filter'); // filter フィルタ関数の取得
    21 |   $scope.$watch('todos', function (todos) {
    22 |     var length = todos.length;
    23 | 
    24 |     $scope.allCount = length;                                   // 総件数モデル
    25 |     $scope.doneCount = where(todos, $scope.filter.done).length; // 完了件数モデル
    26 |     $scope.remainingCount = length - $scope.doneCount;          // 未了件数モデル
    27 |   }, true);
    28 | }]);
    29 |

    filter 関数(変数 where)を利用して完了した ToDo だけの配列を取得しその長さを完了件数モデルとしました。 30 | さらに、総件数から完了件数を引いた値を利用して未了件数モデルに代入します。

    31 |

    $scope.$watch に登録された関数はコントローラがインスタンス化された時初期化処理として一度実行されるので関数外で各モデルの初期値を定義しておく必要はありません。

    32 |

    最後にモックアップを編集して各モデルを表示しましょう。

    33 |
    <button class="active">全部 <span>{{ allCount }}</span></button>
    34 | <button>未了 <span>{{ remainingCount }}</span></button>
    35 | <button>完了 <span>{{ doneCount }}</span></button>
    36 |
    37 | 38 |

    $watch メソッドを使用しない実装方法

    39 |

    慣れないうちは $watch は少々扱いづらいので別のアプローチでの実装法も紹介します。

    40 |
    $scope.getDoneCount = function () {
    41 |   return where($scope.todos, $scope.filter.done).length;
    42 | };
    43 |
    <button class="active">全部 <span>{{ todos.length }}</span></button>
    44 | <button>未了 <span>{{ todos.length - getDoneCount() }}</span></button>
    45 | <button>完了 <span>{{ getDoneCount() }}</span></button>
    46 |

    ビューは $scope からの更新通知を受け取ると式を再評価するためこの実装は結果的に $watch メソッドを使った実装と 47 | 同じことになります。

    48 |

    ただし $watch を使った実装ならば再評価時の動作は単純なプロパティ参照で済むのに対し、この実装は再評価時に必ず関数を実行するため多用するとパフォーマンスが低下する可能性があります。

    49 | 50 | --------------------------------------------------------------------------------