In this tutorial, I am going to teach, how you can build a simple shopping cart using keystone.JS.
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.
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.
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
});
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();
};
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');
};
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
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'); };