⚡ JavaScript กับ HTML

บทนำ - JavaScript ทำให้ Web มีชีวิต

JavaScript คือภาษาโปรแกรมที่ทำให้ HTML page เต็มไปด้วยชีวิต (interactive) โดยไม่ต้อง refresh หน้า ระบบสามารถตอบสนองต่อการกระทำของผู้ใช้ทันที:

ความสามารถหลักของ JavaScript

ความสามารถ คำอธิบาย ตัวอย่าง
DOM Manipulation เปลี่ยนเนื้อหา สไตล์ attributes ของ HTML elements เปลี่ยนข้อความ, เปลี่ยนสี, ซ่อน/แสดง elements
Event Handling ตอบสนองต่อการกระทำของผู้ใช้ (click, input, mouse) ปุ่ม click, การพิมพ์, การไล่หา
Form Validation ตรวจสอบข้อมูลฟอร์มก่อนส่งไปยัง server ตรวจสอบ email, password, ข้อมูลที่จำเป็น
Real-time Updates อัปเดตข้อมูลแบบ real-time โดยไม่ reload Live search, character counter, todo list
Animations & Effects สร้างเอฟเฟกต์ transitions และ animations Fade in/out, slide, toggle menu
API Communication สื่อสารกับ server โดยใช้ Fetch API ดึงข้อมูลจาก backend, ส่งข้อมูล
💡 ข้อสำคัญ: JavaScript รันฝั่ง client (ใน browser) ไม่ใช่ฝั่ง server เหมือน PHP/Python ดังนั้นผู้ใช้สามารถเห็น source code ได้

วิธีการเขียน JavaScript - 3 วิธี

JavaScript สามารถเขียนได้ 3 วิธี แต่ละวิธีมีข้อดี ข้อเสีย ลำดับความเหมาะสม:

1. Inline JavaScript (ไม่แนะนำ) ❌

Inline หมายถึง เขียน JavaScript โดยตรงในแอตทริบิวต์ HTML เช่น onclick:

<!-- HTML -->
<button onclick="alert('Hello!')">Click me</button>
<a href="javascript:alert('Clicked!)">Link</a>

<!-- ปัญหา: -->
<!-- 1. ผสม HTML และ JavaScript เข้าด้วยกัน (ยากต่อการรักษา) -->
<!-- 2. Code ซ้ำได้ถ้าต้องใช้ onclick กับหลายปุ่ม -->
<!-- 3. ยากต่อการ debug และ test -->
<!-- 4. ไม่ได้มาตรฐาน (best practice) -->
⚠️ หลีกเลี่ยง: Inline JavaScript ไม่ใช่วิธีการที่แนะนำ ยกเว้นกรณีพิเศษ

2. Internal JavaScript (ใช้ได้) ⚠️

Internal หมายถึง เขียน JavaScript ในแท็ก <script> ภายในไฟล์ HTML เดียวกัน:

<!DOCTYPE html>
<html>
  <head>
    <title>My Page</title>
  </head>
  <body>
    <h1>Welcome</h1>
    <button id="myBtn">Click me</button>

    <!-- JavaScript ที่นี่ (ท้าย body) -->
    <script>
      // JavaScript code ที่นี่
      const btn = document.getElementById("myBtn");
      btn.addEventListener("click", () => {
        alert("Hello from Internal JS!");
      });
    </script>
  </body>
</html>
✅ ข้อดี:
  • ทำงานได้ดี เหมาะสำหรับ page ที่ไม่ซับซ้อน
  • เห็นการเชื่อมโยง HTML และ JS อย่างชัดเจน
  • ไม่ต้องจัดการไฟล์หลายไฟล์
❌ ข้อเสีย:
  • HTML และ JS ผสมกัน ยากต่อ maintenance
  • ไม่สามารถใช้ code เดียวกันในหน้าอื่นได้
  • Browser ต้องเรียนรู้ script ทุกครั้งที่โหลด page

3. External JavaScript (แนะนำ) ✅

External หมายถึง เขียน JavaScript ในไฟล์แยกต่างหาก (.js file) แล้ว link เข้ามา:

📄 index.html:

<!DOCTYPE html>
<html>
  <head>
    <title>My Page</title>
  </head>
  <body>
    <h1>Welcome</h1>
    <button id="myBtn">Click me</button>

    <!-- Link ไฟล์ JavaScript ที่ท้าย body -->
    <script src="script.js" defer></script>
    <!-- 
      defer = รอให้ HTML โหลดเสร็จก่อนรัน script
      ทำให้ page โหลดเร็วขึ้น
    -->
  </body>
</html>

📄 script.js (ไฟล์ JavaScript แยก):

// script.js
const btn = document.getElementById("myBtn");

btn.addEventListener("click", () => {
  alert("Hello from External JS!");
  console.log("Button clicked!");
});
✅ ข้อดี:
  • HTML และ JS แยกกัน โค้ดสะอาด และ organized
  • สามารถใช้ script.js ในหลาย HTML pages
  • Browser cache ไฟล์ .js ได้ ทำให้ page โหลดเร็วขึ้น
  • ง่ายต่อ debugging และ testing
  • เป็นมาตรฐาน (best practice) ที่มืองานปกติใช้
❌ ข้อเสีย:
  • ต้องจัดการไฟล์หลายไฟล์
  • ต้องระวัง path ของไฟล์ .js
  • ต้องใช้ web server เพื่อทดสอบ (localhost)
🎯 สรุป: ใช้ External JavaScript เป็นประจำ เพราะ:
  • 🏭 Professional: ใช้ใน production ทั่วไป
  • 📦 Reusable: ใช้ในหลาย pages
  • ⚡ Performance: Browser cache ไฟล์
  • 🧹 Maintainable: โค้ดสะอาดและจัดเก็บดี

DOM Manipulation - การจัดการเนื้อหา HTML

DOM (Document Object Model) คือ tree structure ของ HTML elements ที่ JavaScript สามารถเข้าถึงและแก้ไขได้ DOM Manipulation ทำให้เราสามารถ:

1. การเลือก Elements - Selection

ก่อนแก้ไขใด ๆ ต้องเลือก element ก่อน มีหลายวิธี:

<!-- HTML -->
<div id="header" class="container">
  <h1>Welcome</h1>
  <p class="text">Hello World</p>
  <p class="text">Hello JavaScript</p>
</div>
<p id="footer" class="text highlight">Footer</p>

⭐ วิธีเลือก Elements (เรียงลำดับความเหมาะสม):

วิธี ไวยากรณ์ ผลลัพธ์ หมายเหตุ
querySelector document.querySelector(".class") Element แรกที่ตรง ใช้ CSS selector, ใช้ได้ทั่วไป
querySelectorAll document.querySelectorAll("p") NodeList ทั้งหมด ได้หลาย elements ต้อง loop
getElementById document.getElementById("myId") Element หนึ่ง เก่า แต่ใช้ยาว
getElementsByClassName document.getElementsByClassName("class") HTMLCollection เก่า ต้องจำ class name ทั้งหมด
getElementsByTagName document.getElementsByTagName("p") HTMLCollection เก่า ใช้ tag name

💡 ตัวอย่างจริง:

<script>
  // 1. querySelector - แนะนำ (ใช้ CSS selector)
  const header = document.querySelector("#header");
  const firstPara = document.querySelector(".text");
  const heading = document.querySelector("#header h1");

  // 2. querySelectorAll - ได้หลาย elements
  const allParas = document.querySelectorAll(".text"); // NodeList [p, p, p]
  allParas.forEach(p => {
    console.log(p.textContent);
  });

  // 3. getElementById - เก่า แต่เร็ว
  const footer = document.getElementById("footer");

  // 4. getElementsByClassName - เก่า
  const textElements = document.getElementsByClassName("text");

  // 5. getElementsByTagName - เก่า
  const allPs = document.getElementsByTagName("p");

  console.log("querySelector result:", firstPara);
  console.log("querySelectorAll result:", allParas);
</script>
✅ Best Practice: ใช้ querySelector / querySelectorAll เพราะ:
  • ใช้ CSS selectors (คุ้นเคยกับ CSS แล้ว)
  • Flexible: ID, class, tag, attribute ได้ทั้งหมด
  • โค้ดสั้นและอ่านง่าย

2. การแก้ไขเนื้อหา - Modify Content

มี 4 วิธีเปลี่ยนเนื้อหาข้อความและ HTML:

<!-- HTML -->
<h1 id="title">Old Title</h1>
<p id="description">Old description</p>
<input id="myInput" type="text" value="Old value">
<button id="updateBtn">Update Content</button>

<script>
  const updateBtn = document.getElementById("updateBtn");

  updateBtn.addEventListener("click", () => {
    // 1. textContent - เปลี่ยนเนื้อหาเป็น text (ปลอดภัย)
    //    ❌ ไม่รับ HTML tags
    document.getElementById("title").textContent = "New Title";
    console.log(document.getElementById("title").textContent); // "New Title"

    // 2. innerText - เหมือน textContent แต่ต้องการ rendering
    //    ใช้ textContent ดีกว่า
    document.getElementById("description").innerText = "New description";

    // 3. innerHTML - เปลี่ยนเนื้อหา + รับ HTML tags (อันตรายถ้าได้ user input!)
    //    ⚠️ ระวัง XSS vulnerability!
    document.getElementById("description").innerHTML = 
      "<strong>Bold text</strong> <em>Italic</em>";

    // 4. value - สำหรับ form elements (input, textarea, select)
    //    ใช้เมื่อต้องการเปลี่ยนค่าที่อยู่ในตัวเลือก
    const input = document.querySelector("#myInput");
    input.value = "New value";
    console.log(input.value); // "New value"
  });
</script>
✅ ใช้ textContent
// ปลอดภัยและเร็ว
element.textContent = "Hello";
❌ ระวัง innerHTML
// อันตราย! ถ้า userInput = "<img src=x onerror=alert('XSS')>"
element.innerHTML = userInput; // จะรัน JavaScript!

// ปลอดภัย:
element.textContent = userInput; // แสดงเป็น text ธรรมชาติ

3. การแก้ไขโครงสร้าง - Create, Add, Remove Elements

สร้าง elements ใหม่ เพิ่มลง DOM หรือลบออก:

<!-- HTML -->
<ul id="list">
  <li>Item 1</li>
  <li>Item 2</li>
</ul>
<button id="addBtn">Add Item</button>

<script>
  const addBtn = document.getElementById("addBtn");
  let itemCount = 2;

  // ===== สร้าง (Create) =====
  addBtn.addEventListener("click", () => {
    // 1. createElement - สร้าง element ใหม่ (ยังไม่อยู่ใน DOM)
    const newItem = document.createElement("li");

    // 2. ตั้งค่าเนื้อหา
    itemCount++;
    newItem.textContent = `Item ${itemCount}`;
    newItem.style.color = "blue";

    // 3. appendChild - เพิ่ม element ลงไปที่ท้ายสุด
    document.getElementById("list").appendChild(newItem);
  });

  // ===== ลบ (Remove) =====
  // วิธี 1: remove() - ลบ element ออกจาก DOM
  const firstItem = document.querySelector("li");
  firstItem.remove(); // ไม่มี element นี้อีกแล้ว

  // วิธี 2: removeChild - ลบ child element
  const parent = document.getElementById("list");
  if (parent.firstChild) {
    parent.removeChild(parent.firstChild);
  }

  // ===== เพิ่มที่ตำแหน่งอื่น (Insert) =====
  // insertBefore - ใส่ก่อนหน้า element ที่ระบุ
  const newFirst = document.createElement("li");
  newFirst.textContent = "New First Item";
  parent.insertBefore(newFirst, parent.firstChild);

  // ===== แทนที่ (Replace) =====
  const replacement = document.createElement("li");
  replacement.textContent = "Replaced Item";
  const oldItem = document.querySelector("li");
  oldItem.replaceWith(replacement);
</script>

4. การแก้ไข CSS Classes - classList

ใช้ classList เพื่อเพิ่ม/ลบ/สลับ CSS classes:

<!-- HTML -->
<div id="box" class="box">Box</div>
<button id="toggleBtn">Toggle Style</button>

<style>
  .box { 
    width: 100px; 
    height: 100px; 
    background: blue; 
    transition: all 0.3s ease;
  }
  .highlight { background: red; }
  .big { width: 200px; height: 200px; }
  .hidden { display: none; }
</style>

<script>
  const box = document.getElementById("box");
  const btn = document.getElementById("toggleBtn");

  // 1. classList.add - เพิ่ม class ใหม่
  box.classList.add("highlight");
  console.log(box.className); // "box highlight"

  // 2. classList.remove - ลบ class ออก
  box.classList.remove("highlight");
  console.log(box.className); // "box"

  // 3. classList.toggle - สลับ class (มี ให้ลบ, ไม่มี ให้เพิ่ม)
  btn.addEventListener("click", () => {
    box.classList.toggle("highlight");
    // Click ครั้ง 1: เพิ่ม highlight
    // Click ครั้ง 2: ลบ highlight
    // Click ครั้ง 3: เพิ่ม highlight
    // ... สลับไปมา
  });

  // 4. classList.contains - ตรวจสอบว่ามี class หรือไม่
  if (box.classList.contains("highlight")) {
    console.log("Box has highlight class");
  } else {
    console.log("Box does not have highlight class");
  }

  // 5. classList.replace - แทนที่ class
  box.classList.replace("box", "box big");

  // ❌ ระวัง: className เปลี่ยน class ทั้งหมด (อันตราย!)
  // box.className = "box big";  // ทำให้ class เก่าหมดไป
</script>
💡 ใช้ classList เสมอ เพราะ:
  • ✅ classList.add/remove/toggle เปลี่ยนเฉพาะ class ที่ต้อง
  • ❌ className เปลี่ยน class ทั้งหมด (เสี่ยงสูญเสีย class เก่า)

5. การแก้ไข Attributes - เปลี่ยนแอตทริบิวต์

แอตทริบิวต์คือ meta information ของ element (src, href, alt, data-*, etc.):

<!-- HTML -->
<img id="myImage" src="old.jpg" alt="Old Image" title="Image">
<a id="myLink" href="#">Click</a>
<input id="myInput" type="text" disabled>

<script>
  const img = document.getElementById("myImage");
  const link = document.getElementById("myLink");
  const input = document.getElementById("myInput");

  // ===== วิธีทั่วไป (ใช้กับ attribute ใด ๆ) =====
  
  // 1. setAttribute - ตั้งค่า attribute
  img.setAttribute("src", "new.jpg");
  img.setAttribute("alt", "New Image");
  img.setAttribute("data-category", "landscape");

  // 2. getAttribute - อ่าน attribute
  console.log(img.getAttribute("src"));      // "new.jpg"
  console.log(img.getAttribute("alt"));      // "New Image"
  console.log(img.getAttribute("data-category")); // "landscape"

  // 3. hasAttribute - ตรวจสอบว่ามี attribute หรือไม่
  if (img.hasAttribute("alt")) {
    console.log("Image has alt attribute");
  }

  // 4. removeAttribute - ลบ attribute ออก
  img.removeAttribute("title");
  input.removeAttribute("disabled");

  // ===== Shortcut properties (ใช้ได้เร็ว) =====
  
  // สำหรับ common attributes ใช้ shortcut properties ได้:
  link.href = "https://example.com";
  img.src = "another.jpg";
  input.value = "New value";
  input.disabled = true;  // สำหรับ boolean attributes

  // ===== ตัวอย่างจริง =====
  // เปลี่ยน link attributes
  link.setAttribute("target", "_blank");
  link.setAttribute("rel", "noopener");

  // ใช้ data attributes (เก็บข้อมูล custom)
  const element = document.createElement("div");
  element.setAttribute("data-user-id", "123");
  element.setAttribute("data-action", "delete");
  console.log(element.getAttribute("data-user-id")); // "123"
</script>

6. การแก้ไข CSS Styles - เปลี่ยนสีและลักษณะ

มี 2 วิธีเปลี่ยน CSS style ผ่าน JavaScript:

<!-- HTML -->
<div id="box">Box</div>
<button id="styleBtn">Change Style</button>

<script>
  const box = document.getElementById("box");
  const btn = document.getElementById("styleBtn");

  // ===== วิธี 1: style property (ครั้งละ 1 property) =====
  box.style.width = "200px";
  box.style.height = "200px";
  box.style.backgroundColor = "blue";  // ⚠️ camelCase!
  box.style.border = "2px solid black";
  box.style.borderRadius = "10px";
  box.style.transition = "all 0.3s ease";

  // หมายเหตุ: CSS ใช้ kebab-case แต่ JS ใช้ camelCase
  // CSS: background-color  →  JS: backgroundColor
  // CSS: border-radius     →  JS: borderRadius
  // CSS: font-size         →  JS: fontSize

  // ===== วิธี 2: cssText (เปลี่ยน CSS หลายตัวพร้อมกัน) =====
  btn.addEventListener("click", () => {
    box.style.cssText = `
      width: 300px;
      height: 300px;
      background-color: red;
      border: 3px solid green;
      border-radius: 15px;
      box-shadow: 0 4px 8px rgba(0,0,0,0.2);
    `;
  });

  // ===== ตัวอย่างเพิ่มเติม =====
  // เปลี่ยนสี
  box.style.color = "white";

  // เปลี่ยนขนาด font
  box.style.fontSize = "20px";
  box.style.fontWeight = "bold";

  // เปลี่ยน padding/margin
  box.style.padding = "20px";
  box.style.margin = "10px auto";

  // Display type
  box.style.display = "flex";
  box.style.justifyContent = "center";
  box.style.alignItems = "center";
</script>
⚠️ สำคัญ: CSS ใช้ kebab-case แต่ JavaScript ใช้ camelCase
  • ❌ box.style.background-color (ผิด)
  • ✅ box.style.backgroundColor (ถูก)

Event Handling - ตอบสนองต่อการกระทำผู้ใช้

Event คือ การกระทำของผู้ใช้ (click, type, scroll, etc.) เมื่อ event เกิดขึ้น เราสามารถรัน JavaScript code ได้ทันที:

1. ประเภท Events ทั่วไป

📋 ตารางเปรียบเทียบ Events:

ประเภท Event คำอธิบาย ตัวอย่าง
Mouse Events
click คลิก element ปุ่ม, link, div
dblclick double-click element เลือกข้อความ
mouseover / mouseout เมื่อเมาส์ผ่าน element Hover effects
mouseenter / mouseleave เหมือน mouseover แต่ไม่ bubble ตัวอักษรที่มีขนาดกว่า
Keyboard Events
keydown เมื่อกดปุ่ม (ยังไม่ปล่อย) ตัวอักษรลาดเคราะห์
keyup เมื่อปล่อยปุ่ม ตรวจสอบการกด Enter
keypress เมื่อพิมพ์ตัวอักษร (เก่า) ส่ง form ด้วย Enter
Form Events
input เมื่อพิมพ์ในช่องข้อมูล (ทันที) Live search, character counter
change เมื่อเปลี่ยนค่า แล้วหลุดโฟกัส เมนูเลือก (select)
focus / blur ได้/เสีย focus ไฮไลต์ช่องข้อมูล
submit ส่ง form ตรวจสอบข้อมูล ก่อนส่ง
Document/Window Events
load เมื่อ page/image โหลดเสร็จ ตั้งค่าเริ่มต้น
scroll เมื่อ scroll page Lazy loading, infinite scroll
resize เมื่อ resize window Responsive design

💡 ตัวอย่างจริง:

<!-- HTML -->
<button id="btn">Click me</button>
<input id="input" type="text" placeholder="Type...">
<div id="box">Hover me</div>
<select id="select">
  <option value="">Choose...</option>
  <option value="option1">Option 1</option>
  <option value="option2">Option 2</option>
</select>

<script>
  // ===== Mouse Events =====
  document.getElementById("btn").addEventListener("click", (event) => {
    console.log("✅ Button clicked!");
    console.log("target:", event.target); // องค์ประกอบที่ถูกคลิก
  });

  // ===== Keyboard Events =====
  document.getElementById("input").addEventListener("input", (event) => {
    console.log("🔤 Input value:", event.target.value); // ทันที ทุกครั้งที่พิมพ์
  });

  document.getElementById("input").addEventListener("change", (event) => {
    console.log("✏️ Changed to:", event.target.value); // หลังจากหลุดโฟกัส
  });

  document.getElementById("input").addEventListener("keydown", (event) => {
    console.log("📍 Key down:", event.key); // "a", "Enter", "Backspace", etc.
    if (event.key === "Enter") {
      console.log("👉 User pressed Enter!");
    }
  });

  // ===== Focus Events =====
  document.getElementById("input").addEventListener("focus", () => {
    console.log("🎯 Input focused");
  });

  document.getElementById("input").addEventListener("blur", () => {
    console.log("❌ Input blurred");
  });

  // ===== Hover Events =====
  document.getElementById("box").addEventListener("mouseover", () => {
    console.log("🖱️ Mouse over box");
    event.target.style.backgroundColor = "yellow";
  });

  document.getElementById("box").addEventListener("mouseout", () => {
    console.log("🖱️ Mouse out of box");
    event.target.style.backgroundColor = "";
  });

  // ===== Form Select Change =====
  document.getElementById("select").addEventListener("change", (event) => {
    console.log("Selected:", event.target.value);
  });

  // ===== Window Events =====
  window.addEventListener("scroll", () => {
    console.log("Scrolling...");
  });

  window.addEventListener("resize", () => {
    console.log("Window resized to:", window.innerWidth, "x", window.innerHeight);
  });
</script>

2. addEventListener - วิธีที่ถูก ✅

addEventListener คือวิธี modern และถูก วิธี เพราะสามารถลบ listener ได้:

<!-- HTML -->
<button id="myBtn">Click me</button>
<button id="removeBtn">Remove Listener</button>

<script>
  const btn = document.getElementById("myBtn");
  const removeBtn = document.getElementById("removeBtn");

  // ✅ ดี - สามารถลบ listener ได้
  function handleClick(event) {
    console.log("Button clicked!");
  }

  // เพิ่ม listener
  btn.addEventListener("click", handleClick);

  // ลบ listener (ต้องใช้ชื่อฟังก์ชันเดียวกัน)
  removeBtn.addEventListener("click", () => {
    btn.removeEventListener("click", handleClick);
    console.log("Listener removed!");
  });

  // ✅ Arrow function (สะดวก แต่ลบไม่ได้)
  btn.addEventListener("dblclick", () => {
    console.log("Double clicked!");
  });

  // ===== Event Object =====
  btn.addEventListener("click", (event) => {
    console.log(event.type);       // "click"
    console.log(event.target);     // element ที่ถูกคลิก
    console.log(event.clientX);    // พิกัด X ของเมาส์
    console.log(event.clientY);    // พิกัด Y ของเมาส์
    console.log(event.key);        // สำหรับ keyboard events
  });
</script>
⚠️ หลีกเลี่ยง: onclick / onmouseover / onkeydown attributes
<!-- ❌ ไม่ดี - Inline event handlers (เก่า) -->
<button onclick="alert('Hello')">Click</button>

<!-- ✅ ดี - addEventListener (modern) -->
<button id="btn">Click</button>
<script>
  document.getElementById("btn").addEventListener("click", () => {
    alert("Hello");
  });
</script>

3. Event Delegation - สำหรับ Dynamic Elements

Event Delegation ใช้เมื่อต้องจัดการ events สำหรับ elements ที่สร้างภายหลัง (dynamic):

<!-- HTML -->
<ul id=\"list\">
  <li>Item 1</li>
  <li>Item 2</li>
  <li>Item 3</li>
</ul>
<button id=\"addBtn\">Add Item</button>

<script>
  // ❌ ไม่ดี - เพิ่ม listener ให้กับทุกๆ item
  // document.querySelectorAll(\"li\").forEach(li => {
  //   li.addEventListener(\"click\", handler);
  // });
  // ปัญหา: item ใหม่ที่สร้างภายหลังจะไม่มี listener

  // ✅ ดี - ใช้ Event Delegation
  const list = document.getElementById("list");

  // เพิ่ม listener ให้กับ parent (ul)
  list.addEventListener("click", (event) => {
    // ตรวจสอบว่า element ที่ถูกคลิกเป็น li หรือไม่
    if (event.target.tagName === "LI") {
      console.log("Item clicked:", event.target.textContent);
      event.target.style.backgroundColor = "yellow";
    }
  });

  // สร้าง item ใหม่
  document.getElementById("addBtn").addEventListener("click", () => {
    const newItem = document.createElement("li");
    newItem.textContent = `Item ${document.querySelectorAll("li").length + 1}`;
    list.appendChild(newItem);
    // ไม่ต้องเพิ่ม listener ใหม่ เพราะ event delegation จัดการให้
  });
</script>
✅ ข้อดีของ Event Delegation:
  • 📦 Elements ใหม่อัตโนมัติมี listener (ไม่ต้องทำซ้ำ)
  • ⚡ ประสิทธิภาพดี (listener เดียว แทนหลาย ๆ listener)
  • 🧹 โค้ดสะอาด และ maintainable

Form Validation - ตรวจสอบข้อมูลก่อนส่ง

Form Validation ตรวจสอบให้แน่ใจว่าผู้ใช้ใส่ข้อมูลถูกต้องก่อนส่งไปยัง server วิธีนี้ช่วยป้องกัน invalid data และให้ user experience ที่ดี:

ประเภท Validation

ประเภท คำอธิบาย ตัวอย่าง
HTML Validation ใช้ attributes เช่น required, type="email" <input type="email" required>
Client Validation ตรวจสอบด้วย JavaScript (ก่อนส่ง server) ตรวจสอบความยาว, format, etc.
Server Validation 🔑 ตรวจสอบฝั่ง backend (ที่เหมาะที่สุด) Database validation, business rules
⚠️ สำคัญ: ตรวจสอบฝั่ง JavaScript ได้ยังไม่พอ! ผู้ใช้สามารถบิด code ได้ ต้องตรวจสอบฝั่ง server ด้วยเสมอ

ตัวอย่าง: Contact Form Validation

<!-- HTML -->
<form id="contactForm">
  <div class="form-group">
    <label for="name">Name:</label>
    <input type="text" id="name" name="name" placeholder="Enter your name">
    <span class="error" id="nameError"></span>
  </div>

  <div class="form-group">
    <label for="email">Email:</label>
    <input type="email" id="email" name="email" placeholder="your@email.com">
    <span class="error" id="emailError"></span>
  </div>

  <div class="form-group">
    <label for="message">Message:</label>
    <textarea id="message" name="message" rows="5" placeholder="Your message..."></textarea>
    <span class="error" id="messageError"></span>
  </div>

  <div class="form-group">
    <label for="age">Age:</label>
    <input type="number" id="age" name="age" min="18" max="100">
    <span class="error" id="ageError"></span>
  </div>

  <button type="submit">Submit</button>
  <button type="reset">Clear</button>
</form>

<style>
  .form-group {
    margin: 15px 0;
  }

  label {
    display: block;
    margin-bottom: 5px;
    font-weight: bold;
  }

  input, textarea {
    width: 100%;
    padding: 8px;
    border: 1px solid #ccc;
    border-radius: 4px;
  }

  input.error-input, textarea.error-input {
    border: 2px solid #e74c3c;
    background-color: #fadbd8;
  }

  .error {
    color: #e74c3c;
    font-size: 0.9em;
    display: block;
    margin-top: 3px;
  }

  button {
    padding: 10px 20px;
    margin: 5px;
    background: #3498db;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
  }

  button:hover {
    background: #2980b9;
  }
</style>

