How to build HTML5 Shopping Cart app using Ionic Cordova : Part 5

Here i will give you an overview on, how i implemented a FoodKart Shopping App. You can also refer to my complete project via GitHub.

If you find it difficult to follow, please go through my previous set of tutorials on Ionic Development:

Ionic HTML5 Basic concepts

How to design HTML5 app using Ionic Cordova : Part 2

How to build HTLM5 app using Ionic Cordova: Part 3

How to build HTML5 Shopping Cart app using Ionic Cordova : Part 4

UPDATE (10-07-16):

Latest FoodKart V0.3 is released. Read the complete tutorial here.

fk-latest

UPDATE (21-02-16) :-

FoodKart V0.2 is now available on GitHub .

Also read this article on code modification and features.

Make sure to follow me on github to get the latest updates.

NUTSHELL :-

  1. share global variable using Services.
  2. Use sessionStorage to store the product details.
  3. Creating ionic popup using $ionicPopup
  4. To hide an html element if the user is logged in
    <div ng-show="loggedin()" >
  5. Radio button usage
    <ion-radio ng-model="sortby" ng-value="'name'" >Name</ion-radio>
  6. To submit the list of check values we use
    <form ng-submit="getCategory((Categories|filter:{selected: true}))">
  7. Function declaration:
    $scope.showProductInfo=function (parameter1, parameter2 ...) { ... }

