keystoneJS simple shopping cart part1

In this tutorial, I am  going to teach, how you can build a simple shopping cart using keystone.JS. 

(1)Add models.

 

Creating model is equivalent to creating tables in Mysql. Lets create two model. A product and a Order.

Make sure the first letter is capital and don't use plural name. because mongoose automatically converts it into plural while creating mongo db collection.

models/Product.js


var keystone = require('keystone');
var Types = keystone.Field.Types;

var Product = new keystone.List('Product');

Product.add({
	name: { type: Types.Text, required: true,initial:true },
	price: { type: Types.Number, required: true,initial:true },
	description: { type: String },

});

Product.defaultSort = '-createdAt';
Product.defaultColumns = 'name, price, description';
Product.register();


models/Order.js


var keystone = require('keystone');
var Types = keystone.Field.Types;

var Order = new keystone.List('Order');

Order.add({
	customer: { type: Types.Relationship, ref: 'User', many: false, index: true,initial:true },
	products: { type: Types.Relationship, ref: 'Product', many: true, index: true , required: true,initial:true},
});

Order.defaultSort = '-createdAt';
Order.defaultColumns = 'customer, products';
Order.register();

The fileds types are Types.Text,Types.Number,String. There are many other predefined types that you can use. The other things you  inform about fields are

required - this field can not be unset.

initial- At list this fileld will be asked for quick creation of object.

Now check the Order model. There is one  important Type  i.e  Types.Relationship. this is to set up a relation between two model. additionally you can set many as true to indicate that this relation is one-to-many.

customer: { type: Types.Relationship, ref: 'User', many: false, index: true,initial:true },

This line indicates that  order is related to only one User and can be access as <object>.customer.

products: { type: Types.Relationship, ref: 'Product', many: true, index: true , required: true,initial:true},

This line indicates that  order is related to many products and can be accessed as array  i.e <object>.products.

(2) Create some products and Orders

 

Keystone.Js give a nice admin panel. where  you can easily do CRUD operation on model. You dont need any additional code to do this. Step1 is sufficient enough  for that.  Just go to /keystone relative path in browser and add 3 products and two order.

(3) stop admin from creating order

 

its good tha admin can create some products from admin panel. However  order has to be generated by customer and not by admin. So lets stop the CRUD operation from admin panel. for that make small change in Order model like this. and verify that now you can not create Order from admin panel.


var Order = new keystone.List('Order', {
	nocreate: true,
	noedit: true
});

 

(4) Add links in navigation bar(site menu)

Add two links in navigation bars. /product  and /myorders. for the edit   export.initiLocals function as follow. link to myorder page is restricted to only loggedin users. by using if(req.user) here req.user is current loggedin User object.

routes/middleware.js


exports.initLocals = function(req, res, next) {
	
	var locals = res.locals;
	
	locals.navLinks = [
		{ label: 'Home',		key: 'home',		href: '/' },
		{ label: 'Contact',		key: 'contact',		href: '/contact' },
		{ label: 'Products',		key: 'product',		href: '/products' }
	];
	if(req.user) locals.navLinks.push( {label: 'MyOrders',		key: 'order',		href: '/myorders' });
	locals.user = req.user;
	next();
};

 

(5) Create Object listing page with pagination

 

To create any page we have make changes at three place.

(a) add router in routes/index.js


exports = module.exports = function(app) {
	app.get('/products', routes.views.products);
	app.get('/myorders', routes.views.myorders);

};

(2) Add the request handler in routes/views/

routes/views/products.js


var keystone = require('keystone');
var Product = keystone.list('Product');
exports = module.exports = function(req, res) {
	
	var view = new keystone.View(req, res);
	var locals = res.locals;
	
	// Set locals
	locals.section = 'products';
	locals.title = 'products';
	

	view.on('init', function(next) {
	Product.paginate({
		page: req.query.page || 1,
		perPage: 2,
		maxPages: 10
	}).exec(function(err, res) {
		locals.products = res;
		//console.log("==---="+JSON.stringify(res));
		next(err);
	});
	});
	// Render the view
	view.render('Products');
	
};

routes/views/myorders.js


var keystone = require('keystone');
var Order = keystone.list('Order');
exports = module.exports = function(req, res) {
	var view = new keystone.View(req, res);
	if(req.user == undefined){
		view.render("errors/404");
		return;
	}
	console.log("loggedin user ="+req.user.email);

	var locals = res.locals;
	
	// Set locals
	locals.section = 'myorders';
	locals.title = 'MyOrders';
	

	view.on('init', function(next) {
	Order.paginate({
		page: req.query.page || 1,
		perPage: 2,
		maxPages: 10
	})
	.where('customer', req.user.id)
	.sort('-publishedDate')
	.exec(function(err, res) {
		locals.Orders = res;
		next(err);
	});
	});
	// Render the view
	view.render('myorders');
	
};

(3) Add the page rendering code using jade. at template/views/

 


extends ../layouts/default
include ../mixins/product


block content
  .container
    h1 Products list 
      if products.results.length
        if products.totalPages > 1
          .lead.text-muted Showing 
            strong #{products.first}
            |  to 
            strong #{products.last}
            |  of 
            strong #{products.total}
            |  products
        else
            .lead.text-muted Showing #{utils.plural(products.results.length, '* post')}
        .blog
           each product in products.results
             ul
               +product(product)
         if products.totalPages > 1
            ul.pagination
              if products.previous
                li: a(href='?page=' + products.previous): span.glyphicon.glyphicon-chevron-left
              else
                li.disabled: a(href='?page=' + 1): span.glyphicon.glyphicon-chevron-left
              each p, i in products.pages
                li(class=products.currentPage === p ? 'active' : null)
                  a(href='?page=' + (p === '...' ? (i ? products.totalPages : 1) : p ))= p
              if products.next
                li: a(href='?page=' + products.next): span.glyphicon.glyphicon-chevron-right
              else
                  li.disabled: a(href='?page=' + products.totalPages):   
                  span.glyphicon.glyphicon-chevron-right
          else
              .jumbotron.text-center
              h3(style="margin-bottom:0;margin-top:0;") There are no products yet.

Notice  +product(product)   in above code. it looks like a function call. yes its kind of function but called mixin in terms of Jade.  we are using it repetitively  in jade loop for each product. here is the code of that function.


mixin product(product)
    li
      h3: a(href='product/' + product.id)=product.name
      h4='$ '+ product.price
      hr

I m not showing the jade code for Order snce it is almost same as product.

 

(5) Create Object full view page

in above mixin you can see that every product list has one link to product full view. e.g  /product/577a18f7af376d830980b675. this page will show the full detail of the product.

So lets add router in routes/index.js


exports = module.exports = function(app) {
    app.get('/product/:product_no?', routes.views.product);
    app.get('/order/:order_id?', routes.views.order);

};

Here /:product_no is a placeholder for particular URL segment. in handler you can access it as  req.params.product_no.     and incase its url queiry like. /product?product_no=xyz then you can access it as req.query.product_no.

routes/views/produt.js

 var keystone = require('keystone');

exports = module.exports = function(req, res) {
	
	var view = new keystone.View(req, res);
	locals = res.locals;
	
	locals.section = 'product';
	
	view.on('init', function(next) {
		var q = keystone.list('Product').model.findById(req.params.product_no);
		
		q.exec(function(err, result) {
			locals.product = result;
			next(err);
		});
		
	});
	
	view.render('product');
};