Skip to content

tvmogul/AngularJSCart

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

38 Commits
 
 
 
 
 
 
 
 

Repository files navigation

AngularJS Responsive Video Mobile Shopping Cart

See Full-Featured Professional Version Here
Read CodeProject Article for Amgular Shopping Cart

Download Source Code for Angular Editor

How to Install Mobile Apps on Any TV & Get Free Movies Legally

This article presents a full-featured, Mobile AngularJS Shopping Cart with Videos and many other goodies.

Shopping Cart Features

Here are some of the practical features I included:

  • Must be responsive so it will display and scroll perfectly on any mobile device.
  • Must have a cool-looking, responsive Bootstrap Menu. 
  • Allow Multiple Stores in our cart.
  • Must read the products and their descriptions from an external JSON text file.
  • Must be able to play a Video (TV Commercial) about a product in addition to a picture of the product.
  • Must at least include merchant gateways for PayPal, Google Wallet, and Stripe.
  • Must be extensible so that adding new features like payment methods is easy.
  • Must allow FREE products that can't be added to the cart.
  • Must handle Google Analytics using AngularJS.
  • Must include Dialog Service WITHOUT using ui.bootstrap because we don't want the headaches of trying to keep up with their changes.
  • Must be able to include links to thrird-party websites like Google Play, etc.
  • Must have a directory structure that allows it to be "dropped" at the root level onto any existing website.
  • Must display text as HTML so it attracts the potential customer visually.
  • Must be able to display products in a Pinterest Style Layout or a Listview Layout
  • Must follow MVC architecture.
  • Must include Pagination to control number of products displayed per page
  • Include Bootstrap 3 without adding ui.bootstrap
  • Must include Filter & Sort Options

I used Bootstrap 3 but NOT ui.bootstrap because ui.bootstrap gives me headaches trying to keep up with their changes. Bootstrap 3 has navbars where it easy to change the look-and-feel of the navbars from in side your app using AngularJS as demonstrated below in the shopping cart.​

I decided to add some Color Coordination with the navbars so that each navbar would have its own hover color when hovering over the pills. In each style sheet for each navbar we have the hover css as follows.

 .nav-pills li:hover{
    background-color: #6d0019 !important;  
    color:#fff;
    box-shadow: 0 2px 6px rgba(0,0,0,0.5)   
}

Which produces the different hover effects for each navbar.

I added a dialog service, see 'storeMessages', because I  didn't want to pollute the DOM with modal content. As a service we defer it until the point the service is called.passing the data into the modal. Whereas with a directive we would need custom attributes (that would differ from modal to modal) and that means the data would have to be put on the scope before it could be passed in, which is not always convenient. You should customize this dialog service to whatever look and feel and functionality you want in your own cart. You should customize this rudimentary dialog service I added to suite your own needs in your shopping cart.

My goal is an AngularJS app that looks and behaves nicely on any mobiel device or laptop as show below.

In a week or two I will be adding an editor I  wrote in AngularJS to easily create and edit the JSON products.txt file where the store's products are stored. And you can always find the latest code for my projects on my website at: www.software-rus.com

AngularJS App Structure

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 passes in two dependcies, namely ['ui.bootstrap', 'favicon'].  I decide to use "ui.bootstrap" in a minimal way as a dependency to "AngularStore" for the menu and part of the UI look I wanted. And I added favicon to help to add images from websites to the menu. In addition, I also added jQuery to simply demonstrate how to integrate it with an AngularJS application. The “shoppingCart” class contains all the logic and provides the object model needed to create fully responsive and attractive views.

var storeApp = angular.module('AngularStore', ['favicon', 'storeMessages.services'])
  .config(['$routeProvider', function ($routeProvider) {
  $routeProvider.
    when('/store', {
        templateUrl: 'ac_partials/store.htm',
        controller: storeController
    }).
    when('/products/:productSku', {
        templateUrl: 'ac_partials/product.htm',
        controller: storeController
    }).
    when('/cart', {
        templateUrl: 'ac_partials/cart.htm',
        controller: storeController
    }).
    otherwise({
        redirectTo: '/store'
    });
  } ]);

