《Pro AngularJS》学习小结-02
上一篇的项目只有一个单独的模板页面,加入了相应的controller,filter,使得页面上的数据能够动态的变化。现在我们开始建立并整合多个模板,加入购物车模块和结账checkout模块。
一、在页面中处理Ajax的错误
在storesSport.js中我们已经有了在申请Ajax请求错误时候的处理代码,其实在页面中也可以加入Ajax请求错误的信息,这样更容易调试,更加符合用户经验(user experience)设计原则。
修改index.html
<body ng-controller="sportsStoreCtrl"> <div class="navbar navbar-inverse"> <a class="navbar-brand" href="#">SPORTS STORE</a> </div> <div class="alert alert-danger" ng-show="data.error"> Error ({{data.error.status}}). The product data was not loaded. <a href="/index.html" class="alert-link">Click here to try again</a> </div> <div class="panel panel-default row" ng-controller="productListCtrl" ng-hide="data.error"> <div class="col-xs-3 col-md-3"> ..... </body>
二、建立多模板 —— partial views
有两种方法使用多个模板
- ng-include —— 该方法最简单,也是模板简单的包含进当前模板
- ng-view —— 必须配合angular.route.js使用,更加灵活,可以同时设置多个模板
1. 分离模板—— 先使用ng-include方法
建立partial/productList.html文件
<div class="panel panel-default row" ng-controller="productListCtrl" ng-hide="data.error"> <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> <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> </div> </div>
在index.html文件中删除相应内容,更改为
......
<body ng-controller="sportsStoreCtrl"> <div class="navbar navbar-inverse"> <a class="navbar-brand" href="#">SPORTS STORE</a> </div> <div class="alert alert-danger" ng-show="data.error"> Error ({{data.error.status}}). The product data was not loaded. <a href="/app.html" class="alert-link">Click here to try again</a> </div> <ng-include src="‘partial/productList.html‘"></ng-include> </body>
......
2. 加入购物车
The basic flow of the shopping cart
定义购物车module和service —— cart.js
angular.module("cart", []) .factory("cart", function () { var cartData = []; return { addProduct: function (id, name, price) { var addedToExistingItem = false; for (var i = 0; i < cartData.length; i++) { if (cartData[i].id == id) { cartData[i].count++; addedToExistingItem = true; break; } } if (!addedToExistingItem) { cartData.push({ count: 1, id: id, price: price, name: name }); } }, removeProduct: function (id) { for (var i = 0; i < cartData.length; i++) { if (cartData[i].id == id) { cartData.splice(i, 1); break; } } }, getProducts: function () { return cartData; } } });
这里在新的cart模块中创建了一个自定义的service——cart。每个AngularJS的service都是一个简单的Javascript的单例对象,可以在整个程序application中访问之。
所以把购物车cart设置为service,因为在整个程序中每个组件component都可以访问cart,看到cart中的信息。
有多种方法建立service,什么情况下该建立什么样的service,这个以后慢慢研究,^_^。
3. 创建购物车directive ——一个Cart Widget
3.1 创建directive
angular.module("cart", []) .factory("cart", function () { var cartData = []; return { ...... } }) .directive("cartSummary", function (cart) { return { restrict: "E", templateUrl: "/partial/cartSummary.html", controller: function ($scope) { var cartData = cart.getProducts(); $scope.total = function () { var total = 0; for (var i = 0; i < cartData.length; i++) { total += (cartData[i].price * cartData[i].count); } return total; }; $scope.itemCount = function () { var total = 0; for (var i = 0; i < cartData.length; i++) { total += cartData[i].count; } return total; }; } }; });
在这个cartSummary中
- 定义了一个controller,告诉AngularJS使用cartSummary.html
- 在controller中定义了total和itemCount两个behavior来操作购物车,这两个behavior的作用在cartSummary.html这个view中。
3.2 创建cartSummary.html模板——partial view
<style> .navbar-right { float: right !important; margin-right: 5px;} .navbar-text { margin-right: 10px; } </style> <div class="navbar-right"> <div class="navbar-text"> <b>Your cart:</b> {{itemCount()}} item(s), {{total() | currency}} </div> <a href="#" class="btn btn-default navbar-btn">Checkout</a> </div>
3.3 应用到index.html
<!DOCTYPE html> <html ng-app="sportsStore"> <head> <title>First Test</title> ...... <script> angular.module("sportsStore", ["customFilters", "cart"]); </script> <script src="js/controllers/sportsStore.js"></script> <script src="js/controllers/productListControllers.js"></script> <script src="js/filters/customFilters.js"></script> <scriptsrc="js/factory/cart.js"></script> </head> <body ng-controller="sportsStoreCtrl"> <div class="navbar navbar-inverse"> <a class="navbar-brand" href="#">SPORTS STORE</a> <cart-summary /> </div> <div class="alert alert-danger" ng-show="data.error"> Error ({{data.error.status}}). The product data was not loaded. <a href="index.html" class="alert-link">Click here to try again</a> </div> <ng-include src="‘productList.html‘"></ng-include> </body> </html>
4. 增加“添加到购物车”按钮
4.1 修改productListControllers.js
angular.module("sportsStore") .constant("productListActiveClass","btn-primary") //highlight the selected category .constant("productListPageCount",3) //define pagination .controller("productListCtrl", function ($scope, $filter, productListActiveClass, productListPageCount,cart) { ...... $scope.addProductToCart = function(product){ cart.addProduct(product.id,product.name,product.price); } });
4.2 修改productList.html
<div class="panel panel-default row" ng-controller="productListCtrl" ng-hide="data.error"> ...... <div class="col-xs-8 col-md-8"> <div class="well" ng-repeat="item in data.products | filter: categoryFilter | range:selectedPage:pageSize"> <h3> ...... </h3> <button ng-click="addProductToCart(item)" class="btn btn-success pull-right">Add to cart</button> <span class="lead">{{item.description}}</span> </div> ...... </div> </div>
5. 定义URL路由导航——URL Navigation
5.1 新建partial/checkoutSummary.html
<div class="lead"> This is the checkout summary view </div> <a href="#/products" class="btn btn-primary">Back</a>
说明: 这里的href="#/products"中的#不能省略,我一开始没有使用#,结果打死也找不到页面。以后其他模板中的链接都是一样。
5.2 定义URL routes
接下来我们希望点击checkout按钮跳转到checkoutSummary.html页面中,在checkoutSummary.html页面中点击“Back”跳转到产品信息页面中,这是ng-include就不够用了。
<!DOCTYPE html> <html ng-app="sportsStore"> <head> ...... <script> angular.module("sportsStore", ["customFilters", "cart", "ngRoute"]) .config(function ($routeProvider) { $routeProvider.when("/complete", { templateUrl: "/partial/thankYou.html" }) .when("/placeorder", { templateUrl: "/partial/placeOrder.html" }) .when("/checkout", { templateUrl: "/partial/checkoutSummary.html" }) .when("/products", { templateUrl: "/partial/productList.html" }) .otherwise({ templateUrl: "/partial/productList.html" }); }); </script> <script src="js/controllers/sportsStore.js"></script> <script src="js/controllers/productListControllers.js"></script> <script src="js/filters/customFilters.js"></script> <script src="js/factory/cart.js"></script> <script src="js/lib/angular-route.js"></script> </head> <body ng-controller="sportsStoreCtrl"> ...... <ng-view/> <!-- 也可以写成 <div ng-view></div> --> </body> </html>
- 引入angular-route.js
- 将ng-include改为ng-view
- 模块中加入ngRoute
5.3 修改cartSummary.html
<div class="navbar-right"> ...... <a href="#/checkout" class="btn btn-default navbar-btn">Checkout</a> </div>
现在可以实现结账跳转功能了。
6. 完善结账功能
6.1 新建一个checkoutControllers.js
angular.module("sportsStore") .controller("cartSummaryController", function ($scope, cart) { $scope.cartData = cart.getProducts(); $scope.total = function () { var total = 0; for (var i = 0; i < $scope.cartData.length; i++) { total += ($scope.cartData[i].price * $scope.cartData[i].count); } return total; } $scope.remove = function (id) { cart.removeProduct(id); } });
该js中包含一个controller——cartSummaryController,依赖于cart service,通过定义一个scope属性——cartData来获得cart的数据,并定义了一个total行为计算购物车的总价,一个remove行为来从购物车中移除产品。
6.2 完善checkoutSummary.html
<h2>Your cart</h2> <div ng-controller="cartSummaryController"> <div class="alert alert-warning" ng-show="cartData.length == 0"> There are no products in your shopping cart. <a href="#/products" class="alert-link">Click here to return to the catalogue</a> </div> <div ng-hide="cartData.length == 0"> <table class="table"> <thead> <tr> <th>Quantity</th> <th>Item</th> <th class="text-right">Price</th> <th class="text-right">Subtotal</th> </tr> </thead> <tbody> <tr ng-repeat="item in cartData"> <td class="text-center">{{item.count}}</td> <td class="text-left">{{item.name}}</td> <td class="text-right">{{item.price | currency}}</td> <td class="text-right">{{ (item.price * item.count) | currency}}</td> <td><button ng-click="remove(item.id)" class="btn btn-sm btn-warning">Remove</button></td> </tr> </tbody> <tfoot> <tr> <td colspan="3" class="text-right">Total:</td> <td class="text-right">{{total() | currency}}</td> </tr> </tfoot> </table> <div class="text-center"> <a class="btn btn-primary" href="#/products">Continue shopping</a> <a class="btn btn-primary" href="#/placeorder">Place order now</a> </div> </div> </div>
三、订单管理功能——如何使用Angular Form和Form validation
1. 新建placeOrder.html
<!-- 在这里可自定义AngularJS表单验证的样式ng-valid和ng-invalid -->
<style> .ng-invalid { background-color: lightpink; } .ng-valid { background-color: lightgreen; } span.error { color: red; font-weight: bold; } </style> <h2>Check out now</h2> <p>Please enter your details, and we‘ll ship your goods right away!</p> <form name="shippingForm" novalidate> <!-- 表示该表单必须通过验证 --> <div class="well"> <h3>Ship to</h3> <div class="form-group"> <label>Name</label> <input name="name" class="form-control" ng-model="data.shipping.name" required /> <span class="error" ng-show="shippingForm.name.$error.required"> Please enter a name </span> </div> <h3>Address</h3> <div class="form-group"> <label>Street Address</label> <input name="street" class="form-control" ng-model="data.shipping.street" required /> <span class="error" ng-show="shippingForm.street.$error.required"> Please enter a street name </span> </div> </form>
2. “complete order”按钮的验证——如果不填写完表单,该按钮无效
... <div class="text-center"> <button ng-disabled="shippingForm.$invalid" class="btn btn-primary">Complete order</button> </div> ...
<style> .ng-invalid { background-color: lightpink; } .ng-valid { background-color: lightgreen; } span.error { color: red; font-weight: bold; } </style> <h2>Check out now</h2> <p>Please enter your details, and we‘ll ship your goods right away!</p> <form name="shippingForm" novalidate> <div class="well"> <h3>Ship to</h3> <div class="form-group"> <label>Name</label> <input name="name" class="form-control" ng-model="data.shipping.name" required /> <span class="error" ng-show="shippingForm.name.$error.required"> Please enter a name </span> </div> <h3>Address</h3> <div class="form-group"> <label>Street Address</label> <input name="street" class="form-control" ng-model="data.shipping.street" required /> <span class="error" ng-show="shippingForm.street.$error.required"> Please enter a street name </span> </div> <div cla="form-group"> <label>City</label> <input name="city" class="form-control" ng-model="data.shipping.city" required /> <span class="error" ng-show="shippingForm.city.$error.required"> Please enter a city </span> </div> <div class="form-group"> <label>State</label> <input name="state" class="form-control" ng-model="data.shipping.state" required /> <span class="error" ng-show="shippingForm.state.$error.required"> Please enter a state </span> </div> <div class="form-group"> <label>Zip</label> <input name="zip" class="form-control" ng-model="data.shipping.zip" required /> <span class="error" ng-show="shippingForm.zip.$error.required"> Please enter a zip code </span> </div> <div class="form-group"> <label>Country</label> <input name="country" class="form-control" ng-model="data.shipping.country" required /> <span class="error" ng-show="shippingForm.country.$error.required"> Please enter a country </span> </div> <h3>Options</h3> <div class="checkbox"> <label> <input name="giftwrap" type="checkbox" ng-model="data.shipping.giftwrap" /> Gift wrap these items </label> </div> <div class="text-center"> <button ng-disabled="shippingForm.$invalid" class="btn btn-primary">Complete order</button> </div> </div> </form>
3. 表单发送功能
3.1 修改sportsStore.js——加入controller(behavior)
angular.module("sportsStore") //.contant("dataUrl","http://localhost:8081/products") .constant("orderUrl", "http://localhost:8081/orders") .controller("sportsStoreCtrl", function ($scope, $http, $location, orderUrl, cart) { $scope.data = {}; $http.get("products.json") .success(function(data){ $scope.data.products = data; }).error(function(error){ $scope.data.error = error; }); $scope.sendOrder = function(shippingDetails){ var order = angular.copy(shippingDetails); order.products = cart.getProducts(); $http.post(orderUrl, order) .success(function(data){ $scope.data.orderId = data.id; cart.getProducts().length = 0; }) .error(function(error){ $scope.data.orderError = error; }).finally(function(){ $location.path("/complete") }); }; });
3.2 在placeOrder.html加入ng-click事件
<div class="text-center"> <button ng-disabled="shippingForm.$invalid" ng-click="sendOrder(data.shipping)" class="btn btn-primary"> Complete order </button> </div>
3.3 新建thankYou.html
<div class="alert alert-danger" ng-show="data.orderError"> Error ({{data.orderError.status}}). The order could not be placed. <a href="#/placeorder" class="alert-link">Click here to try again</a> </div> <div class="well" ng-hide="data.orderError"> <h2>Thanks!</h2> Thanks for placing your order. We‘ll ship your goods as soon as possible. If you need to contact us, use reference {{data.orderId}}. </div>
说明: 这里要用到数据库,我没有实现,还在研究NodeJS和数据库中,估计等过一阵子再发布Angular的文章了