<link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
<script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0/js/bootstrap.min.js"></script>
<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
<!------ Include the above in your HEAD tag ---------->
<section class="shop js-shop">
<script>
/*
* Make sure to read this from the bottom up to see what's going on.
*/
function Item(name, description, price, photo) {
// Make sure that the object is accessible from at any scope inside it
var _this = this;
(function init() {
// Initialise Item object, probably from JSON file
_this.name = name;
_this.description = description;
_this.price = price;
_this.photo = photo;
// Count and cost start off at 0
_this.count = 0;
_this.cost = 0;
// Have the wrapper, which is the node for this item
_this.wrapper = null;
// Their values will need to change, so keep track of them here
_this.countView = null;
_this.costView = null;
_this.countHidden = null;
_this.costHidden = null;
})();
}
// Update the visible and hidden fields for count and cost
Item.prototype.update = function() {
var _this = this;
_this.costView.textContent = poundify(_this.cost);
_this.countView.textContent = _this.count;
_this.costHidden.value = _this.cost;
_this.countHidden.value = _this.count;
};
Item.prototype.addItem = function(e) {
var _this = this;
// Prevent button from submitting the form
e.preventDefault();
// Add to the count and price
_this.count++;
_this.cost += _this.price;
// Update the item's markup
_this.update();
};
Item.prototype.removeItem = function(e) {
var _this = this;
// Prevent button from submitting the form
e.preventDefault();
// Do not allow count/cost to go negative
if (_this.count > 0) {
// Subtract from the count and price
_this.count--;
_this.cost -= _this.price;
// Update the item's markup
_this.update();
}
};
// Prints the item to a container
// It's a big function with lots of similar code, but no logic.
// Counter is used for creating PHP POST variable
Item.prototype.print = function(container, counter) {
var _this = this;
// Create all elements that will make up the HTML element
_this.wrapper = document.createElement("article");
var header = document.createElement("header");
var name = document.createElement("h2");
var nameHidden = document.createElement("input");
var img = document.createElement("img");
var desc = document.createElement("p");
var moneyContainer = document.createElement("div");
var price = document.createElement("span");
_this.costView = document.createElement("span");
var countContainer = document.createElement("div");
var remove = document.createElement("button");
_this.countView = document.createElement("span");
var add = document.createElement("button");
// Add hidden inputs that will post their data with the form
_this.countHidden = document.createElement("input");
_this.costHidden = document.createElement("input");
// Using BEM for Object Oriented styling/markup
_this.wrapper.classList.add("item");
// Give the item a name and append the name and header to the wrapper
name.appendChild(document.createTextNode(_this.name));
name.classList.add("item__name");
nameHidden.setAttribute("type", "hidden");
nameHidden.setAttribute("name", "item["+counter+"][name]");
nameHidden.setAttribute("value", _this.name);
header.appendChild(name);
header.appendChild(nameHidden);
header.classList.add("item__header");
_this.wrapper.appendChild(header);
// Give the image a source, alt and title, then append to wrapper
img.setAttribute("src", _this.photo);
img.setAttribute("alt", _this.name);
img.setAttribute("title", _this.name);
img.classList.add("item__img");
_this.wrapper.appendChild(img);
// Now add the description
desc.appendChild(document.createTextNode(_this.description));
desc.classList.add("item__description");
_this.wrapper.appendChild(desc);
// Price and cost are displayed together in boxes
// Use poundify function to make them into prices
price.appendChild(document.createTextNode(poundify(_this.price)));
_this.costView.appendChild(document.createTextNode(poundify(_this.cost)));
price.classList.add("item__price");
_this.costView.classList.add("item__cost");
moneyContainer.classList.add("item__money-container");
moneyContainer.appendChild(price);
moneyContainer.appendChild(_this.costView);
_this.wrapper.appendChild(moneyContainer);
// These inputs will hold the information to be posted to the php file
_this.countHidden.setAttribute("type", "hidden");
_this.costHidden.setAttribute("type", "hidden");
_this.countHidden.setAttribute("name", "item["+counter+"][count]");
_this.costHidden.setAttribute("name", "item["+counter+"][cost]");
_this.countHidden.setAttribute("value", _this.count);
_this.costHidden.setAttribute("value", _this.cost);
_this.wrapper.appendChild(_this.countHidden);
_this.wrapper.appendChild(_this.costHidden);
// Now add + and - buttons, and count display
remove.appendChild(document.createTextNode("-"));
_this.countView.appendChild(document.createTextNode(_this.count));
add.appendChild(document.createTextNode("+"));
remove.classList.add("item__remove");
remove.classList.add("js-remove");
_this.countView.classList.add("item__count");
add.classList.add("item__add");
add.classList.add("js-add");
// Add click event listeners
remove.addEventListener("click", _this.removeItem.bind(_this), false);
add.addEventListener("click", _this.addItem.bind(_this), false);
countContainer.classList.add("item__count-container");
countContainer.appendChild(remove);
countContainer.appendChild(_this.countView);
countContainer.appendChild(add);
_this.wrapper.appendChild(countContainer);
// Finally, add the wrapper to its container
container.appendChild(_this.wrapper);
};
// Create an item object and add it to the shop
Shop.prototype.addItem = function(element) {
this.items.push(new Item(
element.name,
element.description,
element.price,
element.photo));
};
function Shop(filename) {
// Allows this keyword to be used in all contexts
var _this = this;
(function init() {
// Get the array of items in the JSON file
var items = makeXHR(filename, function(s) { return JSON.parse(s); }).items;
// Initialise the list of items as an empty array
_this.items = [];
// Add each element to the shop
items.forEach(function (element, index, array) {
_this.addItem(element);
});
// HTML elements for the shop
_this.vatView = document.querySelector("input[name=vat]");
_this.subtotalView = document.querySelector("input[name=subtotal]");
_this.deliveryView = document.querySelector("input[name=delivery]");
_this.totalCostView = document.querySelector("input[name=total_cost]");
})();
}
// Print each item in the shop
Shop.prototype.print = function(container) {
var _this = this;
_this.items.forEach(function (element, index, array) {
element.print(container, index);
});
};
// Although inconsequential, it keeps the code grouped together and scoped
(function() {
// Get the shop element in the document
var shopContainer = document.querySelector(".js-shop");
// Initiate the shop object
var shop = new Shop("https://codepen.io/_Billy_Brown/pen/FAucG.js");
shop.print(shopContainer);
setCardTypeListener();
addChecks();
highlightRequired();
})();
// Convert amount into pounds
function poundify(amount) {
return "£" + amount.toFixed(2);
}
// Make HTTP request to a location and run the callback on its response
function makeXHR(loc, callback) {
var req = new XMLHttpRequest();
req.open("get", loc, false);
req.send();
// Return the result of the callback
return callback(req.responseText);
}
</script>
*,
button,
input,
select {
box-sizing: inherit;
}
html {
background: #e5eaee;
box-sizing: border-box;
font-family: sans-serif;
}
body {
margin: 0;
}
.shop {
align-items: center;
display: flex;
flex-flow: row wrap;
justify-content: space-around;
}
.item {
background: white;
box-shadow: 0 2px 5px #c5cacc;
flex: 0 auto;
margin: 1em;
text-align: center;
width: 15em;
}
.item__name {
text-transform: capitalize;
}
.item__money-container,
.item__count-container {
display: flex;
}
.item__price,
.item__cost,
.item__remove,
.item__count,
.item__add {
flex: 1;
height: 2.5rem;
line-height: 1rem;
padding: 0.75rem;
}
.item__price,
.item__cost {
position: relative;
}
/* add a name just above the field */
.item__price::before,
.item__cost::before {
display: block;
font-size: 0.8em;
position: absolute;
text-align: center;
top: -6px;
right: 0;
left: 0;
}
.item__price::before {
content: "price";
}
.item__cost {
color: #59b;
}
.item__cost::before {
content: "cost";
}
.item__remove,
.item__add {
appearance: none;
border: none;
color: white;
cursor: pointer;
font-size: 1.25em;
margin: 0;
outline: none;
}
.item__remove {
background: #f77;
}
.item__count {
background: #ed6;
}
.item__add {
background: #6d6;
}