Introduction
If you want to create any kind of on-line store, you will
probably need a shopping cart.
A shopping cart is basically a list that contains products
selected by the user while he shops. When the user is finished shopping, he
will usually examine the list to double-check that the items, quantities, and
prices are correct. If he finds any errors he should be able to edit the list.
Once he is ready, he should be able to check out. The checkout process involves
an exchange of information that results in a sale.
Sounds simple, right? And it actually is. The only challenge
is performing the checkout, because that involves personal information and money.
Fortunately, there are services that handle this type of transaction and you
can leverage them. Some of the most popular are PayPal and Google
Wallet.
This article describes the implementation of a shopping cart
using JavaScript. The cart uses PayPal and Google Wallet payment services.
Adding other providers is fairly easy. If you have your own payment
infrastructure for example, you can extend the shopping cart to use that in
addition to the PayPal and Google Wallet options. Offering more
payment options should increase sales.
The article includes a sample application called “Angular
Store” that demonstrates how to use the shopping cart in AngularJS
applications.
Shopping Cart Requirements
When I started developing the shopping cart, I had the
following requirements in mind:
- Must be 100% pure JavaScript (so it is easy to integrate into any site)
- Must follow the MVVM architecture (so it is easy to customize its look and feel)
- Must be safe (we don't want to be responsible for storing people's credit card numbers etc.)
- Must be fast and reliable (we don’t want users to give up before they checkout!)
- Must be flexible (it should allow payments to be processed using different services)
- Must be extensible (adding new payment methods should be easy)
- Must be easy to use (because there's no reason for it to be complicated)
I believe the “shoppingCart” class described below addresses
all these requirements. It uses jQuery and integrates well with AngularJS
applications. The “shoppingCart” class contains all the logic and provides the
object model needed to create flexible and attractive views.
The Angular Store Sample Application
To understand how the cart works, let’s take a quick look at
a typical application. The Angular Store app has three main views:
Store: This is the
main view. It presents a list of the products available. Users can search for
items using a filter, get detailed information about specific products by
clicking their names, add products to the shopping cart, and see a quick
summary of what is in their cart. Clicking the summary navigates to the cart.
This is what the store view looks like:

Product Details: This view
shows details about a product and allows users to add or remove the product
to/from the shopping cart. The view also presents a quick summary of the cart
so users can tell whether this product is already in the cart. This is what the
product details view looks like:

Shopping Cart: This
view shows the shopping cart. Users can edit the cart and checkout using PayPal
or Google Wallet. Offering more payment options tends to increase sales,
because some users may have accounts with one service or the other. This is
what the shopping cart view looks like:

AngularJS Infrastructure
The sample application starts with the definition of an AngularJS
module that represents the application. The module AngularStore module is
defined in the app.js file as follows:
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' });
}]);
This first block of code defines the storeApp object
that represents the application. It contains a routeProvider that
specifies which view should be displayed based on the URL.
For example, when the URL ends with “/cart”, the app should
display the view defined in the “partials/shoppingCart.htm” file. The view
should be bound to a controller of type “storeController”.
When the URL ends with “/product/:productSku”, the app
should display the view defined in the “partials/product.htm” file. The view
should be bound to a controller of the same type “storeController”. In this
case, the “/:productSku” represents a variable parameter used to identify the
product being shown. It will be replaced at runtime with an actual product
code.
In this case, all views have the same type of controller, a
class that contains a “store” and a “cart”.
Because in this case all views refer to the same store and
cart, it makes sense to create these data objects once, at the app level, and
allow all controllers to use them. This will improve performance because it
eliminates the need to re-load the store and cart items whenever a new view is
displayed.
The easiest way to share data between controllers in AngularJS
is by defining an app-level “service”, and later using this service to
initialize the controllers that need them. This links shows a simple example
that illustrates the concept:
http://egghead.io/video/angularjs-sharing-data-between-controllers/
Here is the definition of the “DataService” that provides
data shared by all views in the Angular Store application:
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
};
});
The service creates a “store” object that contains a list of
the products available and a “shoppingCart” object that represents the shopping
cart.
When the “shoppingCart” object is created, it automatically
loads its contents from local storage, so users can add items to the cart,
close the application, and continue shopping later on.
After creating the cart, the service configures the cart’s
checkout parameters. In this example, the cart provides two checkout options:
- The “PayPal” option specifies the merchant account to use for checking out, and has
no additional options. To use the shopping cart with PayPal, you have to create
a merchant account with PayPal. You can do that here:
https://www.paypal.com/webapps/mpp/merchant
- The “Google” option specified the merchant account and additional options related
to shipping charges. To use the shopping cart with Google Wallet, you have to
create a merchant account with Google. You can do that here:
https://developers.google.com/commerce/wallet/digital/training/getting-started/merchant-setup
Once the data service has been created, it can be used by
the “storeController” objects that will drive all the views in the application.
This is done in the controller.js file:
function storeController($scope, $routeParams, DataService) {
$scope.store = DataService.store;
$scope.cart = DataService.cart;
if ($routeParams.productSku != null) {
$scope.product = $scope.store.getProduct($routeParams.productSku);
}
}
The “storeController” function retrieves the store and cart
from the “DataService” discussed earlier and adds them to the AngularJS $scope
object. The $scope object works as a data context for the view.
The app.js and controller.js files contain all
the AngularJS part of the application code. The remaining classes (store.js,
product.js, and shoppingCart.js) are platform-agnostic.
The ‘store’ and ‘product’ classes
The ”store” class is defined in the store.js file.
It exposes a list of products and provides a getProduct
method that retrieves an individual product by SKU. This method is used by the
“storeController” to set the current product when the URL routing specifies a productSku.
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),
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;
}
The ”product” class is defined in the product.js file
as follows:
function product(sku, name, description, price,
cal, carot, vitc, folate, potassium, fiber) {
this.sku = sku; 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
};
}
The product class has three properties that will be used by
the shopping cart: sku (unique ID), name, and price. All
other members are used elsewhere within the application, but not by the cart.
Decoupling the cart from the raw product class makes it
easier to integrate the cart with existing applications (which often already
have product classes generated automatically from databases).
The ‘shoppingCart’ class
The “shoppingCart” class is the most interesting class in
the project. It is defined in the shoppingCart.js file and implements an
object model as follows:
shoppingCart(cartName)
This is the constructor.
The cartName parameter identifies the cart when
saving it to or loading it from local storage.
Before you can actually use the cart for checkout operations, you must
initialize it by adding one or more payment providers. This is done with the addCheckoutParameters
method.
addCheckoutParameters(serviceName, merchantID, [options])
This method defines a set of checkout parameters.
The serviceName parameter defines the name of the
payment provider to use. In the current implementation, this must be set to
either “PayPal” or “Google”.
The merchantID parameter specifies the merchant
account associated with the service. You can create PayPal and Google merchant
accounts using these links:
PayPal:
https://www.paypal.com/webapps/mpp/merchant
Google:
https://developers.google.com/commerce/wallet/digital/training/getting-started/merchant-setup
The options parameter defines additional provider-specific fields.
In our example, we used this parameter to specify custom shipping methods associated with the
Google checkout. Both PayPal and Google support a large number of optional parameters that
you can use to customize the checkout process.
addItem(sku, name, price, quantity)
This method adds or removes items from the cart.
If the cart already contains items with the given sku,
then the quantity of that item is modified. If the quantity reaches zero, the
item is automatically removed from the cart.
If the cart does not contain items with the given sku,
then a new item is created and added to the cart using the specified sku,
name, price, and quantity.
After the cart has been updated, it is automatically saved
to local storage.
clearItems()
This method clears the cart by removing all items. It also saves
the empty cart to local storage.
getTotalCount([sku])
This method gets the quantity of items or a given type or
for all items in the cart.
If the sku is provided, then the method returns the
quantity of items with that sku. It the sku is omitted, then the
method returns the quantity of all items in the cart.
getTotalPrice([sku])
This method gets the total price (unit price * quantity) for
one or all items in the cart.
If the sku is provided, then the method returns the
price of items with that sku. It the sku is omitted, then the
method returns the total price of all items in the cart.
checkout([serviceName], [clearCart])
This method initiates a checkout transaction by building a
form object and submitting it to the specified payment provider.
If provided, the serviceName parameter must match one
of the service names registered with calls to the addCheckoutParameters
method. If omitted, the cart will use the first payment service registered.
The clearCart parameter specifies whether the cart should be cleared after
the checkout transaction is submitted.
The checkout method is the most interesting in this
class, and is listed below:
shoppingCart.prototype.checkout = function (serviceName, clearCart) {
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 + "'.";
}
switch (parms.serviceName) {
case "PayPal":
this.checkoutPayPal(parms, clearCart);
break;
case "Google":
this.checkoutGoogle(parms, clearCart);
break;
default:
throw "Unknown checkout service: " + parms.serviceName;
}
}
The method starts by making sure it has a valid payment
service, and then defers the actual work to the checkoutPayPal or checkoutGoogle
methods. These methods are very similar but are service-specific. The checkoutPayPal
method is implemented as follows:
shoppingCart.prototype.checkoutPayPal = function (parms, clearCart) {
var data = {
cmd: "_cart",
business: parms.merchantID,
upload: "1",
rm: "2",
charset: "utf-8"
};
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);
}
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);
this.clearCart = clearCart == null || clearCart;
form.submit();
form.remove();
}
The checkoutPayPal method builds a form, populates it
with hidden input fields that contain the cart data, and submits the form to
the PayPal servers. The whole process is described here:
https://www.paypal.com/cgi-bin/webscr?cmd=p/pdn/howto_checkout-outside
The checkoutGoogle method is very similar. It also
builds and submits a form, the only difference being the name and content of
the fields. Details are available here:
https://developers.google.com/checkout/developer/Google_Checkout_Custom_Cart_How_To_HTML.
Both checkout methods allow you to add custom fields specified
in the options parameter of the cart’s addCheckoutParameters
method. These custom fields can be used to specify things like return URLs,
custom images for the cart on the server’s site, custom shipping rules and
prices, etc.
When the checkout method submits the form, the user
is taken to the appropriate site (PayPal or Google Wallet), where he can review
the information about the items, update his own personal and credit card
information, and finalize the transaction. All this happens outside the scope
of the application. The payment provider will then use the information
associated with the merchant id provided by the form to notify you of the
transaction so you can collect the payment and ship the goods to the customer.
If you wanted to add more payment options to the cart, you
would have to:
- Modify the addCheckoutParameters method to accept the new service name.
- Create a new checkout<ServiceName> method to handle the checkouts using
the new service. This would probably be similar to the existing checkoutPayPal
and checkoutGoogle methods.
- Modify the checkout method to call the new method depending on the service name specified by the user.
For example, if you wanted to leverage an existing payment
infrastructure you have on your site, you could create a method similar to checkoutPayPal,
but with a URL on your site. The server would receive the form with all the
information encoded as hidden fields, and would have access to the current
session, user, etc. At this point, you would have all the information required
by your payment infrastructure (cart and user).
AngularJS Views
Now that we have covered the AngularJS infrastructure and
the controller classes, let’s turn our attention to the views.
The default.htm file contains the master view. It is
implemented as follows:
<!doctype html>
<html ng-app="AngularStore">
<head>
<!---->
<!---->
<!---->
<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>
Notice the following important points:
- The “ng-app” attribute associates the page with the AngularStore module defined in
the app.js file. This attribute takes care of the URL routing, view
injection, and providing each view with the appropriate controllers.
- The “ng-view” div marks the place where AngularJS will inject the partial pages that
correspond to the routed views. Recall that our application has three partial
pages: store.htm, product.htm, and shoppingCart.htm.
- The parts of the page around the “ng-view” div remain in place as you switch
views, acting as a master page. In this sample, this area shows the app logo
and a title.
- The sample application uses Bootstrap, twitter’s public framework that
includes powerful and easy to use css styles. Bootstrap makes it easy to create
adaptive layouts that work well on the desktop and on mobile devices (for
details, see
http://twitter.github.io/bootstrap/).
The store.htm partial view is implemented as follows:
<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>
The view consists of a table with three regions: the first
row contains a single cell that spans the entire table and shows a summary of
the shopping cart. Notice how it uses the getTotalCount and getTotalPrice
methods to retrieve the cart information. Clicking this element redirects the
browser to “default.htm#/cart”, which shows the shopping cart.
The view uses Bootstrap’s built-in icons, in this
case the “icon-shopping-cart” class to enhance the view with simple and
attractive icons. Bootstrap includes a set of 140 icons that cover a lot
of common scenarios (see the complete list here:
http://twitter.github.io/bootstrap/base-css.html#icons).
The body of the table uses an ng-repeat attribute to
show a sorted, filtered list of all products. Each product row contains an
image, a description that is also a link to the product details view, the
product price, and a link that adds the product to the shopping cart. Adding
items to the cart is accomplished by using the “ng-click” attribute to invoke
the cart’s addItem method.
The “orderBy” and “filter” clauses are filters provided by AngularJS.
You can learn more about AngularJS filters here:
http://egghead.io/video/rough-draft-angularjs-built-in-filters/
The last row is a copy of the first. It shows another
summary of the cart below the product list, making navigation easier in stores
that have a lot of products.
The product.htm partial view is very similar, so we
will not list it here.
The most interesting partial view is the shopping cart
itself, in 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">
The first part of the view shows a title and sets up a Bootstrap
“fluid-row” div that will show two items: the cart items on the left and the
cart buttons on the right.
<!---->
<div class="span8">
<table class="table table-bordered">
<!---->
<tr class="well">
<td><b>Item</b></td>
<td class="tdCenter"><b>Quantity</b></td>
<td class="tdRight"><b>Price</b></td>
<td />
</tr>
<!---->
<tr ng-hide="cart.getTotalCount() > 0" >
<td class="tdCenter" colspan="4">Your cart is empty. </td>
</tr>
<!---->
<tr ng-repeat="item in cart.items | orderBy:'name'">
<td>{{item.name}}</td>
<td class="tdCenter">
<div class="input-append">
<!---->
<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>
<!---->
<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>
The items are shown in a “span8” div. Bootstrap layouts are
based on 12 width units, so this div will be approximately two-thirds of the
width available.
The table that contains the cart items starts with a header
row, followed by an empty cart indicator. The “ng-hide” attribute is
used to ensure the indicator is visible only when the cart is empty.
The body of the table is generated with an “ng-repeat”
attribute that loops through the items in the cart.items array. For each
item, the table shows the item name, followed the item quantity and price.
The item quantity is shown using a composite element made up
of an input field bound to the item.quantity property and two buttons
used to increment or decrement the quantity.
Notice how the “ng-change” attribute is used to save
the cart contents when the quantity changes. Notice also how the decrement
button is disabled when the item quantity reaches one. At this point,
decrementing the quantity would remove the item from the cart, and we don’t
want users to do that by accident.
After the quantity field, the table shows the total price of
the item (unit price times quantity) and a button that allows users to remove
the item from the cart.
The table footer shows a summary of the cart contents, and
is automatically updated as the user edits quantities or removes items from the
cart. The updates are handled automatically by AngularJS.
In addition to the cart items, the view has a section with
buttons used to return to the store, to clear the cart, and to check out:
<!---->
<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>
The section starts with a “span4” div which fills up the
page (remember the items were placed in a “span8” div).
The “back to store” button navigates back to the
“default.htm” page, which maps to the store.
The “clear cart” button invokes the cart’s clearItems
method, and is enabled only if the cart is not already empty.
<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>
The checkout buttons call the cart’s checkout method
passing in the appropriate service name. Remember we configured the cart in the
app.js file to accept PayPal and Google as valid payment service
providers.
<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>
These buttons provides the same cart checkout services, but
use images provided by PayPal and Google. Personally, I think the provider
buttons may look a little less consistent on the page, but provide a familiar
feeling to the user.
The nice thing about Bootstrap’s layout mechanism is
that it is ‘adaptive’. If you view the page on mobile devices, the layout
automatically adapts to the screen width. The screenshots below illustrate
this. The image on the left shows a wide view, with buttons on the right of the
items (typical desktop view). The image on the right shows a narrow view, with
buttons below the items (typical mobile view).

Conclusion
The “shoppingCart” class presented here fulfills the
requirements outlined in the beginning of the article. It is 100% JavaScript,
and has no requirements on the server, so it should be easy to add to existing
projects. The cart supports PayPal and Google Wallet, which are
popular payment services. Many applications will probably want to extend this
to support their own custom payment services, and that should be easy to do.
The MVVM pattern allows the same cart object to be exposed
in multiple views, which contain very simple markup and virtually no logic. The
sample application for example has a view that shows the whole cart, and allows
users to edit it; but it also shows cart summaries on the store and product
pages. These views are easy to create and customize, and there is no impact on
the application logic.
I am a big fan of AngularJS. In addition to the MVVM
support it provides, which is great, it has an amazing list of features that
include routing and partial views, filters, custom directives, and more.
I especially like the fact that AngularJS’s data
binding features work with plain JavaScript objects. Some MVVM libraries (like KnockoutJS)
require special “observable” properties, which are declared and accessed using
a syntax that is different from plain properties.
The one aspect of AngularJS I do not like is the lack
of documentation. You can find a lot of information about the details of pretty
much any aspect of AngularJS, but I have not found a good reference that
presents an overall conceptual view of the framework. My favorite source of documentation
on AngularJS is a series of videos created by John Lindquist which you can find
here:
http://www.youtube.com/user/johnlindquist.
I also like Bootstrap, because it makes it easy to
create attractive, responsive HTML layouts. In addition to a nice set of styles
and icons, Bootstrap also provides some JavaScript components that you
can use to enhance your UIs with things like tooltips, pop-overs, menus, etc.
You can learn about Bootstrap here:
http://twitter.github.io/bootstrap/.
References
- AngularJS by Google.
The AngularJS home page, with links to samples and documentation.
- A Look Into AngularJS – The "Super-heroic JavaScript MVW Framework".
A nice, brief summary of the AngularJS framework.
- Building Huuuuuge Apps with AngularJS.
The best documentation I found on how to structure large (or not so large) AngularJS applications.
- Egghead.io.
John Lindquist’s series of how-to videos on AngularJS.
- nopCommerce - An open-source shopping cart framework.
A CodeProject article describing a full (client/server) shopping cart framework.
- Use of the PayPal payment system in ASP.NET.
A CodeProject article about the PayPal payment system, including a lot of interesting and useful details.