mirror of
https://github.com/cloudmaker97/DurstRechner.git
synced 2025-12-05 23:48:39 +00:00
Berechnen von Gesamtpreisen nach Hinzufügen von Elementen; JavaScript Grundfunktionalität
This commit is contained in:
parent
358fcc2745
commit
9296f456fc
5 changed files with 225 additions and 19 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
||||||
node_modules
|
node_modules
|
||||||
|
.idea
|
||||||
40
index.html
40
index.html
|
|
@ -5,51 +5,57 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
<meta name="theme-color" content="#2196f3"/>
|
<meta name="theme-color" content="#2196f3"/>
|
||||||
<link rel="manifest" href="manifest.json">
|
<link rel="manifest" href="manifest.json">
|
||||||
<link rel="stylesheet" href="stylesheet.css">
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="favicon/apple-touch-icon.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="favicon/apple-touch-icon.png">
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="favicon/favicon-32x32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="favicon/favicon-32x32.png">
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="favicon/favicon-16x16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="favicon/favicon-16x16.png">
|
||||||
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
|
<link rel="stylesheet" href="node_modules/bootstrap/dist/css/bootstrap.min.css">
|
||||||
<link rel="stylesheet" href="node_modules/bootstrap-icons/font/bootstrap-icons.min.css">
|
<link rel="stylesheet" href="node_modules/bootstrap-icons/font/bootstrap-icons.min.css">
|
||||||
|
<link rel="stylesheet" href="stylesheet.css">
|
||||||
<title>Der Durstrechner</title>
|
<title>Der Durstrechner</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="d-flex flex-column vh-100">
|
<body class="d-flex flex-column vh-100">
|
||||||
<nav class="navbar navbar-expand-lg border-bottom mb-3">
|
<nav class="navbar navbar-expand-lg border-bottom mb-3">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="#">Durstrechner</a>
|
<a class="navbar-brand" href="#">Der Durstrechner</a>
|
||||||
<form class="d-flex" role="search">
|
<form class="d-flex" role="search" method="dialog">
|
||||||
<button class="btn" data-toggle-bs-theme><i class="bi bi-lightbulb-fill"></i> Tag / Nacht</button>
|
<button class="btn" data-toggle-bs-theme><i class="bi bi-lightbulb-fill"></i> Tag / Nacht</button>
|
||||||
<button class="btn">Einstellungen</button>
|
<button class="btn"><i class="bi bi-gear"></i> Einstellungen</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="container-fluid">
|
<main class="container-fluid">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-3">
|
<div class="col-12 col-md-4">
|
||||||
<ul class="list-group d-none">
|
<li class="list-group-item d-flex justify-content-between" data-template="product-line-item">
|
||||||
<li class="list-group-item">Test</li>
|
<span class="line-item-details">
|
||||||
</ul>
|
<span class="amount quantity-value" data-attr="quantity">5</span>
|
||||||
<div class="alert alert-info">Keine Produkte ausgewählt</div>
|
<span class="price currency-value" data-attr="value">2,50</span>
|
||||||
|
<span class="name name-value" data-attr="name">Wasser mit Kohlensäure</span>
|
||||||
|
</span>
|
||||||
|
<i class="bi bi-dash-circle"></i>
|
||||||
|
</li>
|
||||||
|
<ul class="list-group cart-items"></ul>
|
||||||
|
<div class="alert alert-info cart-empty">Keine Produkte ausgewählt</div>
|
||||||
<hr>
|
<hr>
|
||||||
<button class="btn btn-secondary btn-lg w-100">
|
<button class="btn btn-secondary btn-lg w-100 cart-value mb-3">
|
||||||
<span class="currency-value" data-total-value>0,00</span>
|
<span class="currency-value" data-total-value>0,00</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-9">
|
<div class="col-12 col-md-8">
|
||||||
<div class="row row-cols-6 row-gap-3">
|
<div class="row row-cols-4 row-gap-3 product-list">
|
||||||
<div class="col product-box">
|
<div class="col product-box" data-template="product" data-id="0">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<img src="https://placehold.co/250x250" alt="Produktbild" class="card-img-top">
|
<img data-attr="image" src="https://placehold.co/250x250" alt="Produktbild" class="card-img-top">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<span>Wasser mit Kohlensäure</span>
|
<span data-attr="name">Wasser mit Kohlensäure</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
|
|
||||||
|
|
||||||
<script src="node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="node_modules/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
import './theme.js'
|
import './theme.js'
|
||||||
|
import './calculator.js'
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
class TemplateElement {
|
||||||
|
static getProductTemplate() {
|
||||||
|
const element = document.querySelector('[data-template=product]').cloneNode(true);
|
||||||
|
return this.removeTemplate(element);
|
||||||
|
}
|
||||||
|
static getCartLineTemplate() {
|
||||||
|
const element = document.querySelector('[data-template=product-line-item]').cloneNode(true);
|
||||||
|
return this.removeTemplate(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
static removeTemplate(element) {
|
||||||
|
element.removeAttribute('data-template');
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Product {
|
||||||
|
constructor(name, price, image) {
|
||||||
|
this.id = Product.generateId();
|
||||||
|
this.name = name;
|
||||||
|
this.price = price;
|
||||||
|
this.image = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
static generateId() {
|
||||||
|
return Math.floor(Math.random() * 1000000000) * Math.floor(Math.random() * 1000000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
getProductElement() {
|
||||||
|
const productElement = TemplateElement.getProductTemplate();
|
||||||
|
productElement.setAttribute('data-id', this.id);
|
||||||
|
productElement.querySelector('[data-attr=name]').textContent = this.name;
|
||||||
|
productElement.querySelector('[data-attr=image]').src = this.image;
|
||||||
|
productElement.addEventListener('click', () => {
|
||||||
|
cart.addProduct(this);
|
||||||
|
})
|
||||||
|
return productElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CartLine {
|
||||||
|
constructor(product, quantity) {
|
||||||
|
this.product = product;
|
||||||
|
this.quantity = quantity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProductList {
|
||||||
|
constructor() {
|
||||||
|
this.products = [];
|
||||||
|
this.products.push(new Product('Product 1', 10, 'https://placehold.co/250x250/000000/FFF?text=Product 1'));
|
||||||
|
this.products.push(new Product('Product 2', 5, 'https://placehold.co/250x250/000000/FFF?text=Product 2'));
|
||||||
|
this.products.push(new Product('Product 3', 2.5, 'https://placehold.co/250x250/000000/FFF?text=Product 3'));
|
||||||
|
this.products.push(new Product('Product 4', 1, 'https://placehold.co/250x250/000000/FFF?text=Product 4'));
|
||||||
|
this.renderProductList();
|
||||||
|
}
|
||||||
|
|
||||||
|
getProductJson() {
|
||||||
|
return JSON.stringify(this.products);
|
||||||
|
}
|
||||||
|
|
||||||
|
getProductListElement() {
|
||||||
|
return document.querySelector('.product-list');
|
||||||
|
}
|
||||||
|
|
||||||
|
renderProductList() {
|
||||||
|
this.products.forEach(e => {
|
||||||
|
this.getProductListElement().appendChild(e.getProductElement());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Cart {
|
||||||
|
constructor() {
|
||||||
|
this.cartLines = [];
|
||||||
|
this.getCartButton().addEventListener('click', () => {
|
||||||
|
this.cartLines = [];
|
||||||
|
this.renderCart();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addProduct(product) {
|
||||||
|
const cartLine = this.cartLines.find(e => e.product.id === product.id);
|
||||||
|
if (cartLine) {
|
||||||
|
cartLine.quantity++;
|
||||||
|
} else {
|
||||||
|
this.cartLines.push(new CartLine(product, 1));
|
||||||
|
}
|
||||||
|
this.renderCart();
|
||||||
|
}
|
||||||
|
|
||||||
|
removeProduct(product) {
|
||||||
|
const cartLine = this.cartLines.find(e => e.product.id === product.id);
|
||||||
|
if (cartLine) {
|
||||||
|
cartLine.quantity--;
|
||||||
|
if (cartLine.quantity <= 0) {
|
||||||
|
this.cartLines = this.cartLines.filter(e => e.product.id !== product.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.renderCart();
|
||||||
|
}
|
||||||
|
|
||||||
|
renderCart() {
|
||||||
|
// Clear the cart element before rendering except the template
|
||||||
|
this.getCartElement().querySelectorAll('[data-id]').forEach(e => {
|
||||||
|
if (e.getAttribute('data-template') === null) {
|
||||||
|
e.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(this.cartLines.length === 0) {
|
||||||
|
this.getAlertElement().classList.remove('d-none');
|
||||||
|
} else {
|
||||||
|
this.getAlertElement().classList.add('d-none');
|
||||||
|
}
|
||||||
|
this.calculateCartValue();
|
||||||
|
|
||||||
|
// Render each cart line
|
||||||
|
this.cartLines.forEach(cartLine => {
|
||||||
|
const cartLineElement = this.getCartLineElement(cartLine);
|
||||||
|
this.getCartElement().appendChild(cartLineElement);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getCartLineElement(cartLine) {
|
||||||
|
const cartLineElement = TemplateElement.getCartLineTemplate();
|
||||||
|
cartLineElement.setAttribute('data-id', cartLine.product.id);
|
||||||
|
cartLineElement.querySelector('[data-attr=name]').textContent = cartLine.product.name;
|
||||||
|
cartLineElement.querySelector('[data-attr=value]').textContent = Cart.getNumberFormatter().format(cartLine.product.price);
|
||||||
|
cartLineElement.querySelector('[data-attr=quantity]').textContent = cartLine.quantity;
|
||||||
|
cartLineElement.addEventListener('click', () => {
|
||||||
|
this.removeProduct(cartLine.product);
|
||||||
|
})
|
||||||
|
return cartLineElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
getAlertElement() {
|
||||||
|
return document.querySelector('.alert.cart-empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
getCartElement() {
|
||||||
|
return document.querySelector('.cart-items');
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateCartValue() {
|
||||||
|
let cartValue = this.cartLines.reduce((acc, cartLine) => {
|
||||||
|
return acc + (cartLine.product.price * cartLine.quantity);
|
||||||
|
}, 0);
|
||||||
|
this.getCartButton().querySelector('[data-total-value]').textContent = Cart.getNumberFormatter().format(cartValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getNumberFormatter() {
|
||||||
|
return new Intl.NumberFormat('de-DE', {
|
||||||
|
currency: 'EUR',
|
||||||
|
minimumFractionDigits: 2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getCartButton() {
|
||||||
|
return document.querySelector('button.cart-value');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cart = new Cart();
|
||||||
|
const productList = new ProductList();
|
||||||
|
|
@ -1,3 +1,36 @@
|
||||||
|
.line-item-details {
|
||||||
|
.amount {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 1.5em;
|
||||||
|
}
|
||||||
|
.price {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 3em;
|
||||||
|
}
|
||||||
|
}
|
||||||
.currency-value::after {
|
.currency-value::after {
|
||||||
content: ' €';
|
content: ' €';
|
||||||
}
|
}
|
||||||
|
.quantity-value::after {
|
||||||
|
content: 'x ';
|
||||||
|
}
|
||||||
|
.name-value {
|
||||||
|
margin-left: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
[data-template] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
.cart-items, .product-list, .alert, .navbar-brand {
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
}
|
||||||
|
.product-box, .cart-items {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue