14 KiB
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
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
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 cataloggetProductById(id)
- Retrieves specific productaddToCart(productId, quantity)
- Adds items to cartremoveFromCart(productId)
- Removes items from cartupdateCartQuantity(productId, quantity)
- Updates item quantitiesgetCart()
- Returns current cart contentsgetCartTotal()
- Calculates cart total pricegetCartItemCount()
- Returns total item countclearCart()
- Empties the cartsaveCartToStorage()
/loadCartFromStorage()
- LocalStorage persistence
Data Structure:
// 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:
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 headergenerateProductsPage(products)
- Product listing pagegenerateProductDetailPage(product)
- Product detail pagegenerateCartPage(cartItems, total, itemCount)
- Shopping cart pagecreateProductCard(product)
- Individual product cardcreateCartItem(item)
- Individual cart item
3. Controller (controller.js
)
Purpose: Handles user interactions and coordinates between Model and View
Navigation Functions:
navigateToProducts()
- Shows product listingnavigateToCart()
- Shows shopping cartshowProductDetail(productId)
- Shows product details
Cart Management:
handleAddToCart(productId)
- Adds product to carthandleRemoveFromCart(productId)
- Removes product from cartchangeQuantity(productId, change)
- Updates product quantityhandleClearCart()
- Empties entire cartupdateCartDisplay()
- 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:
- Wait for DOM to load
- Initialize cart from localStorage
- Load products from model
- Render initial products page
- Update cart counter
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:
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:
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:
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:
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
- Open
index.html
in a web browser - The application initializes automatically
- Navigate between products, product details, and cart
- 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:
- Phase 2: Add TypeScript for type safety
- Phase 3: Implement Vue.js for reactive UI
- Phase 4: Add user authentication
- Phase 5: Integrate payment processing
- Phase 6: Add product search and filtering
- 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.