second commit
This commit is contained in:
parent
0d337a818f
commit
bffdcf4bdf
16 changed files with 2366 additions and 4 deletions
|
@ -1 +1 @@
|
|||
flake-profile-2-link
|
||||
flake-profile-6-link
|
|
@ -1 +0,0 @@
|
|||
/nix/store/rsbmjvqa6n7vf10pm0cg0fv437ixj57q-nix-shell-env
|
1
.direnv/flake-profile-6-link
Symbolic link
1
.direnv/flake-profile-6-link
Symbolic link
|
@ -0,0 +1 @@
|
|||
/nix/store/6813nrz8y11cdvr8iw2czgdmqhsd6yr6-nix-shell-env
|
60
CLAUDE.md
Normal file
60
CLAUDE.md
Normal file
|
@ -0,0 +1,60 @@
|
|||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Development Environment
|
||||
|
||||
This project uses **Nix Flakes** for development environment management:
|
||||
- Run `nix develop` to enter the development shell with Node.js v22 and git
|
||||
- The flake.nix configures a reproducible development environment
|
||||
|
||||
## Project Structure
|
||||
|
||||
This is a **frontend web development educational project** with two main areas:
|
||||
|
||||
### 1. Pensum (Curriculum Modules)
|
||||
Five learning modules in `/pensum/` directory, each focusing on different aspects of frontend development:
|
||||
- `Framworkless/` - Vanilla JavaScript/HTML/CSS implementations
|
||||
- `HTML-CSS/` - Web fundamentals and styling
|
||||
- `Functional Programming/` - Programming paradigm concepts
|
||||
- `Typescript/` - Type-safe JavaScript development
|
||||
- `Vue/` - Vue.js framework implementations
|
||||
|
||||
### 2. Webshop Application (`/webshop_app/`)
|
||||
The main practical project implementing a progressive web store:
|
||||
- **PRD Location**: `webshop_app/prd.md` contains complete requirements and implementation phases
|
||||
- **Current Phase**: MVC implementation in `webshop_app/mvc-emne2/`
|
||||
- **Architecture**: Designed for progressive complexity - starting with plain JS/HTML/CSS MVC, then enhanced with TypeScript, Vue.js, and advanced features
|
||||
|
||||
## Key Implementation Details
|
||||
|
||||
### MVC Architecture (Current Phase)
|
||||
- **Model**: Handle data (products, cart) using local storage/JSON
|
||||
- **View**: HTML templates and DOM manipulation
|
||||
- **Controller**: Business logic and user interaction handling
|
||||
- **Requirements**: Pure JavaScript (ES6+), HTML5, CSS3, no external frameworks
|
||||
|
||||
### Progressive Enhancement Strategy
|
||||
The webshop will be rewritten multiple times during the course:
|
||||
1. **Phase 1**: Plain JS/HTML/CSS with MVC pattern
|
||||
2. **Phase 2**: Enhanced with modern JavaScript features
|
||||
3. **Phase 3**: TypeScript implementation
|
||||
4. **Phase 4**: Vue.js implementation
|
||||
5. **Phase 5**: Advanced features (auth, payment, etc.)
|
||||
|
||||
## Core Application Features
|
||||
- Product listing page with basic product information
|
||||
- Product detail page with purchase functionality
|
||||
- Basic navigation and routing
|
||||
- Shopping cart functionality (localStorage-based)
|
||||
- Responsive design for desktop and mobile
|
||||
|
||||
## Data Structure
|
||||
- **Products**: id, name, price, description, image, category
|
||||
- **Cart**: product references and quantities
|
||||
- **Storage**: localStorage for client-side persistence
|
||||
|
||||
## Development Notes
|
||||
- No package.json files exist yet - modules are currently empty placeholders
|
||||
- Entry point for webshop MVC implementation: `webshop_app/mvc-emne2/index.html`
|
||||
- Educational focus: demonstrating core web development concepts through practical implementation
|
|
@ -19,11 +19,13 @@
|
|||
buildInputs = [
|
||||
pkgs.nodejs
|
||||
pkgs.git
|
||||
pkgs.zsh
|
||||
];
|
||||
shellHook = ''
|
||||
export SHELL=${pkgs.zsh}/bin/zsh
|
||||
echo "Node.js development environment ready."
|
||||
node --version
|
||||
npm --version
|
||||
echo "Node version: $(node --version)"
|
||||
echo "Npm version: $(npm --version)"
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
|
252
webshop_app/mvc-emne2/ACCESSIBILITY.md
Normal file
252
webshop_app/mvc-emne2/ACCESSIBILITY.md
Normal file
|
@ -0,0 +1,252 @@
|
|||
# Accessibility & Semantic HTML Improvements
|
||||
|
||||
## Overview
|
||||
The MVC webshop application has been enhanced with proper semantic HTML elements and accessibility features to ensure it's usable by all users, including those using assistive technologies.
|
||||
|
||||
## Semantic HTML Elements Added
|
||||
|
||||
### 1. Document Structure
|
||||
- **`<main>`**: Wraps the primary content area
|
||||
- **`<header>`**: Used for page headers and section headers
|
||||
- **`<footer>`**: Used for cart totals and action areas
|
||||
- **`<nav>`**: Navigation elements including breadcrumbs
|
||||
- **`<section>`**: Content sections with proper roles
|
||||
- **`<article>`**: Individual content items (products, cart items)
|
||||
|
||||
### 2. Navigation Improvements
|
||||
```html
|
||||
<!-- Before -->
|
||||
<div class="navbar">
|
||||
<h1 class="logo">Nettbutikk</h1>
|
||||
<ul class="nav-links">...</ul>
|
||||
</div>
|
||||
|
||||
<!-- After -->
|
||||
<header>
|
||||
<nav class="navbar">
|
||||
<h1 class="logo">Nettbutikk</h1>
|
||||
<ul class="nav-links">...</ul>
|
||||
</nav>
|
||||
</header>
|
||||
```
|
||||
|
||||
### 3. Product Cards
|
||||
```html
|
||||
<!-- Before -->
|
||||
<div class="product-card">
|
||||
<div class="product-image">🎧</div>
|
||||
<div class="product-name">Hodetelefoner</div>
|
||||
<!-- ... -->
|
||||
</div>
|
||||
|
||||
<!-- After -->
|
||||
<article class="product-card" role="listitem" tabindex="0">
|
||||
<header>
|
||||
<div class="product-image" role="img" aria-label="Produktbilde: Hodetelefoner">🎧</div>
|
||||
<h3 class="product-name">Hodetelefoner</h3>
|
||||
</header>
|
||||
<div class="product-info">
|
||||
<p class="product-price" aria-label="Pris: 99.99 kroner">
|
||||
<span class="currency">kr</span> <span class="amount">99.99</span>
|
||||
</p>
|
||||
<!-- ... -->
|
||||
</div>
|
||||
<footer>
|
||||
<button aria-label="Legg Hodetelefoner i handlekurv">...</button>
|
||||
</footer>
|
||||
</article>
|
||||
```
|
||||
|
||||
### 4. Product Detail Page
|
||||
```html
|
||||
<main>
|
||||
<section role="main" aria-label="Produktdetaljer">
|
||||
<nav aria-label="Breadcrumb">
|
||||
<button aria-label="Gå tilbake til produktoversikt">← Tilbake</button>
|
||||
</nav>
|
||||
<article class="product-detail">
|
||||
<header>
|
||||
<div role="img" aria-label="Produktbilde: Produktnavn">🎧</div>
|
||||
<h1 class="product-name">Produktnavn</h1>
|
||||
</header>
|
||||
<!-- ... -->
|
||||
</article>
|
||||
</section>
|
||||
</main>
|
||||
```
|
||||
|
||||
### 5. Shopping Cart
|
||||
```html
|
||||
<section role="main" aria-label="Handlekurv">
|
||||
<header>
|
||||
<h2>Handlekurv</h2>
|
||||
<p class="cart-summary" aria-live="polite">3 varer i handlekurven</p>
|
||||
</header>
|
||||
<div role="list" aria-label="Varer i handlekurv">
|
||||
<article class="cart-item" role="listitem">
|
||||
<header class="cart-item-info">
|
||||
<h3 class="cart-item-name">Produktnavn</h3>
|
||||
<!-- ... -->
|
||||
</header>
|
||||
<!-- ... -->
|
||||
</article>
|
||||
</div>
|
||||
<footer class="cart-total">
|
||||
<div role="status" aria-live="polite" aria-label="Totalt beløp: 299.97 kroner">
|
||||
Totalt: <span class="amount">299.97 kr</span>
|
||||
</div>
|
||||
<!-- ... -->
|
||||
</footer>
|
||||
</section>
|
||||
```
|
||||
|
||||
## ARIA Attributes Added
|
||||
|
||||
### 1. Roles
|
||||
- **`role="main"`**: Main content areas
|
||||
- **`role="list"`**: Product grids and cart items
|
||||
- **`role="listitem"`**: Individual products and cart items
|
||||
- **`role="img"`**: Emoji images with descriptions
|
||||
- **`role="status"`**: Dynamic content updates
|
||||
- **`role="application"`**: Root app container
|
||||
- **`role="alert"`**: Error messages
|
||||
|
||||
### 2. Labels
|
||||
- **`aria-label`**: Descriptive labels for interactive elements
|
||||
- **`aria-labelledby`**: References to heading elements
|
||||
- **`aria-describedby`**: Additional descriptions
|
||||
|
||||
### 3. Live Regions
|
||||
- **`aria-live="polite"`**: Non-urgent updates (cart count, totals)
|
||||
- **`aria-live="assertive"`**: Important notifications
|
||||
|
||||
### 4. Groups
|
||||
- **`role="group"`**: Quantity controls
|
||||
- **`aria-label`** on groups: "Antall Produktnavn"
|
||||
|
||||
## Keyboard Navigation
|
||||
|
||||
### 1. Focus Management
|
||||
- All interactive elements are keyboard accessible
|
||||
- Product cards have `tabindex="0"` and keyboard event handlers
|
||||
- Clear focus indicators with CSS outline styles
|
||||
|
||||
### 2. Event Handlers
|
||||
```javascript
|
||||
// Product cards support both click and keyboard activation
|
||||
onkeydown="if(event.key==='Enter'||event.key===' ') showProductDetail(${product.id})"
|
||||
```
|
||||
|
||||
## Screen Reader Support
|
||||
|
||||
### 1. Screen Reader Only Content
|
||||
```css
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Descriptive Labels
|
||||
- Product images: `aria-label="Produktbilde: [Product Name]"`
|
||||
- Prices: `aria-label="Pris: [Amount] kroner"`
|
||||
- Buttons: `aria-label="Legg [Product Name] i handlekurv"`
|
||||
- Quantity controls: `aria-label="Øk antall [Product Name]"`
|
||||
|
||||
## Dynamic Content Updates
|
||||
|
||||
### 1. Live Regions
|
||||
```html
|
||||
<!-- Cart summary updates automatically -->
|
||||
<p class="cart-summary" aria-live="polite">3 varer i handlekurven</p>
|
||||
|
||||
<!-- Total amount announces changes -->
|
||||
<div role="status" aria-live="polite" aria-label="Totalt beløp: 299.97 kroner">
|
||||
Totalt: <span class="amount">299.97 kr</span>
|
||||
</div>
|
||||
|
||||
<!-- Quantity changes announced -->
|
||||
<span class="quantity" role="status" aria-live="polite" aria-label="3 stykker">3</span>
|
||||
```
|
||||
|
||||
### 2. Loading States
|
||||
```html
|
||||
<!-- Initial loading indicator -->
|
||||
<div class="loading" role="status" aria-live="polite">Laster innhold...</div>
|
||||
|
||||
<!-- Empty cart state -->
|
||||
<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>
|
||||
```
|
||||
|
||||
## Meta Information
|
||||
|
||||
### 1. Document Meta Tags
|
||||
```html
|
||||
<meta name="description" content="En demonstrasjon av MVC-arkitektur i en nettbutikk-applikasjon">
|
||||
<html lang="no">
|
||||
```
|
||||
|
||||
### 2. Structured Pricing
|
||||
```html
|
||||
<p class="product-price" aria-label="Pris: 99.99 kroner">
|
||||
<span class="currency">kr</span> <span class="amount">99.99</span>
|
||||
</p>
|
||||
```
|
||||
|
||||
## CSS Accessibility Features
|
||||
|
||||
### 1. Focus Indicators
|
||||
```css
|
||||
button:focus,
|
||||
a:focus,
|
||||
[tabindex]:focus {
|
||||
outline: 2px solid #007bff;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Button Variants
|
||||
- **Primary buttons**: `.btn-primary` for main actions
|
||||
- **Danger buttons**: `.btn-danger` for destructive actions
|
||||
- **Remove buttons**: `.btn-remove` for cart item removal
|
||||
|
||||
## Testing Recommendations
|
||||
|
||||
### 1. Screen Reader Testing
|
||||
- Test with NVDA, JAWS, or VoiceOver
|
||||
- Verify all content is announced properly
|
||||
- Check navigation flow makes sense
|
||||
|
||||
### 2. Keyboard Navigation
|
||||
- Tab through entire interface
|
||||
- Verify all interactive elements are reachable
|
||||
- Test Enter and Space key activation
|
||||
|
||||
### 3. ARIA Validation
|
||||
- Use axe-core or similar accessibility testing tools
|
||||
- Validate ARIA attributes are used correctly
|
||||
- Check for missing or redundant labels
|
||||
|
||||
## Benefits of These Improvements
|
||||
|
||||
1. **Screen Reader Compatibility**: All content is properly announced
|
||||
2. **Keyboard Navigation**: Full keyboard accessibility without mouse
|
||||
3. **Clear Structure**: Semantic HTML provides document outline
|
||||
4. **Dynamic Updates**: Live regions announce changes
|
||||
5. **Context Awareness**: ARIA labels provide context for all interactions
|
||||
6. **Standards Compliance**: Follows WCAG 2.1 guidelines
|
||||
7. **Better SEO**: Semantic HTML improves search engine understanding
|
||||
|
||||
---
|
||||
|
||||
*These accessibility improvements ensure the webshop is usable by everyone, regardless of their abilities or the assistive technologies they use.*
|
481
webshop_app/mvc-emne2/README-NO.md
Normal file
481
webshop_app/mvc-emne2/README-NO.md
Normal file
|
@ -0,0 +1,481 @@
|
|||
# MVC Nettbutikk Applikasjon
|
||||
|
||||
En ren JavaScript-implementering av en nettbutikk som bruker Model-View-Controller (MVC) arkitekturmønsteret. Dette utdanningsprosjektet demonstrerer kjerne webutviklingskonsepter uten eksterne rammeverk.
|
||||
|
||||
## 🏗️ Arkitekturoversikt
|
||||
|
||||
Denne applikasjonen følger MVC-mønsteret med et sentralisert view-renderingssystem:
|
||||
|
||||
- **Model**: Håndterer databehandling og forretningslogikk
|
||||
- **View**: Administrerer HTML-generering og DOM-manipulering
|
||||
- **Controller**: Koordinerer brukerinteraksjoner og dataflyt
|
||||
|
||||
## 📊 UML Diagrammer
|
||||
|
||||
### Klassediagram
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Model {
|
||||
-products: Array~Product~
|
||||
-cart: Array~CartItem~
|
||||
+getAllProducts() Array~Product~
|
||||
+getProductById(id) Product
|
||||
+addToCart(productId, quantity) boolean
|
||||
+removeFromCart(productId) void
|
||||
+updateCartQuantity(productId, quantity) void
|
||||
+getCart() Array~CartItem~
|
||||
+getCartTotal() number
|
||||
+getCartItemCount() number
|
||||
+clearCart() void
|
||||
+saveCartToStorage() void
|
||||
+loadCartFromStorage() Array~CartItem~
|
||||
+initializeCart() void
|
||||
}
|
||||
|
||||
class View {
|
||||
-currentPage: string
|
||||
-cartCount: number
|
||||
+updateView(viewType, data) void
|
||||
+generateNavbar() string
|
||||
+generateProductsPage(products) string
|
||||
+generateProductDetailPage(product) string
|
||||
+generateCartPage(cartItems, total, itemCount) string
|
||||
+createProductCard(product) string
|
||||
+createCartItem(item) string
|
||||
+attachEventListeners() void
|
||||
+updateCartCountInNav() void
|
||||
+showNotification(message) void
|
||||
}
|
||||
|
||||
class Controller {
|
||||
+loadProducts() void
|
||||
+showProductDetail(productId) void
|
||||
+handleAddToCart(productId) void
|
||||
+handleRemoveFromCart(productId) void
|
||||
+changeQuantity(productId, change) void
|
||||
+handleClearCart() void
|
||||
+navigateToProducts() void
|
||||
+navigateToCart() void
|
||||
+updateCartDisplay() void
|
||||
}
|
||||
|
||||
class Product {
|
||||
+id: number
|
||||
+name: string
|
||||
+price: number
|
||||
+description: string
|
||||
+image: string
|
||||
+category: string
|
||||
}
|
||||
|
||||
class CartItem {
|
||||
+productId: number
|
||||
+quantity: number
|
||||
+product: Product
|
||||
}
|
||||
|
||||
class App {
|
||||
+initialize() void
|
||||
}
|
||||
|
||||
Controller --> Model : bruker
|
||||
Controller --> View : oppdaterer
|
||||
View --> Controller : sender hendelser
|
||||
Model --> Product : inneholder
|
||||
Model --> CartItem : administrerer
|
||||
CartItem --> Product : refererer til
|
||||
App --> Controller : initialiserer
|
||||
App --> Model : setter opp
|
||||
App --> View : starter
|
||||
```
|
||||
|
||||
### Sekvensdiagram - Legg til i handlekurv
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as Bruker
|
||||
participant V as View
|
||||
participant C as Controller
|
||||
participant M as Model
|
||||
|
||||
U->>V: Klikker "Legg i handlekurv"
|
||||
V->>C: handleAddToCart(productId)
|
||||
C->>M: addToCart(productId)
|
||||
M->>M: getProductById(productId)
|
||||
M->>M: Finn eksisterende element eller lag nytt
|
||||
M->>M: saveCartToStorage()
|
||||
M-->>C: return true/false
|
||||
|
||||
alt Vellykket
|
||||
C->>M: getProductById(productId)
|
||||
M-->>C: return product
|
||||
C->>V: updateView('notification', {message})
|
||||
C->>C: updateCartDisplay()
|
||||
C->>M: getCart()
|
||||
M-->>C: return cart
|
||||
C->>M: getCartTotal()
|
||||
M-->>C: return total
|
||||
C->>M: getCartItemCount()
|
||||
M-->>C: return itemCount
|
||||
C->>V: updateView('cart', {cartItems, total, itemCount})
|
||||
C->>V: updateView('cart-count', {count})
|
||||
V->>V: generateCartPage() eller updateCartCountInNav()
|
||||
V-->>U: Oppdatert UI med notifikasjon
|
||||
else Feilet
|
||||
C->>V: updateView('notification', {message: 'Feil'})
|
||||
V-->>U: Feilmelding
|
||||
end
|
||||
```
|
||||
|
||||
## 📁 Prosjektstruktur
|
||||
|
||||
```
|
||||
mvc-emne2/
|
||||
├── index.html # Hoved HTML-fil med enkelt app-container
|
||||
├── styles.css # Applikasjonsstyling
|
||||
├── js/
|
||||
│ ├── app.js # Applikasjonsinitialisering
|
||||
│ ├── model.js # Datalag og forretningslogikk
|
||||
│ ├── view.js # UI-rendering og HTML-generering
|
||||
│ └── controller.js # Brukerinteraksjonshåndtering
|
||||
├── flow-diagram.md # Applikasjonsflytvisualisering
|
||||
├── README.md # Engelsk dokumentasjon
|
||||
├── README-NO.md # Norsk dokumentasjon (denne filen)
|
||||
└── ACCESSIBILITY.md # Tilgjengelighetsdokumentasjon
|
||||
```
|
||||
|
||||
## 🔧 Nøkkelkomponenter
|
||||
|
||||
### 1. Model (`model.js`)
|
||||
|
||||
**Formål**: Administrerer applikasjonsdata og persistering
|
||||
|
||||
**Nøkkelfunksjoner**:
|
||||
- `getAllProducts()` - Returnerer produktkatalog
|
||||
- `getProductById(id)` - Henter spesifikt produkt
|
||||
- `addToCart(productId, quantity)` - Legger til varer i handlekurv
|
||||
- `removeFromCart(productId)` - Fjerner varer fra handlekurv
|
||||
- `updateCartQuantity(productId, quantity)` - Oppdaterer vareantall
|
||||
- `getCart()` - Returnerer gjeldende handlekurvinnhold
|
||||
- `getCartTotal()` - Beregner total handlekurvpris
|
||||
- `getCartItemCount()` - Returnerer totalt antall varer
|
||||
- `clearCart()` - Tømmer handlekurven
|
||||
- `saveCartToStorage()` / `loadCartFromStorage()` - LocalStorage-persistering
|
||||
|
||||
**Datastruktur**:
|
||||
```javascript
|
||||
// Produkt
|
||||
{
|
||||
id: number,
|
||||
name: string,
|
||||
price: number,
|
||||
description: string,
|
||||
image: string (emoji),
|
||||
category: string
|
||||
}
|
||||
|
||||
// Handlekurvvare
|
||||
{
|
||||
productId: number,
|
||||
quantity: number,
|
||||
product: Product
|
||||
}
|
||||
```
|
||||
|
||||
### 2. View (`view.js`)
|
||||
|
||||
**Formål**: Håndterer all UI-rendering gjennom en sentralisert `updateView`-funksjon
|
||||
|
||||
**Kjernearkitektur**:
|
||||
```javascript
|
||||
function updateView(viewType, data = {}) {
|
||||
switch (viewType) {
|
||||
case 'products': // Rendrer produktliste
|
||||
case 'product-detail': // Rendrer enkelt produkt
|
||||
case 'cart': // Rendrer handlekurv
|
||||
case 'cart-count': // Oppdater handlekurvmerke
|
||||
case 'notification': // Vis brukermeldinger
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Nøkkelfunksjoner**:
|
||||
- **Enkelt inngangspunkt**: All rendering går gjennom `updateView()`
|
||||
- **Komplett sidegenerering**: Genererer hele side-HTML inkludert navigasjon
|
||||
- **Event Listener-administrasjon**: Fester automatisk event listeners etter hver rendering
|
||||
- **Mallfunksjoner**: Modulære HTML-genereringsfunksjoner
|
||||
|
||||
**HTML-genereringsfunksjoner**:
|
||||
- `generateNavbar()` - Lager navigasjonsheader
|
||||
- `generateProductsPage(products)` - Produktlisteside
|
||||
- `generateProductDetailPage(product)` - Produktdetaljside
|
||||
- `generateCartPage(cartItems, total, itemCount)` - Handlekurvside
|
||||
- `createProductCard(product)` - Individuelt produktkort
|
||||
- `createCartItem(item)` - Individuell handlekurvvare
|
||||
|
||||
### 3. Controller (`controller.js`)
|
||||
|
||||
**Formål**: Håndterer brukerinteraksjoner og koordinerer mellom Model og View
|
||||
|
||||
**Navigasjonsfunksjoner**:
|
||||
- `navigateToProducts()` - Viser produktliste
|
||||
- `navigateToCart()` - Viser handlekurv
|
||||
- `showProductDetail(productId)` - Viser produktdetaljer
|
||||
|
||||
**Handlekurvadministrasjon**:
|
||||
- `handleAddToCart(productId)` - Legger til produkt i handlekurv
|
||||
- `handleRemoveFromCart(productId)` - Fjerner produkt fra handlekurv
|
||||
- `changeQuantity(productId, change)` - Oppdaterer produktantall
|
||||
- `handleClearCart()` - Tømmer hele handlekurven
|
||||
- `updateCartDisplay()` - Oppdaterer handlekurvvisning og teller
|
||||
|
||||
**Nøkkeldesignmønstre**:
|
||||
- Alle funksjoner kaller `updateView()` for UI-oppdateringer
|
||||
- Brukernotifikasjoner håndteres konsistent
|
||||
- Handlekurvtilstand persisteres automatisk etter endringer
|
||||
|
||||
### 4. Applikasjonsinitialisering (`app.js`)
|
||||
|
||||
**Formål**: Bootstrapper applikasjonen
|
||||
|
||||
**Initialiseringsflyt**:
|
||||
1. Vent på at DOM skal laste
|
||||
2. Initialiser handlekurv fra localStorage
|
||||
3. Last produkter fra model
|
||||
4. Renderer initial produktside
|
||||
5. Oppdater handlekurvteller
|
||||
|
||||
```javascript
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Fjern lastingstilstand
|
||||
const loadingElement = document.querySelector('.loading');
|
||||
if (loadingElement) {
|
||||
loadingElement.remove();
|
||||
}
|
||||
|
||||
initializeCart();
|
||||
|
||||
const products = getAllProducts();
|
||||
const itemCount = getCartItemCount();
|
||||
|
||||
updateView('products', { products });
|
||||
updateView('cart-count', { count: itemCount });
|
||||
|
||||
console.log('Nettbutikk MVC Demo initialisert!');
|
||||
});
|
||||
```
|
||||
|
||||
## 🔄 Dataflyt
|
||||
|
||||
### 1. Applikasjonsoppstart
|
||||
```
|
||||
HTML-lasting → DOMContentLoaded → initializeCart() → loadCartFromStorage() →
|
||||
getAllProducts() → updateView('products') → generateProductsPage() → attachEventListeners()
|
||||
```
|
||||
|
||||
### 2. Brukerinteraksjoner
|
||||
```
|
||||
Brukerklikk → Controller-funksjon → Model-operasjon → updateView() →
|
||||
HTML-generering → Event Listener-tilkobling → UI-oppdatering
|
||||
```
|
||||
|
||||
### 3. Handlekurvoperasjoner
|
||||
```
|
||||
Legg til i handlekurv → addToCart() → saveCartToStorage() → updateCartDisplay() →
|
||||
updateView('cart') + updateView('cart-count') → showNotification()
|
||||
```
|
||||
|
||||
## 🎨 View-renderingssystem
|
||||
|
||||
### Sentralisert rendering
|
||||
Alle UI-oppdateringer flyter gjennom den enkelte `updateView()`-funksjonen:
|
||||
|
||||
```javascript
|
||||
updateView('products', { products: [...] });
|
||||
updateView('product-detail', { product: {...} });
|
||||
updateView('cart', { cartItems: [...], total: 0, itemCount: 0 });
|
||||
updateView('cart-count', { count: 0 });
|
||||
updateView('notification', { message: 'Vellykket!' });
|
||||
```
|
||||
|
||||
### Komplett sidegenerering
|
||||
I motsetning til tradisjonell DOM-manipulering genererer dette systemet komplett side-HTML:
|
||||
|
||||
```javascript
|
||||
function generateProductsPage(products) {
|
||||
return `
|
||||
${generateNavbar()}
|
||||
<main>
|
||||
<section class="page active">
|
||||
<h2>Våre Produkter</h2>
|
||||
<div class="products-grid">
|
||||
${products.map(product => createProductCard(product)).join('')}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
`;
|
||||
}
|
||||
```
|
||||
|
||||
### Event Listener-administrasjon
|
||||
Etter hver rendering festes event listeners på nytt:
|
||||
|
||||
```javascript
|
||||
function attachEventListeners() {
|
||||
const productsLink = document.querySelector('[data-page="products"]');
|
||||
const cartLink = document.querySelector('[data-page="cart"]');
|
||||
|
||||
if (productsLink) {
|
||||
productsLink.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
navigateToProducts();
|
||||
});
|
||||
}
|
||||
// ... flere listeners
|
||||
}
|
||||
```
|
||||
|
||||
## 💾 Datapersistering
|
||||
|
||||
### LocalStorage-integrasjon
|
||||
Handlekurvdata persisteres automatisk:
|
||||
|
||||
```javascript
|
||||
function saveCartToStorage() {
|
||||
try {
|
||||
localStorage.setItem('webstore_cart', JSON.stringify(cart));
|
||||
} catch (error) {
|
||||
console.error('Feil ved lagring av handlekurv til localStorage:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function loadCartFromStorage() {
|
||||
try {
|
||||
const savedCart = localStorage.getItem('webstore_cart');
|
||||
if (savedCart) {
|
||||
const parsedCart = JSON.parse(savedCart);
|
||||
return parsedCart.map(item => ({
|
||||
...item,
|
||||
product: products.find(p => p.id === item.productId) ||
|
||||
{ id: item.productId, name: 'Ukjent Produkt', price: 0 }
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Feil ved lasting av handlekurv fra localStorage:', error);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
```
|
||||
|
||||
## 🚀 Bruk
|
||||
|
||||
### Kjøring av applikasjonen
|
||||
1. Åpne `index.html` i en nettleser
|
||||
2. Applikasjonen initialiseres automatisk
|
||||
3. Naviger mellom produkter, produktdetaljer og handlekurv
|
||||
4. Handlekurvtilstand persisteres på tvers av nettlesersesjoner
|
||||
|
||||
### Nøkkelfunksjoner
|
||||
- **Produktbrowsing**: Vis alle produkter i rutenettlayout
|
||||
- **Produktdetaljer**: Klikk på hvilket som helst produkt for detaljert visning
|
||||
- **Handlekurv**: Legg til, fjern og modifiser antall
|
||||
- **Persistent handlekurv**: Handlekurvinnhold lagret i localStorage
|
||||
- **Responsiv design**: Fungerer på desktop og mobil
|
||||
- **Brukernotifikasjoner**: Tilbakemelding for alle brukerhandlinger
|
||||
|
||||
## ♿ Tilgjengelighet og semantisk HTML
|
||||
|
||||
Applikasjonen er bygget med tilgjengelighet i fokus:
|
||||
|
||||
### Semantiske HTML-elementer
|
||||
- **`<main>`**, **`<header>`**, **`<footer>`**, **`<nav>`**, **`<section>`**, **`<article>`**
|
||||
- Riktig heading-hierarki (`<h1>`, `<h2>`, `<h3>`)
|
||||
- Beskrivende ARIA-attributter
|
||||
|
||||
### ARIA-funksjoner
|
||||
- **`role`**-attributter for klargjøring av elementfunksjoner
|
||||
- **`aria-label`** for beskrivende etiketter
|
||||
- **`aria-live`** for dynamiske innholdsoppdateringer
|
||||
- **`aria-describedby`** for tilleggsinformasjon
|
||||
|
||||
### Tastaturnavigasjon
|
||||
- Alle interaktive elementer er tilgjengelige via tastatur
|
||||
- Fokusindikatorer for alle interaktive elementer
|
||||
- Logisk tab-rekkefølge
|
||||
|
||||
## 🎯 Utdanningsmessige fordeler
|
||||
|
||||
### MVC-mønsterdemonstrasjon
|
||||
- Tydelig separasjon av ansvar
|
||||
- Model håndterer data, View håndterer presentasjon, Controller håndterer logikk
|
||||
- Demonstrerer hvordan komponenter samhandler i MVC-arkitektur
|
||||
|
||||
### Moderne JavaScript-funksjoner
|
||||
- ES6+-syntaks (arrow-funksjoner, template literals, destructuring)
|
||||
- LocalStorage API-bruk
|
||||
- Event handling-mønstre
|
||||
- Funksjonelle programmeringskonsepter
|
||||
|
||||
### Webutviklingsfundamentaler
|
||||
- DOM-manipuleringsteknikker
|
||||
- Event listener-administrasjon
|
||||
- Klientsidehåndtering av tilstand
|
||||
- HTML-generering og templating
|
||||
- CSS-stylingintegrasjon
|
||||
|
||||
## 🔧 Tekniske beslutninger
|
||||
|
||||
### Hvorfor sentralisert rendering?
|
||||
- **Konsistens**: Alle UI-oppdateringer følger samme mønster
|
||||
- **Vedlikeholdbarhet**: Lett å spore og debugge UI-endringer
|
||||
- **Skalerbarhet**: Enkelt å legge til nye visningstyper
|
||||
- **Tilstandshåndtering**: Klar dataflyt fra model til view
|
||||
|
||||
### Hvorfor komplett sidegenerering?
|
||||
- **Enkelhet**: Ingen kompleks DOM-diffing eller tilstandssporing
|
||||
- **Pålitelighet**: Fresh render eliminerer foreldede tilstandsproblemer
|
||||
- **Ytelse**: Moderne nettlesere håndterer innerHTML effektivt
|
||||
- **Debugging**: Lett å se nøyaktig hvilken HTML som genereres
|
||||
|
||||
### Hvorfor LocalStorage?
|
||||
- **Persistering**: Handlekurv overlever nettleseroppstart
|
||||
- **Enkelhet**: Ingen serversidekompleksitet
|
||||
- **Ytelse**: Øyeblikkelige lastetider
|
||||
- **Utdanningsmessig**: Demonstrerer klientsidelagringskonsepter
|
||||
|
||||
## 🎨 Stylingarkitektur
|
||||
|
||||
Applikasjonen bruker et rent, moderne CSS-design:
|
||||
- Responsive rutenettlayouter
|
||||
- Kortbasert produktvisning
|
||||
- Konsistent knapp- og formstiling
|
||||
- Mobilvennlig navigasjon
|
||||
- Glatte overganger og hover-effekter
|
||||
|
||||
## 🔮 Fremtidige forbedringer
|
||||
|
||||
Denne MVC-implementeringen fungerer som et grunnlag for progressiv forbedring:
|
||||
|
||||
1. **Fase 2**: Legg til TypeScript for typesikkerhet
|
||||
2. **Fase 3**: Implementer Vue.js for reaktiv UI
|
||||
3. **Fase 4**: Legg til brukerautentisering
|
||||
4. **Fase 5**: Integrer betalingsbehandling
|
||||
5. **Fase 6**: Legg til produktsøk og filtrering
|
||||
6. **Fase 7**: Implementer ekte backend-API
|
||||
|
||||
## 📚 Læringsutbytte
|
||||
|
||||
Ved å studere denne kodebasen vil du forstå:
|
||||
- MVC-arkitekturmønsterimplementering
|
||||
- Klientsidehåndtering av tilstand
|
||||
- Moderne JavaScript-utviklingspraksis
|
||||
- HTML-generering og templating-tilnærminger
|
||||
- Hendelsesdrevne programmeringsmønstre
|
||||
- LocalStorage API-bruk
|
||||
- Responsive webdesignprinsipper
|
||||
- Tilgjengelighetsstandarder og semantisk HTML
|
||||
|
||||
---
|
||||
|
||||
*Dette prosjektet er en del av GET Frontend-utviklingslæreplanen og demonstrerer kjerne webutviklingskonsepter gjennom praktisk implementering.*
|
452
webshop_app/mvc-emne2/README.md
Normal file
452
webshop_app/mvc-emne2/README.md
Normal file
|
@ -0,0 +1,452 @@
|
|||
# MVC Webshop Application
|
||||
|
||||
A pure JavaScript implementation of a webshop using the Model-View-Controller (MVC) architectural pattern. This educational project demonstrates core web development concepts without external frameworks.
|
||||
|
||||
## 🏗️ Architecture Overview
|
||||
|
||||
This application follows the MVC pattern with a centralized view rendering system:
|
||||
|
||||
- **Model**: Handles data management and business logic
|
||||
- **View**: Manages HTML generation and DOM manipulation
|
||||
- **Controller**: Coordinates user interactions and data flow
|
||||
|
||||
## 📊 UML Diagrams
|
||||
|
||||
### Class Diagram
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Model {
|
||||
-products: Array~Product~
|
||||
-cart: Array~CartItem~
|
||||
+getAllProducts() Array~Product~
|
||||
+getProductById(id) Product
|
||||
+addToCart(productId, quantity) boolean
|
||||
+removeFromCart(productId) void
|
||||
+updateCartQuantity(productId, quantity) void
|
||||
+getCart() Array~CartItem~
|
||||
+getCartTotal() number
|
||||
+getCartItemCount() number
|
||||
+clearCart() void
|
||||
+saveCartToStorage() void
|
||||
+loadCartFromStorage() Array~CartItem~
|
||||
+initializeCart() void
|
||||
}
|
||||
|
||||
class View {
|
||||
-currentPage: string
|
||||
-cartCount: number
|
||||
+updateView(viewType, data) void
|
||||
+generateNavbar() string
|
||||
+generateProductsPage(products) string
|
||||
+generateProductDetailPage(product) string
|
||||
+generateCartPage(cartItems, total, itemCount) string
|
||||
+createProductCard(product) string
|
||||
+createCartItem(item) string
|
||||
+attachEventListeners() void
|
||||
+updateCartCountInNav() void
|
||||
+showNotification(message) void
|
||||
}
|
||||
|
||||
class Controller {
|
||||
+loadProducts() void
|
||||
+showProductDetail(productId) void
|
||||
+handleAddToCart(productId) void
|
||||
+handleRemoveFromCart(productId) void
|
||||
+changeQuantity(productId, change) void
|
||||
+handleClearCart() void
|
||||
+navigateToProducts() void
|
||||
+navigateToCart() void
|
||||
+updateCartDisplay() void
|
||||
}
|
||||
|
||||
class Product {
|
||||
+id: number
|
||||
+name: string
|
||||
+price: number
|
||||
+description: string
|
||||
+image: string
|
||||
+category: string
|
||||
}
|
||||
|
||||
class CartItem {
|
||||
+productId: number
|
||||
+quantity: number
|
||||
+product: Product
|
||||
}
|
||||
|
||||
class App {
|
||||
+initialize() void
|
||||
}
|
||||
|
||||
Controller --> Model : uses
|
||||
Controller --> View : updates
|
||||
View --> Controller : sends events
|
||||
Model --> Product : contains
|
||||
Model --> CartItem : manages
|
||||
CartItem --> Product : references
|
||||
App --> Controller : initializes
|
||||
App --> Model : sets up
|
||||
App --> View : starts
|
||||
```
|
||||
|
||||
### Sequence Diagram - Add to Cart Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as User
|
||||
participant V as View
|
||||
participant C as Controller
|
||||
participant M as Model
|
||||
|
||||
U->>V: Click "Add to Cart"
|
||||
V->>C: handleAddToCart(productId)
|
||||
C->>M: addToCart(productId)
|
||||
M->>M: getProductById(productId)
|
||||
M->>M: Find existing item or create new
|
||||
M->>M: saveCartToStorage()
|
||||
M-->>C: return true/false
|
||||
|
||||
alt Success
|
||||
C->>M: getProductById(productId)
|
||||
M-->>C: return product
|
||||
C->>V: updateView('notification', {message})
|
||||
C->>C: updateCartDisplay()
|
||||
C->>M: getCart()
|
||||
M-->>C: return cart
|
||||
C->>M: getCartTotal()
|
||||
M-->>C: return total
|
||||
C->>M: getCartItemCount()
|
||||
M-->>C: return itemCount
|
||||
C->>V: updateView('cart', {cartItems, total, itemCount})
|
||||
C->>V: updateView('cart-count', {count})
|
||||
V->>V: generateCartPage() or updateCartCountInNav()
|
||||
V-->>U: Updated UI with notification
|
||||
else Failure
|
||||
C->>V: updateView('notification', {message: 'Error'})
|
||||
V-->>U: Error message
|
||||
end
|
||||
```
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
mvc-emne2/
|
||||
├── index.html # Main HTML file with single app container
|
||||
├── styles.css # Application styling
|
||||
├── js/
|
||||
│ ├── app.js # Application initialization
|
||||
│ ├── model.js # Data layer and business logic
|
||||
│ ├── view.js # UI rendering and HTML generation
|
||||
│ └── controller.js # User interaction handling
|
||||
├── flow-diagram.md # Application flow visualization
|
||||
└── README.md # This documentation
|
||||
```
|
||||
|
||||
## 🔧 Key Components
|
||||
|
||||
### 1. Model (`model.js`)
|
||||
|
||||
**Purpose**: Manages application data and persistence
|
||||
|
||||
**Key Functions**:
|
||||
- `getAllProducts()` - Returns product catalog
|
||||
- `getProductById(id)` - Retrieves specific product
|
||||
- `addToCart(productId, quantity)` - Adds items to cart
|
||||
- `removeFromCart(productId)` - Removes items from cart
|
||||
- `updateCartQuantity(productId, quantity)` - Updates item quantities
|
||||
- `getCart()` - Returns current cart contents
|
||||
- `getCartTotal()` - Calculates cart total price
|
||||
- `getCartItemCount()` - Returns total item count
|
||||
- `clearCart()` - Empties the cart
|
||||
- `saveCartToStorage()` / `loadCartFromStorage()` - LocalStorage persistence
|
||||
|
||||
**Data Structure**:
|
||||
```javascript
|
||||
// Product
|
||||
{
|
||||
id: number,
|
||||
name: string,
|
||||
price: number,
|
||||
description: string,
|
||||
image: string (emoji),
|
||||
category: string
|
||||
}
|
||||
|
||||
// Cart Item
|
||||
{
|
||||
productId: number,
|
||||
quantity: number,
|
||||
product: Product
|
||||
}
|
||||
```
|
||||
|
||||
### 2. View (`view.js`)
|
||||
|
||||
**Purpose**: Handles all UI rendering through a centralized `updateView` function
|
||||
|
||||
**Core Architecture**:
|
||||
```javascript
|
||||
function updateView(viewType, data = {}) {
|
||||
switch (viewType) {
|
||||
case 'products': // Render product listing
|
||||
case 'product-detail': // Render single product
|
||||
case 'cart': // Render shopping cart
|
||||
case 'cart-count': // Update cart badge
|
||||
case 'notification': // Show user messages
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Features**:
|
||||
- **Single Entry Point**: All rendering goes through `updateView()`
|
||||
- **Complete Page Generation**: Generates entire page HTML including navigation
|
||||
- **Event Listener Management**: Automatically attaches event listeners after each render
|
||||
- **Template Functions**: Modular HTML generation functions
|
||||
|
||||
**HTML Generation Functions**:
|
||||
- `generateNavbar()` - Creates navigation header
|
||||
- `generateProductsPage(products)` - Product listing page
|
||||
- `generateProductDetailPage(product)` - Product detail page
|
||||
- `generateCartPage(cartItems, total, itemCount)` - Shopping cart page
|
||||
- `createProductCard(product)` - Individual product card
|
||||
- `createCartItem(item)` - Individual cart item
|
||||
|
||||
### 3. Controller (`controller.js`)
|
||||
|
||||
**Purpose**: Handles user interactions and coordinates between Model and View
|
||||
|
||||
**Navigation Functions**:
|
||||
- `navigateToProducts()` - Shows product listing
|
||||
- `navigateToCart()` - Shows shopping cart
|
||||
- `showProductDetail(productId)` - Shows product details
|
||||
|
||||
**Cart Management**:
|
||||
- `handleAddToCart(productId)` - Adds product to cart
|
||||
- `handleRemoveFromCart(productId)` - Removes product from cart
|
||||
- `changeQuantity(productId, change)` - Updates product quantity
|
||||
- `handleClearCart()` - Empties entire cart
|
||||
- `updateCartDisplay()` - Refreshes cart view and counter
|
||||
|
||||
**Key Design Patterns**:
|
||||
- All functions call `updateView()` for UI updates
|
||||
- User notifications are handled consistently
|
||||
- Cart state is automatically persisted after changes
|
||||
|
||||
### 4. Application Initialization (`app.js`)
|
||||
|
||||
**Purpose**: Bootstraps the application
|
||||
|
||||
**Initialization Flow**:
|
||||
1. Wait for DOM to load
|
||||
2. Initialize cart from localStorage
|
||||
3. Load products from model
|
||||
4. Render initial products page
|
||||
5. Update cart counter
|
||||
|
||||
```javascript
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initializeCart();
|
||||
|
||||
const products = getAllProducts();
|
||||
const itemCount = getCartItemCount();
|
||||
|
||||
updateView('products', { products });
|
||||
updateView('cart-count', { count: itemCount });
|
||||
|
||||
console.log('Nettbutikk MVC Demo initialisert!');
|
||||
});
|
||||
```
|
||||
|
||||
## 🔄 Data Flow
|
||||
|
||||
### 1. Application Startup
|
||||
```
|
||||
HTML Load → DOMContentLoaded → initializeCart() → loadCartFromStorage() →
|
||||
getAllProducts() → updateView('products') → generateProductsPage() → attachEventListeners()
|
||||
```
|
||||
|
||||
### 2. User Interactions
|
||||
```
|
||||
User Click → Controller Function → Model Operation → updateView() →
|
||||
HTML Generation → Event Listener Attachment → UI Update
|
||||
```
|
||||
|
||||
### 3. Cart Operations
|
||||
```
|
||||
Add to Cart → addToCart() → saveCartToStorage() → updateCartDisplay() →
|
||||
updateView('cart') + updateView('cart-count') → showNotification()
|
||||
```
|
||||
|
||||
## 🎨 View Rendering System
|
||||
|
||||
### Centralized Rendering
|
||||
All UI updates flow through the single `updateView()` function:
|
||||
|
||||
```javascript
|
||||
updateView('products', { products: [...] });
|
||||
updateView('product-detail', { product: {...} });
|
||||
updateView('cart', { cartItems: [...], total: 0, itemCount: 0 });
|
||||
updateView('cart-count', { count: 0 });
|
||||
updateView('notification', { message: 'Success!' });
|
||||
```
|
||||
|
||||
### Complete Page Generation
|
||||
Unlike traditional DOM manipulation, this system generates complete page HTML:
|
||||
|
||||
```javascript
|
||||
function generateProductsPage(products) {
|
||||
return `
|
||||
${generateNavbar()}
|
||||
<main>
|
||||
<section class="page active">
|
||||
<h2>Våre Produkter</h2>
|
||||
<div class="products-grid">
|
||||
${products.map(product => createProductCard(product)).join('')}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
`;
|
||||
}
|
||||
```
|
||||
|
||||
### Event Listener Management
|
||||
After each render, event listeners are reattached:
|
||||
|
||||
```javascript
|
||||
function attachEventListeners() {
|
||||
const productsLink = document.querySelector('[data-page="products"]');
|
||||
const cartLink = document.querySelector('[data-page="cart"]');
|
||||
|
||||
if (productsLink) {
|
||||
productsLink.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
navigateToProducts();
|
||||
});
|
||||
}
|
||||
// ... more listeners
|
||||
}
|
||||
```
|
||||
|
||||
## 💾 Data Persistence
|
||||
|
||||
### LocalStorage Integration
|
||||
Cart data is automatically persisted:
|
||||
|
||||
```javascript
|
||||
function saveCartToStorage() {
|
||||
try {
|
||||
localStorage.setItem('webstore_cart', JSON.stringify(cart));
|
||||
} catch (error) {
|
||||
console.error('Feil ved lagring av handlekurv til localStorage:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function loadCartFromStorage() {
|
||||
try {
|
||||
const savedCart = localStorage.getItem('webstore_cart');
|
||||
if (savedCart) {
|
||||
const parsedCart = JSON.parse(savedCart);
|
||||
return parsedCart.map(item => ({
|
||||
...item,
|
||||
product: products.find(p => p.id === item.productId) ||
|
||||
{ id: item.productId, name: 'Ukjent Produkt', price: 0 }
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Feil ved lasting av handlekurv fra localStorage:', error);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
```
|
||||
|
||||
## 🚀 Usage
|
||||
|
||||
### Running the Application
|
||||
1. Open `index.html` in a web browser
|
||||
2. The application initializes automatically
|
||||
3. Navigate between products, product details, and cart
|
||||
4. Cart state persists across browser sessions
|
||||
|
||||
### Key Features
|
||||
- **Product Browsing**: View all products in a grid layout
|
||||
- **Product Details**: Click any product for detailed view
|
||||
- **Shopping Cart**: Add, remove, and modify quantities
|
||||
- **Persistent Cart**: Cart contents saved in localStorage
|
||||
- **Responsive Design**: Works on desktop and mobile
|
||||
- **User Notifications**: Feedback for all user actions
|
||||
|
||||
## 🎯 Educational Benefits
|
||||
|
||||
### MVC Pattern Demonstration
|
||||
- Clear separation of concerns
|
||||
- Model handles data, View handles presentation, Controller handles logic
|
||||
- Demonstrates how components interact in MVC architecture
|
||||
|
||||
### Modern JavaScript Features
|
||||
- ES6+ syntax (arrow functions, template literals, destructuring)
|
||||
- LocalStorage API usage
|
||||
- Event handling patterns
|
||||
- Functional programming concepts
|
||||
|
||||
### Web Development Fundamentals
|
||||
- DOM manipulation techniques
|
||||
- Event listener management
|
||||
- Client-side state management
|
||||
- HTML generation and templating
|
||||
- CSS styling integration
|
||||
|
||||
## 🔧 Technical Decisions
|
||||
|
||||
### Why Centralized Rendering?
|
||||
- **Consistency**: All UI updates follow the same pattern
|
||||
- **Maintainability**: Easy to track and debug UI changes
|
||||
- **Scalability**: Simple to add new view types
|
||||
- **State Management**: Clear data flow from model to view
|
||||
|
||||
### Why Complete Page Generation?
|
||||
- **Simplicity**: No complex DOM diffing or state tracking
|
||||
- **Reliability**: Fresh render eliminates stale state issues
|
||||
- **Performance**: Modern browsers handle innerHTML efficiently
|
||||
- **Debugging**: Easy to see exactly what HTML is generated
|
||||
|
||||
### Why LocalStorage?
|
||||
- **Persistence**: Cart survives browser restarts
|
||||
- **Simplicity**: No server-side complexity
|
||||
- **Performance**: Instant load times
|
||||
- **Educational**: Demonstrates client-side storage concepts
|
||||
|
||||
## 🎨 Styling Architecture
|
||||
|
||||
The application uses a clean, modern CSS design:
|
||||
- Responsive grid layouts
|
||||
- Card-based product display
|
||||
- Consistent button and form styling
|
||||
- Mobile-friendly navigation
|
||||
- Smooth transitions and hover effects
|
||||
|
||||
## 🔮 Future Enhancements
|
||||
|
||||
This MVC implementation serves as a foundation for progressive enhancement:
|
||||
|
||||
1. **Phase 2**: Add TypeScript for type safety
|
||||
2. **Phase 3**: Implement Vue.js for reactive UI
|
||||
3. **Phase 4**: Add user authentication
|
||||
4. **Phase 5**: Integrate payment processing
|
||||
5. **Phase 6**: Add product search and filtering
|
||||
6. **Phase 7**: Implement real backend API
|
||||
|
||||
## 📚 Learning Outcomes
|
||||
|
||||
By studying this codebase, you'll understand:
|
||||
- MVC architectural pattern implementation
|
||||
- Client-side state management techniques
|
||||
- Modern JavaScript development practices
|
||||
- HTML generation and templating approaches
|
||||
- Event-driven programming patterns
|
||||
- LocalStorage API usage
|
||||
- Responsive web design principles
|
||||
|
||||
---
|
||||
|
||||
*This project is part of the GET Frontend Development curriculum, demonstrating core web development concepts through practical implementation.*
|
105
webshop_app/mvc-emne2/flow-diagram.md
Normal file
105
webshop_app/mvc-emne2/flow-diagram.md
Normal file
|
@ -0,0 +1,105 @@
|
|||
# MVC Webshop Application Flow Diagram
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[App Start] --> B[DOMContentLoaded Event]
|
||||
B --> C[initializeCart()]
|
||||
C --> D[loadCartFromStorage()]
|
||||
D --> E[getAllProducts()]
|
||||
E --> F[updateView('products', products)]
|
||||
F --> G[generateProductsPage()]
|
||||
G --> H[attachEventListeners()]
|
||||
|
||||
H --> I{User Action}
|
||||
|
||||
I -->|Click Product| J[showProductDetail(id)]
|
||||
I -->|Add to Cart| K[handleAddToCart(id)]
|
||||
I -->|Navigate to Cart| L[navigateToCart()]
|
||||
I -->|Navigate to Products| M[navigateToProducts()]
|
||||
|
||||
J --> N[getProductById(id)]
|
||||
N --> O[updateView('product-detail', product)]
|
||||
O --> P[generateProductDetailPage()]
|
||||
P --> Q[attachEventListeners()]
|
||||
Q --> I
|
||||
|
||||
K --> R[addToCart(id)]
|
||||
R --> S[saveCartToStorage()]
|
||||
S --> T[updateCartDisplay()]
|
||||
T --> U[updateView('cart', cartData)]
|
||||
U --> V[updateView('cart-count', count)]
|
||||
V --> W[showNotification()]
|
||||
W --> I
|
||||
|
||||
L --> X[getCart() + getCartTotal() + getCartItemCount()]
|
||||
X --> Y[updateView('cart', cartData)]
|
||||
Y --> Z[generateCartPage()]
|
||||
Z --> AA[attachEventListeners()]
|
||||
AA --> I
|
||||
|
||||
M --> BB[getAllProducts()]
|
||||
BB --> CC[updateView('products', products)]
|
||||
CC --> DD[generateProductsPage()]
|
||||
DD --> EE[attachEventListeners()]
|
||||
EE --> I
|
||||
|
||||
I -->|Change Quantity| FF[changeQuantity(id, change)]
|
||||
FF --> GG[updateCartQuantity(id, newQty)]
|
||||
GG --> HH[saveCartToStorage()]
|
||||
HH --> T
|
||||
|
||||
I -->|Remove from Cart| II[handleRemoveFromCart(id)]
|
||||
II --> JJ[removeFromCart(id)]
|
||||
JJ --> KK[saveCartToStorage()]
|
||||
KK --> T
|
||||
|
||||
I -->|Clear Cart| LL[handleClearCart()]
|
||||
LL --> MM{Confirm?}
|
||||
MM -->|Yes| NN[clearCart()]
|
||||
MM -->|No| I
|
||||
NN --> OO[saveCartToStorage()]
|
||||
OO --> T
|
||||
|
||||
style A fill:#e1f5fe
|
||||
style F fill:#f3e5f5
|
||||
style O fill:#f3e5f5
|
||||
style U fill:#f3e5f5
|
||||
style Y fill:#f3e5f5
|
||||
style CC fill:#f3e5f5
|
||||
|
||||
classDef modelClass fill:#e8f5e8
|
||||
classDef viewClass fill:#fff3e0
|
||||
classDef controllerClass fill:#fce4ec
|
||||
|
||||
class C,D,E,N,R,S,X,BB,GG,HH,JJ,KK,NN,OO modelClass
|
||||
class F,G,O,P,U,Y,Z,CC,DD viewClass
|
||||
class J,K,L,M,FF,II,LL,T controllerClass
|
||||
```
|
||||
|
||||
## Component Responsibilities
|
||||
|
||||
### Model (Green)
|
||||
- Data storage and retrieval
|
||||
- Cart management
|
||||
- LocalStorage operations
|
||||
- Product data
|
||||
|
||||
### View (Orange)
|
||||
- HTML generation
|
||||
- DOM manipulation
|
||||
- Event listener attachment
|
||||
- User interface rendering
|
||||
|
||||
### Controller (Pink)
|
||||
- User interaction handling
|
||||
- Business logic
|
||||
- Coordination between Model and View
|
||||
- Navigation management
|
||||
|
||||
## Data Flow Summary
|
||||
|
||||
1. **Initialization**: App loads, initializes cart from localStorage, renders products
|
||||
2. **User Interactions**: Click events trigger controller functions
|
||||
3. **Model Updates**: Controller modifies data through model functions
|
||||
4. **View Updates**: Model changes trigger view re-rendering via updateView()
|
||||
5. **Persistence**: Cart changes are automatically saved to localStorage
|
21
webshop_app/mvc-emne2/index.html
Normal file
21
webshop_app/mvc-emne2/index.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="no">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="En demonstrasjon av MVC-arkitektur i en nettbutikk-applikasjon">
|
||||
<title>Nettbutikk - MVC Demo</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" role="application" aria-label="Nettbutikk applikasjon">
|
||||
<!-- All content will be rendered here by updateView -->
|
||||
<div class="loading" role="status" aria-live="polite">Laster innhold...</div>
|
||||
</div>
|
||||
|
||||
<script src="js/model.js"></script>
|
||||
<script src="js/view.js"></script>
|
||||
<script src="js/controller.js"></script>
|
||||
<script src="js/app.js"></script>
|
||||
</body>
|
||||
</html>
|
30
webshop_app/mvc-emne2/js/app.js
Normal file
30
webshop_app/mvc-emne2/js/app.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Hovedinitialisering av applikasjonen når DOM er ferdig lastet
|
||||
// Setter opp initial tilstand og starter renderingsloopen
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Fjerner loading-indikator hvis den finnes
|
||||
// Defensiv sjekk fordi HTML-struktur kan endres
|
||||
const loadingElement = document.querySelector('.loading');
|
||||
if (loadingElement) {
|
||||
loadingElement.remove();
|
||||
}
|
||||
|
||||
// Laster lagret handlekurv fra localStorage før første rendering
|
||||
// Må skje først fordi andre operasjoner avhenger av handlekurvstatus
|
||||
initializeCart();
|
||||
|
||||
// Henter initial data fra model-lag
|
||||
const products = getAllProducts();
|
||||
const cart = getCart();
|
||||
const itemCount = getCartItemCount();
|
||||
|
||||
// Starter med produktoversikt som standardside
|
||||
// Viser brukeren det de mest sannsynlig ønsker å se først
|
||||
updateView('products', { products });
|
||||
|
||||
// Oppdaterer handlekurvteller i navigasjon basert på lagret data
|
||||
// Viktig for å vise korrekt status fra tidligere sessjon
|
||||
updateView('cart-count', { count: itemCount });
|
||||
|
||||
// Bekrefter vellykket initialisering i konsoll for debugging
|
||||
console.log('Nettbutikk MVC Demo initialisert!');
|
||||
});
|
95
webshop_app/mvc-emne2/js/controller.js
Normal file
95
webshop_app/mvc-emne2/js/controller.js
Normal file
|
@ -0,0 +1,95 @@
|
|||
// Laster og viser alle produkter - brukes ved app-initialisering og navigasjon
|
||||
// Henter data fra model og sender til view for rendering
|
||||
function loadProducts() {
|
||||
const products = getAllProducts();
|
||||
updateView('products', { products });
|
||||
}
|
||||
|
||||
// Viser detaljside for spesifikt produkt basert på ID
|
||||
// Kaller updateView som håndterer både gyldige og ugyldige produkter
|
||||
function showProductDetail(productId) {
|
||||
const product = getProductById(productId);
|
||||
updateView('product-detail', { product });
|
||||
}
|
||||
|
||||
// Hovedfunksjon for å legge produkter i handlekurv
|
||||
// Koordinerer mellom model (dataendring) og view (brukernotifikasjon + UI-oppdatering)
|
||||
function handleAddToCart(productId) {
|
||||
const success = addToCart(productId); // Model-operasjon som returnerer boolean
|
||||
if (success) {
|
||||
// Henter produktnavn for personlig notifikasjon
|
||||
const product = getProductById(productId);
|
||||
updateView('notification', { message: product.name + ' lagt til i handlekurv!' });
|
||||
// Oppdaterer handlekurvvisning og teller umiddelbart
|
||||
updateCartDisplay();
|
||||
} else {
|
||||
// Viser feilmelding hvis produktet ikke eksisterer
|
||||
updateView('notification', { message: 'Feil ved å legge produktet i handlekurven' });
|
||||
}
|
||||
}
|
||||
|
||||
// Fjerner produkt fra handlekurv med brukerbekreftelse
|
||||
// Kombinerer model-operasjon med brukernotifikasjon
|
||||
function handleRemoveFromCart(productId) {
|
||||
removeFromCart(productId); // Model-operasjon
|
||||
updateView('notification', { message: 'Vare fjernet fra handlekurv' });
|
||||
updateCartDisplay(); // Oppdaterer UI umiddelbart
|
||||
}
|
||||
|
||||
// Endrer antall av et produkt i handlekurv (+1 eller -1)
|
||||
// Inneholder defensiv sjekk selv om UI-logikk ikke burde kalle dette for ikke-eksisterende varer
|
||||
function changeQuantity(productId, change) {
|
||||
const cart = getCart();
|
||||
const item = cart.find(item => item.productId === productId);
|
||||
|
||||
// Sikkerhetsjekk - kun hvis varen faktisk finnes i handlekurven
|
||||
// Forhindrer feil hvis UI og model kommer ut av synk
|
||||
if (item) {
|
||||
const newQuantity = item.quantity + change;
|
||||
updateCartQuantity(productId, newQuantity); // Model håndterer edge cases (quantity <= 0)
|
||||
updateCartDisplay(); // Oppdaterer UI for å reflektere endringer
|
||||
}
|
||||
// Ingen else - ignorerer stille forsøk på å endre ikke-eksisterende varer
|
||||
}
|
||||
|
||||
// Tømmer hele handlekurven med bekreftelsdialog
|
||||
// Forhindrer utilsiktet tap av handlekurvinnhold
|
||||
function handleClearCart() {
|
||||
// Browser-standard confirm() for brukersikkerhet
|
||||
if (confirm('Er du sikker på at du vil tømme handlekurven?')) {
|
||||
clearCart(); // Model-operasjon
|
||||
updateView('notification', { message: 'Handlekurv tømt' });
|
||||
updateCartDisplay(); // Oppdaterer til tom handlekurv
|
||||
}
|
||||
// Ingen else - kansellering ignoreres stille
|
||||
}
|
||||
|
||||
// Navigerer til produktoversikt - brukes fra tilbake-knapp og hovednavigasjon
|
||||
// Laster ferske produktdata og rendrer siden
|
||||
function navigateToProducts() {
|
||||
const products = getAllProducts();
|
||||
updateView('products', { products });
|
||||
}
|
||||
|
||||
// Navigerer til handlekurvside med komplett handlekurvstatus
|
||||
// Samler all nødvendig data fra model i én operasjon
|
||||
function navigateToCart() {
|
||||
const cart = getCart();
|
||||
const total = getCartTotal();
|
||||
const itemCount = getCartItemCount();
|
||||
// Sender all handlekurvdata til view for komplett rendering
|
||||
updateView('cart', { cartItems: cart, total, itemCount });
|
||||
}
|
||||
|
||||
// Oppdaterer handlekurvvisning etter endringer (add, remove, quantity change)
|
||||
// Koordinerer mellom handlekurvside og navigationsteller
|
||||
function updateCartDisplay() {
|
||||
const cart = getCart();
|
||||
const total = getCartTotal();
|
||||
const itemCount = getCartItemCount();
|
||||
|
||||
// Oppdaterer handlekurvside (hvis bruker er på den siden)
|
||||
updateView('cart', { cartItems: cart, total, itemCount });
|
||||
// Oppdaterer også telleren i navigasjonen (synlig på alle sider)
|
||||
updateView('cart-count', { count: itemCount });
|
||||
}
|
195
webshop_app/mvc-emne2/js/model.js
Normal file
195
webshop_app/mvc-emne2/js/model.js
Normal file
|
@ -0,0 +1,195 @@
|
|||
// Statisk produktkatalog - bruker hardkodede data for å fokusere på MVC-arkitektur
|
||||
// fremfor backend-integrasjon i denne fasen av prosjektet
|
||||
const products = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Trådløse Hodetelefoner",
|
||||
price: 99.99,
|
||||
description: "Høykvalitets trådløse hodetelefoner med støydemping og 30 timer batteritid.",
|
||||
image: "🎧", // Bruker emoji for enkelhet - unngår bildefilhåndtering
|
||||
category: "Elektronikk"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Smartklokke",
|
||||
price: 199.99,
|
||||
description: "Funksjonsrik smartklokke med treningssporing, pulsmåler og GPS.",
|
||||
image: "⌚",
|
||||
category: "Elektronikk"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Kaffetrakter",
|
||||
price: 79.99,
|
||||
description: "Programmerbar kaffetrakter med termokanne og automatisk bryggtimer.",
|
||||
image: "☕",
|
||||
category: "Hjem"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Ryggsekk",
|
||||
price: 49.99,
|
||||
description: "Slitesterke laptop-ryggsekk med flere rom og vannavstøtende materiale.",
|
||||
image: "🎒",
|
||||
category: "Livsstil"
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Skrivebordslampe",
|
||||
price: 39.99,
|
||||
description: "LED skrivebordslampe med justerbar lysstyrke og USB-ladeport.",
|
||||
image: "💡",
|
||||
category: "Hjem"
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "Trådløs Mus",
|
||||
price: 29.99,
|
||||
description: "Ergonomisk trådløs mus med presis sporing og lang batteritid.",
|
||||
image: "🖱️",
|
||||
category: "Elektronikk"
|
||||
}
|
||||
];
|
||||
|
||||
// Handlekurv lagres i minnet som et array - enkelt å arbeide med og testbar
|
||||
// Hver handlekurvvare inneholder productId, quantity og en referanse til produktobjektet
|
||||
let cart = [];
|
||||
|
||||
// Getter-funksjoner for produktdata - gir kontrollert tilgang til data
|
||||
// og muliggjør fremtidig validering eller transformasjon
|
||||
function getAllProducts() {
|
||||
return products;
|
||||
}
|
||||
|
||||
// Konverterer id til integer fordi URL-parametere alltid er strings
|
||||
// Bruker find() for å returnere første match eller undefined hvis ikke funnet
|
||||
function getProductById(id) {
|
||||
return products.find(product => product.id === parseInt(id));
|
||||
}
|
||||
|
||||
// Legger til produkt i handlekurv med intelligent sammenslåing
|
||||
// quantity = 1 som standard fordi de fleste klikk legger til én vare
|
||||
function addToCart(productId, quantity = 1) {
|
||||
const product = getProductById(productId);
|
||||
// Kritisk validering - forhindrer at ugyldig produkter legges til
|
||||
// Returnerer false for å signalisere feil til kallende kode
|
||||
if (!product) return false;
|
||||
|
||||
// Sjekker om produktet allerede finnes for å unngå duplikater
|
||||
// Øker quantity istedenfor å lage nye poster - bedre brukeropplevelse
|
||||
const existingItem = cart.find(item => item.productId === productId);
|
||||
|
||||
if (existingItem) {
|
||||
// Øker eksisterende quantity - intuitivt for brukeren
|
||||
existingItem.quantity += quantity;
|
||||
} else {
|
||||
// Lager ny handlekurvpost med både ID og fullt produktobjekt
|
||||
// product-referansen unngår gjentatte oppslag senere
|
||||
cart.push({
|
||||
productId: productId, // For identifikasjon og sammenligning
|
||||
quantity: quantity, // Hvor mange brukeren vil ha
|
||||
product: product // Full produktinfo for visning
|
||||
});
|
||||
}
|
||||
|
||||
// Persisterer umiddelbart for å unngå tap av data
|
||||
saveCartToStorage();
|
||||
return true; // Signaliserer suksess til UI-lag
|
||||
}
|
||||
|
||||
// Fjerner hele produktlinjen fra handlekurv uavhengig av quantity
|
||||
// Bruker filter() for å lage nytt array - funksjonell programmering
|
||||
function removeFromCart(productId) {
|
||||
cart = cart.filter(item => item.productId !== productId);
|
||||
saveCartToStorage(); // Persister endring umiddelbart
|
||||
}
|
||||
|
||||
// Oppdaterer antall av eksisterende handlekurvvare
|
||||
// Håndterer edge case hvor quantity blir 0 eller negativ
|
||||
function updateCartQuantity(productId, quantity) {
|
||||
const item = cart.find(item => item.productId === productId);
|
||||
if (item) {
|
||||
// Forretningsregel: quantity <= 0 betyr fjern produktet helt
|
||||
// Mer intuitivt enn å ha 0 varer i handlekurven
|
||||
if (quantity <= 0) {
|
||||
removeFromCart(productId);
|
||||
} else {
|
||||
item.quantity = quantity;
|
||||
saveCartToStorage();
|
||||
}
|
||||
}
|
||||
// Ingen else - ignorerer oppdateringer til ikke-eksisterende varer
|
||||
}
|
||||
|
||||
// Returnerer hele handlekurv-arrayet for visning
|
||||
// Andre komponenter kan ikke direkte mutere cart-variabelen
|
||||
function getCart() {
|
||||
return cart;
|
||||
}
|
||||
|
||||
// Beregner total pris for alle varer i handlekurv
|
||||
// Bruker reduce() for elegant summering av pris * quantity
|
||||
function getCartTotal() {
|
||||
return cart.reduce((total, item) => {
|
||||
return total + (item.product.price * item.quantity);
|
||||
}, 0); // Starter med 0 som akkumulator
|
||||
}
|
||||
|
||||
// Teller totalt antall individuelle varer (ikke unike produkter)
|
||||
// Eksempel: 2 hodetelefoner + 3 kaffetraktere = 5 varer totalt
|
||||
function getCartItemCount() {
|
||||
return cart.reduce((count, item) => count + item.quantity, 0);
|
||||
}
|
||||
|
||||
// Tømmer hele handlekurven - brukes ved utsjekking eller manuell tømming
|
||||
// Setter cart til tomt array istedenfor å mutere eksisterende
|
||||
function clearCart() {
|
||||
cart = [];
|
||||
saveCartToStorage(); // Persister tom handlekurv
|
||||
}
|
||||
|
||||
// Lagrer handlekurv til nettleserens localStorage for persistering
|
||||
// Try-catch fordi localStorage kan feile i private browsing eller ved kvotaoverskridelse
|
||||
function saveCartToStorage() {
|
||||
try {
|
||||
// Konverterer JavaScript-objekt til JSON-string for lagring
|
||||
localStorage.setItem('webstore_cart', JSON.stringify(cart));
|
||||
} catch (error) {
|
||||
// Logger feil men lar appen fortsette å fungere
|
||||
// Graceful degradation - handlekurv fungerer fortsatt i denne sesjonen
|
||||
console.error('Feil ved lagring av handlekurv til localStorage:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Laster handlekurv fra localStorage og rekonstruerer produktreferanser
|
||||
// Håndterer scenarioer hvor lagret data kan være korrupt eller utdatert
|
||||
function loadCartFromStorage() {
|
||||
try {
|
||||
const savedCart = localStorage.getItem('webstore_cart');
|
||||
// Sjekker om data finnes før parsing - første gangs brukere har tom localStorage
|
||||
if (savedCart) {
|
||||
const parsedCart = JSON.parse(savedCart);
|
||||
// Rekonstruerer produktreferanser fordi localStorage kun lagrer JSON
|
||||
// Fallback til "Ukjent Produkt" hvis produktet er fjernet fra katalogen
|
||||
return parsedCart.map(item => ({
|
||||
...item,
|
||||
// Kritisk: Sikrer at produktreferanse alltid finnes
|
||||
// Håndterer tilfeller hvor produktkatalog endres mellom sesjoner
|
||||
product: products.find(p => p.id === item.productId) ||
|
||||
{ id: item.productId, name: 'Ukjent Produkt', price: 0 }
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
// JSON parsing kan feile hvis data er korrupt
|
||||
// Logger feil og returnerer tom array som fallback
|
||||
console.error('Feil ved lasting av handlekurv fra localStorage:', error);
|
||||
}
|
||||
// Returnerer tom array som sikker fallback
|
||||
return [];
|
||||
}
|
||||
|
||||
// Initialiserer handlekurv ved appstart
|
||||
// Setter cart-variabelen basert på lagret data eller tom array
|
||||
function initializeCart() {
|
||||
cart = loadCartFromStorage();
|
||||
}
|
283
webshop_app/mvc-emne2/js/view.js
Normal file
283
webshop_app/mvc-emne2/js/view.js
Normal file
|
@ -0,0 +1,283 @@
|
|||
// 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);
|
||||
}
|
345
webshop_app/mvc-emne2/styles.css
Normal file
345
webshop_app/mvc-emne2/styles.css
Normal file
|
@ -0,0 +1,345 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
|
||||
/* Screen reader only content */
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* Focus styles for accessibility */
|
||||
button:focus,
|
||||
a:focus,
|
||||
[tabindex]:focus {
|
||||
outline: 2px solid #007bff;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* Loading state */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
font-size: 1.1rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* Improved button styles for different types */
|
||||
.btn-primary {
|
||||
background-color: #007bff;
|
||||
border-color: #007bff;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #0056b3;
|
||||
border-color: #0056b3;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: #dc3545;
|
||||
border-color: #dc3545;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background-color: #c82333;
|
||||
border-color: #bd2130;
|
||||
}
|
||||
|
||||
.btn-remove {
|
||||
background-color: #6c757d;
|
||||
border-color: #6c757d;
|
||||
}
|
||||
|
||||
.btn-remove:hover {
|
||||
background-color: #545b62;
|
||||
border-color: #4e555b;
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background: #2c3e50;
|
||||
color: white;
|
||||
padding: 1rem 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.nav-links a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.nav-links a:hover {
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.cart-link {
|
||||
background-color: #e74c3c;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
#app {
|
||||
max-width: 1200px;
|
||||
margin: 2rem auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.page {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.page.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.products-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 2rem;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.product-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 5px 20px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
background-color: #ecf0f1;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 1rem;
|
||||
font-size: 3rem;
|
||||
color: #95a5a6;
|
||||
}
|
||||
|
||||
.product-name {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.product-price {
|
||||
font-size: 1.1rem;
|
||||
color: #e74c3c;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.product-description {
|
||||
color: #666;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background-color: #27ae60;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background-color: #219a52;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
background-color: #95a5a6;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.back-btn:hover {
|
||||
background-color: #7f8c8d;
|
||||
}
|
||||
|
||||
.product-detail {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.product-detail .product-image {
|
||||
height: 300px;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.product-detail .product-name {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.product-detail .product-price {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.product-detail .product-description {
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.cart-items {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.cart-item {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cart-item-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.cart-item-name {
|
||||
font-weight: bold;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.cart-item-price {
|
||||
color: #e74c3c;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.cart-item-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.quantity-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.quantity-btn {
|
||||
background-color: #ecf0f1;
|
||||
border: none;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.quantity-btn:hover {
|
||||
background-color: #d5dbdb;
|
||||
}
|
||||
|
||||
.cart-total {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.total-amount {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: #e74c3c;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.empty-cart {
|
||||
text-align: center;
|
||||
padding: 3rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.navbar {
|
||||
padding: 1rem;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.products-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.cart-item {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.cart-item-controls {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
# Web Store PRD
|
||||
|
||||
## Project Overview
|
||||
A simple web store application for frontend web development course demonstrating progressive complexity through multiple implementations.
|
||||
|
||||
## Core Requirements
|
||||
|
||||
### Phase 1: Plain JS/HTML/CSS (MVC Architecture)
|
||||
- **Product Listing Page**: Display available products with basic information
|
||||
- **Product Detail Page**: Show detailed product information and purchase option
|
||||
- **Basic Navigation**: Simple routing between pages
|
||||
- **MVC Structure**: Clear separation of Model, View, and Controller layers
|
||||
|
||||
### Technical Specifications
|
||||
- Pure JavaScript (ES6+)
|
||||
- HTML5 semantic markup
|
||||
- CSS3 for styling
|
||||
- No external frameworks or libraries
|
||||
- Local data storage (JSON/localStorage)
|
||||
|
||||
### User Stories
|
||||
1. As a user, I can view a list of products with names, prices, and images
|
||||
2. As a user, I can click on a product to see detailed information
|
||||
3. As a user, I can navigate between product list and detail pages
|
||||
4. As a user, I can add products to a shopping cart (basic functionality)
|
||||
|
||||
### Data Structure
|
||||
- Products: id, name, price, description, image, category
|
||||
- Cart: product references and quantities
|
||||
|
||||
### Future Phases
|
||||
- Phase 2: Enhanced with modern JavaScript frameworks
|
||||
- Phase 3: TypeScript implementation
|
||||
- Phase 4: Vue.js implementation
|
||||
- Phase 5: Advanced features (user auth, payment, etc.)
|
||||
|
||||
## Success Criteria
|
||||
- Clean, maintainable MVC code structure
|
||||
- Responsive design works on desktop and mobile
|
||||
- Fast loading and smooth navigation
|
||||
- Educational value for demonstrating core web development concepts
|
Loading…
Add table
Add a link
Reference in a new issue