Skip to content

En varukorg med CSS och JavaScript

Du bör ha jobbat igenom kmom02, lämnat in och skapat en ny branch kmom03. Du kan med fördel göra nedanstående direkt i ditt webshop-repo.

Jag kommer i exemplet utgå från den produktlistningen som jag har i index.html och JavaScript koden i main.js som skriver ut den produktlistningen. I main.js-filen använder jag map-för att iterera över produkterna som jag har hämtat från API:t. Här lägger jag till ett span element med klassen add-to-cart och den specifika produktens id som id för att lägga till varor i varukorgen.

main.js
products.map((product) => {
return `<div class="product">
<img src="${product.image_url}" alt="Album art ${product.name}" />
<div class="bottom">
<p>${product.name}</p>
<span class="add-to-cart" id="${product.id}">+</span>
</div>
</div>`
}).join("\n")

Jag skapar sedan filen cart.css som jag lägger till i head-elementet. Här lägger jag till CSS för att få en rund cirkel runt + tecknet som finns i span-elementet. Vi använder samma höjd och bredd, samt border-radius 50% för att skapa en rund cirkel, sedan använder vi flexbox för att centrera innehållet.

cart.css
.add-to-cart {
display: flex;
align-items: center;
justify-content: center;
background-color: var(--text-color);
color: var(--background-color);
border-radius: 50%;
font-size: 2rem;
width: 3ch;
height: 3ch;
cursor: pointer;
}

Vi skapar nu cart.js som kommer innehålla JavaScript-koden för vår varukorg och lägger till den som de andra JavaScript-filerna i head-delen. Vi vill nu koppla en eventListener till de knapparna som vi precis skapade i main.js. Vi gör det genom funktionen instantiateCartButtons som vi sedan exporterar från filen.

cart.js
function instantiateCartButtons() {
document.querySelectorAll(".add-to-cart").forEach(
(elem) => elem.addEventListener("click", addToCart)
)
}
function addToCart(event) {
console.log(event.target.id)
}
export { instantiateCartButtons }

Vi kan sedan i main.js importera och anropa funktionen efter att vi hämtat produkterna och lagt till produkterna och lägg-till-i-varukorgs-knappen i DOM-trädet.

main.js
import { instantiateCartButtons } from "./cart.js"
// Andra funktioner och kod
productList.innerHTML = products.map((product) => {
return `<div class="product">
<img src="${product.image_url}" alt="Album art ${product.name}" />
<div class="bottom">
<p>${product.name}</p>
<span class="add-to-cart" id="${product.id}">+</span>
</div>
</div>`
}).join("\n")
instantiateCartButtons()

Vi kan nu börja med att lägga till saker i varukorgen i funktionen addToCart, som anropas från instantiateCartButtons. Vi kommer som i kmom02 använda oss av LocalStorage för att hantera varukorgen. Vi kan bara spara ner strängar till LocalStorage, men vi använder os av JSON.parse och JSON.stringify (Dokumentation) för att göra om objekt till strängar och omvänt.

Vi hämtar eller skapar först varukorgen utifrån LocalStorage eller ett tomt objekt. Sedan kollar vi om produkten redan finns i varukorgen och lägger då till ytterligare annars lägger vi till produkten i varukorgen. Vi avslutar med med att göra objektet till en sträng igen och sedan

cart.js
function addToCart(event) {
const productID = event.target.id
let cart = JSON.parse(localStorage.getItem("cart") || "{}")
if (cart[productID]) {
cart[productID] = cart[productID] + 1
} else {
cart[productID] = 1
}
localStorage.setItem("cart", JSON.stringify(cart))
}

Högst upp i index.html lägger jag i det elementet som jag vill ska innehålla min varukorg .cart-content. Jag lägger även till objektet <div class="cart" id="cart">🛒</div> som kan användas för att öppna och stänga varukorgen.

index.html
<body class="site">
<div class="cart-content" id="cart-content">
<div class="cart-header">
<h3>Inköpsvagn</h3>
<p class="close-cart" id="close-cart">x</p>
</div>
<div class="cart-products" id="cart-products"></div>
</div>
<header class="header">
<div class="inner-header">
<h1><a href="index.html">Emil's skivor</a></h1>
<div class="right">
<a href="category.html">Kategori-sidan</a>
<div class="cart" id="cart">🛒</div>
<select id="mode" class="mode">
<option value="variables.css">Light</option>
<option value="variables-dark.css">Dark</option>
</select>
</div>
</div>
</header>
<!-- resten av koden -->