The first thing you will notice is that I prefaced each of our AmgularJS folders with "ac_" so that when we can just drop the cart into an existing website on a server at the root level and our folders will not conflict with existing folders or files.

We have a routeProvider that specifies which view should be displayed based on the URL. For example, when the URL ends with “/cart”, the app should load the view defined in the "ac_partials/cart.htm” file. And we will bind all of our views to a controller “storeController,” a class that contains a “store” and a “cart”.  

The easiest way to share data between controllers in AngularJS is by defining an app-level “service” to initialize the controllers that need them. We will 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 to improve performance by eliminating the need to re-load the store and cart items each time a new view is displayed.  We want our “DataService” to retrieve our sotre products data from a JSON text file. Here is the definition of the “DataService” that provides data shared by all views in the Angular Store application.

Our DataService will load data from a json file asynchronously so we will need to use promise and deferred. A promise in Angular.js act as an placeholder from where a javascript object returns some result as data which is done in an asynchronous way and it does not guarantee any fixed response time. This deferred object is constructed with $q.defer(). This Api is used to notify the success or unsuccesful completion of the asynchronous work, which is within the context of Deferred Api. After completing the task in deferred object, we can have access to the result in promise object.

// 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 ($http, $q) {
    function Store() {
        var productsDeferred = $q.defer();
        this.products = productsDeferred.promise;
        $http.get('ac_products/products.txt').success(function (data) {
            var products = [];
            for (var i = 0, len = data.length; i < len; i++) {
                var prod = data[i];
                if (prod.storeid == "7cc6cb94-0938-4675-b84e-6b97ada53978") {
                    products.push(prod);
                }
            }
            productsDeferred.resolve(products);
        });
    }
    Store.prototype.getProduct = function (sku) {
        return this.products.then(function (products) {
            // MUST use products, it's the real value; this.products is a promise
            for (var i = 0; i < products.length; i++) { 
                if (products[i].sku == sku)
                    return products[i];
            }
            return null;
        });
    };
    Store.prototype.getProducts = function () {
        return this.products.then(function (products) {
            return products;
        });
    };
    // create store
    var myStore = new Store();

    // create shopping cart
    var myCart = new shoppingCart("AngularStore");
    // enable PayPal checkout
    // note: the second parameter identifies the merchant; in order 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
    //myCart.addCheckoutParameters("PayPal", "paypaluser@youremail.com");
    myCart.addCheckoutParameters("PayPal", "paypaluser@youremail.com");

    // enable Google Wallet checkout
    // note: the second parameter identifies the merchant; in order 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
    myCart.addCheckoutParameters("Google", "GooGle_Wallet_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"
        }
    );

    // enable Stripe checkout
    // note: the second parameter identifies your publishable key; in order to use the 
    // shopping cart with Stripe, you have to create a merchant account with 
    // Stripe. You can do that here:
    // https://manage.stripe.com/register
    myCart.addCheckoutParameters("Stripe", "pk_test_stripe",
        {
            chargeurl: "https://localhost:1234/processStripe.aspx"
        }
    );

    // return data object with store and cart
    return {
        store: myStore,
        cart: myCart
    };
});

The Bootstrap 3 Menu Control

I kept the menu control for our Bootstrap 3 menu very simple as you can see below. To set the active tab I used ng-controller to run a single controller outside of the ng-view as shown below.

<li ng-class="{ active: isActive('/store')}"><a ng-href="storefront.html#/store">Store </a></li>

and... 

