《Pro AngularJS》该书以一个SportsStore案例为主线铺开。
一、开发环境设置
该书中所用的server开发环境是Deployed,从来没听说过,而且作者也说该server没什么人用,我干脆弃用之。其他的环境包括
- NodeJS——这个必须装
- karma——测试环境,前期还没有用到,以后认真研究,毕竟AngularJS一大特点是Unit Test
- bootstrap——这个现在应该普遍使用了,O(∩_∩)O
- webstorm——现在唯一支持AngularJS插件的IDE
我基本没学过NodeJS,只好使用了webstorm中的内置项目:Node.js Boilerplate来傻瓜化新建项目了。其启动NodeJS过程如下图(基本可按指示一步一步做⊙﹏⊙):
新建好项目后将所有文件放在static目录下,就先这样凑合着用了。我也暂时不使用数据库,直接使用JSON文件。
我的项目目录如下:
说明:
- 首页文件名必须是index.html,其他的html文件我放在partial目录下(这个可以自己定义)
- 在server.js中可以修改NodeJs的port,默认的是8081,在浏览器中输入http://localhost:8081可直接跳转到static目录下的index.html
二、准备数据
我不熟悉NodeJs,使用一个Json文件作为数据源
[ { "name" : "Product #1", "description" : "A product", "category": "Category #1", "price": "100" }, { "name" : "Product #2", "description" : "A product", "category": "Category #1", "price": "110" }, { "name" : "Product #3", "description" : "A product", "category": "Category #2", "price": "210" }, { "name" : "Product #4", "description" : "A product", "category": "Category #3", "price": "202" }, { "name" : "Product #1", "description" : "A product", "category": "Category #1", "price": "100" }, { "name" : "Product #2", "description" : "A product", "category": "Category #1", "price": "110" }, { "name" : "Product #3", "description" : "A product", "category": "Category #2", "price": "210" }, { "name" : "Product #4", "description" : "A product", "category": "Category #3", "price": "202" } ]
创建sportsStore.js来引入并定义数据
angular.module("sportsStore") .controller("sportsStoreCtrl", function ($scope, $http) { $scope.data = {}; $http.get("products.json") .success(function(data){ $scope.data.products = data; }).error(function(error){ $scope.data.error = error; }); });
三、显示产品细节页面——直接上代码,即index.html
该代码主要用来显示产品的详细信息,即右边的面板panel的内容
<!DOCTYPE html> <html ng-app="sportsStore"> <!-- 标志AngularJs的开始 --> <head> <title>SportsStore</title> <script src="js/lib/angular.js"></script> <link href="css/bootstrap.css" rel="stylesheet" /> <link href="css/bootstrap-theme.css" rel="stylesheet" /> <script> angular.module("sportsStore", []); <!--加入sportsStore模块 --> </script> <script src="js/controllers/sportsStore.js"></script> </head> <body ng-controller="sportsStoreCtrl"> <!-- 控制器sportStoreCtrl将应用于整个页面 --> <div class="navbar navbar-inverse"> <a class="navbar-brand" href="#">SPORTS STORE</a> </div> <div class="panel panel-default row"> <div class="col-xs-3 col-md-3">Categories go here</div> <div class="col-xs-8 col-md-8"> <div class="well" ng-repeat="item in data.products"> <h3> <strong>{{item.name}}</strong> <!--数据绑定 --> <span class="pull-right label label-primary"> {{item.price | currency}} <!-- currency filter将使该span的内容显示为货币格式 --> </span> </h3> <span class="lead">{{item.description}}</span> <!--数据绑定 --> </div> </div> </div> </body> </html>
四、定义左侧Category导航面板
1、加入的HTML
...
angular.module("sportsStore", ["customFilters"]); <!--加入customFilters模块 -->
...
<div class="panel panel-default row"> <div class="col-xs-3 col-md-3"> <a ng-click="selectCategory()" class="btn btn-block btn-default btn-lg">Home</a> <!--加入click事件 --> <a ng-repeat="item in data.products | orderBy:‘category‘ | unique:‘category‘" <!--添加unique过滤,定义为按category属性过滤 --> ng-click="selectCategory(item)" class=" btn btn-block btn-default btn-lg"> <!--加入click事件 --> {{item}} </a> </div> ...... </div>
2. 新建customFilters.js——创建一个unique过滤,用来过滤Category
angular.module("customFilters", []) .filter("unique", function () { return function (data, propertyName) { if (angular.isArray(data) && angular.isString(propertyName)) { var results = []; var keys = {}; for (var i = 0; i < data.length; i++) { var val = data[i][propertyName]; if (angular.isUndefined(keys[val])) { //? keys[val] = true; results.push(val); } } return results; } else { return data; } } });
3. 新建productListControllers.js —— 实现按分类category显示产品信息(动态显示右边的面板内容)
angular.module("sportsStore") .controller("productListCtrl", function ($scope, $filter) { var selectedCategory = null; $scope.selectCategory = function (newCategory) { selectedCategory = newCategory; } $scope.categoryFilterFn = function (product) { return selectedCategory == null || product.category == selectedCategory; } });
注意:
- selectedCategory只是一般普通的Javascript变量,没有定义在$scope范围内,也就是说不能从directive或视图view中绑定的数据访问该变量
- categoryFilterFn是用来在产品详细信息面板中过滤product objects
4. 应用productListControllers和过滤产品——修改index.html
<divclass="panel panel-default row" ng-controller="productListCtrl"> <!--加入新的div层,使左右面板包含到该div,并加入productListCtrl --> <div class="col-xs-3 col-md-3"> <a ng-click="selectCategory()" class="btn btn-block btn-default btn-lg">Home</a> <a ng-repeat="item in data.products | orderBy:‘category‘ | unique:‘category‘" ng-click="selectCategory(item)" class=" btn btn-block btn-default btn-lg"> {{item}} </a> </div> <div class="col-xs-8 col-md-8"> <div class="well" ng-repeat="item in data.products | filter:categoryFilterFn">
<!--加入categoryFilterFn,使得该div的内容随着右边面板的点击事件而变化--> <h3> <strong>{{item.name}}</strong> <span class="pull-right label label-primary"> {{item.price | currency}} </span> </h3> <span class="lead">{{item.description}}</span> </div> </div> </div>
5. 高亮显示左边导航按钮(Highlighting the Selected Category)——使用
修改productListControllers.js
angular.module("sportsStore") .constant("productListActiveClass", "btn-primary") //使用constant方法定义一个常量productListActiveClass .controller("productListCtrl", function ($scope, $filter, productListActiveClass) {// var selectedCategory = null; $scope.selectCategory = function (newCategory) { selectedCategory = newCategory; } $scope.categoryFilterFn = function (product) { return selectedCategory == null || product.category == selectedCategory; } $scope.getCategoryClass = function(category) { return selectedCategory == category ? productListActiveClass : ""; } });
修改index.html中左边面板相关内容
... .controller("productListCtrl", function ($scope, $filter, productListActiveClass) { ... <div class="col-xs-3 col-md-3"> <a ng-click="selectCategory()" class="btn btn-block btn-default btn-lg">Home</a> <a ng-repeat="item in data.products | orderBy:‘category‘ | unique:‘category‘" ng-click="selectCategory(item)" class=" btn btn-block btn-default btn-lg" ng-class="getCategoryClass(item)"> {{item}} </a> </div> ...
四、加入分页(pagination)
1. 修改productListControllers.js
angular.module("sportsStore") .constant("productListActiveClass", "btn-primary") .constant("productListPageCount", 3) //常量productlistPageCount,每页显示的产品数目 .controller("productListCtrl", function ($scope, $filter, productListActiveClass, productListPageCount) { var selectedCategory = null; $scope.selectedPage = 1; $scope.pageSize = productListPageCount; $scope.selectCategory = function (newCategory) { selectedCategory = newCategory; $scope.selectedPage = 1; } $scope.selectPage = function (newPage) { $scope.selectedPage = newPage; } $scope.categoryFilterFn = function (product) { return selectedCategory == null || product.category == selectedCategory; } $scope.getCategoryClass = function (category) { return selectedCategory == category ? productListActiveClass : ""; } $scope.getPageClass = function (page) { return $scope.selectedPage == page ? productListActiveClass : ""; //为分页加入高亮filter } });
2. 修改customFilters.js——添加了两个过滤,range和pageCount
angular.module("customFilters", []) .filter("unique", function () { ...... }) .filter("range", function ($filter) { return function (data, page, size) { if (angular.isArray(data) && angular.isNumber(page) && angular.isNumber(size)) { var start_index = (page - 1) * size; if (data.length < start_index) { return []; } else { return $filter("limitTo")(data.splice(start_index), size); } }else { return data; } } }) .filter("pageCount", function () { return function (data, size) { if (angular.isArray(data)) { var result = []; for (var i = 0; i < Math.ceil(data.length / size) ; i++) { result.push(i); } return result; } else { return data; } } });
3. 修改index.html——加入分页代码
......
<div class="col-xs-8 col-md-8"> <div class="well" ng-repeat="item in data.products | filter:categoryFilterFn | range:selectedPage:pageSize"> <h3> <strong>{{item.name}}</strong> <span class="pull-right label label-primary"> {{item.price | currency}} </span> </h3> <span class="lead">{{item.description}}</span> </div> <div class="pull-right btn-group"> <a ng-repeat="page in data.products | filter:categoryFilterFn | pageCount:pageSize" ng-click="selectPage($index + 1)" class="btn btn-default" ng-class="getPageClass($index + 1)"> {{$index + 1}} </a> </div>
......