# สัปดาห์ที่ 10.4 - Hash-Based Routing

## บทนำ

**Hash-Based Routing** คือวิธีการนำทางในแอปพลิเคชัน SPA โดยใช้ URL hash (`#`) เพื่อระบุหน้า/สถานะปัจจุบัน โดยไม่ต้องรีโหลดหน้า

ตัวอย่าง URL:

- `https://myapp.com/#/home`
- `https://myapp.com/#/users/123`
- `https://myapp.com/#/settings`

---

## 1. ความเข้าใจพื้นฐาน

### URL Components

```
https://myapp.com:3000/path?query=value#/page/123
│        │         │    │      │              │
└─ Protocol        │    │      │              └─ Fragment/Hash
                   │    │      └─ Query String
                   │    └─ Path
                   └─ Domain:Port

Hash (Fragment) คือที่บอก SPA ว่าจะแสดงหน้าไหน
```

### ข้อมูลใน Window

```javascript
// ถ้า URL คือ: https://myapp.com/#/users/123?sort=name

window.location.href; // ค่า URL ทั้งหมด
window.location.hash; // "#/users/123"
window.location.pathname; // "/"
window.location.search; // "?sort=name"
window.location.protocol; // "https:"
window.location.host; // "myapp.com"
```

---

## 2. Event Listener - ฟังการเปลี่ยน Hash

### ตัวอย่างพื้นฐาน

```javascript
// ฟังเมื่อ hash เปลี่ยน
window.addEventListener("hashchange", function () {
  console.log("Hash changed to:", window.location.hash);
  // ทำอะไรบางอย่างเมื่อ hash เปลี่ยน
});

// เมื่อหน้า load ครั้งแรก
window.addEventListener("load", function () {
  console.log("Page loaded");
  handleRoute(); // เรียกฟังก์ชัน routing
});
```

### Practical Example

```javascript
function handleRoute() {
  const hash = window.location.hash.slice(2); // ตัด "#/"
  console.log("Current route:", hash);

  if (hash === "home" || hash === "") {
    showHome();
  } else if (hash === "about") {
    showAbout();
  } else if (hash.startsWith("users")) {
    const id = hash.split("/")[1];
    showUserDetail(id);
  }
}

// สมัครรับเหตุการณ์
window.addEventListener("hashchange", handleRoute);
window.addEventListener("load", handleRoute);
```

---

## 3. URL Patterns

### Basic Routes

```javascript
// ✅ Simple routes
#/home              → Route: home
#/about             → Route: about
#/contact           → Route: contact

// ❌ ไม่มี hash ก็ได้ (ใช้ '' หรือ 'home')
```

### Routes ที่มี Parameters

```javascript
// ✅ พารามิเตอร์ใน path
#/users/123         → Route: users, ID: 123
#/posts/456/edit    → Route: posts, ID: 456, Action: edit

// ✅ Query string
#/search?q=javascript&sort=date
```

### Parser Function

```javascript
function parseHash() {
  const hash = window.location.hash.slice(2); // ตัด "#/"
  const parts = hash.split("/");

  return {
    page: parts[0] || "home",
    id: parts[1],
    action: parts[2],
  };
}

// ใช้งาน
const route = parseHash();
console.log(route); // { page: 'users', id: '123', action: 'edit' }
```

### Advanced Parser

```javascript
class Router {
  static parse() {
    const hash = window.location.hash.slice(2);
    const [page, ...params] = hash.split("/");
    const query = new URLSearchParams(window.location.search);

    return {
      page: page || "home",
      params: params,
      query: Object.fromEntries(query),
    };
  }
}

// ใช้งาน
const route = Router.parse();
console.log(route);
// {
//   page: 'users',
//   params: ['123', 'edit'],
//   query: { sort: 'name' }
// }
```

---

## 4. Simple Router Implementation

### ตัวอย่างที่ 1: Router ง่ายๆ