function MyMenu($scope, $location) {
    $scope.name = 'MyMenu';
    $scope.isCollapsed = false;

    $scope.changeBackgroundImage = function (event) {
        event.preventDefault();
        var x = 0
        for (x = 0; x < arBGs.length; x++) {
            if (_bgImage === arBGs[x]) { break; }
        }
        if (x + 1 < arBGs.length) {
            _bgImage = arBGs[x + 1];
        }
        else {
            x = 0;
            _bgImage = arBGs[x];
        }
        $("body").css('background-image', '');
        $("body").css("background", "#ffffff url(" + _bgImage + ") no-repeat center center fixed");
        localStorage['bg_cart'] = _bgImage;

    }
    
    $scope.changeNavBar = function (css_name) {
        event.preventDefault();
        var _path = "ac_css/" + css_name + ".css";
        $("#link_index").attr("href", _path);

        _navbar_theme = css_name;
        localStorage["navbar_theme"] = _navbar_theme;

        return false;
    }

    // Author: Bill SerGio - An elegant way to set the active tab is to use ng-controller 
    // to run a single controller outside of the ng-view as shown below.
    $scope.isActive = function (viewLocation) {
        return viewLocation === $location.path();
    };
}

Our Angular Views: Store, Product, and Cart

The look and feel of a shopping cart is very important so I decided to use a Pinterest Style Layout that can be switched with a ListView Layout which is commonly seen in more expensive shopping carts. I used as a starting point a non-AngularJS css layout called ViewModeSwitch that I modified for AngularJS that I found on GitHub at: https://github.com/codrops/ViewModeSwitch

Our responsive AngularJS Store App has three main views:

Store View: This is the first view that is loaded when the app runs showing the products available. Users can search for items using a filter, and obtain detailed information about specific products by watching the product's TV commercial (i.e., video) if the product has one, or by clicking the product name. Users can also add products to the shopping cart if they have a price or obtain a free sample of a product if a product has a zero cost.  Users can also view a summary of what is in their cart by clicking the summary which navigates to the cart. Shown below are what the responsive store view looks like on both a laptop and on a mobile device.

Product View: This view shows more details about a product and also allows users to add or remove the product in/from the shopping cart and shows how many of the product are added to the cart. You can display a video of the product or an image. If an image of the product is displayed then clicking on the image will popup a dialog showing a larger view of the image. You can see below what the Product View looks like with an image displayed.

Cart View: This view shows the shopping cart. Users can edit the cart and checkout using PayPal, Google Wallet, and stripe. Check my website in the next week and I will also add a Bit Coin Payment option as well. Offering more payment options increases sales by boosting the seller's credibility. Below is what the Cart View looks like on a laptop.

The service reads our "products.txt" JSON file of products and creates a “store” object that containing the products available and a “shoppingCart” object that automatically loads its contents from local storage. The cart provides three checkout methods:

  1. PayPal. Thispayment method specifies the merchant account or BuyNow account(not a merchant account) to use for payment. To use PayPal, you have to create either a BuyNow Account or a merchant account with PayPal. You can do that here: https://www.paypal.com/webapps/mpp/merchantaypal.com/webapps/mpp/merchant
  2. Google Wallet. This payment method requires that you create a merchant account with Google. You can do that here: https://developers.google.com/commerce/wallet/digital/training/getting-started/merchant-setup
  3. Stripe. This payment method allows you to embed their API on a websites to accept payments, without the need of getting a merchant account. Stripe has no setup fees, monthly fees, minimum charges, validation fees, card storage fees, or charges for failed payments. Stripe has a 7-day waiting period for transactions to be completed so that Stripe can profile the businesses involved and detect fraud. https://stripe.com

Our DataService will be used by the storeController to display the various views in the application. The storeController retrieves the store and cart from the DataService and adds them to the AngularJS $scope object which functions as a data context for the views. The storeController is where we can set the currentPage, the number of products per page and the maximum number of products used for our Pagination.

// the storeController contains two objects:
// store: contains the product list
// cart: the shopping cart object
// - DataService: called to retrieve products from JSON file
function storeController($scope, $filter, $routeParams, DataService) {
    $scope.isActive = false;
    $scope.sections = [{ name: 'list', class: 'cbp-vm-icon cbp-vm-list' }];

    $scope.setMaster = function (section) {
        $scope.selected = section;
        $scope.isActive = !$scope.isActive;

        // Let's flip our icons.
        // Grid View <===> List View
        if (section.class.toString() === 'cbp-vm-icon cbp-vm-grid') {
            $scope.sections = [{ name: 'list', class: 'cbp-vm-icon cbp-vm-list'}];
        }
        else {
            $scope.sections = [{ name: 'grid', class: 'cbp-vm-icon cbp-vm-grid'}];
        }
    }

    $scope.isSelected = function (section) {
        return $scope.selected === section;
    }

    $scope.fToggleOverlay = function () {
        $scope.overlayFlag = !$scope.overlayFlag; // toggle state of overlay flag.
    };

    $scope.filteredItems = [];
    $scope.groupedItems = [];
    $scope.pagedItems = [];

    $scope.currentPage = 1;
    $scope.pageSize = 9;
    $scope.maxSize = 25;

    // get store & cart from service
    $scope.store = DataService.store;
    $scope.cart = DataService.cart;
    $scope.products = [];

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

    DataService.store.getProducts().then(function (data) {

        //Executes when AJAX call completes
        $scope.products = data;

        $scope.numberOfPages = function () {
            return Math.ceil($scope.products.length / $scope.pageSize);
        };

        //$scope.$watch('currentPage + pageSize', function () {
        //    var begin = (($scope.currentPage - 1) * $scope.pageSize);
        //    var end = begin + $scope.pageSize;
        //    $scope.filteredItems = $scope.products.slice(begin, end);
        //});

        var searchMatch = function (haystack, needle) {
            if (!needle) {
                return true;
            }
            return haystack.toLowerCase().indexOf(needle.toLowerCase()) !== -1;
        };
        $scope.myFilter = function (categoryname) {
            //$('#searchfield').val('');
            $scope.filteredItems = $filter('filter')($scope.products, function (product) {
                for (var attr in product) {
                    if (searchMatch(product[categoryname], $scope.query))
                        return true;
                }
                return false;
            });
            $scope.currentPage = 0;
            $scope.groupToPages();
        };
        $scope.myFilter = function (column, categoryname) {
            //$('#searchfield').val('');
            $scope.filteredItems = $filter('filter')($scope.products, function (product) {
                for (var attr in product) {
                    if (searchMatch(product[column], categoryname))
                        return true;
                }
                return false;
            });
            $scope.currentPage = 0;
            $scope.groupToPages();
        };
        $scope.groupToPages = function () {
            $scope.pagedItems = [];
            for (var i = 0; i < $scope.filteredItems.length; i++) {
                if (i % $scope.pageSize === 0) {
                    $scope.pagedItems[Math.floor(i / $scope.pageSize)] = [$scope.filteredItems[i]];
                } else {
                    $scope.pagedItems[Math.floor(i / $scope.pageSize)].push($scope.filteredItems[i]);
                }
            }
        };
        // functions have been describe process the data for display
        $scope.myFilter();
        $scope.search();
    });
}

The JSON 'products.txt' File

I decided to use a JSON format to storte the products and their properties and retrieve them using AJAX as shown below.

[
    {
        "productid": "7D6A083B-01C4-4E74-9F10-2916543188B8",
        "sku": "WildWorkout",
        "productname": "WildWorkout&#174;",
        "storeid": "7cc6cb94-0938-4675-b84e-6b97ada53978",
        "categoryname": "software",
        "header": "<strong><span style=\"text-decoration:underline;\"><em>Exercises based on the principles of how the wild animals stay in shape</em></span></strong>",
        "shortdesc": "Exercises based on the principles of how the wild animals stay in shape. In the Wild Workout&#174; Mobile App we selected wild animals with massive strength in certain areas of their bodies to develop a total body workout of 45 muscle building, fat burning, body shaping exercises like no other that will have the jaws of others dropping in disbelief and envy.",
        "description": "Exercises based on the principles of how the wild animals stay in shape. In the Wild Workout&#174; Mobile App we selected wild animals with massive strength in certain areas of their bodies to develop a total body workout of 45 muscle building, fat burning, body shaping exercises like no other that will have the jaws of others dropping in disbelief and envy.",
        "link": "http://www.software-rus.com/simulator2.html?app=wildworkout",
        "linktext": "try it",
        "imageurl": "",
        "imagename": "ww.gif",
        "tube": "youtube",
        "videoid": "YyZNIarRYSc",
        "showvideo": true,
        "unitprice": 0,
        "saleprice": 0,
        "unitsinstock": 22,
        "unitsonorder": 0,
        "reorderlevel": 0,
        "expecteddate": null,
        "discontinued": null,
        "notes": "",
        "faux": null,
        "sortorder": 1
    },
    ...

The 'shoppingCart' class

The shoppingCart class implements the object model, i.e., shoppingCart(cartName), with a cartName parameter that identifies the cart when saving it to or loading it from local storage and exposes a number of essential methods.

addCheckoutParameters(serviceName, merchantID, [options])

The addCheckoutParameters(serviceName, merchantID, [options]) method initializes the cart by adding one or more payment providers using the that requires two parameters. The serviceName parameter is the payment provider to use. The merchantID parameter is the merchant account or gateway associated with the service. 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)

