65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 AngularJS 构建的购物车应用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (104投票s)

2013年4月11日

CPOL

18分钟阅读

viewsIcon

1009701

downloadIcon

36154

创建一个购物车类并在您的 MVVM HTML5 应用程序中使用它。

引言

如果您想创建任何在线商店,您可能需要一个购物车。

购物车基本上是一个列表,其中包含用户在购物时选择的产品。当用户完成购物后,他通常会检查列表以仔细核对商品、数量和价格是否正确。如果他发现任何错误,他应该能够编辑列表。一旦他准备好了,他应该能够结账。结账过程涉及信息的交换,从而促成销售。

听起来很简单,对吧?实际上就是这样。唯一的挑战在于完成结账,因为它涉及到个人信息和金钱。幸运的是,有一些服务可以处理这类交易,您可以利用它们。其中一些最受欢迎的是 PayPalGoogle Wallet

本文介绍了使用 JavaScript 实现购物车。该购物车使用 PayPalGoogle Wallet 付款服务。添加其他提供商也很容易。例如,如果您有自己的支付基础设施,您可以扩展购物车以除了 PayPalGoogle Wallet 选项外,还可以使用它。提供更多付款选项应该会增加销售额。

本文包含一个名为“Angular Store”的示例应用程序,演示了如何在 AngularJS 应用程序中使用购物车。

购物车要求

当我开始开发购物车时,我考虑了以下要求:

  • 必须是 100% pure JavaScript(因此易于集成到任何网站)
  • 必须遵循 MVVM 架构(因此易于定制外观和感觉)
  • 必须安全(我们不想负责存储人们的信用卡号等信息)
  • 必须快速可靠(我们不希望用户在结账前放弃!)
  • 必须灵活(应该允许使用不同的服务处理付款)
  • 必须可扩展(添加新付款方式应该很简单)
  • 必须易于使用(因为没有理由让它变得复杂)

我相信下面描述的“shoppingCart”类满足所有这些要求。它使用 jQuery 并与 AngularJS 应用程序很好地集成。 “shoppingCart”类包含了所有逻辑,并提供了创建灵活且吸引人的视图所需的对象模型。

Angular Store 示例应用程序

为了理解购物车的工作原理,让我们快速浏览一下典型的应用程序。Angular Store 应用有三个主要视图:

商店

这是主视图。它显示了可用产品的列表。用户可以使用过滤器搜索商品,通过点击其名称获取特定产品的详细信息,将产品添加到购物车,并查看购物车内容的快速摘要。点击摘要会导航到购物车。商店视图如下所示:

产品详情

此视图显示了产品的详细信息,并允许用户将产品添加到/从中移除购物车。该视图还提供了购物车的快速摘要,因此用户可以知道该产品是否已在购物车中。产品详情视图如下所示:

购物车

此视图显示购物车。用户可以编辑购物车并通过 PayPalGoogle Wallet 结账。提供更多付款选项往往会增加销售额,因为有些用户可能拥有其中一个服务的账户。购物车视图如下所示:

AngularJS 基础架构

示例应用程序从定义表示应用程序的 AngularJS 模块开始。AngularStore 模块在 app.js 文件中定义如下:

// App Module: the name AngularStore matches the ng-app attribute
// in the main <html> tag. The route provides parses the URL and
// injects the appropriate partial page
var storeApp = angular.module('AngularStore', []).
  config(['$routeProvider', function($routeProvider) {
  $routeProvider.
    when('/store', { 
      templateUrl: 'partials/store.htm',
      controller: storeController }).
    when('/products/:productSku', {
      templateUrl: 'partials/product.htm',
      controller: storeController }).
    when('/cart', { 
      templateUrl: 'partials/shoppingCart.htm',
      controller: storeController }).
    otherwise({
      redirectTo: '/store' });
}]);

第一段代码定义了表示应用程序的 storeApp 对象。它包含一个 routeProvider,该路由指定了应根据 URL 显示哪个视图。

例如,当 URL 以“/cart”结尾时,应用程序应显示“partials/shoppingCart.htm”文件中定义的视图。视图应绑定到类型为“storeController”的控制器。