```html
<!DOCTYPE html>
<html>
  <head>
    <title>Simple Router</title>
    <style>
      nav {
        background: #333;
        color: white;
        padding: 10px;
      }
      nav a {
        color: white;
        margin-right: 15px;
        text-decoration: none;
      }
      nav a:hover {
        text-decoration: underline;
      }
      main {
        padding: 20px;
      }
      .active {
        font-weight: bold;
        text-decoration: underline;
      }
    </style>
  </head>
  <body>
    <nav>
      <a href="#/">Home</a>
      <a href="#/about">About</a>
      <a href="#/services">Services</a>
      <a href="#/contact">Contact</a>
    </nav>

    <main id="app"></main>

    <script>
      const pages = {
        home: `
        <h1>🏠 Home</h1>
        <p>ยินดีต้อนรับสู่เว็บไซต์ของเรา</p>
      `,
        about: `
        <h1>ℹ️ About</h1>
        <p>นี่คือหน้า About</p>
      `,
        services: `
        <h1>🛠️ Services</h1>
        <p>เรามีบริการดีๆ ให้ไว้</p>
      `,
        contact: `
        <h1>📧 Contact</h1>
        <p>ติดต่อเราได้ที่ contact@example.com</p>
      `,
      };

      function handleRoute() {
        let route = window.location.hash.slice(2) || "home";
        // ตัด "#/" ออก

        const app = document.getElementById("app");
        app.innerHTML = pages[route] || pages.home;

        // Update active link
        document.querySelectorAll("nav a").forEach((link) => {
          const href = link.getAttribute("href").slice(2);
          link.classList.toggle("active", href === route);
        });
      }

      // ฟังเหตุการณ์
      window.addEventListener("hashchange", handleRoute);
      window.addEventListener("load", handleRoute);
    </script>
  </body>
</html>
```

---

### ตัวอย่างที่ 2: Router ที่มี Dynamic Data

```html
<!DOCTYPE html>
<html>
  <head>
    <title>Dynamic Router</title>
    <style>
      body {
        font-family: Arial;
        max-width: 600px;
        margin: 50px auto;
      }
      nav {
        margin-bottom: 20px;
      }
      nav a {
        margin-right: 10px;
        text-decoration: none;
      }
      .user-link {
        color: blue;
        cursor: pointer;
        text-decoration: underline;
      }
      .back-link {
        color: gray;
        text-decoration: none;
        margin-bottom: 10px;
        display: inline-block;
      }
    </style>
  </head>
  <body>
    <nav>
      <a href="#/">Home</a>
      <a href="#/users">Users</a>
    </nav>

    <main id="app"></main>

    <script>
      const users = [
        { id: 1, name: "Alice", email: "alice@example.com", age: 25 },
        { id: 2, name: "Bob", email: "bob@example.com", age: 30 },
        { id: 3, name: "Charlie", email: "charlie@example.com", age: 28 },
      ];

      function renderHome() {
        return `
        <h1>🏠 Home</h1>
        <p>ยินดีต้อนรับ!</p>
        <p><a href="#/users">ดูรายชื่อผู้ใช้</a></p>
      `;
      }

      function renderUsers() {
        let html = "<h1>👥 Users</h1><ul>";
        users.forEach((user) => {
          html += `
          <li>
            <a href="#/users/${user.id}" class="user-link">${user.name}</a>
          </li>
        `;
        });
        html += "</ul>";
        return html;
      }

      function renderUserDetail(id) {
        const user = users.find((u) => u.id === parseInt(id));
        if (!user) {
          return "<h1>❌ ไม่พบผู้ใช้</h1>";
        }

        return `
        <a href="#/users" class="back-link">← ย้อนกลับ</a>
        <h1>${user.name}</h1>
        <p><strong>Email:</strong> ${user.email}</p>
        <p><strong>Age:</strong> ${user.age}</p>
      `;
      }

      function handleRoute() {
        const hash = window.location.hash.slice(2); // ตัด "#/" ออก
        const parts = hash.split("/");
        const page = parts[0];
        const id = parts[1];

        const app = document.getElementById("app");

        if (page === "" || page === "home") {
          app.innerHTML = renderHome();
        } else if (page === "users" && !id) {
          app.innerHTML = renderUsers();
        } else if (page === "users" && id) {
          app.innerHTML = renderUserDetail(id);
        } else {
          app.innerHTML = "<h1>404 - Page Not Found</h1>";
        }
      }

      window.addEventListener("hashchange", handleRoute);
      window.addEventListener("load", handleRoute);
    </script>
  </body>
</html>
```

---

## 5. Router Class - ขั้นสูง

```javascript
class SimpleRouter {
  constructor() {
    this.routes = {};
    this.currentRoute = null;
    this.setupListeners();
  }

  // ลงทะเบียน route
  register(path, handler) {
    this.routes[path] = handler;
  }

  // ตั้งค่า listeners
  setupListeners() {
    window.addEventListener("hashchange", () => this.handleRoute());
    window.addEventListener("load", () => this.handleRoute());
  }

  // จัดการ route
  handleRoute() {
    const hash = window.location.hash.slice(2);
    const path = hash.split("/")[0] || "home";
    const params = hash.split("/").slice(1);

    const handler = this.routes[path];
    if (handler) {
      this.currentRoute = path;
      handler(...params);
    } else {
      console.warn(`Route not found: ${path}`);
    }
  }

  // นำทางไปยัง route
  navigate(path) {
    window.location.hash = `#/${path}`;
  }
}

// ใช้งาน
const router = new SimpleRouter();

router.register("home", () => {
  document.getElementById("app").innerHTML = "<h1>Home Page</h1>";
});

router.register("about", () => {
  document.getElementById("app").innerHTML = "<h1>About Page</h1>";
});

router.register("user", (id) => {
  document.getElementById("app").innerHTML = `<h1>User #${id}</h1>`;
});

// นำทาง
router.navigate("user/123");
```

---

## 6. Browser History & Back Button

### Back/Forward Navigation

```javascript
// ✅ Hash routing สนับสนุน back/forward โดยอัตโนมัติ
// เมื่อผู้ใช้กด back button เบราว์เซอร์จะเปลี่ยน hash โดยอัตโนมัติ

// แต่ถ้าต้องการ custom behavior:
window.addEventListener("hashchange", function () {
  console.log("User navigated using back/forward button or URL");
});
```

### Browser History API

```javascript
// ใช้ History API แทน hash (ขั้นสูง)
// ต้อง server-side support

history.pushState(state, title, url);
history.replaceState(state, title, url);
history.back();
history.forward();
history.go(-1); // ไปศหลังหน้าหนึ่ง

window.addEventListener("popstate", (event) => {
  console.log("URL changed:", event.state);
});
```

---

## 7. Query Parameters

```javascript
// URL: #/search?q=javascript&sort=date

function getQueryParams() {
  const hash = window.location.hash;
  const queryStart = hash.indexOf("?");

  if (queryStart === -1) return {};

  const queryString = hash.substring(queryStart + 1);
  const params = new URLSearchParams(queryString);

  return Object.fromEntries(params);
}

// ใช้งาน
const params = getQueryParams();
console.log(params); // { q: 'javascript', sort: 'date' }

// ตั้งค่า query params
function setQueryParams(obj) {
  const query = new URLSearchParams(obj).toString();
  window.location.hash = `#/search?${query}`;
}

setQueryParams({ q: "vue", sort: "popular" });
// URL กลายเป็น: #/search?q=vue&sort=popular
```

---

## 8. ตัวอย่าง: Complete SPA with Router

```html
<!DOCTYPE html>
<html>
  <head>
    <title>Complete SPA</title>
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }

      body {
        font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
        background-color: #f5f5f5;
      }

      header {
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        color: white;
        padding: 20px;
        text-align: center;
      }

      nav {
        display: flex;
        gap: 20px;
        padding: 20px;
        background: white;
        border-bottom: 1px solid #ddd;
      }

      nav a {
        text-decoration: none;
        color: #333;
        font-weight: bold;
        transition: color 0.3s;
      }

      nav a:hover {
        color: #667eea;
      }

      nav a.active {
        color: #667eea;
        border-bottom: 2px solid #667eea;
        padding-bottom: 5px;
      }

      main {
        max-width: 1000px;
        margin: 20px auto;
        padding: 20px;
        background: white;
        border-radius: 8px;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
      }

      footer {
        text-align: center;
        padding: 20px;
        color: #666;
        margin-top: 50px;
        border-top: 1px solid #ddd;
      }

      .product {
        border: 1px solid #ddd;
        padding: 15px;
        margin: 10px 0;
        border-radius: 4px;
        cursor: pointer;
        transition: box-shadow 0.3s;
      }

      .product:hover {
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
      }
    </style>
  </head>
  <body>
    <header>
      <h1>🛒 E-Commerce SPA</h1>
    </header>

    <nav>
      <a href="#/">Home</a>
      <a href="#/products">Products</a>
      <a href="#/about">About</a>
    </nav>

    <main id="app"></main>

    <footer>
      <p>&copy; 2024 E-Commerce SPA | Made with ❤️</p>
    </footer>

    <script>
      // Data
      const products = [
        {
          id: 1,
          name: "Laptop",
          price: 999,
          description: "High-performance laptop",
        },
        { id: 2, name: "Phone", price: 599, description: "Latest smartphone" },
        { id: 3, name: "Tablet", price: 299, description: "Portable tablet" },
      ];

      // Router
      class Router {
        constructor() {
          this.routes = {};
          this.currentPath = "";
          this.setupListeners();
        }

        register(path, handler) {
          this.routes[path] = handler;
        }

        setupListeners() {
          window.addEventListener("hashchange", () => this.navigate());
          window.addEventListener("load", () => this.navigate());
        }

        navigate() {
          const hash = window.location.hash.slice(2) || "/";
          const parts = hash.split("/").filter(Boolean);
          const page = parts[0] || "home";
          const id = parts[1];

          this.currentPath = page;

          const handler = this.routes[page];
          if (handler) {
            handler(id);
          }

          // Update nav links
          this.updateNav();
        }

        updateNav() {
          document.querySelectorAll("nav a").forEach((link) => {
            const href =
              link.getAttribute("href").slice(2).split("/")[0] || "home";
            link.classList.toggle("active", href === this.currentPath);
          });
        }
      }

      // Initialize Router
      const router = new Router();

      // Routes
      router.register("home", () => {
        document.getElementById("app").innerHTML = `
        <h1>🏠 Welcome to Our Store</h1>
        <p>Browse our amazing products and find what you need!</p>
        <a href="#/products"><button style="padding: 10px 20px; background: #667eea; color: white; border: none; border-radius: 4px; cursor: pointer;">See Products</button></a>
      `;
      });

      router.register("products", () => {
        let html = "<h1>📦 Our Products</h1>";
        products.forEach((product) => {
          html += `
          <div class="product" onclick="location.hash='#/products/${product.id}'">
            <h2>${product.name}</h2>
            <p>${product.description}</p>
            <p><strong>$${product.price}</strong></p>
          </div>
        `;
        });
        document.getElementById("app").innerHTML = html;
      });

      router.register("products", (id) => {
        if (id) {
          const product = products.find((p) => p.id === parseInt(id));
          if (product) {
            document.getElementById("app").innerHTML = `
            <a href="#/products" style="color: blue; text-decoration: none;">← Back to Products</a>
            <h1>${product.name}</h1>
            <p>${product.description}</p>
            <p><strong>Price: $${product.price}</strong></p>
            <button style="padding: 10px 20px; background: #667eea; color: white; border: none; border-radius: 4px; cursor: pointer;">Add to Cart</button>
          `;
          }
        }
      });

      router.register("about", () => {
        document.getElementById("app").innerHTML = `
        <h1>ℹ️ About Us</h1>
        <p>We are a modern e-commerce store built with vanilla JavaScript and hash-based routing.</p>
        <p>Our SPA provides a fast, smooth shopping experience.</p>
      `;
      });
    </script>
  </body>
</html>
```

---

## 9. ข้อดี & ข้อเสีย Hash Routing

### ✅ ข้อดี

| ข้อดี                   | ลักษณะ                          |
| ----------------------- | ------------------------------- |
| **ง่ายต่อการใช้**       | ไม่ต้อง server config           |
| **ใช้ได้ทั้งเดสก์ท็อป** | ใช้ได้กับไฟล์ local             |
| **Back button works**   | เบราว์เซอร์สนับสนุนโดยอัตโนมัติ |
| **SEO-friendly URLs**   | เหมาะกับ JS framework           |

### ❌ ข้อเสีย

| ข้อเสีย                        | ปัญหา                       |
| ------------------------------ | --------------------------- |
| **SEO ยาก**                    | Search engine ยากจนเข้าหน้า |
| **URL ไม่สะอาด**               | `#/page` แทน `/page`        |
| **Server-side routing ไม่ได้** | ทำเฉพาะ client-side         |

---

## 10. สรุป Hash-Based Routing

- **Hash** (`#/`) ใช้ระบุ route ใน SPA
- **hashchange event** ฟังการเปลี่ยน hash
- **URL parsing** แยก path และ parameters
- **Router class** ช่วยจัดการ routes
- **Browser history** สนับสนุน back/forward
- **ข้อดี**: ง่าย, ไม่ต้อง server config
- **ข้อเสีย**: URL ไม่สะอาด, SEO ยาก