[wpi_designer_button text=’Donwload’ link=’https://github.com/arjunsk/FoodKart&#8217; style_id=’48’ icon=’github’ target=’_blank’]

LEVEL 3  [ Advanced ] :-

[color-box ]   INDEX PAGE  [/color-box]

1 .  index.html [The container]

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width">
    <title></title>

    <style>
      .angular-google-map-container {
          width: 100%;
          height: 504px;
      }
    </style>

    <link href="lib/ionic/css/ionic.css" rel="stylesheet">
    http://lib/ionic/js/ionic.bundle.js
	

    <link href="http://code.ionicframework.com/ionicons/1.5.2/css/ionicons.min.css" rel="stylesheet">

    <!-- IF using Sass (run gulp sass first), then uncomment below and remove the CSS includes above
    <link href="css/ionic.app.css" rel="stylesheet">
    -->

    http://js/app.js
    http://js/controllers.js
    http://js/routes.js
    http://js/services.js
    http://js/directives.js

  </head>
	<body ng-app="app" animation="slide-left-right-ios7"> // The body tag
	  <div>
		<div>
			<ion-nav-bar class="bar-positive" >     // The navigation bar container 
		
				<ion-nav-back-button   side="left" class="button-icon icon ion-arrow-left-c">  //Back arrow icon
				</ion-nav-back-button>
			
				<ion-nav-buttons side="right"   >    // "right" aligned			
					<div ng-controller="indexCtrl">   // "indexCtrl" (\www\js\controllers.js) contains the action calls  
					   
						//icons for home , profile and cart
						<a href="#/page1" class="button  button-icon ion-android-home"></a>    
						<a href="#/page4" class="button  button-icon ion-android-person"></a>
						<a  href="#/page2" class="button  button-icon ion-android-cart" ></a>
						
					</div>
				</ion-nav-buttons>
					
			</ion-nav-bar>

			<ion-nav-view></ion-nav-view>   // container that shows the html pages in template folder [Reference 1]
		</div>
	  </div>
	</body>
</html>

[color-box ]   MENU PAGE  [/color-box]

menu

1 . Template file for MENU

templates\menu.html [Menu page i.e. Main page]

<ion-view title="Menu">  // [Reference 1]
	
	// avoid padding to get the native feel.
	// Header section is used to show title for the page ie Menu, Product, Cart etc
    <ion-content padding="'false'" class="has-header">  
	
		// Search bar
        <form class="list">
            <label class="item item-input">
                <i class="icon ion-search placeholder-icon"></i>
                <input type="search" placeholder="">
            </label>
        </form>
    
	 //Sort bars
      <div class="button-bar">
            <a href="#/page6" class="button button-stable button-block  icon-left ion-android-funnel">Filter</a>
            <a href="#/page7" class="button button-stable button-block  icon ion-android-restaurant">Sort</a>
        </div>
	
	// List	
        <ion-list ng-repeat="item in menu_items">  // ng-repeat is a loop ie  for-each(item in menu_items)
        

			// individual list item
			<ion-item class="item-thumbnail-left" >  // sets thumbnail to left
			
				  // <img> is used to show product image
			     // ng-src is similar to src in html. It is used to give the image location
				 // ng-click is similar to  onClickListener() . Here it trigers showProductInf() function passing the respective item parameters.
                <img  ng-src="{{'img/'+ item.p_image_id +'.jpg'}}"  ng-click="showProductInfo(item.p_id,item.p_description,item.p_image_id,item.p_name,item.p_price)" >
				
				//used <p> to position cart icon to right 
				<p style="position:absolute;right:10px;">
				<a  ng-click="addToCart(item.p_id,item.p_image_id,item.p_name,item.p_price)" class="button  button-balanced button-clear   icon ion-android-cart">  </a> 
				</p>
                
				//product name displayed using heading tag ie <h2>
				<h2  ng-click="showProductInfo(item.p_id,item.p_description,item.p_image_id,item.p_name,item.p_price)" > {{item.p_name}} </h2>
                
				//<p> is used to show product price
				<p   ng-click="showProductInfo(item.p_id,item.p_description,item.p_image_id,item.p_name,item.p_price)">Price: ${{item.p_price}}</p>
			
			</ion-item>
			 
        </ion-list>
    </ion-content>
</ion-view>

 

2. Cart is a variable that is used by both Menu page and Cart page. We need to share this variable.Here i have used services to pass cart data between these pages.

Controller Section for Menu

  js\controllers.js [ menuCtrl ]

// $scope is passed to access the variable in the HTML scope.
// sharedCartService is used to store the cart details.  
// sharedFilterService is used to store the Filter details. ie Sort  and Category
//Both sharedCartService and sharedFilterService should be passed as parameter inorder to use it in the menuController.

.controller('menuCtrl', function($scope,$http,sharedCartService,sharedFilterService) {

	//global variable shared between different pages. 
	var cart = sharedCartService.cart;
				
				
	//$scope.$on---->onload event
	$scope.$on('$stateChangeSuccess', function () {
	
		//Filter details are a passed using URL appending.So we get the URL from sharedFilterService variable.
		//For eg: "server_side/food_menu.php?sort="
		str=sharedFilterService.getUrl();
		
		// used for debugging purpose. 
		// In chrome you can see the logs in Developermode-( Ctrl + shift + I )
		console.log("URL: ",str);  
		
		$http.get(str).success(function (response){$scope.menu_items = response.records;});	
			
	});

	 //show product page
	$scope.showProductInfo=function (id,desc,img,name,price) {	
		// we use session to store details about the current product displayed in the product page
		 sessionStorage.setItem('product_info_id', id);
		 sessionStorage.setItem('product_info_desc', desc);
		 sessionStorage.setItem('product_info_img', img);
		 sessionStorage.setItem('product_info_name', name);
		 sessionStorage.setItem('product_info_price', price);
		 
		 // used to move to a different page 
		 window.location.href = "#/page13";
	 };

	 //add to cart function
	 $scope.addToCart=function(id,image,name,price){   
		// function cart.add is declared in services.js
		cart.add(id,image,name,price,1);	
	 };	 
})

[color-box]

Note:

$scope.$on(‘$stateChangeSuccess’, function () { . . . } is used to detect if the page url has changed.

It can also be used to detect if the controller page is active or not.

[/color-box]

 

Shared Services used for Globalizing variables.

popup

 $ionicPopup is used to show ionic popups

js\services.js [ Services ]

.factory('sharedCartService', ['$ionicPopup',function($ionicPopup){  // $ionicPopup has to be defined here
	
	var cartObj = {}; 			// note that this is an Cart Object. It contains product list, total qty, and total amt
		cartObj.cart=[]; 		// array of product items
		cartObj.total_amount=0; // total cart amount
		cartObj.total_qty=0;    // total cart qty
	
	
	cartObj.cart.add=function(id,image,name,price,qty){
		if( cartObj.cart.find(id)!=-1 ){  //find() is declared in the bottom. 
										 // It is used to check if the product is already added to the cart or not
		
			//Ionic popup
			var alertPopup = $ionicPopup.alert({
                title: 'Product Already Added',
                template: 'Increase the qty from the cart'
            });
			
		}
		else{
			//insert this into cart array
		    cartObj.cart.push( { "cart_item_id": id , "cart_item_image": image , "cart_item_name": name , "cart_item_price": price , "cart_item_qty": qty } );
			cartObj.total_qty+=1;	// increase the cartqty
			cartObj.total_amount+=parseInt(price);	//increase the cart amount
		}
	};
	
	cartObj.cart.find=function(id){	
		var result=-1;
		for( var i = 0, len = cartObj.cart.length; i < len; i++ ) {   // cart.length() gives the size of product list.
			if( cartObj.cart[i].cart_item_id === id ) {
				result = i;
				break;
			}
		}
		return result;
	};
	
	// used to delete a product 
	cartObj.cart.drop=function(id){
	 var temp=cartObj.cart[cartObj.cart.find(id)]; //used to find the price and qty of the object to be deleted
	 cartObj.total_qty-= parseInt(temp.cart_item_qty);  // decrements the product qty
	 cartObj.total_amount-=( parseInt(temp.cart_item_qty) * parseInt(temp.cart_item_price) ); //decrements the product amt
	 cartObj.cart.splice(cartObj.cart.find(id), 1); //used to remove product from the cart array. 
													//splice() is a build in function to remove an array element.

	};
	
	//used to increment the product qty from the cart page
	// when a  product is added to cart. You can only increment the qty.
	cartObj.cart.increment=function(id){
		 cartObj.cart[cartObj.cart.find(id)].cart_item_qty+=1;
		 cartObj.total_qty+= 1;
		 cartObj.total_amount+=( parseInt( cartObj.cart[cartObj.cart.find(id)].cart_item_price) );	
	};
	
	// used to decrement the product qty from the cart page
	cartObj.cart.decrement=function(id){
		 cartObj.cart[cartObj.cart.find(id)].cart_item_qty-=1;
		 cartObj.total_qty-= 1;
		 cartObj.total_amount-= parseInt( cartObj.cart[cartObj.cart.find(id)].cart_item_price) ;	
		

		if(cartObj.total_qty==0 || cartObj.total_amount<=0 ){
			cartObj.total_amount=0;
			cartObj.total_qty==0
		}

		 //if qty is 0 then remove it from the cart array.
		 if(cartObj.cart[cartObj.cart.find(id)].cart_item_qty <= 0){
			cartObj.cart.splice(cartObj.cart[cartObj.cart.find(id)], 1);
			
		 }
		 
	};
	
	return cartObj;
}])


// used to apply filters to the listing.
.factory('sharedFilterService', [function(){

	var obj = {}; // the variable object 'obj' is set in the Filters page. 
    obj.str = "http://www.yoursite.com/ftpserver/foodcart/server_side/food_menu.php";
	obj.sort = "";
	obj.search = "";
	obj.category = "";
	
	//getUrl() function is used to dynamically create 'data retrieval url'
	obj.getUrl=function(){
		if(obj.sort!="" && obj.category!=""){
			obj.str="http://www.yoursite.com/foodcart/server_side/food_menu.php?category="+obj.category+"&sort="+obj.sort;
		}
		else if(obj.category!="" ){
			obj.str="http://www.yoursite.com/foodcart/server_side/food_menu.php?category="+obj.category;
		}
		else if(obj.sort!=""){  
			obj.str="http://www.yoursite.com/foodcart/server_side/food_menu.php?sort="+obj.sort;
		}
		return obj.str;
	};
	return obj;
}])

 

[color-box ]  CART PAGE  [/color-box]

1. templates\cart.html [ Cart Page ]

Cart

<ion-view title="Cart">
    <ion-content padding="'true'" class="has-header">
        <div>
		
		    // displays total_amount and qty
            <p>Total ( {{ total_qty }} ) =
                <strong>${{total_amount}}</strong>
            </p>
        </div>
        
		// checkout button
		<button ng-click="checkout()" class="button button-positive button-block ">Check Out</button>
		
		// populating cart list
		<div ng-repeat="item in cart"   >
		
		// used card container
			<div class="list card" >
				
				//used to give title of the product name
				<div class="item item-divider">{{item.cart_item_name}} </div>
				
				//used to give the details about the cart item
				<div class="item item-body">
					
					//loads the cart item image
					<img ng-src="{{'img/'+ item.cart_item_image +'.jpg'}}" width="100%" height="auto" style="width: 100%; height: auto;">
						
					<div>
						<p>{{item.cart_item_qty}} x ${{item.cart_item_price}} =
							<strong>${{ item.cart_item_qty *  item.cart_item_price  }}</strong>
						</p>
					</div>
					
					// the bottom buttons for cart items
					<div class="button-bar">
						<button ng-click="removeFromCart(item.cart_item_id)" class="button button-assertive button-block button-small icon ion-android-delete" ></button>
						<button class="button button-stable button-clear button-block button-small icon ion-plus" ng-click="inc(item.cart_item_id)"></button>
						<button class="button button-stable button-clear button-block button-small">{{item.cart_item_qty}}</button>
						<button class="button button-stable button-clear button-block  icon ion-minus" ng-click="dec(item.cart_item_id)" ></button>
					</div>
	
				</div>
			</div>
		</div>

    </ion-content>
</ion-view>

2 .    js\controllers.js [ cartCtrl ]

.controller('cartCtrl', function($scope,sharedCartService,$ionicPopup,$state) { // define $ionicPopup here
		
		// Loads the '$scope variable' cart i.e. 'HTML variable'
		$scope.$on('$stateChangeSuccess', function () {
			$scope.cart=sharedCartService.cart;
			$scope.total_qty=sharedCartService.total_qty;
			$scope.total_amount=sharedCartService.total_amount;		
		});
		
		//remove function
		$scope.removeFromCart=function(c_id){
			$scope.cart.drop(c_id);	 // deletes the product from cart. 
			
			// dynamically update the current $scope data.
			$scope.total_qty=sharedCartService.total_qty;
			$scope.total_amount=sharedCartService.total_amount;
			
		};
		
		// increments the qty
		$scope.inc=function(c_id){
			$scope.cart.increment(c_id);
			$scope.total_qty=sharedCartService.total_qty;
			$scope.total_amount=sharedCartService.total_amount;
		};
		
		// decrements the qty
		$scope.dec=function(c_id){
			$scope.cart.decrement(c_id);
			$scope.total_qty=sharedCartService.total_qty;
			$scope.total_amount=sharedCartService.total_amount;
		};
		
		$scope.checkout=function(){
			if($scope.total_amount>0){
				$state.go('checkOut');  // used to move to checkout page.
			}
			else{
				//alerts the user that cart is empty.
				var alertPopup = $ionicPopup.alert({
					title: 'No item in your Cart',
					template: 'Please add Some Items!'
				});
			}
		};

})

[color-box ]  PRODUCT PAGE  [/color-box]

product page

1. templates\productPage.html [ PRODUCT DETAILS PAGE]

$scope variables get data from the productPageCtrl .

<ion-view title="Product">
    <ion-content padding="'true'" class="has-header">
        <img ng-src="{{img}}" width="100%" height="auto" style="width: 100%; height: auto;">
        <div class="button-bar">
            <button class="button button-positive button-clear button-block  icon ion-android-cart"></button>
            <button class="button button-assertive button-clear button-block  icon ion-android-favorite-outline"></button>
            <button class="button button-balanced button-clear button-block  icon ion-share"></button>
        </div>
        <div class="list card">
            <div class="item item-divider">Description</div>
            <div class="item item-body">
                <div>
					<p> {{name}}</p>
					<p>Price: ${{price}}</p>
                    <p>{{desc}}</p>
                </div>
            </div>
        </div>
    </ion-content>
</ion-view>

 

2 . I haven’t added much functionalities in this page. The readers can add there respective features & functionalities.

  js\controllers.js [ productPageCtrl ]

.controller('productPageCtrl', function($scope) {

	//onload event
	angular.element(document).ready(function () {
		$scope.id= sessionStorage.getItem('product_info_id');
		$scope.desc= sessionStorage.getItem('product_info_desc');
		$scope.img= "img/"+ sessionStorage.getItem('product_info_img')+".jpg";
		$scope.name= sessionStorage.getItem('product_info_name');
		$scope.price= sessionStorage.getItem('product_info_price');
	});
})

 

[color-box ]  FILTER PAGE  [/color-box]

filter

1. templates\filterBy.html  [ FILTER BY PAGE]

<ion-view title="Filter By">
    <ion-content padding="'true'" class="has-header">
        
		// passes the 'list of checked values' in Categories array to getCategory() 
		<form ng-submit="getCategory((Categories|filter:{selected: true}))">
		
		    //populates list 
			<ion-list ng-repeat="category in Categories">     
				  <ion-checkbox ng-model="category.selected">
					<h2 class="calm">{{category.name}}</h2>
				  </ion-checkbox>
			  </ion-list>
			
			  //NOTE: submit is used  			
			  <div class="list box">
				<button class="button button-block button-calm" type="submit">Apply</button>
			  </div>
		  
		</form>

    </ion-content>
</ion-view>

2 .   js\controllers.js [ filterByCtrl ]

.controller('filterByCtrl', function($scope,sharedFilterService) {

  //filter by options 
  $scope.Categories = [
    {id: 1, name: 'Veg Curry'},
    {id: 2, name: 'Arabic'}
  ];
  
  //cat_list contains the selected category list 
  $scope.getCategory = function(cat_list){
  
    categoryAdded = cat_list;
	var c_string=""; // will hold the category items as string
	
	//since we are appending category to the end of URL, we are making category items to a string. 
	for(var i=0;i<categoryAdded.length;i++){ c_string+=(categoryAdded[i].id+"||"); }
	
	// removes the last '||'
	c_string = c_string.substr(0, c_string.length-2);
	
	//updates the category details in sharedFilterService variable
	sharedFilterService.category=c_string;
	
	// Moves to the menu page
	window.location.href = "#/page1";
  };
	
})

 

[color-box ]  SORT BY PAGE  [/color-box]

sort by

1.  templates\sortBy.html  [ SORT BY PAGE]

<ion-view title="Sort By">
    <ion-content padding="'true'" class="has-header">
        
		//passes the value of sortby ie the selected option
		<form class="list" ng-submit="sort(sortby)">	
			
			// radio button is used	
			 <ion-list>
				<ion-radio ng-model="sortby" ng-value="'n'" >Name</ion-radio>
				<ion-radio ng-model="sortby" ng-value="'plh'" >Price (Low-High)</ion-radio>
				<ion-radio ng-model="sortby" ng-value="'phl'" >Price (High-Low)</ion-radio>
			 </ion-list>
			 
			 //Note : submit is used
			 <div class="list box">
				<button class="button button-block button-calm" type="submit">Apply</button>
			</div>  
			
        </form>
    </ion-content>
</ion-view>

 

2 .    js\controllers.js [ sortByCtrl ]

.controller('sortByCtrl', function($scope,sharedFilterService) {
	$scope.sort=function(sort_by){
		sharedFilterService.sort=sort_by;  
		window.location.href = "#/page1";  // moves to menu page
	};
})

 

[color-box ]  CHECK OUT PAGE  [/color-box]

check out

1.  templates\checkOut.html  [ CHECK OUT PAGE]

<ion-view title="Check Out">
    <ion-content padding="'true'" class="has-header">
	
		//if the user is loggedin the div will be invisible
		//loggedin() returns true if the user is not logged input
		// and false if the user has logged in 
		<div ng-show="loggedin()" >
			<div class="button-bar">
				<a href="#/page4" class="button button-balanced button-block ">Login</a>
				<a href="#/page5" class="button button-calm button-block ">Register</a>
			</div>
			<div>
				<p>Or</p>
			</div>
		</div>
        
		//loads the form elements with $scope values.
		<form class="list">
            <label class="item item-input">
                <span class="input-label" style="padding-right:75px">Name*</span>
                <textarea placeholder="" > {{" "+loggedin_name}}</textarea>
            </label>
            <label class="item item-input">
                <span class="input-label" style="padding-right:10px" >Phone Number*</span>
                <textarea placeholder=""  > {{" "+loggedin_phone}}</textarea>
            </label>
            <label class="item item-input">
                <span class="input-label" style="padding-right:0px">Address*</span>
				<textarea placeholder=""  > {{" "+loggedin_address}}</textarea>
            </label>
            <label class="item item-input">
                <span class="input-label" style="padding-right:15px">Pincode*</span>
                 <textarea placeholder=""  > {{" "+loggedin_pincode}}</textarea>
            </label>
        </form>
        <div class="spacer" style="width: 300px; height: 15px;"></div>
        <form class="list">
            <label class="item item-input">
                <span class="input-label">Coupon</span>
                <input type="text" placeholder="(Optional)">   // to give a hint
            </label>
        </form>
        <a href="#/page8" class="button button-balanced button-outline button-block ">Check Out</a>
    </ion-content>
</ion-view>

2 .   js\controllers.js [ checkOutCtrl]

If the user is logged in, the users details will be automatically filled.

.controller('checkOutCtrl', function($scope) {
	$scope.loggedin=function(){
		if(sessionStorage.getItem('loggedin_id')==null){return 1;}
		else{
			$scope.loggedin_name= sessionStorage.getItem('loggedin_name');
			$scope.loggedin_id= sessionStorage.getItem('loggedin_id');
			$scope.loggedin_phone= sessionStorage.getItem('loggedin_phone');
			$scope.loggedin_address= sessionStorage.getItem('loggedin_address');
			$scope.loggedin_pincode= sessionStorage.getItem('loggedin_pincode');
			return 0;
		}
	};
})

 

This almost concludes the part for Shopping Cart.

Read my next article for setting up Login and Registration.

Also Read my article on JS Files in Ionic.

Comments, Suggestions and Bug Fixes are most welcomed.

 

UPDATE: ( 31-07-2016)

Here is a video demo of setting up Ionic Cart!

 

8 thoughts on “How to build HTML5 Shopping Cart app using Ionic Cordova : Part 5

  1. In:

    //add to cart function
    $scope.addToCart=function(id,image,name,price){
    // function cart.add is declared in services.js
    cart.add(id,image,name,price,1);
    };

    Put in an array, but i don´t undertain that…

    Like

    1. Function add() is declared in services.js

      We are calling the add() function using the cart object.

      In services,
      Initially we are checking if the cart array ( ie cart[] ) already has the element.
      if so, we increment its quantity.
      else,
      we all the product to the cart.

      Like

  2. are you getting by any chance into ingredients per item on MySQL ?
    as that would be awesome since a user might decide to customize his product less Milk, with or without sugar , more, less pepper and so on

    Like

  3. I am adding the item into the cart and going in the cart i can view the item.
    but i am refresh the page i can’t view the item.
    how can i store the item into the localstorage.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s