当 URL 以“/product/:productSku”结尾时,应用程序应显示“partials/product.htm”文件中定义的视图。视图应绑定到相同类型“storeController”的控制器。在这种情况下,“/:productSku”表示用于标识正在显示的产品变量参数。它将在运行时替换为实际的产品代码。

在这种情况下,所有视图都具有相同类型的控制器,一个包含“store”和“cart”的类。

由于在这种情况下,所有视图都引用相同的商店和购物车,因此有意义的是在应用程序级别创建这些数据对象一次,并允许所有控制器使用它们。这将提高性能,因为它消除了在显示新视图时重新加载商店和购物车项目的需要。

AngularJS 中共享控制器之间数据最简单的方法是定义一个应用程序级别的“service”,然后使用该服务初始化需要它们的控制器。 此链接显示了一个说明该概念的简单示例。

这是“DataService”的定义,它提供 Angular Store 应用程序中所有视图共享的数据。

// create a data service that provides a store and a shopping
// cart that will be shared by all views
// (instead of creating fresh ones for each view).
storeApp.factory("DataService", function() {
  var myStore = new store();
  var myCart = new shoppingCart("AngularStore");
  myCart.addCheckoutParameters("PayPal", "your PayPal merchant account id");
  myCart.addCheckoutParameters("Google", "your Google merchant account id ", {
    ship_method_name_1: "UPS Next Day Air",
    ship_method_price_1: "20.00",
    ship_method_currency_1: "USD",
    ship_method_name_2: "UPS Ground",
    ship_method_price_2: "15.00",
    ship_method_currency_2: "USD"
  });
  return {
    store: myStore,
    cart: myCart
  };
});

该服务创建了一个包含可用产品列表的“store”对象和一个表示购物车的“shoppingCart”对象。

当创建“shoppingCart”对象时,它会自动从本地存储加载其内容,因此用户可以向购物车添加商品,关闭应用程序,然后稍后继续购物。

创建购物车后,该服务会配置购物车的结账参数。在此示例中,购物车提供了两个结账选项:

  1. “PayPal”选项指定用于结账的商户账户,并且没有其他选项。要使用 PayPal 购物车,您必须在 PayPal 创建商户账户。您可以在这里进行操作。
  2. “Google”选项指定了商户账户和与运费相关的其他选项。要使用 Google Wallet 购物车,您必须在 Google 创建商户账户。您可以在这里进行操作。

数据服务创建后,就可以由将驱动应用程序中所有视图的“storeController”对象使用。这在 controller.js 文件中完成。

function storeController($scope, $routeParams, DataService) {

  // get store and cart from service
  $scope.store = DataService.store;
  $scope.cart = DataService.cart;

  // use routing to pick the selected product
  if ($routeParams.productSku != null) {
    $scope.product = $scope.store.getProduct($routeParams.productSku);
  }
}

storeController”函数从前面讨论的“DataService”检索商店和购物车,并将它们添加到 AngularJS $scope 对象中。 $scope 对象作为视图的数据上下文。

app.jscontroller.js 文件包含应用程序代码的全部 AngularJS 部分。其余类(store.jsproduct.jsshoppingCart.js)是平台无关的。

‘store’和‘product’类

store”类定义在 store.js 文件中。

它公开了产品列表,并提供了一个 getProduct 方法,该方法通过 SKU 检索单个产品。此方法用于“storeController”在 URL 路由指定 productSku 时设置当前产品。

// store (contains the products)
function store() {
  this.products = [
    new product("APL", "Apple", "Eat one every…", 12, 90, 0, 2, 0, 1, 2),
    new product("AVC", "Avocado", "Guacamole…", 16, 90, 0, 1, 1, 1, 2),
    new product("BAN", "Banana", "These are…", 4, 120, 0, 2, 1, 2, 2),
    // more products…
    new product("WML", "Watermelon", "Nothing…", 4, 90, 4, 4, 0, 1, 1)
  ];
  this.dvaCaption = ["Negligible", "Low", "Average", "Good", "Great" ];
  this.dvaRange = ["below 5%", "between 5 and 10%",… "above 40%"];
}
store.prototype.getProduct = function (sku) {
  for (var i = 0; i < this.products.length; i++) {
    if (this.products[i].sku == sku)
      return this.products[i];
  }
  return null;
}

product”类定义在 product.js 文件中,如下所示:

// product class
function product(sku, name, description, price,
                 cal, carot, vitc, folate, potassium, fiber) {
  this.sku = sku; // product code (SKU = stock keeping unit)
  this.name = name;
  this.description = description;
  this.price = price;
  this.cal = cal;
  this.nutrients = {
    "Carotenoid": carot,
    "Vitamin C": vitc,
    "Folates": folate,
    "Potassium": potassium,
    "Fiber": fiber
  };
}

product 类有三个将被购物车使用的属性:sku(唯一 ID)、nameprice。所有其他成员都在应用程序的其他地方使用,但不在购物车中使用。

将购物车与原始 product 类解耦,可以更容易地将购物车集成到现有应用程序中(这些应用程序通常已经从数据库自动生成了 product 类)。

‘shoppingCart’类

shoppingCart”类是项目中class中最有趣的一个。它定义在 shoppingCart.js 文件中,并实现了一个对象模型,如下所示:

shoppingCart(cartName)

这是构造函数。

cartName 参数在保存到或从本地存储加载购物车时标识购物车。在实际可以使用购物车进行结账操作之前,您必须通过添加一个或多个付款提供程序来初始化它。这是通过 addCheckoutParameters 方法完成的。

addCheckoutParameters(serviceName, merchantID, [options])

此方法定义了一组结账参数。

serviceName 参数定义了要使用的付款提供程序的名称。在当前实现中,它必须设置为“PayPal”或“Google”。

merchantID 参数指定了与服务关联的商户账户。您可以使用以下链接创建 PayPal 和 Google 商户账户:

options 参数定义了附加的提供商特定字段。在我们的示例中,我们使用此参数来指定与 Google 结账相关的自定义运费。PayPal 和 Google 都支持大量可选参数,您可以使用它们来定制结账过程。

addItem(sku, name, price, quantity)

此方法向购物车添加或移除商品。

如果购物车已包含具有给定 sku 的商品,则会修改该商品的数量。如果数量达到零,商品将自动从购物车中移除。

如果购物车不包含具有给定 sku 的商品,则会创建一个新商品并使用指定的 skunamepricequantity 添加到购物车。

更新购物车后,它会自动保存到本地存储。

clearItems()

此方法通过移除所有商品来清空购物车。它还会将空的购物车保存到本地存储。

getTotalCount([sku])

此方法获取购物车中特定类型商品或所有商品的总数量。

如果提供了 sku,则该方法返回具有该 sku 的商品的数量。如果省略了 sku,则该方法返回购物车中所有商品的数量。

getTotalPrice([sku])

此方法获取购物车中一个或所有商品的总价格(单价 * 数量)。

如果提供了 sku,则该方法返回具有该 sku 的商品的总价。如果省略了 sku,则该方法返回购物车中所有商品的总价。

checkout([serviceName], [clearCart])

此方法通过构建一个 form 对象并将其提交给指定的付款提供程序来启动结账交易。

如果提供了 serviceName 参数,它必须与调用 addCheckoutParameters 方法时注册的服务名称之一匹配。如果省略,购物车将使用注册的第一个付款服务。 clearCart 参数指定在提交 checkout 交易后是否应清空购物车。

checkout 方法是此类中最有趣的一个,下面列出:

shoppingCart.prototype.checkout = function (serviceName, clearCart) {
  // select service
  if (serviceName == null) {
    var p = this.checkoutParameters[Object.keys(this.checkoutParameters)[0]];
    serviceName = p.serviceName;
  }
  if (serviceName == null) {
    throw "Define at least one checkout service.";
  }
  var parms = this.checkoutParameters[serviceName];
  if (parms == null) {
    throw "Cannot get checkout parameters for '" + serviceName + "'.";
  }

  // invoke service
  switch (parms.serviceName) {
    case "PayPal":
      this.checkoutPayPal(parms, clearCart);
      break;
    case "Google":
      this.checkoutGoogle(parms, clearCart);
      break;
    default:
      throw "Unknown checkout service: " + parms.serviceName;
  }
}

该方法首先确保它有一个有效的付款服务,然后将实际工作推迟到 checkoutPayPalcheckoutGoogle 方法。这些方法非常相似,但特定于服务。 checkoutPayPal 方法实现如下:

// check out using PayPal; for details see:
// http://www.paypal.com/cgi-bin/webscr?cmd=p/pdn/howto_checkout-outside
shoppingCart.prototype.checkoutPayPal = function (parms, clearCart) {

  // global data
  var data = {
    cmd: "_cart",
    business: parms.merchantID,
    upload: "1",
    rm: "2",
    charset: "utf-8"
  };

  // item data
  for (var i = 0; i < this.items.length; i++) {
    var item = this.items[i];
    var ctr = i + 1;
    data["item_number_" + ctr] = item.sku;
    data["item_name_" + ctr] = item.name;
    data["quantity_" + ctr] = item.quantity;
    data["amount_" + ctr] = item.price.toFixed(2);
  }

  // build form
  var form = $('<form></form>');
  form.attr("action", "https://www.paypal.com/cgi-bin/webscr");
  form.attr("method", "POST");
  form.attr("style", "display:none;");
  this.addFormFields(form, data);
  this.addFormFields(form, parms.options);
  $("body").append(form);

  // submit form
  this.clearCart = clearCart == null || clearCart;
  form.submit();
  form.remove();
}

checkoutPayPal 方法构建一个表单,用包含购物车数据的隐藏输入字段填充它,并将表单提交到 PayPal 服务器。整个过程在此描述。

checkoutGoogle 方法非常相似。它也构建并提交一个表单,唯一的区别是字段的名称和内容。详细信息可在此处获得。

两种结账方法都允许您添加在购物车 addCheckoutParameters 方法的 options 参数中指定的自定义字段。这些自定义字段可用于指定回跳 URL、服务器网站上购物车的自定义图片、自定义运费规则和价格等。

checkout 方法提交表单时,用户将被带到相应的网站(PayPal 或 Google Wallet),在那里他可以查看商品信息,更新自己的个人和信用卡信息,并最终完成交易。所有这些都发生在应用程序范围之外。付款提供商将使用表单提供的商家 ID 相关信息来通知您交易情况,以便您可以收款并发货。

如果您想为购物车添加更多付款选项,您需要:

  1. 修改 addCheckoutParameters 方法以接受新的服务名称。
  2. 创建一个新的 checkout<ServiceName> 方法来处理使用新服务的结账。这可能与现有的 checkoutPayPalcheckoutGoogle 方法类似。
  3. 修改结账方法,使其根据用户指定的服务名称调用新方法。

例如,如果您想利用您网站上现有的支付基础设施,您可以创建一个类似于 checkoutPayPal 的方法,但 URL 指向您的网站。服务器将接收带有编码为隐藏字段的所有信息的表单,并可以访问当前会话、用户等。此时,您将拥有支付基础设施所需的所有信息(购物车和用户)。

AngularJS 视图

现在我们已经介绍了 AngularJS 基础架构和控制器类,让我们将注意力转向视图。

default.htm 文件包含主视图。其实现如下:

<!doctype html>
<html ng-app="AngularStore">
  <head>
    <!-- includes for jQuery, Angular, and Bootstrap -->
    <!-- … -->
    <!-- includes for the Angular Store app -->
    <script src="js/product.js" type="text/javascript"></script>
    <script src="js/store.js" type="text/javascript"></script>
    <script src="js/shoppingCart.js" type="text/javascript"></script>
    <script src="js/app.js" type="text/javascript"></script>
    <script src="js/controller.js" type="text/javascript"></script>
    <link href="css/style.css" rel="stylesheet" type="text/css"/>
  </head>
  <body>
    <div class="container-fluid">
        <div class="row-fluid">
            <div class="span10 offset1">
                <h1 class="well" >
                    <a href="default.htm">
                        <img src="img/logo.png" height="60" width="60" alt="logo"/>
                    </a>
                    Angular Store
                </h1>
                <div ng-view></div>
            </div>
        </div>
    </div>
  </body>
</html>

