Skip to content

Instantly share code, notes, and snippets.

@janusnic
Created March 27, 2024 14:15
Show Gist options
  • Save janusnic/8e9fd7c8919b15bc17451e4f9bf8f54b to your computer and use it in GitHub Desktop.
Save janusnic/8e9fd7c8919b15bc17451e4f9bf8f54b to your computer and use it in GitHub Desktop.
Modal dialog
"use strict";
const currency = (total) => parseFloat(Math.round(total * 100) / 100).toFixed(2);
const compare = (key, order='acs') => (a, b) => {
if (!a.hasOwnProperty(key) || !b.hasOwnProperty(key)) return 0;
const A = (typeof a[key] === 'string') ? a[key].toUpperCase() : a[key];
const B = (typeof b[key] === 'string') ? b[key].toUpperCase() : b[key];
let comparison = 0;
comparison = (A > B) ? 1 : -1;
return (order === 'desc') ? -comparison : comparison;
}
const findByProps = function(items, props, what) {
let founds = [];
items.find((item, i) => {
if (item[props] === what) {
founds.push(items[i]);
}
})
return founds;
}
function Product(id, name, price, image) {
this.id = id;
this.name = name;
this.price = price;
this.image = image;
}
const Singleton = (function () {
let instance;
function createInstance(tax, ship) {
let object = new Cart(tax, ship);
return object;
}
return {
getInstance: function (tax, ship) {
if (!instance) {
instance = createInstance(tax, ship);
}
return instance;
}
};
})();
function CardProduct(item) {
this.item = item;
const detailTemplate = (item) => `
<div class="detail-container">
<div class="col-left">
<img src="${item.image}">
</div>
<div class="col-right">
<div class="info-container">
<h2 class="info-header">${item.name}</h2>
<div class="info-price">Price: <span class="price">${item.price}</span></div>
<div class="info-shipping">Free shipping</div>
<div class="info-button to-cart" data-id="${item.id}">
<a href="#!" class="btn btn-submit add-to-cart"><i class="fas fa-cart-plus"></i> Add to Cart</a>
</div>
<h2 class="qty-header py-2">Amount:</h2>
<div class="qty qty-buttons">
<div class="number-input quantity" data-id="${item.id}">
<button class="btn btn-dec">-</button>
<input class="quantity-result"
type="number"
value="1"
min="1"
max="10"
required
/>
<button class="btn btn-inc">+</button>
</div>
</div>
<div class="info-description">${item.description}</div>
<div class="info-link">
<a class="btn-link far fa-heart add-to-wishlist" href="#!" data-id="${item.id}">&nbsp;Add to wish list</a>
</div>
</div>
</div>
</div>
`;
const showButton = this.item.querySelector(".show-details");
const dialog = document.querySelector("dialog");
const closeButton = dialog.querySelector("dialog .close");
let dialogMain = dialog.querySelector("dialog .dialog-main");
showButton.addEventListener("click", event => {
let parent = event.target.closest('.product');
let id = parent.dataset.id;
dialogMain.innerHTML = detailTemplate(productList.getProductById(id))
dialog.showModal();
});
closeButton.addEventListener("click", () => {
dialog.close();
});
const addToCartButton = this.item.querySelector('.add-to-cart');
addToCartButton.addEventListener('click', this);
this.handleEvent = function(event) {
let parent = event.target.closest('.product');
let id = parent.dataset.id;
let product = productList.getProductById(id);
product = {...product, amount: 1};
shoppingCart.addItemToCart(product);
document.getElementById('cart-amount').textContent = shoppingCart.totalAmount();
}
}
function Cart(tax = 0.07, shipping = 0) {
console.log("Cart constructor", this);
this.tax = tax;
this.shipping = shipping;
let cart = [];
this.saveCart = function() {
console.log(cart);
}
function Item (id, price, amount) {
this.id = id;
this.price = price;
this.amount = amount;
}
//
this.addItemToCart = function(product) {
// console.log(product);
let inCart = cart.some(item => item.id === product.id);
if (inCart){
let index = cart.findIndex(item => item.id === product.id);
cart[index].amount += product.amount;
}else{
let item = new Item(product.id, product.price, product.amount);
cart.push(item);
}
this.saveCart();
}
this.setCountForItem = function(id, amount) {
for (let i in cart) {
if(cart[i].id === id) {
cart[i].amount = amount;
}
}
}
this.totalAmount = function() {
let total = 0;
for (let item in cart) {
total += cart[item].amount;
}
return total;
}
this.totalInCart = function() {
let total = 0;
for (let item in cart) {
total += cart[item].price * cart[item].amount;
}
return currency(total*(1 + this.tax) + this.shipping);
}
this.removeItemFromCart = function(id) {
for (let item in cart) {
if (cart[item].id === id) {
cart[item].amount--;
if (cart[item].amount === 0) {
cart.splice(item, 1);
}
break;
}
}
this.saveCart();
}
this.removeAllItemFromCart = function(id) {
for (let item in cart) {
if (cart[item].id === id) {
cart.splice(item, 1);
break;
}
}
this.saveCart();
}
this.clearCart = function() {
cart = [];
this.saveCart();
}
}
// let shoppingCart = new Cart();
let shoppingCart = Singleton.getInstance(0.2, 15);
let productList = new ProductList(products);
const starsTemplate = (n) => {
// let tmp = Array(n).fill('&starf;');
// const stars = Array(n).fill('&starf;').concat(Array(5 - n).fill('&star;'));
return Array(n).fill('&starf;').concat(Array(5 - n).fill('&star;')).join('');
};
function ProductList(products) {
this.products = products;
this.productTemplate = (product) => `
<article class="product" data-id="${product.id}">
<div class="icons">
<a href="#!" class="fas fa-shopping-cart add-to-cart"></a>
<a href="#!" class="fas fa-heart add-to-wishlist"></a>
<a href="#!" class="fas fa-eye show-details"></a>
</div>
<div class="image">
<div class="badge bg-${product.badge.bg}">${product.badge.title}</div>
<img src="${product.image}" alt="${product.name}">
</div>
<div class="content" data-id="${product.id}">
<h3 class="product-name">${product.name}</h3>
<span><span class="price"></span><span class="price product-price">${product.price}</span></span> <span class="starf">${starsTemplate(product.stars)}</span>
</div>
</article>`;
this.populateProductList = function (products) {
let content = "";
products.forEach(item => content += this.productTemplate(item))
return content;
}
this.getProductById = (id) => this.products.find(item => item.id == id);
}
function main() {
const productContainer = document.querySelector('.product-container');
productContainer.innerHTML = productList.populateProductList(products);
let productCards = productContainer.querySelectorAll('.product');
productCards.forEach(item => new CardProduct(item));
// productCards.forEach(function(item) {
// let id = item.querySelector('.content').getAttribute('id')
// let name = item.querySelector('.product-name').textContent
// let price = item.querySelector('.product-price').textContent
// let action = item.querySelector('.badge').textContent
// products = [...products, {id: +id, name: name, price: +price, action: action}]
// });
// console.log(products)
// const isPositive = (current) => current.price > 0;
// console.log(products.every(isPositive)); // Expected output: true
// let sorted = products.sort(compare('price', 'asc'));
// console.log("Asc sorted: ", sorted);
// console.log(products.sort(compare('price', 'asc')))
// console.log("Reversed: ", products.reverse());
// console.log(products.sort(compare('price', 'desc')))
// console.log(products.sort(compare('name', 'desc')))
// console.log(products.sort(compare('name', 'asc')))
// console.log("findByProps founds items: ", findByProps(products, "action", 'Sale'));
// let result = []
// const findItemByAction = (items, what) => items.filter(item => item.action == what)
// result.push(products.filter(item => item.action == 'Sale'))
// result.push(findItemByAction(products, 'Sale'))
// result.push(findItemByAction(products, 'New'))
// console.log(result);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
main();
});
} else {
main();
}
let products = [
{
id: 1,
badge: {
title: "Sold",
bg: "sold"
},
image: "/images/product-1.jpg",
name: "Kui Ye Chen’s AirPods",
price: 21,
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ut ullamcorper leo, eget euismod orci. Cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus. Vestibulum ultricies aliquam convallis.",
stars: 2,
category: 1,
},
{
id: 2,
badge: {
title: "",
bg: ""
},
image: "/images/product-2.jpg",
name: "Apple wireless keyboard",
price: 30,
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ut ullamcorper leo, eget euismod orci. Cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus. Vestibulum ultricies aliquam convallis.",
stars: 5,
category: 2,
},
{
id: 3,
badge: {
title: "New",
bg: "new"
},
image: "/images/product-3.jpg",
name: "Cyan cotton t-shirt",
price: 5,
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ut ullamcorper leo, eget euismod orci. Cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus. Vestibulum ultricies aliquam convallis.",
stars: 4,
category:3,
},
{
id: 4,
badge: {
title: "",
bg: ""
},
image: "/images/product-4.jpg",
name: "Timex Unisex Originals",
price: 51,
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ut ullamcorper leo, eget euismod orci. Cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus. Vestibulum ultricies aliquam convallis.",
stars: 1,
category: 1,
},
{
id: 5,
badge: {
title: "Sale",
bg: "sale"
},
image: "/images/product-5.jpg",
name: "Red digital smartwatch",
price: 10,
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ut ullamcorper leo, eget euismod orci. Cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus. Vestibulum ultricies aliquam convallis.",
stars: 0,
category: 4,
},
{
id: 6,
badge: {
title: "",
bg: ""
},
image: "/images/product-6.jpg",
name: "Nike air max 95",
price: 31,
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ut ullamcorper leo, eget euismod orci. Cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus. Vestibulum ultricies aliquam convallis.",
stars: 5,
category: 5,
},
{
id: 7,
badge: {
title: "Sale",
bg: "sale"
},
image: "/images/product-7.jpg",
name: "Joemalone Women perfume",
price: 35,
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ut ullamcorper leo, eget euismod orci. Cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus. Vestibulum ultricies aliquam convallis.",
stars: 4,
category: 6,
},
{
id: 8,
badge: {
title: "",
bg: ""
},
image: "/images/product-8.jpg",
name: "Apple Watch",
price: 25,
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ut ullamcorper leo, eget euismod orci. Cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus. Vestibulum ultricies aliquam convallis.",
stars: 3,
category: 4,
},
{
id: 9,
badge: {
title: "Sold",
bg: "sold"
},
image: "/images/product-9.jpg",
name: "Black Canon EOS camera",
price: 24,
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ut ullamcorper leo, eget euismod orci. Cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus. Vestibulum ultricies aliquam convallis.",
stars: 5,
category: 4,
},
{
id: 10,
badge: {
title: "New",
bg: "new"
},
image: "/images/product-10.jpg",
name: "Silver black round Ipod",
price: 16,
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ut ullamcorper leo, eget euismod orci. Cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus. Vestibulum ultricies aliquam convallis.",
stars: 0,
category: 1,
},
{
id: 11,
badge: {
title: "Sale",
bg: "sale"
},
image: "/images/product-11.jpg",
name: "Digital smartwatch",
price: 13,
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ut ullamcorper leo, eget euismod orci. Cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus. Vestibulum ultricies aliquam convallis.",
stars: 5,
category: 5,
},
{
id: 12,
badge: {
title: "New",
bg: "new"
},
image: "/images/product-12.jpg",
name: "Black camera lens",
price: 11,
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. In ut ullamcorper leo, eget euismod orci. Cum sociis natoque penatibus et magnis dis parturient montes nascetur ridiculus mus. Vestibulum ultricies aliquam convallis.",
stars: 2,
category: 1,
},
];
let categories = [
{
id: 1,
image: "https://couchjanus.github.io/images/product-1.jpg",
section: "Electronics",
name: "Headphones"
},
{
id: 2,
name: "Bags",
image: "https://couchjanus.github.io/images/product-2.jpg",
section: "Health & Beauty",
},
{
id: 3,
image: "https://couchjanus.github.io/images/product-3.jpg",
name: "T-Shirts",
section: "Fashion & Acc"
},
{
id: 4,
image: "https://couchjanus.github.io/images/product-4.jpg",
name: "Smartwatches",
section: "Electronics"
},
{
id: 5,
image: "https://couchjanus.github.io/images/product-5.jpg",
name: "Shoes",
section: "Fashion & Acc"
},
{
id: 6,
image: "https://couchjanus.github.io/images/product-7.jpg",
name: "Parphume",
section: "Health & Beauty"
},
{
id: 7,
image: "https://couchjanus.github.io/images/product-9.jpg",
name: "Watches",
section: "Health & Beauty",
},
{
id: 8,
image: "https://couchjanus.github.io/images/product-8.jpg",
name: "Apple Watch",
section: "Electronics"
},
{
id: 9,
image: "https://couchjanus.github.io/images/product-9.jpg",
name: "Byron Watch",
section: "Health & Beauty"
},
{
id: 10,
image: "https://couchjanus.github.io/images/product-10.jpg",
name: "Camera",
section: "Electronics"
},
{
id: 11,
image: "https://couchjanus.github.io/images/product-11.jpg",
name: "Nike shoes",
section: "Fashion & Acc"
},
{
id: 12,
image: "https://couchjanus.github.io/images/product-12.jpg",
name: "DSLR lense",
section: "Electronics"
},
];
dialog#details{
z-index: 10;
width: 100%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
border-radius: 1rem;
border: 1px solid rgba(0, 0, 0, 0.5);
overflow-x: hidden;
overflow-y: auto;
max-width: 50%;
}
.close {
position: absolute;
right: 1rem;
top: 1rem;
text-align: center;
text-decoration: none;
font-weight: bold;
background: lightgrey;
color: #000;
width: 24px;
height: 24px;
line-height: 24px;
border-radius: 24px;
border: grey 1px solid;
transition: all 0.5s ease-out;
z-index: 10;
}
.close:hover {
background: salmon;
border-color: salmon;
color: #fff;
transition: all 0.5s ease-out;
}
.detail-container {
display: flex;
width: 100%;
font-size: 0.7rem;
}
.detail-container h2 {
margin: 0 0 1rem 0;
font-size: 1rem;
font-weight: 700;
}
.detail-container p {
margin: 0 0 20px 0;
font-size: 1.1rem;
font-weight: 500;
line-height: 1.4rem;
}
.col-left {
flex: 1;
width: 100%;
}
.col-right {
flex: 1;
padding: 1rem 1.5rem;
width: 100%;
}
.detail-container img {
max-width: 100%;
object-fit: cover;
}
.price-container {
display: flex;
justify-content: space-between;
}
.info-container {
display: grid;
gap: 1rem;
}
.info-header {
grid-column: 1 / 3;
grid-row: 1;
}
.info-price {
grid-column: 1 / 2 ;
grid-row: 2;
font-size: 1rem;
font-weight: 500;
.price::before {
color: var(--anchor-active-color);
content: var(--currency-symbol);
}
}
.info-shipping {
grid-column: 2 / 3;
grid-row: 2;
font-size: 1rem;
font-weight: 500;
}
.info-qty {
grid-column: 1 / 3;
grid-row: 3;
}
/* pseudo class */
.qty input[type=number] {
max-width: 2.5rem;
height: 2rem;
border: 1px dotted lightgoldenrodyellow;
}
.qty input:in-range {
background-color: rgba(0, 255, 0, 0.25);
}
.qty input:out-of-range {
background-color: rgba(255, 0, 0, 0.25);
border: 2px solid red;
}
input[type="number"] {
-webkit-appearance: textfield;
-moz-appearance: textfield;
appearance: textfield;
}
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
}
.number-input {
border: 1px solid var(--active-color);
display: inline-flex;
border-radius: 9%;
}
.number-input button {
outline:none;
background-color: #fff;
align-items: center;
justify-content: center;
width: 2.5rem;
cursor: pointer;
margin: 0;
padding:0;
font-weight: 700;
font-size: 1.5rem;
}
.number-input button:hover {
background-color: var(--active-color);
color: white;
}
.number-input input[type=number] {
max-width: 4.5rem;
padding: .5rem;
border:0;
text-align: center;
outline:none;
}
.info-button {
grid-column: 1 / 3;
grid-row: 4;
}
.info-button a.btn {
display: inline-block;
padding: .5rem 1.2rem;
font-size: 1.2rem;
letter-spacing: 1px;
text-decoration: none;
border-radius: 30px;
color: #ffffff;
outline: none;
border: 1px solid #ffffff;
box-shadow: inset 0 0 0 0 #ffffff;
transition: .3s;
}
.info-description {
grid-column: 1 / 3;
grid-row: 5;
}
.info-button a.btn-submit {
width: 100%;
margin-top: 5px;
color: salmon;
font-size: 1rem;
letter-spacing: 1px;
cursor: pointer;
background: transparent;
border: 1px solid salmon;
border-radius: 30px;
box-shadow: inset 0 0 0 0 salmon;
transition: .3s;
}
.info-button a.btn-submit:hover {
color: #ffffff;
box-shadow: inset 240px 0 0 0 salmon;
}
.info-link {
grid-column: 1 / 3;
grid-row: 6;
}
@media (max-width: 45rem) {
.modal-body {
flex-wrap: wrap;
flex-direction: row;
}
.modal-body main, .modal-body aside {
flex: auto;
width: 100%;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment