Table of Contents
Create Dynamic Shopping Cart with JS and CSS
Updated by Cesar Banchio
In this article we will provide step-by-step guidelines to help you create a shopping cart that meets your needs based on an Airtable database. The example provided is a Restaurant type bot where you can select the meals you would like to order.
This is how the bot looks
And this is how the builder is structured:
The Builder
Let's dive in on some blocks:
The Airtable block is Get Multiple Items. What this block will do is fetch all items from the Airtable shown below. This is the structure:
We will save the records in an array type variable called @result
The block is a Javascript block with the following snippet:
let menu = @{result}
this.window.menu = menu
We are passing the variable @result which holds all of our items from the Airtable to the Window object.
The set variable block contains the HTML structure where we will display the elements:
<section class="menu">
<div class="title">
<h2>Landbot's Menu</h2>
<div class="underline"></div>
</div>
<div class="btn-container">
<!-- <button class="filter-btn" type="button" data-id="all">all</button>
<button class="filter-btn" type="button" data-id="breakfast">
breakfast
</button>
<button class="filter-btn" type="button" data-id="lunch">lunch</button>
<button class="filter-btn" type="button" data-id="shakes">
shakes
</button> -->
</div>
<div class="section-center">
<!-- <article class="menu-item">
<img src="menu-item.jpeg" class="photo" alt="menu item" />
<div class="item-info">
<header>
<h4>buttermilk pancakes</h4>
<h4 class="price">$15</h4>
</header>
<p class="item-text">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Cumque
quidem nihil exercitationem fugiat facilis distinctio nulla aut
voluptas deleniti? Laboriosam!
</p>
</div>
</article> -->
</div>
<div id="total">
<h3>Total: $ 0</h3>
</div>
</section>
We will save this in a string type variable called @menu
After this block, we use a Message block to display this variable:
Then, we will have 2 Code Set block to save the items selected and the total price into 2 variables:
Structure of the Airtable
Keep in mind that the column names have to remain the same in order for the Javascript snippet to work just as it is:
We have 6 columns, Name which is the name of the item, Photo which contains the URL of the photo, ID, Category which will serve for the buttons at the top to filter the items based on this (only use a single word for each category), Price and Description.
The CSS
Please copy and paste this CSS on the Design Section >> Custom CSS of your bot.
:root {
/* dark shades of primary color*/
--clr-primary-1: hsl(205, 86%, 17%);
--clr-primary-2: hsl(205, 77%, 27%);
--clr-primary-3: hsl(205, 72%, 37%);
--clr-primary-4: hsl(205, 63%, 48%);
/* primary/main color */
--clr-primary-5: #49a6e9;
/* lighter shades of primary color */
--clr-primary-6: hsl(205, 89%, 70%);
--clr-primary-7: hsl(205, 90%, 76%);
--clr-primary-8: hsl(205, 86%, 81%);
--clr-primary-9: hsl(205, 90%, 88%);
--clr-primary-10: hsl(205, 100%, 96%);
/* darkest grey - used for headings */
--clr-grey-1: hsl(209, 61%, 16%);
--clr-grey-2: hsl(211, 39%, 23%);
--clr-grey-3: hsl(209, 34%, 30%);
--clr-grey-4: hsl(209, 28%, 39%);
/* grey used for paragraphs */
--clr-grey-5: hsl(210, 22%, 49%);
--clr-grey-6: hsl(209, 23%, 60%);
--clr-grey-7: hsl(211, 27%, 70%);
--clr-grey-8: hsl(210, 31%, 80%);
--clr-grey-9: hsl(212, 33%, 89%);
--clr-grey-10: hsl(210, 36%, 96%);
--clr-white: #fff;
--clr-red-dark: hsl(360, 67%, 44%);
--clr-red-light: hsl(360, 71%, 66%);
--clr-green-dark: hsl(125, 67%, 44%);
--clr-green-light: hsl(125, 71%, 66%);
--clr-gold: #c59d5f;
--clr-black: #222;
--ff-primary: "Roboto", sans-serif;
--ff-secondary: "Open Sans", sans-serif;
--transition: all 0.3s linear;
--spacing: 0.25rem;
--radius: 0.5rem;
--light-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
--dark-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
--max-width: 1170px;
--fixed-width: 620px;
}
/*
===============
Global Styles
===============
*/
*,
::after,
::before {
margin: 0;
padding: 0;
box-sizing: border-box;
}
ul {
list-style-type: none;
}
a {
text-decoration: none;
}
img:not(.logo) {
width: 100%;
}
img {
display: block;
}
h1,
h2,
h3,
h4 {
text-transform: capitalize;
line-height: 1.25;
margin-bottom: 0.75rem;
font-family: var(--ff-primary);
}
h1 {
font-size: 2rem;
}
h2 {
font-size: 1.5rem;
}
h3 {
font-size: 1.25rem;
}
h4 {
font-size: 0.875rem;
}
p {
margin-bottom: 1.25rem;
color: var(--clr-grey-5);
}
@media screen and (min-width: 800px) {
h1 {
font-size: 4rem;
}
h2 {
font-size: 2.5rem;
}
h3 {
font-size: 1.75rem;
}
h4 {
font-size: 1rem;
}
body {
font-size: 1rem;
}
h1,
h2,
h3,
h4 {
line-height: 1;
}
}
/* global classes */
.btn {
text-transform: uppercase;
background: transparent;
color: var(--clr-black);
padding: 0.375rem 0.75rem;
letter-spacing: var(--spacing);
display: inline-block;
transition: var(--transition);
font-size: 0.875rem;
border: 2px solid var(--clr-black);
cursor: pointer;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
border-radius: var(--radius);
}
.btn:hover {
color: var(--clr-white);
background: var(--clr-black);
}
/* section */
/*
===============
Menu
===============
*/
.btn--menu {
text-transform: uppercase;
background: transparent;
color: var(--clr-black);
padding: 0.375rem 0.75rem;
letter-spacing: var(--spacing);
display: inline-block;
transition: var(--transition);
font-size: 0.55rem;
border: 2px solid var(--clr-black);
cursor: pointer;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
border-radius: var(--radius);
}
.btn--menu:hover {
color: var(--clr-white);
background: var(--clr-black);
}
.btn--menu:active {
background-color:gray;
}
.title {
text-align: center;
margin-bottom: 2rem;
}
.underline {
width: 5rem;
height: 0.25rem;
background: var(--clr-gold);
margin-left: auto;
margin-right: auto;
}
.btn-container {
margin-bottom: 4rem;
display: flex;
justify-content: center;
}
.filter-btn {
background: transparent;
border-color: var(--clr-gold);
font-size: 1rem;
text-transform: capitalize;
margin: 0 0.5rem;
letter-spacing: 1px;
border-radius: var(--radius);
padding: 0.375rem 0.75rem;
color: var(--clr-gold);
cursor: pointer;
transition: var(--transition);
}
.filter-btn:hover {
background: var(--clr-gold);
color: var(--clr-white);
}
.menu-item {
display: grid;
gap: 1rem 2rem;
max-width: 25rem;
animation-name:appear;
animation-duration: 4s;
}
.photo {
object-fit: cover;
height: 100px;
border: 0.25rem solid var(--clr-gold);
border-radius: var(--radius);
}
.item-info header {
display: flex;
justify-content: space-between;
border-bottom: 0.5px dotted var(--clr-grey-5);
}
.item-info h4 {
margin-bottom: 0.5rem;
}
.price {
color: var(--clr-gold);
}
.item-text {
margin-bottom: 0;
}
@media screen and (min-width: 768px) {
.menu-item {
grid-template-columns: 225px 1fr;
gap: 0 1.25rem;
max-width: 40rem;
}
.photo {
height: 175px;
}
}
@media screen and (min-width: 1200px) {
.section-center {
width: 95vw;
grid-template-columns: 1fr 1fr;
}
.photo {
height: 150px;
}
}
@keyframes appear {
from {opacity:0;}
to {opacity:1;}
}
The Javascript
Please copy and paste the JS snippet in the Design Section >> Custom Code
<script>
var sectionCenter= null
var container = null
window.selectedItems = []
window.total = 0;
function logging (e) {
const found = window.selectedItems.some(el => el.item === e.target.innerText)
if (found) {
window.selectedItems = window.selectedItems.filter(el => el.item !== e.target.innerText)
let item = e.target.closest(".menu-item")
item.style.backgroundColor= "rgba(255,255,255,0.1)"
}
else {
window.selectedItems.push({"item": e.target.innerText, "price": e.target.dataset.price, "photo": e.target.dataset.photo})
let item = e.target.closest(".menu-item")
item.style.backgroundColor= "rgba(0,0,0,0.3)"
}
let total = window.document.querySelector("#total").querySelector("h3")
let amount = window.selectedItems.reduce( (acc, curr, ind, _) =>{ return acc + (curr.price * 1) }, 0)
total.innerText = `Total: $ ${amount.toFixed(2)}`
window.total = amount.toFixed(2);
}
function addListener(elem) {
for (var i = 0; i < elem.length; i++) {
elem[i].addEventListener("click", logging);
};
}
function displayMenuItems(menuItems) {
let displayMenu = menuItems.map(function (item) {
// console.log(item);
return `<article class="menu-item">
<img src=${item.Photo} class="photo" alt=${item.Name} />
<div class="item-info">
<header>
<button class="btn--menu" data-price="${item.Price}" data-photo="${item.Photo}">${item.Name}</button>
<h4 class="price">$${item.Price}</h4>
</header>
<p class="item-text">
${item.Description}
</p>
</div>
</article>`;
});
displayMenu = displayMenu.join("");
sectionCenter.innerHTML = displayMenu;
let buttons = window.document.getElementsByClassName("btn--menu")
addListener(buttons)
}
function displayMenuButtons(menu) {
const categories = menu.reduce(
function (values, item) {
if (!values.includes(item.Category)) {
values.push(item.Category);
}
return values;
},
["all"]
);
const categoryBtns = categories
.map(function (category) {
return `<button class="filter-btn" type="button" data-id=${category}>
${category}
</button>`;
})
.join("");
container.innerHTML = categoryBtns;
const filterBtns = container.querySelectorAll(".filter-btn");
// filter items
filterBtns.forEach(function (btn) {
btn.addEventListener("click", function (e) {
const category = e.currentTarget.dataset.id;
const menuCategory = menu.filter(function (menuItem) {
// console.log(menuItem.category);
if (menuItem.Category === category) {
return menuItem;
}
});
if (category === "all") {
displayMenuItems(menu);
} else {
displayMenuItems(menuCategory);
}
});
});
}
const findMenu = () => {
let menus = window.document.querySelector(".menu")
if (menus) {
let menu = window.menu
sectionCenter = window.document.querySelector(".section-center");
container = window.document.querySelector(".btn-container");
displayMenuItems(menu);
displayMenuButtons(menu);
let buttons = window.document.getElementsByClassName("btn--menu")
addListener(buttons)
clearInterval(id)
}
}
let id = setInterval(findMenu, 1000)
</script>
Template
Download the template here