请注意以下重要事项:

  1. ng-app”属性将页面与 app.js 文件中定义的 AngularStore 模块关联起来。此属性负责 URL 路由、视图注入以及为每个视图提供相应的控制器。
  2. ng-viewdiv 标记了 AngularJS 将注入与路由视图对应的部分页面的位置。请记住,我们的应用程序有三个部分页面:store.htmproduct.htmshoppingCart.htm
  3. ng-viewdiv 周围的页面部分会在您切换视图时保持不变,充当主页。在此示例中,此区域显示应用程序徽标和标题。
  4. 示例应用程序使用了 Bootstrap,Twitter 的公共框架,它包含了强大且易于使用的 CSS 样式。Bootstrap 使创建适用于桌面和移动设备的自适应布局变得容易(详情请参阅 http://twitter.github.io/bootstrap/)。

store.htm 部分视图的实现如下:

<p class="text-info">
  Welcome to the Angular Store<br />
  Please select the products you want ….<br /></p>
<p>
  Search: <input ng-model="search"></p>

<table class="table table-bordered">

  <tr class="well">
    <td class="tdRight" colspan="4" >
      <a href="default.htm#/cart" title="go to shopping cart"
         ng-disabled="cart.getTotalCount() < 1">
        <i class="icon-shopping-cart" />
        <b>{{cart.getTotalCount()}}</b> items,
        <b>{{cart.getTotalPrice() | currency}}</b>
      </a>
    </td>
  </tr>

  <tr ng-repeat="product in store.products | orderBy:'name' | filter:search" >
    <td class="tdCenter">
      <img ng-src="img/products/{{product.sku}}.jpg" alt="{{product.name}}" />
    </td>
    <td>
      <a href="#/products/{{product.sku}}"><b>{{product.name}}</b></a>
      <br />{{product.description}}
    </td>
    <td class="tdRight">
      {{product.price | currency}}
    </td>
    <td class="tdCenter">
      <a href="" 
       ng-click="cart.addItem(product.sku, product.name, product.price, 1)">
         add to cart
      </a>
    </td>
  </tr>

  <tr class="well">
    <td class="tdRight" colspan="4">
      <a href="default.htm#/cart" title="go to shopping cart"
         ng-disabled="cart.getTotalCount() < 1">
         <i class="icon-shopping-cart" />
         <b>{{cart.getTotalCount()}}</b> items,
         <b>{{cart.getTotalPrice() | currency}}</b>
      </a>
    </td>
  </tr>
</table>

视图由一个包含三个区域的表组成:第一行包含一个跨越整个表的单元格,显示了购物车的摘要。注意它如何使用 getTotalCountgetTotalPrice 方法来检索购物车信息。点击此元素会将浏览器重定向到“default.htm#/cart”,该地址会显示购物车。

该视图使用 Bootstrap 内置的图标,在此示例中是“icon-shopping-cart”类,以简单的吸引人的图标增强视图。Bootstrap 包含一套 140 个图标,涵盖了许多常见场景(请参阅完整的列表 here)。

表体使用 ng-repeat 属性显示一个排序、过滤后的所有产品列表。每件产品行包含一张图片、一个也是产品详情视图链接的描述、产品价格以及一个将产品添加到购物车的链接。通过使用“ng-click”属性调用购物车 addItem 方法来完成商品添加到购物车。

orderBy”和“filter”子句是 AngularJS 提供的过滤器。您可以在此处了解更多关于 AngularJS 过滤器的信息。

最后一行是第一行的副本。它在产品列表下方显示了购物车的另一个摘要,使得在产品量大的商店中导航更加容易。

product.htm 部分视图非常相似,因此我们在此不列出。

最有趣的部分视图是购物车本身,在 shoppingCart.htm 中:

<p class="text-info">
  Thanks for shopping at the Angular Store.<br />
  This is your shopping cart. Here you can edit the items, 
  go back to the store, clear the cart, or check out.</p>

<div class="container-fluid">
  <div class="row-fluid">

视图的第一部分显示一个标题,并设置了一个 Bootstrapfluid-row” div,它将显示两个项目:左侧的购物车项目和右侧的购物车按钮。

<!-- items -->
<div class="span8">
  <table class="table table-bordered">

    <!-- header -->
    <tr class="well">
      <td><b>Item</b></td>
      <td class="tdCenter"><b>Quantity</b></td>
      <td class="tdRight"><b>Price</b></td>
      <td />
    </tr>
  
    <!-- empty cart message -->
    <tr ng-hide="cart.getTotalCount() > 0" >
      <td class="tdCenter" colspan="4">Your cart is empty. </td>
    </tr>
  
    <!-- cart items -->
    <tr ng-repeat="item in cart.items | orderBy:'name'">
      <td>{{item.name}}</td>
      <td class="tdCenter">
        <div class="input-append">
          <!-- use type=tel instead of number to prevent spinners -->
          <input
            class="span3 text-center" type="tel" 
            ng-model="item.quantity" ng-change="cart.saveItems()" />
          <button 
            class="btn btn-success" type="button" 
            ng-disabled="item.quantity >= 1000"
            ng-click="cart.addItem(item.sku, item.name, item.price, +1)">+
          </button>
          <button 
            class="btn btn-inverse" type="button" 
            ng-disabled="item.quantity <= 1"
            ng-click="cart.addItem(item.sku, item.name, item.price, -1)">-
          </button>
        </div>
      </td>
      <td class="tdRight">{{item.price * item.quantity | currency}}</td>
      <td class="tdCenter" title="remove from cart">
        <a href="" ng-click="cart.addItem(item.sku, item.name, item.price, -10000000)" >
          <i class="icon-remove" />
        </a>
      </td>
    </tr>
  
    <!-- footer -->
    <tr class="well">
      <td><b>Total</b></td>
      <td class="tdCenter"><b>{{cart.getTotalCount()}}</b></td>
      <td class="tdRight"><b>{{cart.getTotalPrice() | currency}}</b></td>
      <td />
    </tr>
  </table>
</div>

项目显示在一个“span8div 中。Bootstrap 布局基于 12 个宽度单位,因此这个 div 将占可用宽度的约三分之二。

包含购物车项目的表从一个标题行开始,后面跟着一个空购物车指示器。 “ng-hide”属性用于确保该指示器仅在购物车为空时可见。

表体是通过“ng-repeat”属性生成的,该属性遍历 cart.items 数组中的项目。对于每个项目,该表显示项目名称,然后是项目数量和价格。

项目数量显示在一个复合元素中,该复合元素由绑定到 item.quantity 属性的输入字段和用于增加或减少数量的两个按钮组成。

注意“ng-change”属性如何用于在数量更改时保存购物车内容。另外请注意,当项目数量达到一时,递减按钮将禁用。此时,减少数量会将项目从购物车中移除,我们不希望用户意外地这样做。

在数量字段之后,表格显示了项目的总价(单价乘以数量)以及一个允许用户从购物车中移除项目的按钮。

表格页脚显示购物车内容的摘要,并在用户编辑数量或从购物车中移除项目时自动更新。更新由 AngularJS 自动处理。

除了购物车项目外,视图还有一个包含按钮的部分,用于返回商店、清空购物车和结账:

<!-- buttons -->
<div class="span4">
  <p class="text-info">
    <button 
      class="btn btn-block" 
      onclick="window.location.href='default.htm'">
      <i class="icon-chevron-left" /> back to store
    </button>
    <button 
      class="btn btn-block btn-danger" 
      ng-click="cart.clearItems()" 
      ng-disabled="cart.getTotalCount() < 1" >
      <i class="icon-trash icon-white" /> clear cart
    </button>
  </p>

该部分以一个“span4div 开始,该 div 填充了页面(记住项目被放置在“span8div 中)。

返回商店”按钮导航回“default.htm”页面,该页面映射到商店。

清空购物车”按钮调用购物车的 clearItems 方法,并且仅在购物车不为空时启用。

<p class="text-info">
  <button
    class="btn btn-block btn-primary"
    ng-click="cart.checkout('PayPal')"
    ng-disabled="cart.getTotalCount() < 1">
    <i class="icon-ok icon-white" /> check out using PayPal
  </button>
  <button 
    class="btn btn-block btn-primary" 
    ng-click="cart.checkout('Google')" 
    ng-disabled="cart.getTotalCount() < 1">
    <i class="icon-ok icon-white" /> check out using Google
  </button>
</p>

结账按钮调用购物车的 checkout 方法,并传入相应的服务名称。请记住,我们在 app.js 文件中配置了购物车以接受 PayPal 和 Google 作为有效的付款服务提供商。

<p class="text-info">
  <button 
    class="btn btn-block btn-link"
    ng-click="cart.checkout('PayPal')"
    ng-disabled="cart.getTotalCount() < 1" >
    <img
      src=https://www.paypal.com/en_US/i/btn/btn_xpressCheckout.gif
      alt="checkout PayPal"/>
  </button>
  <button 
    class="btn btn-block btn-link" 
    ng-click="cart.checkout('Google')" 
    ng-disabled="cart.getTotalCount() < 1" >
    <img
      src=https://checkout.google.com/buttons/checkout.gif?... 
      alt="checkoutGoogle"/>
  </button>
</p>

这些按钮提供相同的购物车结账服务,但使用了 PayPal 和 Google 提供的图片。我个人认为提供商按钮在页面上看起来可能不太一致,但为用户提供了熟悉的感觉。

Bootstrap 布局机制的优点在于它是“自适应的”。如果您在移动设备上查看页面,布局会自动适应屏幕宽度。下面的屏幕截图说明了这一点。左侧的图像显示了宽视图,按钮位于项目右侧(典型的桌面视图)。右侧的图像显示了窄视图,按钮位于项目下方(典型的移动视图)。

结论

本文介绍的“shoppingCart”类满足了文章开头概述的要求。它 100% 是 JavaScript,并且对服务器没有要求,因此应该很容易添加到现有项目中。购物车支持 PayPalGoogle Wallet,这是流行的付款服务。许多应用程序可能会希望扩展它以支持自己的自定义付款服务,而且这应该很容易做到。

MVVM 模式允许将同一个购物车对象暴露在多个视图中,这些视图包含非常简单的标记,几乎不包含逻辑。例如,示例应用程序有一个显示整个购物车并允许用户编辑它的视图;但它还在商店和产品页面上显示购物车摘要。这些视图易于创建和自定义,并且对应用程序逻辑没有影响。

我是 AngularJS 的忠实粉丝。除了它提供的 MVVM 支持(非常棒)之外,它还有一系列令人惊叹的功能,包括路由和部分视图、过滤器、自定义指令等等。

我特别喜欢 AngularJS 的数据绑定功能可以与纯 JavaScript 对象配合使用的这一事实。一些 MVVM 库(如 KnockoutJS)需要特殊的“可观察”属性,这些属性使用与纯属性不同的语法进行声明和访问。

我不喜欢 AngularJS 的一点是文档的缺乏。您可以找到关于 AngularJS 几乎任何方面细节的大量信息,但我还没有找到一个好的参考来呈现框架的整体概念视图。我对 AngularJS 文档最喜欢的来源是 John Lindquist 创建的一系列视频,您可以在此处找到。

我也喜欢 Bootstrap,因为它使创建吸引人、响应式 HTML 布局变得容易。除了漂亮的样式和图标集外,Bootstrap 还提供了一些 JavaScript 组件,您可以使用它们来增强您的 UI,例如工具提示、弹出窗口、菜单等。您可以在此处了解 Bootstrap

Github 版本

不久前,一位读者请求我将此项目发布到 github,以便他和其他人可以 fork 它并添加新功能,包括 PayPalGoogle Wallet 之外的付款处理器。读者有兴趣添加对 *Stripe.js* 的支持,Stripe.js 是一种专门为开发人员设计的付款处理器。

我喜欢这个主意,但我不够快,所以他抢在我前面完成了。该项目现在可以在 github 上找到,包括对 *Stripe.js* 的支持,这一切都归功于 Spike!所以谢谢 Spike,对于那些对此新改进的购物车版本感兴趣的人,这是链接:

参考文献

  1. Google 的 AngularJS。AngularJS 主页,包含示例和文档链接。
  2. 深入了解 AngularJS –“超级英雄般的 JavaScript MVW 框架”。对 AngularJS 框架的精彩、简短的总结。
  3. 使用 AngularJS 构建巨大的应用程序。我找到的关于如何构建大型(或不大)AngularJS 应用程序的最佳文档。
  4. Egghead.io。John Lindquist 关于 AngularJS 的一系列操作指南视频。
  5. nopCommerce - 一个开源购物车框架。一篇 CodeProject 文章,介绍了一个完整的(客户端/服务器)购物车框架。
  6. 在 ASP.NET 中使用 PayPal 付款系统。一篇关于 PayPal 付款系统的 CodeProject 文章,包含许多有趣且有用的细节。
© . All rights reserved.