283 lines
No EOL
13 KiB
JavaScript
283 lines
No EOL
13 KiB
JavaScript
// Holder styr på gjeldende side for navigasjonslogikk
|
|
let currentPage = 'products';
|
|
// Lokal kopi av handlekurvantall for rask oppdatering uten model-kall
|
|
let cartCount = 0;
|
|
|
|
/**
|
|
* Sentral renderingsfunksjon - alle UI-oppdateringer går gjennom denne
|
|
* Bruker switch-pattern for å håndtere forskjellige visningstyper
|
|
* data-parameter bruker default empty object for å unngå undefined errors
|
|
*/
|
|
function updateView(viewType, data = {}) {
|
|
const app = document.getElementById('app');
|
|
|
|
switch (viewType) {
|
|
case 'products':
|
|
currentPage = 'products';
|
|
// Fallback til tom array hvis products er undefined - forhindrer krasj
|
|
app.innerHTML = generateProductsPage(data.products || []);
|
|
// Fester event listeners etter hver render fordi innerHTML erstatter alt
|
|
attachEventListeners();
|
|
break;
|
|
case 'product-detail':
|
|
currentPage = 'product-detail';
|
|
app.innerHTML = generateProductDetailPage(data.product);
|
|
attachEventListeners();
|
|
break;
|
|
case 'cart':
|
|
currentPage = 'cart';
|
|
// Flere fallbacks fordi handlekurvdata kan være incomplete
|
|
app.innerHTML = generateCartPage(data.cartItems || [], data.total || 0, data.itemCount || 0);
|
|
attachEventListeners();
|
|
break;
|
|
case 'cart-count':
|
|
// Spesiell case - oppdaterer kun handlekurvteller uten full re-render
|
|
// Mer effektivt enn å regenerere hele siden
|
|
cartCount = data.count;
|
|
updateCartCountInNav();
|
|
break;
|
|
case 'notification':
|
|
// Brukernotifikasjoner håndteres separat fra hovedrendering
|
|
showNotification(data.message);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Genererer navigasjonsbar som inkluderes på alle sider
|
|
// Returnerer HTML-string istedenfor DOM-manipulering for konsistens
|
|
function generateNavbar() {
|
|
return `
|
|
<header>
|
|
<nav class="navbar">
|
|
<h1 class="logo">Nettbutikk</h1>
|
|
<ul class="nav-links">
|
|
<li><a href="#" data-page="products">Produkter</a></li>
|
|
<li><a href="#" data-page="cart" class="cart-link">Handlekurv (<span id="cart-count">${cartCount}</span>)</a></li>
|
|
</ul>
|
|
</nav>
|
|
</header>
|
|
`;
|
|
}
|
|
|
|
// Genererer komplett produktlisteside med navbar og produktrutenett
|
|
// Bruker template literals for lesbar HTML-struktur
|
|
function generateProductsPage(products) {
|
|
return `
|
|
${generateNavbar()}
|
|
<main>
|
|
<section class="page active" role="main" aria-label="Produktoversikt">
|
|
<header>
|
|
<h2>Våre Produkter</h2>
|
|
</header>
|
|
<div class="products-grid" role="list" aria-label="Liste over produkter">
|
|
${/* Mapper hvert produkt til HTML-kort og slår sammen til string */
|
|
products.map(product => createProductCard(product)).join('')}
|
|
</div>
|
|
</section>
|
|
</main>
|
|
`;
|
|
}
|
|
|
|
// Genererer produktdetaljside med error handling for ugyldig produkt
|
|
// Håndterer both null/undefined produkter gracefully
|
|
function generateProductDetailPage(product) {
|
|
// Kritisk sjekk - håndterer tilfeller hvor bruker navigerer til ugyldig produkt-ID
|
|
// Viser feilmelding istedenfor å krasje
|
|
if (!product) {
|
|
return `
|
|
${generateNavbar()}
|
|
<main>
|
|
<section class="page active" role="main" aria-label="Produktdetaljer">
|
|
<nav aria-label="Breadcrumb">
|
|
<button class="back-btn" onclick="navigateToProducts()" aria-label="Gå tilbake til produktoversikt">← Tilbake til Produkter</button>
|
|
</nav>
|
|
<div role="alert" aria-live="polite">
|
|
<p>Produktet ble ikke funnet</p>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
`;
|
|
}
|
|
|
|
// Normal rendering med fullstendige produktdetaljer
|
|
// Bruker semantic HTML og ARIA for tilgjengelighet
|
|
return `
|
|
${generateNavbar()}
|
|
<main>
|
|
<section class="page active" role="main" aria-label="Produktdetaljer">
|
|
<nav aria-label="Breadcrumb">
|
|
<button class="back-btn" onclick="navigateToProducts()" aria-label="Gå tilbake til produktoversikt">← Tilbake til Produkter</button>
|
|
</nav>
|
|
<article class="product-detail">
|
|
<header>
|
|
<div class="product-image" role="img" aria-label="Produktbilde: ${product.name}">${product.image}</div>
|
|
<h1 class="product-name">${product.name}</h1>
|
|
</header>
|
|
<div class="product-info">
|
|
<p class="product-price" aria-label="Pris: ${product.price.toFixed(2)} kroner">
|
|
<span class="currency">kr</span> <span class="amount">${product.price.toFixed(2)}</span>
|
|
</p>
|
|
<p class="product-description">${product.description}</p>
|
|
<p class="product-category">Kategori: <span>${product.category}</span></p>
|
|
</div>
|
|
<footer>
|
|
<button class="btn btn-success" onclick="handleAddToCart(${product.id})" aria-label="Legg ${product.name} i handlekurv">
|
|
Legg i handlekurv
|
|
</button>
|
|
</footer>
|
|
</article>
|
|
</section>
|
|
</main>
|
|
`;
|
|
}
|
|
|
|
// Genererer handlekurvside med kondisjonell rendering basert på innhold
|
|
// Håndterer både tom og fylt handlekurv med forskjellige UI-elementer
|
|
function generateCartPage(cartItems, total, itemCount) {
|
|
// Ternary operator for å vise forskjellig innhold basert på handlekurvstatus
|
|
// Tom handlekurv får eget design med oppfordring til handling
|
|
const cartContent = cartItems.length === 0 ?
|
|
`<div class="empty-cart" role="status" aria-live="polite">
|
|
<h3>Handlekurven din er tom</h3>
|
|
<p>Legg til noen produkter for å komme i gang!</p>
|
|
</div>` :
|
|
// Mapper hver handlekurvvare til HTML og slår sammen
|
|
cartItems.map(item => createCartItem(item)).join('');
|
|
|
|
// Viser kun total og handlingsknapper hvis handlekurven har varer
|
|
// Unngår forvirrende UI-elementer når handlekurven er tom
|
|
const cartTotal = itemCount === 0 ? '' :
|
|
`<footer class="cart-total">
|
|
<div class="total-amount" role="status" aria-live="polite" aria-label="Totalt beløp: ${total.toFixed(2)} kroner">
|
|
Totalt: <span class="amount">${total.toFixed(2)} kr</span>
|
|
</div>
|
|
<div class="cart-actions">
|
|
<button class="btn btn-primary" onclick="alert('Utsjekking er ikke implementert i denne demoen')" aria-label="Gå til kasse for å fullføre kjøp">Gå til kasse</button>
|
|
<button class="btn btn-danger" onclick="handleClearCart()" aria-label="Tøm hele handlekurven">Tøm handlekurv</button>
|
|
</div>
|
|
</footer>`;
|
|
|
|
return `
|
|
${generateNavbar()}
|
|
<main>
|
|
<section class="page active" role="main" aria-label="Handlekurv">
|
|
<header>
|
|
<h2>Handlekurv</h2>
|
|
${/* Viser kun sammendrag hvis det finnes varer - bedre UX */
|
|
itemCount > 0 ? `<p class="cart-summary" aria-live="polite">${itemCount} ${itemCount === 1 ? 'vare' : 'varer'} i handlekurven</p>` : ''}
|
|
</header>
|
|
<div class="cart-items" role="list" aria-label="Varer i handlekurv">
|
|
${cartContent}
|
|
</div>
|
|
${cartTotal}
|
|
</section>
|
|
</main>
|
|
`;
|
|
}
|
|
|
|
// Fester event listeners til navigasjonselementer etter hver rendering
|
|
// Må kalles etter innerHTML fordi det erstatter alle eksisterende listeners
|
|
function attachEventListeners() {
|
|
const productsLink = document.querySelector('[data-page="products"]');
|
|
const cartLink = document.querySelector('[data-page="cart"]');
|
|
|
|
// Defensiv programming - sjekker at elementer eksisterer før listener-tilkobling
|
|
// Forhindrer feil hvis HTML-struktur endres
|
|
if (productsLink) {
|
|
productsLink.addEventListener('click', function(e) {
|
|
e.preventDefault(); // Forhindrer standard link-navigasjon
|
|
navigateToProducts();
|
|
});
|
|
}
|
|
|
|
if (cartLink) {
|
|
cartLink.addEventListener('click', function(e) {
|
|
e.preventDefault(); // Forhindrer standard link-navigasjon
|
|
navigateToCart();
|
|
});
|
|
}
|
|
}
|
|
|
|
// Oppdaterer kun handlekurvtelleren uten full side-regenerering
|
|
// Mer effektivt for hyppige oppdateringer som quantity-endringer
|
|
function updateCartCountInNav() {
|
|
const cartCountElement = document.getElementById('cart-count');
|
|
// Sikkerhetsjekk - elementet kan mangle hvis navbar ikke er rendret ennå
|
|
if (cartCountElement) {
|
|
cartCountElement.textContent = cartCount;
|
|
}
|
|
}
|
|
|
|
// Removed dead code: showPage() and renderProducts() functions
|
|
// These were remnants from old multi-page architecture
|
|
|
|
function createProductCard(product) {
|
|
return `
|
|
<article class="product-card" role="listitem" tabindex="0" onclick="showProductDetail(${product.id})"
|
|
onkeydown="if(event.key==='Enter'||event.key===' ') showProductDetail(${product.id})"
|
|
aria-label="Produkt: ${product.name}, Pris: ${product.price.toFixed(2)} kroner">
|
|
<header>
|
|
<div class="product-image" role="img" aria-label="Produktbilde: ${product.name}">${product.image}</div>
|
|
<h3 class="product-name">${product.name}</h3>
|
|
</header>
|
|
<div class="product-info">
|
|
<p class="product-price" aria-label="Pris: ${product.price.toFixed(2)} kroner">
|
|
<span class="currency">kr</span> <span class="amount">${product.price.toFixed(2)}</span>
|
|
</p>
|
|
<p class="product-description">${product.description}</p>
|
|
</div>
|
|
<footer>
|
|
<button class="btn btn-success" onclick="event.stopPropagation(); handleAddToCart(${product.id})"
|
|
aria-label="Legg ${product.name} i handlekurv">
|
|
Legg i handlekurv
|
|
</button>
|
|
</footer>
|
|
</article>
|
|
`;
|
|
}
|
|
|
|
// Removed dead code: renderProductDetail() function
|
|
// Replaced by generateProductDetailPage() in modern architecture
|
|
|
|
// Removed dead code: renderCart() function
|
|
// Replaced by generateCartPage() in modern architecture
|
|
|
|
function createCartItem(item) {
|
|
return `
|
|
<article class="cart-item" role="listitem">
|
|
<header class="cart-item-info">
|
|
<h3 class="cart-item-name">${item.product.name}</h3>
|
|
<p class="cart-item-price" aria-label="Pris per stykk: ${item.product.price.toFixed(2)} kroner">
|
|
<span class="amount">${item.product.price.toFixed(2)}</span> <span class="currency">kr</span> per stk
|
|
</p>
|
|
</header>
|
|
<div class="cart-item-controls">
|
|
<div class="quantity-controls" role="group" aria-label="Antall ${item.product.name}">
|
|
<button class="quantity-btn" onclick="changeQuantity(${item.productId}, -1)"
|
|
aria-label="Reduser antall ${item.product.name}"
|
|
${item.quantity <= 1 ? 'aria-describedby="min-quantity-warning"' : ''}>-</button>
|
|
<span class="quantity" role="status" aria-live="polite" aria-label="${item.quantity} stykker">${item.quantity}</span>
|
|
<button class="quantity-btn" onclick="changeQuantity(${item.productId}, 1)"
|
|
aria-label="Øk antall ${item.product.name}">+</button>
|
|
${item.quantity <= 1 ? '<span id="min-quantity-warning" class="sr-only">Minimum antall er 1</span>' : ''}
|
|
</div>
|
|
<div class="item-total" aria-label="Totalt for ${item.product.name}: ${(item.product.price * item.quantity).toFixed(2)} kroner">
|
|
<span class="amount">${(item.product.price * item.quantity).toFixed(2)}</span> <span class="currency">kr</span>
|
|
</div>
|
|
<button class="btn btn-remove" onclick="handleRemoveFromCart(${item.productId})"
|
|
aria-label="Fjern ${item.product.name} fra handlekurv">
|
|
Fjern
|
|
</button>
|
|
</div>
|
|
</article>
|
|
`;
|
|
}
|
|
|
|
// Removed dead code: renderCartTotal() and updateCartCount() functions
|
|
// Replaced by generateCartPage() and updateCartCountInNav() in modern architecture
|
|
|
|
// Viser brukernotifikasjoner - bruker alert() for enkelhet i prototypefasen
|
|
// I produksjon ville dette vært en toast-notifikasjon eller modal
|
|
function showNotification(message) {
|
|
alert(message);
|
|
} |