<script>
  const form = document.getElementById(\"contactForm\");

  // ===== Helper functions =====
  function showError(inputId, errorId, message) {
    const input = document.getElementById(inputId);
    const error = document.getElementById(errorId);
    error.textContent = message;
    input.classList.add(\"error-input\");
  }

  function clearError(inputId, errorId) {
    const input = document.getElementById(inputId);
    const error = document.getElementById(errorId);
    error.textContent = "";
    input.classList.remove("error-input");
  }

  // ===== Validation functions =====
  function validateName() {
    const name = document.getElementById("name").value.trim();
    if (name === "") {
      showError("name", "nameError", "❌ Name is required");
      return false;
    }
    if (name.length < 3) {
      showError("name", "nameError", "❌ Name must be at least 3 characters");
      return false;
    }
    if (name.length > 50) {
      showError("name", "nameError", "❌ Name must be less than 50 characters");
      return false;
    }
    clearError("name", "nameError");
    return true;
  }

  function validateEmail() {
    const email = document.getElementById("email").value.trim();
    // Regular expression for email validation
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    
    if (email === "") {
      showError("email", "emailError", "❌ Email is required");
      return false;
    }
    if (!emailRegex.test(email)) {
      showError("email", "emailError", "❌ Please enter a valid email");
      return false;
    }
    clearError("email", "emailError");
    return true;
  }

  function validateMessage() {
    const message = document.getElementById("message").value.trim();
    if (message === "") {
      showError("message", "messageError", "❌ Message is required");
      return false;
    }
    if (message.length < 10) {
      showError("message", "messageError", "❌ Message must be at least 10 characters");
      return false;
    }
    clearError("message", "messageError");
    return true;
  }

  function validateAge() {
    const age = document.getElementById("age").value;
    if (age === "") {
      showError("age", "ageError", "❌ Age is required");
      return false;
    }
    if (age < 18) {
      showError("age", "ageError", "❌ You must be at least 18");
      return false;
    }
    if (age > 100) {
      showError("age", "ageError", "❌ Age must be less than 100");
      return false;
    }
    clearError("age", "ageError");
    return true;
  }

  // ===== Real-time validation (ตรวจสอบขณะพิมพ์) =====
  document.getElementById("name").addEventListener("blur", validateName);
  document.getElementById("email").addEventListener("blur", validateEmail);
  document.getElementById("message").addEventListener("blur", validateMessage);
  document.getElementById("age").addEventListener("blur", validateAge);

  // ===== Submit handler =====
  form.addEventListener("submit", (event) => {
    event.preventDefault(); // หยุดการส่ง form ปกติ

    // ตรวจสอบทั้งหมด
    const isNameValid = validateName();
    const isEmailValid = validateEmail();
    const isMessageValid = validateMessage();
    const isAgeValid = validateAge();

    // ถ้าทั้งหมดถูกต้อง ส่ง data
    if (isNameValid && isEmailValid && isMessageValid && isAgeValid) {
      console.log("✅ Form is valid! Sending...");
      
      // สร้าง object data
      const formData = {
        name: document.getElementById("name").value,
        email: document.getElementById("email").value,
        message: document.getElementById("message").value,
        age: document.getElementById("age").value
      };

      console.log("Form data:", formData);

      // ส่งไปยัง server (จำลอง)
      // fetch('/api/contact', {
      //   method: 'POST',
      //   headers: { 'Content-Type': 'application/json' },
      //   body: JSON.stringify(formData)
      // })
      // .then(response => response.json())
      // .then(data => console.log('Success:', data))
      // .catch(error => console.log('Error:', error));

      alert("✅ Form submitted successfully!");
      form.reset(); // ล้างข้อมูล form
    } else {
      alert("❌ Please fix the errors");
    }
  });
</script>

Animation & Effects - เอฟเฟกต์และ Animation

JavaScript สามารถสร้าง animations ได้โดย toggle CSS classes หรือเปลี่ยน CSS properties ในรูปแบบ smooth transitions:

1. Toggle Menu - ซ่อน/แสดง Menu

<!-- HTML -->
<button id="menuBtn" class="menu-toggle">☰ Menu</button>
<nav id="menu" class="menu hidden">
  <a href="#home">🏠 Home</a>
  <a href="#about">ℹ️ About</a>
  <a href="#services">🔧 Services</a>
  <a href="#contact">📧 Contact</a>
</nav>

<style>
  .menu {
    background: #2c3e50;
    color: white;
    padding: 20px;
    transition: all 0.3s ease; /* smooth animation */
    max-height: 300px;
    overflow: hidden;
    opacity: 1;
  }

  .menu.hidden {
    max-height: 0;
    padding: 0 20px;
    opacity: 0;
  }

  .menu a {
    display: block;
    padding: 10px 0;
    text-decoration: none;
    color: white;
    border-bottom: 1px solid rgba(255,255,255,0.2);
  }

  .menu a:hover {
    background: rgba(255,255,255,0.1);
    padding-left: 10px;
  }
</style>

<script>
  const menuBtn = document.getElementById("menuBtn");
  const menu = document.getElementById("menu");

  menuBtn.addEventListener("click", () => {
    menu.classList.toggle("hidden");
    menuBtn.classList.toggle("active");
  });
</script>

2. Fade In/Out - ค่อยๆ ปรากฏและหายไป

<!-- HTML -->
<div id="fadeBox" class="fade-box">
  Fade in and out effect
</div>
<button id="fadeBtn">Toggle Fade</button>

<style>
  .fade-box {
    width: 200px;
    height: 100px;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 10px;
    margin: 20px 0;
    opacity: 1;
    transition: opacity 0.5s ease;
  }

  .fade-box.fade-out {
    opacity: 0;
    pointer-events: none; /* ไม่สามารถ interact ได้เมื่อ fade out */
  }
</style>

<script>
  const box = document.getElementById("fadeBox");
  const fadeBtn = document.getElementById("fadeBtn");

  let isFadedOut = false;

  fadeBtn.addEventListener("click", () => {
    if (isFadedOut) {
      box.classList.remove("fade-out");
      fadeBtn.textContent = "Fade Out";
    } else {
      box.classList.add("fade-out");
      fadeBtn.textContent = "Fade In";
    }
    isFadedOut = !isFadedOut;
  });
</script>

3. Slide Animation - เลื่อนเข้าออก

<!-- HTML -->
<button id="slideBtn">Toggle Content</button>
<div id="slideBox" class="slide-content">
  <h3>Hidden Content</h3>
  <p>This content slides up and down smoothly!</p>
  <ul>
    <li>Item 1</li>
    <li>Item 2</li>
    <li>Item 3</li>
  </ul>
</div>

<style>
  .slide-content {
    background: #ecf0f1;
    border-radius: 8px;
    margin-top: 20px;
    padding: 20px;
    max-height: 300px;
    overflow: hidden;
    transition: all 0.5s ease;
  }

  .slide-content.slide-up {
    max-height: 0;
    padding: 0 20px;
    opacity: 0;
  }
</style>

<script>
  const box = document.getElementById("slideBox");
  const btn = document.getElementById("slideBtn");

  btn.addEventListener(\"click\", () => {
    box.classList.toggle(\"slide-up\");
    btn.textContent = box.classList.contains(\"slide-up\") ? \"Show Content\" : \"Hide Content\";
  });
</script>

4. Color Animation - เปลี่ยนสี

<!-- HTML -->
<div id=\"colorBox\" class=\"color-box\"></div>
<button id=\"colorBtn\">Animate Color</button>

<style>
  .color-box {
    width: 150px;
    height: 150px;
    background: #3498db;
    border-radius: 10px;
    margin: 20px 0;
    transition: background-color 1s ease;
  }
</style>

<script>
  const colorBox = document.getElementById(\"colorBox\");
  const colorBtn = document.getElementById(\"colorBtn\");
  const colors = [\"#3498db\", \"#e74c3c\", \"#2ecc71\", \"#f39c12\", \"#9b59b6\"];
  let colorIndex = 0;

  colorBtn.addEventListener(\"click\", () => {
    colorIndex = (colorIndex + 1) % colors.length;
    colorBox.style.backgroundColor = colors[colorIndex];
  });
</script>
✅ Tips สำหรับ Animations:
  • 🎨 ใช้ CSS transitions สำหรับ animations (ไม่ใช้ setInterval/setTimeout)
  • ⚡ CSS animations ทำให้ browser optimize ได้ดีกว่า
  • 🎯 ใช้ JavaScript เพียง toggle CSS classes
  • 📱 ใช้ pointer-events: none เพื่อป้องกัน interaction ระหว่าง animation

Real-time Updates - อัปเดตทันที

Real-time Updates คือการอัปเดตข้อมูลแบบ live โดยไม่ต้อง refresh page ให้ user experience ที่ดีขึ้น เช่น live search, auto-complete, character counter:

1. Live Search - ค้นหาทันที

<!-- HTML -->
<input id=\"searchInput\" type=\"text\" placeholder=\"🔍 Search fruits...\" class=\"search-box\">
<ul id=\"results\" class=\"search-results\">
  <li>🍎 Apple</li>
  <li>🍌 Banana</li>
  <li>🍒 Cherry</li>
  <li>📅 Date</li>
  <li>🫐 Elderberry</li>
  <li>🍇 Fig</li>
  <li>🍇 Grape</li>
</ul>

<style>
  .search-box {
    width: 100%;
    padding: 10px;
    font-size: 16px;
    border: 2px solid #3498db;
    border-radius: 5px;
    margin-bottom: 10px;
  }

  .search-results {
    list-style: none;
    padding: 0;
    max-height: 300px;
    overflow-y: auto;
  }

  .search-results li {
    padding: 10px;
    background: #ecf0f1;
    margin: 5px 0;
    border-radius: 4px;
    cursor: pointer;
    transition: all 0.2s;
  }

  .search-results li:hover {
    background: #bdc3c7;
    transform: translateX(5px);
  }

  .search-results li.hidden {
    display: none;
  }

  .search-results li.highlight {
    background: #f39c12;
    color: white;
  }
</style>

<script>
  const searchInput = document.getElementById(\"searchInput\");
  const results = document.getElementById(\"results\");

  searchInput.addEventListener(\"input\", (event) => {
    const query = event.target.value.toLowerCase().trim();

    document.querySelectorAll(\"#results li\").forEach(li => {
      const text = li.textContent.toLowerCase();
      if (text.includes(query)) {
        li.classList.remove(\"hidden\");
        // Highlight matching text
        if (query) {
          li.classList.add(\"highlight\");
        } else {
          li.classList.remove(\"highlight\");
        }
      } else {
        li.classList.add(\"hidden\");
      }
    });
  });
</script>

2. Character Counter - นับจำนวนตัวอักษร

<!-- HTML -->
<textarea id=\"textarea\" placeholder=\"Type something...\" maxlength=\"100\" class=\"textarea-input\"></textarea>
<div class=\"char-counter\">
  Characters: <span id=\"count\">0</span><span class=\"max\">/100</span>
</div>

<style>
  .textarea-input {
    width: 100%;
    height: 100px;
    padding: 10px;
    border: 2px solid #3498db;
    border-radius: 5px;
    font-family: Arial, sans-serif;
    resize: vertical;
  }

  .char-counter {
    margin-top: 5px;
    font-size: 0.9em;
    color: #555;
  }

  .char-counter span {
    font-weight: bold;
    color: #3498db;
  }

  .char-counter.warning span {
    color: #f39c12;
  }

  .char-counter.danger span {
    color: #e74c3c;
  }

  .char-counter .max {
    color: #999;
  }
</style>

<script>
  const textarea = document.getElementById(\"textarea\");
  const count = document.getElementById(\"count\");
  const counter = document.querySelector(\".char-counter\");
  const maxLength = 100;

  textarea.addEventListener(\"input\", () => {
    const length = textarea.value.length;
    count.textContent = length;

    // เปลี่ยนสี ตามจำนวน character
    if (length >= maxLength) {
      counter.classList.remove(\"warning\");
      counter.classList.add(\"danger\");
    } else if (length >= maxLength * 0.8) {
      counter.classList.remove(\"danger\");
      counter.classList.add(\"warning\");
    } else {
      counter.classList.remove(\"warning\", \"danger\");
    }
  });
</script>

3. Todo List - จัดการ To-do

<!-- HTML -->
<div id=\"todoApp\" class=\"todo-app\">
  <h2>📋 My Todo List</h2>
  
  <div class=\"input-group\">
    <input id=\"todoInput\" type=\"text\" placeholder=\"Add a new todo...\" class=\"todo-input\">
    <button id=\"addBtn\" class=\"add-btn\">➕ Add</button>
  </div>

  <ul id=\"todoList\" class=\"todo-list\"></ul>

  <div class=\"stats\">
    Total: <span id=\"totalCount\">0</span> | 
    Done: <span id=\"doneCount\">0</span>
  </div>
</div>

<style>
  .todo-app {
    max-width: 500px;
    margin: 20px auto;
    background: #ecf0f1;
    padding: 20px;
    border-radius: 10px;
  }

  .input-group {
    display: flex;
    gap: 10px;
    margin-bottom: 20px;
  }

  .todo-input {
    flex: 1;
    padding: 10px;
    border: none;
    border-radius: 5px;
    font-size: 16px;
  }

  .add-btn {
    padding: 10px 20px;
    background: #3498db;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    font-weight: bold;
  }

  .add-btn:hover {
    background: #2980b9;
  }

  .todo-list {
    list-style: none;
    padding: 0;
  }

  .todo {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px;
    background: white;
    margin: 5px 0;
    border-radius: 5px;
    border-left: 4px solid #3498db;
  }

  .todo.completed {
    opacity: 0.6;
    text-decoration: line-through;
    border-left-color: #2ecc71;
  }

  .todo-text {
    flex: 1;
    cursor: pointer;
  }

  .todo-delete {
    background: #e74c3c;
    color: white;
    border: none;
    padding: 5px 10px;
    border-radius: 3px;
    cursor: pointer;
    font-size: 0.9em;
  }

  .todo-delete:hover {
    background: #c0392b;
  }

  .todo-checkbox {
    cursor: pointer;
    width: 20px;
    height: 20px;
  }

  .stats {
    margin-top: 20px;
    text-align: center;
    color: #555;
    font-size: 0.9em;
  }

  .stats span {
    font-weight: bold;
    color: #3498db;
  }
</style>

<script>
  const input = document.getElementById(\"todoInput\");
  const addBtn = document.getElementById(\"addBtn\");
  const todoList = document.getElementById(\"todoList\");
  const totalCount = document.getElementById(\"totalCount\");
  const doneCount = document.getElementById(\"doneCount\");

  function updateStats() {
    const todos = document.querySelectorAll(\".todo\");
    const completed = document.querySelectorAll(\".todo.completed\").length;
    totalCount.textContent = todos.length;
    doneCount.textContent = completed;
  }

  function addTodo() {
    const text = input.value.trim();
    if (text === \"\") return;

    const li = document.createElement(\"li\");
    li.className = \"todo\";
    li.innerHTML = `
      <input type=\"checkbox\" class=\"todo-checkbox\">
      <span class=\"todo-text\">${text}</span>
      <button class=\"todo-delete\">🗑️ Delete</button>
    `;

    // Mark as complete
    li.querySelector(\".todo-checkbox\").addEventListener(\"change\", () => {
      li.classList.toggle(\"completed\");
      updateStats();
    });

    // Delete
    li.querySelector(\".todo-delete\").addEventListener(\"click\", () => {
      li.remove();
      updateStats();
    });

    // Click text to toggle complete
    li.querySelector(\".todo-text\").addEventListener(\"click\", () => {
      li.querySelector(\".todo-checkbox\").click();
    });

    todoList.appendChild(li);
    input.value = \"\";
    updateStats();
  }

  // Add event listeners
  addBtn.addEventListener(\"click\", addTodo);

  // Enter key
  input.addEventListener(\"keypress\", (event) => {
    if (event.key === \"Enter\") {
      addTodo();
    }
  });

  updateStats(); // Initialize
</script>

Best Practices - วิธีที่ถูกต้อง

Best Practices ช่วยให้โค้ด maintainable, efficient, และ secure มากขึ้น:

1. ใช้ External JavaScript ✅

แยก JavaScript ออกไปในไฟล์ .js เพื่อเก็บโค้ดให้สะอาด:

❌ Internal Script (ไม่ดี)
<body>
  <h1>Title</h1>
  <script>
    // โค้ด JavaScript ยาว ๆ ที่นี่
  </script>
</body>
✅ External Script (ดี)
<body>
  <h1>Title</h1>
  <script src="app.js" defer></script>
</body>

2. ใช้ querySelector แทน getElementById

querySelector ใช้ได้ทั่วไป ใช้ CSS selector ที่คุ้นเคย:

❌ Old way (ต้องจำวิธีเก่า)
const el = document.getElementById("myId");
const els = document.getElementsByClassName("myClass");
const tags = document.getElementsByTagName("p");
✅ Modern way (ใช้ CSS selector)
const el = document.querySelector("#myId");
const els = document.querySelectorAll(".myClass");
const tags = document.querySelectorAll("p");
const nested = document.querySelector("#header h1");

3. Event Delegation สำหรับ Dynamic Elements

เมื่อต้องจัดการ elements ที่สร้างภายหลัง ให้ใช้ event delegation:

❌ ไม่ดี (ลืม elements ใหม่)
// ทุก li ที่มีตอนนี้จะมี listener
document.querySelectorAll("li").forEach(li => {
  li.addEventListener("click", handler);
});

// li ใหม่ที่สร้างภายหลังจะไม่มี listener!
✅ ดี (Event Delegation)
// เพิ่ม listener ที่ parent element
document.getElementById("list").addEventListener("click", (e) => {
  if (e.target.tagName === "LI") {
    console.log("Item clicked:", e.target.textContent);
  }
});

// li ใหม่ได้ listener อัตโนมัติ!

4. ป้องกัน XSS - Sanitize User Input

⚠️ สำคัญ: ใช้ textContent แทน innerHTML:

❌ Dangerous (XSS Vulnerability)
const userInput = "<img src=x onerror=alert('Hacked')>";
element.innerHTML = userInput;
// ❌ JavaScript จะรัน!
✅ Safe
const userInput = "<img src=x onerror=alert('Hacked')>";
element.textContent = userInput;
// ✅ แสดงเป็น text ธรรมชาติ

5. ใช้ const โดยค่าเริ่มต้น

ลำดับ: const > let > var (ไม่ใช้ var เลย):

// ❌ var มี scope issues
var button = document.getElementById("myBtn");

// ✅ const ไม่เปลี่ยนแปลง
const button = document.getElementById("myBtn");
const config = { timeout: 5000 };

// let เมื่อต้องเปลี่ยนค่า
let count = 0;
count++;

6. ตรวจสอบ Element ก่อนใช้

ป้องกัน error เมื่อ element ไม่มี:

❌ Error prone
const button = document.getElementById("myBtn");
button.addEventListener("click", handler);
// ❌ Error: Cannot read property 'addEventListener' of null
✅ Safe
const button = document.getElementById("myBtn");
if (button) {
  button.addEventListener("click", handler);
}

7. Debounce สำหรับ Heavy Operations

Debounce ช่วยลด number of function calls:

function debounce(func, delay) {
  let timeoutId;
  return function(...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func(...args), delay);
  };
}

// ✅ Debounce resize event
const handleResize = debounce(() => {
  console.log("Window resized");
}, 500);

window.addEventListener("resize", handleResize);

8. ใช้ Data Attributes สำหรับ Custom Data

ใช้ data-* attributes เพื่อเก็บ custom data:

<!-- HTML -->
<button data-action="delete" data-id="123">Delete</button>

<script>
  document.addEventListener("click", (e) => {
    if (e.target.hasAttribute("data-action")) {
      const action = e.target.dataset.action;
      const id = e.target.dataset.id;
      console.log(`Action: ${action}, ID: ${id}`);
    }
  });
</script>

9. Error Handling with try/catch

จัดการ error เพื่อให้ app ไม่ crash:

// ❌ ไม่ดี - ไม่มี error handling
const data = JSON.parse(userInput);

// ✅ ดี - มี error handling
try {
  const data = JSON.parse(userInput);
  console.log("Parsed:", data);
} catch (error) {
  console.error("Invalid JSON:", error.message);
}

10. ใช้ Template Literals

ใช้ backticks แทน string concatenation:

// ❌ ยากอ่าน
const html = "<div class='item'>" + name + "</div>";

// ✅ สะอาด
const html = `
  <div class="item">${name}</div>
`;
✅ สรุป Best Practices:
  • 📁 External JavaScript (แยก .js file)
  • 🔍 querySelector (CSS selectors)
  • 📌 Event Delegation (dynamic elements)
  • 🔒 ป้องกัน XSS (textContent, not innerHTML)
  • 📦 const by default, let when needed
  • ✔️ ตรวจสอบ element ก่อนใช้
  • ⚡ Debounce heavy operations
  • 🏷️ Data attributes สำหรับ custom data
  • ❌ try/catch error handling
  • 📝 Template literals

📚 สรุป

JavaScript ทำให้ HTML กลายเป็น web application ที่มีชีวิตและตัวอบสนอง ด้วย:

💡 ให้นึกถึง: HTML คือโครงสร้าง, CSS คือออกแบบ, JavaScript คือการจัดการพฤติกรรม