🎯 Mục tiêu:

  • 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ầnGiải thích
templatesLưu template dạng chuỗi HTML, có {{ }} để bind dữ liệu
dataChứ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
hashchangeNghe 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 inputonchange hoặc oninput để cập nhật data ngược lại