I filen cart.css lägger jag till följande CSS för att designa varukorgen. Jag använder position: absolute; för att lägga varukorgen utanför det normala flödet så det ser ut som den är ovanför resten av sidan. Just nu kan du inte se varukorgen men om du kommentarer ut display: none; bör den visas som att den tar upp knappt hälften av skärmen.

cart.css
.cart {
font-size: 2rem;
cursor: pointer;
}
.cart-content {
display: none;
position: absolute;
top: 1vh;
right: 0;
height: 98vh;
width: 40vw;
background-color: var(--background-color);
border-top-left-radius: 1.75rem;
border-bottom-left-radius: 1.75rem;
padding: 1.75rem;
z-index: 1;
box-shadow: -4px 4px 20px 0 rgba(0 0 0 / 75%);
overflow-y: scroll;
}

Vi lägger även till selektorn .cart-content.active i cart.css. När vi har två klasser utan mellanrum i våra CSS-filer innebär det element som har båda klasserna.

cart.css
.cart-content.active {
display: block;
}

Vi kan nu börja titta på logiken för att öppna och stänga varukorgen. Vi fortsätter med denna kod i cart.js. Först ser vi till att hämta ut de element som vi vill påverka och skapar sedan en eventListener för cart.

cart.js
const cartContent = document.getElementById("cart-content")
const cart = document.getElementById("cart")
cart.addEventListener("click", toggleCart)

I funktion toggleCart sätter vi först style.top attributet till hur långt vi har scrollat ner på sidan plus 1% av höjden av webbläsar fönstret. 1% av höjden motsvarar 1vh som vi satte i CSS filen. Sedan togglar vi klassen active på varukorgselementet. När vi använder funktionen toggle läggs klassen till om den inte redan finns på elementet. Om klassen redan finns tas den bort.

Vi kollar sedan om klassen finns där och kapar då bort all overflow (det som finns utanför webbläsarfönstret) vilket göra att vi inte kan scrolla på sidan när varukorgen är öppen. Vi anropar även funktionen renderCartProducts() som renderar produkterna.

cart.js
function toggleCart() {
cartContent.style.top = `${(window.scrollY + document.documentElement.clientHeight / 100)}px`
cartContent.classList.toggle("active")
if (cartContent.classList.contains("active")) {
document.body.style.overflow = "hidden"
renderCartProducts()
} else {
document.body.style.overflow = "auto"
}
}

I renderCartProducts() hämtar vi ut vår varukorgsobjekt från LocalStorage och renderar ut varje produkt med det antal som ligger i varukorgen. Hela koden för cart.js ser alltså ut på detta sättet. Filen models/products.js kan ni skapa själv för att strukturera koden på ett bra sätt.

cart.js
import products from "../models/products.js"
const cartContent = document.getElementById("cart-content")
const cart = document.getElementById("cart")
cart.addEventListener("click", toggleCart)
const closeCart = document.getElementById("close-cart")
closeCart.addEventListener("click", toggleCart)
function toggleCart() {
cartContent.style.top = `${(window.scrollY + document.documentElement.clientHeight / 100)}px`
cartContent.classList.toggle("active")
if (cartContent.classList.contains("active")) {
document.body.style.overflow = "hidden"
renderCartProducts()
} else {
document.body.style.overflow = "auto"
}
}
function instantiateCartButtons() {
document.querySelectorAll(".add-to-cart").forEach(
(elem) => elem.addEventListener("click", addToCart)
)
}
function addToCart(event) {
const productID = event.target.id
let cart = JSON.parse(localStorage.getItem("cart") || "{}")
if (cart[productID]) {
cart[productID] = cart[productID] + 1
} else {
cart[productID] = 1
}
localStorage.setItem("cart", JSON.stringify(cart))
toggleCart()
}
async function renderCartProducts() {
let cartHTML = "<p>No products in cart.</p>"
if (localStorage.getItem("cart")) {
const cart = JSON.parse(localStorage.getItem("cart"))
const cartItems = []
for (let product in cart) {
const productInfo = await products.fetchProduct(product)
const cartItem = `<div class="cart-item">
<img src="${productInfo.data.image_url}" alt="Album art ${productInfo.data.name}" />
<p>${productInfo.data.name}</p><p>Antal: ${cart[product]}</p>
</div>`
cartItems.push(cartItem)
}
cartHTML = cartItems.join("\n")
}
document.getElementById("cart-products").innerHTML = cartHTML
}
export { instantiateCartButtons }