• 8.1:SportsStore:Orders and Administration


    本章,作者将通过收集和验证购物明细,来完成SportsStore应用,并在Deployd服务器上存储该订单。作者也构建了一个管理应用,允许认证用户查看订单,和管理产品分类。

    1、准备实例项目

    2、获取产品明细

    在给用户显示购物车中的产品汇总后,作者将购物明细用于订单。这需要使用AngularJS的表单特性,你会在大多数web应用中需要。作者已经创建Views/placeOrder.html文件,捕获用户的购物明细。作者将介绍一些与表单相关度的特性,来避免重复大量相似的代码。作者首先添加一组数据属性(用户的名字和街道地址),然后再其他添加的属性。下面是placeOrder.html文件。

    <h2>Check out now</h2>
    <p>Please enter your details, and we'll ship your goods right away!</p>
    <div class="well">
    <h3>Ship to</h3>
    <div class="form-group">
    <label>Name</label>
    <input class="form-control" ng-model="data.shipping.name" />
    </div>
    <h3>Address</h3>
    <div class="form-group">
    <label>Street Address</label>
    <input class="form-control" ng-model="data.shipping.street" />
    </div>
    <div class="text-center">
    <button class="btn btn-primary">Complete order</button>
    </div>
    </div>

    第一件事情,是作者没有使用ng-controller指令,来为视图指定一个控制器。这意味着视图会被顶级controller ,sportsStoreCtrl支持,该控制器管理包含ng-view指令的视图。当视图不需要任何额外控制器行为时,不用为部分视图定义控制器。

    这里,AngularJS的一个重要特性是,在input元素上,使用ng-model指令。

    <input class="form-control" ng-model="data.shipping.name"/>

    该ng-model指令,设置了一个双向数据绑定。作者会在第10章深入解释数据绑定,但这里简短介绍。它使用{{}},是单向绑定,意味着简单地从scope显示一个值。该单向绑定的值可以被过滤,它可以是一个表达式,而不仅仅是一个数据值,单他是一个只读关系。如果scope改变,显示的值也会变。它只能从scope到binding。

    双向数据绑定用于表单元素,允许用户输入值,改变scope。更新流在scope和data binding之间。通过一个JavaScript功能,执行更新scope数据属性。这里只要知道,如果用户在input元素中输入了一个值,该值被指派到通过ng-model指令指定的scope属性上,在本例中是,data.shipping.name属性和data.shipping.street属性。

    提示:注意作者没有必要更新控制器,让他在scope上定义一个data.shipping对象,或单独的name或street属性。如果该属性不存在,AngularJS scopes会假设你想动态地定义一个属性。作者将在第13章深入讲。

    2.1、添加表单校验

    如果你写过任何种类的web应用,使用表单元素,你会知道用户会在input字段中输入任何东西。要确保你得到预期的数据,AngularJS支持form validation,允许检查值。

    AngularJS表单校验,基于标准HTML属性,应用到表单元素,例如type和required。表达那校验会自动执行,但必须显示校验feedback给用户。

    提示:HTML5在input元素上定义了一组新的type属性的值,能被用于该值是一个e-mail地址或一个数字。作者会在第12章解释。

    2.1.1、准备校验

    设置表单校验的第一步,是添加一个form元素到视图,并添加校验属性到作者的input元素。下面是placeOrder.html文件。

    <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 class="form-control" ng-model="data.shipping.name" required />
    </div>
    <h3>Address</h3>
    <div class="form-group">
    <label>Street Address</label>
    <input class="form-control" ng-model="data.shipping.street" required />
    </div>
    <div class="text-center">
    <button class="btn btn-primary">Complete order</button>
    </div>
    </div>
    </form>

    form元素有三个目的,作者没有使用浏览器为提交表单提供的内建支持的。

    第一个目的,是启用校验。AngularJS使用自定义的指令,重定义了一些HTML元素,来启用制定特性,例如form。没有form元素,AngularJS不验证元素的内容,如input,select,textarea等。

    第二个谜底,form元素禁用浏览器可能会尝试执行的任何校验,它通过novalidate属性。该属性是标准的HTML5特性,它确保只有AngularJS检查数据。如果你忽略了novalidate属性,那么用户可能会得到冲突,或多次校验feedback,基于浏览器的会被使用。

    最后的目的,form元素会定义一个变量,用于报告表单校验。它是通过name属性做的,作者将它设为shippingForm。当用户点击button按钮,用户表单的内容被验证时,该值用于显示feedback。

    2.1.2、显示验证Feedback

    一旦form元素和校验属性放置好,AngularJS开始校验用户提供的数据,作者要为用户显示feedback。作者将在第12章详细讲解,但这里作者会用两种类型的feedback:定义CSS样式给AngularJS通过验证或没有通过验证的表单元素。可以使用scope变量控制feedback消息显示的元素的可见性。placeOrder.html文件:

    <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 address
    </span>
    </div>
    <div class="text-center">
    <button class="btn btn-primary">Complete order</button>
    </div>
    </div>
    </form>

    AngularJS会给表单元素制定ng-valid和ng-invalid classes。Form元素总是会持有这些样式中的一个。

    该CSS样式表明校验的效果。所以作者必须给每个元素添加一个name属性,使用AngularJS添加到scope的校验数据,控制error消息的可见性。

    <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 address
    </span>

    input元素,用于捕获用户的街道,将name属性指派为street。AngularJS在scope上创建一个shippingForm.street对象(它是form元素的name和input元素的name的结合)。该对象定义一个$error属性,该属性是一个对象,为每个校验属性提供一个属性,表示input元素失败的原因。如果shippingForm.street.$error.required属性为真,知道street input元素通过没有校验通过,用于显示error消息给用户的空间,会执行ng-show指令。

    记住:作者用的简单,但AngularJS可以用于创建更复杂的校验。

    2.1.3、将按钮链接到校验

    在多数web应用,用户在提供所有表单数据,并校验通过后,才能进入下一步。当表单没有通过校验,作者想禁用Complete order按钮,并在用户完成表单属性后,自动启用它。

    要做到这点,作者要改进AngularJS添加到scope的校验信息。作者可以得到整个表单的状态。当input元素为没有通过校验,shippingForm.$invalid属性为true,作者将基于此,使用ng-disabled指令,管理按钮元素的状态。作者将在第11章描述ng-disable指令。

    <div class="text-center">
    <button ng-disabled="shippingForm.$invalid"
    class="btn btn-primary">Complete order</button>
    </div>

    2.2、添加剩下的表单字段

    现在你知道AngularJS是怎么进行表单校验的了,作者要将剩下的input元素添加到表单。

    <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 address
    </span>
    </div>
    <div class="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>

    提示:你可能会尝试使用ng-repeat指令,来生成input元素。这样生成的,不能很好滴工作,因为有些指令属性值,如ng-model,ng-show,是计算过的。作者建议这样做,单你想用更先进的技术,看第15-17章,作者会描述创建自定义指令的方式。

    3、存储订单

    本节,我们会扩展Deployd服务器提供的数据库,使用Ajax请求发送订单数据给服务器,在流程的最后,播放一个感谢信息。

    3.1、扩展Deployd服务器

    使用dashboard,选择collection,将集合的名字设为/orders,点击创建按钮。定义如下属性:

    Name Type Required
    name string Yes
    street string Yes
    city string Yes
    state string Yes
    zip string Yes
    country string Yes
    giftwrap boolean No
    products array Yes

    多花点注意,在gifwrap和products属性的类型上。

    3.2、定义控制器行为

    下一步要定义使用Ajax请求,发送订单给Deployd服务器的控制器行为。我可以以很多不同的方式定义该功能——创建一个服务或创建一个新的控制器。你可以以你喜欢的方式,构建,没有绝对的对与错。作者将保持一切都很简单,添加行为到顶级sportsStore控制器上,该控制器已经包含用Ajax请求加载产品数据的代码。

    angular.module("sportsStore")
    .constant("dataUrl", "http://localhost:5500/products")
    .constant("orderUrl", "http://localhost:5500/orders")
    .controller("sportsStoreCtrl", function ($scope, $http, $location,
    dataUrl, orderUrl, cart) {
    $scope.data = {
    };
    $http.get(dataUrl)
    .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");
    });
    }
    });

    Depolyd会在数据库中创建一个新的对象,来相应POST请求,并返回刚刚创建的对象,包括id属性。

    作者定义了一个新的constant,制定URL,你会用于POST请求,并添加一个cart服务的依赖,以得到产品明细。作者添加到控制器的行为叫做sendOrder,它将用户的购物明细,作为参数。

    作者使用angular.copy工具方法,他在第5章描述过,用来创建shipping details对象的拷贝,所以他可以安全地操作它,而不影响应用的其他部分。该shipping details对象的属性,通过ng-model指令创建,代表作者的orders Deployd集合。左右要做的,就是定义一个products属性,来引用购物车中的products数组。

    作者使用$http.post方法,创建一个Ajax POST请求指定URL和数据,他使用success和error方法,在第5章介绍过,来相应请求的结果。

    作者也使用在$http.post方法返回的promise上,使用then方法。该then方法,持有一个功能,无论Ajax请求的结果如何,都会被调用。无论发生什么,他都想要显示相同的视图给用户,所以他使用then方法,调用$location.path方法。这是编程的方式设置URL的path组建,它将会通过第7章创建的URL配置,触发视图改变。

    3.3、调用控制器行为

    要调用心控制器行为,必须添加ng-click指令到shipping details视图的button元素。

    <div class="text-center">
    <button ng-disabled="shippingForm.$invalid"
    ng-click="sendOrder(data.shipping)"
    class="btn btn-primary">
    Complete order
    </button>
    </div

    3.4、定义视图

    作者定义的Ajax请求完成后的URL路径是/complete,该URL路由配置映射到/views/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>

    该视图定义了两个不同的内容块,来处理Ajax请求的success和unsuccessful。如果发生error,error的明细会显示,通过一个a链接,让用户返回到shipping details视图,让他可以再试一次。如果请求成功,为用户显示thank-you消息,包含新订单对象的id。

    4、做改进

    以后会做改进的地方:

    第一,当你加载app.html文件到浏览器,你可能注意到一个小的延迟,在视图显示和products的元素和分类被生成。这是因为Ajax请求在后台获取数据,在等待服务器返回数据旗舰,AngularJS继续执行应用,并显示视图,当数据抵达后,在更新。在第22章,作者将描述如何使用URL路由特性,让AngularJS在Ajax请求已经完成后,再显示视图。

    第二,作者将产品数据中的分类,提取出来,用于导航和分页特性。在一个真实的项目中,作者会考虑,在产品数据第一次抵达时,生成该信息。在第20章,作者描述如何使用promises,来构建行为链。

    最后,作者使用$animate服务,在第23章介绍,当URL路径改变时,在视图切换时使用过度动画。

    4.1、避免优化陷阱

    你注意到,作者说要考虑重用分类和分页数据。这是因为任何类型的优化,都要十分小心,来确保它是明智的,并避免两个主要的陷阱。

    第一个陷阱是,过早地优化。

    第二个陷阱是,translation优化。

    5、管理产品分类

    要完成SportsStore应用,作者要创建一个应用,来允许管理员管理产品分类的内容,和订单队列。这回允许作者,演示AngularJS如何用于执行create,read,update,delete操作。

    记住:每个后端服务实现认证有不同的方式,但基本的前提是相同的:将用户的凭证,通过请求发送给制定URL,如果请求成功,浏览器会返回一个cookie,浏览器会在随后的请求中,自动发送该cookie,用以标识用户。本例中使用Deployd。

    5.1、准备Deployd

    给数据库做一些改变,有些事情只能管理员做。在这里,我们会使用Deployd定义一个管理员用户,并穿件访问策略。

    Collection Admin User
    products create,read,update,delete read
    orders create,read,update,delete create

    总之,管理员能在任何集合上执行任何操作。一般用户可以read产品集合,并创建orders集合中的新对象。

    在dashboard上创建Users Collection ,设置新容器的名字为/users。

    用户集合,定义了id,username,password属性,是作者需要的。创建一个新对象,用户名密码为admin,secret。

    5.1.1、集合的安全

    作者喜欢Deployd的一个特性,是他定义一个简单的JavaScript API,可以用于实现服务端功能。当在一个集合上执行操作时,一系列的事件会被触发。点击products集合的Events,你会看到一系列的tab,代表不同的集合事件:On Get,On Validate,On Post,On Put,On Delete。所有的集合都有这些事件,你可以使用JavaScript,来加强认证策略。填入一下代码到On Put和On Delete tabs:

    if (me === undefined || me.username != "admin") {
        cancel("No authorization", 401);
    }

    在Deployd API中,变量me,代表当前用户,cancel功能,使用制定消息和HTTP状态吗,结束一个请求。该代码,允许有权限的用户,和管理员用户访问,单其他请求都会用401状态吗结束,这代表客户端是没有权限做请求。

    提示:不要担心这些事件是什么,作者会在后面说。

    重复这些步骤,在orders集合的事件里,处理On Post和On Validate。强制执行认证控制的事件:

    Collection Description
    products On Put, On Delete
    orders On Get, On Put, On Delete
    users None

    5.2、创建管理应用

    作者要为管理任务,创建一个分隔的AngularJS应用。作者可以在主应用中集成这些特性,但这将意味着所有用户都将下载admin功能的代码,即使他们永远用不上。作者添加一个新文件,叫admin.html,放到angularjs文件夹:

    <!DOCTYPE html>
    <html ng-app="sportsStoreAdmin">
    <head>
    <title>Administration</title>
    <script src="angular.js"></script>
    <script src="ngmodules/angular-route.js"></script>
    <link href="bootstrap.css" rel="stylesheet" />
    <link href="bootstrap-theme.css" rel="stylesheet" />
    <script>
    angular.module("sportsStoreAdmin", ["ngRoute"])
    .config(function ($routeProvider) {
    $routeProvider.when("/login", {
    templateUrl: "/views/adminLogin.html"
    });
    $routeProvider.when("/main", {
    templateUrl: "/views/adminMain.html"
    });
    $routeProvider.otherwise({
    redirectTo: "/login"
    });
    });
    </script>
    </head>
    <body>
    <ng-view />
    </body>
    </html>

    作者使用Module.config方法,创建了三个路由,让ng-view指令显示body元素。

    URL Path View
    /login /views/adminLogin.html
    /main /views/adminMain.html
    All others Redirects to /login

    otherwise方法定义的路由,作者使用redirectTo选项,将URL路径改变到其他路由。这将让浏览器到/login路径,用于认证用户。作者将在第22章描述。

    5.2.1、添加占位符视图

    作者要实现认证特性,需要为/views/adminMain.html视图文件创建一些占位符内容,当认证成功时会显示。下面是adminMain.html文件的内容:

    <div class="well">
    This is the main view
    </div>

    作者将在后面替换它。

    5.3、实现认证

    Deployd认证用户,使用标准HTTP请求。该应用发送一个POST请求到/users/login URL,包含username和password值。如果认证成功,服务器响应状态吗200,如果失败,返回401。要实现认证,作者要定义一个控制器,发起Ajax调用,并处理响应。下面是controllers/adminControllers.js文件的内容:

    angular.module("sportsStoreAdmin")
    .constant("authUrl", "http://localhost:5500/users/login")
    .controller("authCtrl", function($scope, $http, $location, authUrl) {
    $scope.authenticate = function (user, pass) {
    $http.post(authUrl, {
    username: user,
    password: pass
    }, {
    withCredentials: true
    }).success(function (data) {
    $location.path("/main");
    }).error(function (error) {
    $scope.authenticationError = error;
    });
    }
    });

    5.3.1、定义视图认证

    5.4、定义主视图和控制器

    5.5、实现订单特性

    5.6、实现产品特性

    5.6.1、定义RESTful控制器

    5.6.2、定义视图

    5.6.3、添加对HTML文件的引用

  • 相关阅读:
    第08组 Alpha冲刺 (2/6)
    第08组 Alpha冲刺 (1/6)
    结对编程作业
    第01组 Alpha冲刺总结
    第01组 Alpha冲刺(6/6轮)
    第01组 Alpha冲刺(6/6)
    第01组 Alpha冲刺(5/6)
    第01组 Alpha冲刺(5/6轮)
    第01组 Alpha冲刺(4/6)
    第01组 Alpha冲刺(3/6)
  • 原文地址:https://www.cnblogs.com/msdynax/p/3789625.html
Copyright © 2020-2023  润新知