DOM Manipulation & Event

DOM (Document Object Model) เป็นมาตรฐานของ W3C ที่แทนเอกสาร HTML/XML เป็นโครงสร้าง ต้นไม้ของ object ที่โปรแกรมเข้าถึงและแก้ไขได้

DOM คืออะไร

<h1 id="title">Hello, World!</h1>
<button id="changeTitle">Change Title</button>
<script>
    var titleElement = document.getElementById('title');
    titleElement.textContent = 'Hello, DOM!';

    var button = document.getElementById('changeTitle');
    button.addEventListener('click', function() {
        titleElement.textContent = 'Title Changed!';
    });
</script>

เลือก element 5 วิธี

เมธอดเลือกอะไรคืนอะไร
getElementById('header')element ที่มี id ตรงกัน1 element
getElementsByClassName('text')ทุก element ที่มี class นั้นcollection — ต้องใส่ index เช่น [0]
getElementsByTagName('div')ทุก element ของ tag นั้นcollection
querySelector('.text')ตัวแรกที่ตรงกับ CSS selector1 element
querySelectorAll('.text')ทุกตัวที่ตรงกับ CSS selectorNodeList
var paragraphs = document.getElementsByClassName('text');
console.log(paragraphs[0]);      // ต้องระบุ index ถึงจะได้ element เดียว

var first = document.querySelector('.text');       // ได้ตัวแรกเลย
var all   = document.querySelectorAll('.text');    // ได้ทั้งหมด

var parent = child.parentElement;                  // เดินขึ้นไปหา parent
สังเกตชื่อ: getElementsBy... มี s = ได้หลายตัว ต้องใส่ index ส่วน getElementById ไม่มี s = ได้ตัวเดียว

innerHTML / innerText / textContent

innerHTMLinnerTexttextContent
ตอน setสร้าง element จริงจากแท็กที่ใส่แสดงแท็กเป็นข้อความธรรมดาแสดงแท็กเป็นข้อความธรรมดา
ตอน getได้ HTML ทั้งก้อนรวมแท็กได้เฉพาะข้อความที่มองเห็น (สนใจการขึ้นบรรทัด)ได้ข้อความทั้งหมดรวมที่ซ่อนอยู่
ความปลอดภัยเสี่ยง XSS ถ้าใส่ข้อมูลจากผู้ใช้ปลอดภัยกว่าปลอดภัยกว่า
let text = "<div style='background-color:red;'>Hello Dolly</div>";

document.getElementById("myP").innerHTML = text;    // ได้กล่องสีแดงจริง ๆ
document.getElementById("myP").textContent = text;  // เห็นเป็นตัวอักษร <div ...> ทั้งบรรทัด
อย่าเอาข้อมูลจากผู้ใช้ยัดใส่ innerHTML ตรง ๆ เพราะสคริปต์ของผู้โจมตีจะถูกสร้างขึ้นมาจริง (XSS)

attribute & class

// attribute
image.setAttribute('src', 'new-image.jpg');
console.log(image.getAttribute('src'));       // new-image.jpg
button.removeAttribute('disabled');
console.log(button.hasAttribute('disabled')); // false

element.href = "https://www.w3schools.com";   // property บางตัวตั้งตรง ๆ ได้

// class
div.classList.add('newClass');
div.classList.remove('oldClass');
console.log(div.className);

สร้าง / ลบ / โคลน element

// สร้างและเพิ่ม
var newDiv = document.createElement('div');
newDiv.classList.add('my-class');
newDiv.id = 'myDiv';
newDiv.textContent = 'Hello, World!';
document.body.appendChild(newDiv);      // ต่อท้ายสุด
document.body.prepend(newDiv);          // แทรกหน้าสุด

// ลบ
newDiv.remove();                        // ลบตัวเอง
parentElement.removeChild(childElement);// ให้ parent ลบลูก

// โคลน
document.querySelectorAll("div[data-items='posts']")[0].cloneNode(true);
// true  = ได้ลูกทุกตัวมาด้วย
// false = ได้เฉพาะตัวมันเอง

ผูก event 3 วิธี

<!-- 1. เขียนใน attribute ของ HTML ตรง ๆ -->
<button onclick="alert('Button clicked!')">Click Me</button>

// 2. ใช้ DOM property
button.onclick = function() { alert('Button clicked!'); };

// 3. ใช้ addEventListener  ← วิธีที่แนะนำ
button.addEventListener('click', function() { alert('Button clicked!'); });
ทำไม addEventListener ดีที่สุด: ผูก handler ได้หลายตัวกับ event เดียวกัน ส่วน onclick ตั้งใหม่ทับของเดิมเสมอ และยังกำหนด capturing/bubbling ได้

event ที่ต้องรู้จัก

กลุ่มevent
Mouseclick, mouseover, mouseout
Keyboardkeydown, keyup
Formsubmit, change, input
Focusfocus (ได้โฟกัส), blur (เสียโฟกัส)
Animationanimationstart, animationiteration, animationend
div.addEventListener('mouseover', function() { div.style.backgroundColor = 'red'; });
div.addEventListener('mouseout',  function() { div.style.backgroundColor = 'blue'; });

input.addEventListener('keydown', function(event) { console.log('Key down: ' + event.key); });
input.addEventListener('keyup',   function(event) { console.log('Key up: ' + event.key); });

Bubbling vs Capturing

document / body div button (คลิกที่นี่) Capturing (true) บนลงล่าง Bubbling (false) ล่างขึ้นบน — ค่าปริยาย ค่าปริยายของ addEventListener คือ false = bubbling → button clicked! แล้วตามด้วย body clicked! ลำดับไม่ขึ้นกับว่าเราเขียนโค้ดผูก event ตัวไหนก่อน

ผูก event ไว้หลายชั้น = event ตัวเดียวจะวิ่งผ่านทุกชั้น

// 1. Bubbling (ค่าปริยาย) — พารามิเตอร์ที่ 3 เป็น false หรือไม่ใส่
button.addEventListener('click', function(){ console.log('Button clicked!'); }, false);
document.body.addEventListener('click', function(){ console.log('Body clicked!'); });
// ผลลัพธ์: Button clicked!  →  Body clicked!

// 2. Capturing — ใส่ true
button.addEventListener('click', function(){ console.log('Button clicked!'); }, true);
document.body.addEventListener('click', function(){ console.log('Body clicked!'); }, true);
กับดักข้อสอบ: ถ้าสลับลำดับการเขียนโค้ดผูก event ผลลัพธ์ไม่เปลี่ยน — ลำดับขึ้นกับโครงสร้าง DOM และเฟส (capturing/bubbling) ไม่ใช่ลำดับบรรทัดโค้ด

target vs currentTarget

body.addEventListener('click', function(event) {
    console.log('Target:', event.target);           // element ที่ถูกคลิกจริง เช่น button
    console.log('Current Target:', event.currentTarget); // element ที่ผูก handler ไว้ เช่น body
});
ความต่างนี้คือหัวใจของ Event Delegation — ผูก listener ที่ parent ตัวเดียว แล้วใช้ event.target ดูว่าลูกตัวไหนถูกคลิก ประหยัดหน่วยความจำเมื่อมีปุ่มจำนวนมาก

stopPropagation

button.addEventListener('click', function(event) {
    event.stopPropagation();          // หยุดไม่ให้ event วิ่งต่อไปชั้นบน
    console.log('Button clicked!');
}, false);

document.body.addEventListener('click', function(event) {
    console.log('Body clicked!');     // จะไม่ถูกเรียกอีกต่อไป
}, false);