🎯 Mục tiêu:
- Có template HTML chứa biến (VD:
{{title}}
)
- Dữ liệu được bind vào template
- Hỗ trợ route qua
window.location.hash
để hiển thị nội dung khác nhau
✅ 1. HTML cơ bản:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Mini SPA</title>
<style>
nav a { margin-right: 10px; }
</style>
</head>
<body>
<nav>
<a href="#/">Home</a>
<a href="#/about">About</a>
<a href="#/product">Product</a>
</nav>
<div id="app"></div>
<script src="app.js"></script>
</body>
</html>
✅ 2. File app.js
: Template engine + Router thủ công
// View templates
const templates = {
home: `<h1>{{title}}</h1><p>{{content}}</p>`,
about: `<h1>About Us</h1><p>{{content}}</p>`,
product: `<h1>{{title}}</h1><ul>{{products}}</ul>`
};
// Controller data
const data = {
home: {
title: 'Welcome Home',
content: 'This is the homepage.'
},
about: {
content: 'We are awesome people doing great things.'
},
product: {
title: 'Our Products',
products: ['Book', 'Pen', 'Laptop']
}
};
// Simple template engine
function render(template, data) {
return template.replace(/{{(.*?)}}/g, (match, key) => {
const val = data[key.trim()];
if (Array.isArray(val)) {
return val.map(item => `<li>${item}</li>`).join('');
}
return val !== undefined ? val : '';
});
}
// Router handler
function router() {
const hash = location.hash || '#/';
const route = hash.replace('#/', '') || 'home';
const tpl = templates[route];
const tplData = data[route];
if (tpl && tplData) {
document.getElementById('app').innerHTML = render(tpl, tplData);
} else {
document.getElementById('app').innerHTML = '<h1>404 Not Found</h1>';
}
}
// Listen to route changes
window.addEventListener('hashchange', router);
window.addEventListener('load', router);
✅ Giải thích
Thành phần | Giải thích |
---|
templates | Lưu template dạng chuỗi HTML, có {{ }} để bind dữ liệu |
data | Chứa dữ liệu cho mỗi route |
render() | Thay thế {{key}} bằng giá trị tương ứng trong data |
router() | Lấy window.location.hash , render template + data phù hợp |
hashchange | Nghe sự kiện thay đổi #route để gọi lại render |
✅ Mở rộng nếu muốn:
- Component dạng hàm: có thể viết
templates
là hàm thay vì chuỗi để dynamic hơn
- Nested route, query param, lazy load
- 2 chiều binding: kết hợp
input
và onchange
hoặc oninput
để cập nhật data
ngược lại