The additem(sku, name, price, quantity) 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 is increased or decresed by one. The item is automatically removed from the cart if the quantity reaches zero.  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()

The clearItems() method clears the cart by removing all items and saves the empty cart to local storage.

getTotalCount(sku)

The getTotalCount(sku) 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)

The getTotalPrice(sku) 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)

The checkout(serviceName, clearCart) 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:

// check out
shoppingCart.prototype.checkout = function (serviceName, clearCart) {

    // select serviceName if we have to
    if (serviceName == null) {
        var p = this.checkoutParameters[Object.keys(this.checkoutParameters)[0]];
        serviceName = p.serviceName;
    }

    // sanity
    if (serviceName == null) {
        throw "Use the 'addCheckoutParameters' method to define at least one checkout service.";
    }

    // go to work
    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;
        case "Stripe":
            this.checkoutStripe(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:

// check out using PayPal; for details see:
// http://www.paypal.com/cgi-bin/webscr?cmd=p/pdn/howto_checkout-outside
// check out using PayPal for details see:
// 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;
        var z1 = item.productname;
        var z2 = z1.replace('™', '™');  //™ = TM
        var z3 = z2.replace('℠', '℠');  //℠ = SM
        var z4 = z3.replace('®', '®');  //® = Registered
        var z5 = z4.replace('©', '©');  //© = Copyright
        var z6 = z5.replace('℗', '℗');  //℗ = Patent
        data["item_name_" + ctr] = z6;
        data["quantity_" + ctr] = item.quantity;
        data["amount_" + ctr] = item.unitprice.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();
}

The shoppingCart.prototype.checkoutPayPal = function (parms, clearCart) method builds a form, populates it with hidden input fields that contain the cart data, and submits the form to the PayPal servers.
See: https://www.paypal.com/cgi-bin/webscr?cmd=p/pdn/howto_checkout-outside

The shoppingCart.prototype.checkoutGoogle = function (parms, clearCart) method is very similar. It also builds and submits a form, the only difference being the name and content of the fields.

The shoppingCart.prototype.checkoutStripe = function (parms, clearCart) Cart) method also builds and submits a form, the only difference being the name and content of the fields. See: https://stripe.com/docs/checkout

All of these checkout methods allow you to add custom fields specified in the optionsptions 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:

  1. Modify the addCheckoutParameters method to accept the new service name.
  2. 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.
  3. 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 storefront.html file contains the master view implemented as follows:

<!doctype html>
<html ng-app="AngularStore">
  <head>
 ...

Notice the following important points:

  1. 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.
  2. 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.
  3. 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.
  4. 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 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. 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 layout 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, as is the shopping cart itself, in shoppingCart.htm.

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 “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

I can recommend a series of videos on AngularJS 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/.

There are a number of additional features I plan on adding in the next week or so like a nice-looking Dialog for display images in a variety of different ways, additional Pinterest styled Layouts to work better with AngularJS, more features to aid product displays and selling, and many more goodies.  And I will also be posting a version of this shopping cart written using AngularJS 2.0 in the next few weeks. 

You can always get the latest cod efor this project on my website at: www.Software-rus.com

 

About

AngularJS Responsive Shopping Cart with Video

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published