Add/Update/Remove/empty cart without refreshing the page

master
Mark Moffat 2020-01-04 13:07:35 +10:30
parent f96d8335d6
commit 3373a60d8f
9 changed files with 166 additions and 51 deletions

6
app.js
View File

@ -302,6 +302,12 @@ handlebars = handlebars.create({
'/': lvalue / rvalue,
'%': lvalue % rvalue
}[operator];
},
showCartButtons: (cart) => {
if(!cart){
return'd-none';
}
return'';
}
}
});

View File

@ -1,5 +1,5 @@
/* eslint-disable prefer-arrow-callback, no-var, no-tabs */
/* globals showNotification */
/* globals showNotification, numeral */
$(document).ready(function (){
if($(window).width() < 768){
$('.menu-side').on('click', function(e){
@ -19,13 +19,6 @@ $(document).ready(function (){
$('#offcanvasClose').hide();
}
// If cart was open before reload, open it again
var isCartOpen = (localStorage.getItem('cartOpen') === 'true');
if(isCartOpen === true){
localStorage.setItem('cartOpen', false);
$('body').addClass('pushy-open-right');
}
$('#userSetupForm').validator().on('submit', function(e){
if(!e.isDefaultPrevented()){
e.preventDefault();
@ -304,8 +297,8 @@ $(document).ready(function (){
}
})
.done(function(msg){
$('#cart-count').text(msg.totalCartItems);
showNotification(msg.message, 'success', true);
showNotification(msg.message, 'success');
updateCartDiv();
})
.fail(function(msg){
showNotification(msg.responseJSON.message, 'danger');
@ -335,8 +328,8 @@ $(document).ready(function (){
data: { productId: $(this).attr('data-id') }
})
.done(function(msg){
$('#cart-count').text(msg.totalCartItems);
showNotification(msg.message, 'success', true);
showNotification(msg.message, 'success');
updateCartDiv();
})
.fail(function(msg){
showNotification(msg.responseJSON.message, 'danger');
@ -350,7 +343,7 @@ $(document).ready(function (){
url: '/product/emptycart'
})
.done(function(msg){
$('#cart-count').text(msg.totalCartItems);
updateCartDiv();
showNotification(msg.message, 'success', true);
});
});
@ -405,8 +398,8 @@ function deleteFromCart(element){
data: { productId: element.attr('data-id') }
})
.done(function(msg){
setCartOpen();
showNotification(msg.message, 'success', true);
showNotification(msg.message, 'success');
updateCartDiv();
})
.fail(function(msg){
showNotification(msg.responseJSON.message, 'danger');
@ -423,14 +416,6 @@ function cartUpdate(element){
}
}
function setCartOpen(){
if($('body').hasClass('pushy-open-right') === true){
localStorage.setItem('cartOpen', true);
}else{
localStorage.setItem('cartOpen', false);
}
}
function updateCart(element){
// update cart on server
$.ajax({
@ -442,8 +427,7 @@ function updateCart(element){
}
})
.done(function(msg){
setCartOpen();
showNotification(msg.message, 'success', true);
updateCartDiv();
})
.fail(function(msg){
showNotification(msg.responseJSON.message, 'danger', true);
@ -487,3 +471,128 @@ function getSelectedOptions(){
});
return options;
}
function updateCartDiv(){
$.ajax({
method: 'GET',
url: '/checkout/cartdata'
})
.done(function(result){
// Update the cart div
var cart = result.cart;
var session = result.session;
var productHtml = '';
var totalAmount = numeral(session.totalCartAmount).format('0.00');
// Work out the shipping
var shippingTotalAmt = numeral(session.totalCartShipping).format('0.00');
var shippingTotal = `<strong id="shipping-amount">${result.currencySymbol}${shippingTotalAmt}</strong>`;
if(shippingTotalAmt === 0){
shippingTotal = '<strong id="shipping-amount">FREE</strong>';
}
// If the cart has contents
if(cart){
$('#cart-empty').empty();
Object.keys(cart).forEach(function(productId){
var item = cart[productId];
// Setup the product
var productTotalAmount = numeral(item.totalItemPrice).format('0.00');
var optionsHtml = '';
var optionIndex = 1;
Object.keys(item.options).forEach(function(key){
var option = item.options[key];
if(optionIndex === Object.keys(item.options).length){
optionsHtml += `<strong>${upperFirst(option.name)}</strong>: ${option.value}`;
}else{
optionsHtml += `<strong>${upperFirst(option.name)}</strong>: ${option.value} / `;
}
optionIndex++;
});
var productImage = `<img class="img-fluid" src="/uploads/placeholder.png" alt="${item.title} product image"></img>`;
if(item.productImage){
productImage = `<img class="img-fluid" src="${item.productImage}" alt="${item.title} product image"></img>`;
}
// Setup the product html
productHtml += `
<div class="d-flex flex-row bottom-pad-15">
<div class="col-4 col-md-3">
${productImage}
</div>
<div class="col-12 col-md-7">
<div class="row h-200">
<div class="col-sm-12 text-left no-pad-left">
<h6><a href="/product/${item.link}">${item.title}</a></h6>
</div>
<div class="col-sm-12 text-left no-pad-left">
${optionsHtml}
</div>
<div class="col-md-8 no-pad-left">
<div class="input-group">
<div class="input-group-prepend">
<button class="btn btn-outline-primary btn-qty-minus" type="button">-</button>
</div>
<input type="number" class="form-control cart-product-quantity text-center" id="${productId}-qty" data-id="${productId}" maxlength="2" value="${item.quantity}">
<div class="input-group-append">
<button class="btn btn-outline-primary btn-qty-add" type="button">+</button>
</div>
</div>
</div>
<div class="col-md-4 text-right">
<button class="btn btn-outline-danger btn-delete-from-cart" data-id="${productId}" type="button"><i class="far fa-trash-alt" data-id="${productId}" aria-hidden="true"></i></button>
</div>
</div>
</div>
<div class="col-12 col-md-2 align-self-center text-right no-pad-right">
<strong class="my-auto">${result.currencySymbol}${productTotalAmount}</strong>
</div>
</div>`;
});
$('#cartBodyWrapper').html(productHtml);
$('#cart-count').text(session.totalCartItems);
}else{
$('#cartBodyWrapper').html('');
}
// Set the totals section
var cartTotalsHtml = `
<div class="row">
<div class="cart-contents-shipping col-md-12 no-pad-right">
<div class="text-right">
Shipping: ${shippingTotal}
</div>
<div class="text-right">
Total:
<strong id="total-cart-amount">${result.currencySymbol}${totalAmount}</strong>
</div>
</div>
</div>`;
var cartTotalsEmptyHtml = `
<div id="cart-empty" class="row">
<div class="cart-contents-shipping col-md-12 no-pad-right">
Cart empty
</div>
</div>`;
// Set depending on cart contents
if(cart){
$('#cartTotalsWrapper').html(cartTotalsHtml);
$('#cart-buttons').removeClass('d-none');
}else{
$('#cartTotalsWrapper').html(cartTotalsEmptyHtml);
$('#cart-buttons').addClass('d-none');
}
})
.fail(function(result){
showNotification(result.responseJSON.message, 'danger');
});
}
function upperFirst(value){
return value.replace(/^\w/, (chr) => {
return chr.toUpperCase();
});
}

File diff suppressed because one or more lines are too long

View File

@ -131,6 +131,7 @@ router.get('/checkout/shipping', async (req, res, next) => {
session: req.session,
cartSize: 'part',
cartClose: false,
cartReadOnly: true,
page: 'checkout-shipping',
countryList,
message: clearSessionValue(req.session, 'message'),
@ -146,7 +147,7 @@ router.get('/checkout/cart', (req, res) => {
res.render(`${config.themeViews}checkout-cart`, {
page: req.query.path,
cartSize: 'full',
config: req.app.config,
config,
session: req.session,
message: clearSessionValue(req.session, 'message'),
messageType: clearSessionValue(req.session, 'messageType'),
@ -156,7 +157,13 @@ router.get('/checkout/cart', (req, res) => {
});
router.get('/checkout/cartdata', (req, res) => {
res.status(200).json(req.session.cart);
const config = req.app.config;
res.status(200).json({
cart: req.session.cart,
session: req.session,
currencySymbol: config.currencySymbol || '$'
});
});
router.get('/checkout/payment', (req, res) => {

View File

@ -39,6 +39,7 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.20.2/mode/css/css.min.js" integrity="sha256-D5oJ11cOmRhXSYWELwG2U/XYH3YveZJr9taRYLZ2DSM=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.20.2/mode/xml/xml.min.js" integrity="sha256-ERFGS58tayDq5kkyNwd/89iZZ+HglMH7eYXxG1hxTvA=" crossorigin="anonymous"></script>
{{/if}}
<script src="https://cdnjs.cloudflare.com/ajax/libs/numeral.js/2.0.6/numeral.min.js" integrity="sha256-LlHVI5rUauudM5ZcZaD6hHPHKrA7CSefHHnKgq+/AZc=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.7/js/tether.min.js" integrity="sha256-4lietOiwRDBKx1goZZbRiwB06L+/bPYEGDIKZt82bgg=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/1000hz-bootstrap-validator/0.11.9/validator.min.js"></script>
<script src="/javascripts/jquery.bootpag.min.js"></script>
@ -138,12 +139,10 @@
<div id="cart" class="col-md-12">
{{> (getTheme 'cart')}}
<div class="row">
{{#if @root.session.cart}}
<div class="col-sm-12">
<div id="cart-buttons" class="col-sm-12 {{showCartButtons @root.session.cart}}">
<button class="btn btn-outline-danger float-left" id="empty-cart" type="button">{{ @root.__ "Empty cart" }}</button>
<a href="/checkout/information" class="btn btn-outline-primary float-right">Checkout</a>
</div>
{{/if}}
</div>
</div>
</div>

View File

@ -1,16 +1,15 @@
<div class="card top-marg-15 bottom-marg-15">
<div class="card-body cart-body">
<h5 class="card-title">{{ @root.__ "Cart contents" }}</h5>
<div id="cartBodyWrapper">
{{#each @root.session.cart}}
<div class="d-flex flex-row bottom-pad-15">
<div class="col-xs-4 col-md-3">
<div class="col-4 col-md-3">
{{#if productImage}}
<img class="img-fluid" src="{{this.productImage}}" alt="{{this.title}} product image"> {{else}}
<img class="img-fluid" src="/uploads/placeholder.png" alt="{{this.title}} product image"> {{/if}}
</div>
<div class="col-sm-12 col-md-7">
<div class="col-12 col-md-7">
<div class="row h-200">
<div class="col-sm-12 text-left no-pad-left">
<h6><a href="/product/{{this.link}}">{{this.title}}</a></h6>
@ -20,18 +19,17 @@
{{#if @last}}
<strong>{{#upperFirst this.name}}{{/upperFirst}}</strong>: {{this.value}}
{{else}}
<strong>{{#upperFirst this.name}}{{/upperFirst}}:</strong> {{this.value}} /
<strong>{{#upperFirst this.name}}{{/upperFirst}}:</strong> {{this.value}} /
{{/if}}
{{/each}}
</div>
{{#ifCond cartReadOnly '!=' true}}
{{#ifCond @root.cartReadOnly '!=' true}}
<div class="col-md-8 no-pad-left">
<div class="input-group">
<div class="input-group-prepend">
<button class="btn btn-outline-primary btn-qty-minus" type="button">-</button>
</div>
<input type="number" class="form-control cart-product-quantity text-center" data-id="{{../this.productId}}" data-index="{{@key}}"
maxlength="2" value="{{../this.quantity}}">
<input type="number" class="form-control cart-product-quantity text-center" id="{{../this.productId}}-qty" data-id="{{../this.productId}}" maxlength="2" value="{{../this.quantity}}">
<div class="input-group-append">
<button class="btn btn-outline-primary btn-qty-add" type="button">+</button>
</div>
@ -43,35 +41,35 @@
{{/ifCond}}
</div>
</div>
<div class="align-self-center col-sm-12 col-md-2 text-right no-pad-right">
<div class="col-12 col-md-2 align-self-center text-right no-pad-right">
<strong class="my-auto">{{currencySymbol @root.config.currencySymbol}}{{formatAmount this.totalItemPrice}}</strong>
</div>
</div>
{{/each}}
</div>
<div class="container-fluid">
<div id="cartTotalsWrapper" class="container-fluid">
{{#if @root.session.cart}}
<div class="row">
<div class="cart-contents-shipping col-md-12 no-pad-right">
{{#ifCond @root.session.shippingCostApplied '===' true}}
<div class="text-right">
{{ @root.__ "Shipping" }}
<strong>{{currencySymbol @root.config.currencySymbol}}{{formatAmount @root.session.totalCartShipping}}</strong>
<strong id="shipping-amount">{{currencySymbol @root.config.currencySymbol}}{{formatAmount @root.session.totalCartShipping}}</strong>
</div>
{{else}}
<div class="text-right">
{{ @root.__ "Shipping" }}
<strong>FREE</strong>
<strong id="shipping-amount">FREE</strong>
</div>
{{/ifCond}}
<div class="text-right">
Total:
<strong>{{currencySymbol @root.config.currencySymbol}}{{formatAmount @root.session.totalCartAmount}}</strong>
<strong id="total-cart-amount">{{currencySymbol @root.config.currencySymbol}}{{formatAmount @root.session.totalCartAmount}}</strong>
</div>
</div>
</div>
{{else}}
<div class="row">
<div id="cart-empty" class="row">
<div class="cart-contents-shipping col-md-12 no-pad-right">
Cart empty
</div>

View File

@ -3,12 +3,10 @@
<div id="cart" class="col-md-12">
{{> (getTheme 'cart')}}
<div class="row">
{{#if @root.session.cart}}
<div class="col-sm-12">
<div id="cart-buttons" class="col-sm-12 {{showCartButtons @root.session.cart}}">
<button class="btn btn-outline-danger float-left" id="empty-cart" type="button">{{ @root.__ "Empty cart" }}</button>
<a href="/checkout/information" class="btn btn-outline-danger float-right">Checkout</a>
</div>
{{/if}}
</div>
</div>
</div>

View File

@ -88,7 +88,7 @@
{{> (getTheme 'cart')}}
{{#if @root.session.cart}}
<div class="row">
<div class="col-sm-12">
<div id="cart-buttons" class="col-sm-12 {{showCartButtons @root.session.cart}}">
<button class="btn btn-outline-danger float-right" id="empty-cart" type="button">{{ @root.__ "Empty cart" }}</button>
</div>
{{/if}}

View File

@ -29,11 +29,9 @@
<div id="cart" class="col-md-7 d-none d-sm-block">
{{> (getTheme 'cart')}}
<div class="row">
{{#if @root.session.cart}}
<div class="col-sm-12">
<div id="cart-buttons" class="col-sm-12 {{showCartButtons @root.session.cart}}">
<button class="btn btn-outline-danger float-right" id="empty-cart" type="button">{{ @root.__ "Empty cart" }}</button>
</div>
{{/if}}
</div>
</div>
</div>