481 lines
No EOL
15 KiB
Markdown
481 lines
No EOL
15 KiB
Markdown
# 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.* |