Hướng dẫn handle event listener căn bản cho các bạn mới bắt đầu với javascript

Căn bản nhưng cần thiết

Bắt đầu với một ví dụ đơn giản: một <div/> để user có thể kéo thả vào, chúng ta hiển thị user đã kéo thả vào nào

<section>
  <div id="red" draggable="true">
    <span>R</span>
  </div>
  <div id="yellow" draggable="true">
    <span>Y</span>
  </div>
  <div id="green" draggable="true">
    <span>G</span>
  </div>
</section>

<p id="dragged">Drag a box</p>

Chúng ta sẽ gắn listener lên từng element

document.querySelector('#red').addEventListener('dragstart', evt => {
    document.querySelector('#dragged').textContent = 'Dragged red';
});

document.querySelector('#yellow').addEventListener('dragstart', evt => {
    document.querySelector('#dragged').textContent = 'Dragged yellow';
});

document.querySelector('#green').addEventListener('dragstart', evt => {
    document.querySelector('#dragged').textContent = 'Dragged green';
});

Tất cả các listener trên đều thực hiện một thao tác giống nhau: đổi text. Chúng ta có thể gộp nó lại vào 1 function

function preview(color) {
  document.querySelector('#dragged').textContent = `Dragged ${color}`;
}

document
  .querySelector('#red')
  .addEventListener('dragstart', evt => preview('red'));
document
  .querySelector('#yellow')
  .addEventListener('dragstart', evt => preview('yellow'));
document
  .querySelector('#green')
  .addEventListener('dragstart', evt => preview('green'));

Gọn hơn, nhưng vẫn cần nhiều function khác nhau cho các element khác nhau

Sử dụng Event Object

Khi một listener được gọi, nó cũng sẽ nhận được một Event Object, object nếu chứa một số thông tin như thời gian xảy ra sự kiện, đối tượng gây ra sự kiện

const preview = evt => {
  const color = evt.currentTarget.id;
  document.querySelector('#dragged').textContent = `Dragged ${color}`;
};

document.querySelector('#red').addEventListener('dragstart', preview);
document.querySelector('#yellow').addEventListener('dragstart', preview);
document.querySelector('#green').addEventListener('dragstart', preview);

Sử dụng bubbling

Để giảm số dòng code chúng ta viết ra, thay vì chúng ta gắn các event listener lên từng .box, chúng ta chỉ gắn 1 listener lên section chứa tất cả box

Khi một sự kiện xảy ra trên element, nó ko dừng lại tại element đó, trình duyệt sẽ đi qua tất cả các element cha của nó, gọi tất cả các listener cho đến khi nó đi tới thẻ body. Quá trình này được gọi là bubbling

Bên trong evt.target sẽ chứa element bắn ra sự kiện, chứ không phải element được gắn listener là section

const preview = evt => {
  const color = evt.target.id;
  document.querySelector('#dragged').textContent = `Dragged ${color}`;
};

document.querySelector('section').addEventListener('dragstart', preview);

Sự kiện Click

evt.target chạy tốt với các sự kiện changedragstart vì chỉ có một số lượng rất ít các element có thể nhận focus và giá trị input bị thay đổi tại một thời điểm chỉ có một

Với sự kiện click thì có khác một chút xíu

<section>
  <div id="red" draggable="true">
    <span>R</span>
  </div>
  <div id="yellow" draggable="true">
    <span>Y</span>
  </div>
  <div id="green" draggable="true">
    <span>G</span>
  </div>
</section>

<p id="clicked">Clicked a box</p>
const preview = evt => {
  const color = evt.target.id;
  document.querySelector('#clicked').textContent = `Clicked ${color}`;
};

document.querySelector('section').addEventListener('click', preview);

Nếu chúng ta click vào vùng của span, sự kiện vẫn được bắn ra, tuy nhiên nó không chứa id nên evt.target.id ngủm, đó là lý do tại sao có khi đoạn code trên có khi chạy đúng, có khi sai

Để lấy được element cha gần nhất, chúng ta dùng element.closest()

const preview = evt => {
  const element = evt.target.closest('div[draggable]');
  if (element != null) {
    const color = element.id;
    document.querySelector('#clicked').textContent = `Clicked ${color}`;
  }
};

Một số ví dụ khác

Khi có một list các item, các item có thể thêm bớt số lượng tuỳ thích, chúng ta sẽ không gắn listener vào từng item một

<div id="buttons-container"></div>
<button id="add">Add new button</button>

Tiếp tục sử dụng bubbling, chúng ta chỉ gắn một listener cho container

let buttonCounter = 0;
const container = document.querySelector('#buttons-container');
document.querySelector('#add').addEventListener('click', evt => {
  const newButton = document.createElement('button');
  newButton.dataset.number = buttonCounter;
  buttonCounter++;

  container.appendChild(newButton);
});

// gắn listener cho container, thay vì trên từng button
container.addEventListener('click', evt => {
  const clickedButton = evt.target.closest('button');
  if (clickedButton != null) {
    // log gía trị khi click
    document.querySelector('#clicked').textContent = `Clicked button #${clickedButton.dataset.number}`;
  }
});

Form có rất nhiều input và chúng ta muốn lấy tất cả dữ liệu user nhập vào, nhét trong một object

<form>
  <label>Name: <input name="name" type="text"/></label>
  <label>Email: <input name="email" type="email"/></label>
  <label>Password: <input name="password" type="password"/></label>
</form>
<p id="preview"></p>
let responses = {
  name: '',
  email: '',
  password: ''
};

document.querySelector('form').addEventListener('change', evt => {
  responses[evt.target.name] = evt.target.value;
  document.querySelector('#preview').textContent = JSON.stringify(responses);
});

The Thinking Behind Simplifying Event Handlers

Hướng dẫn handle event listener căn bản cho các bạn mới bắt đầu với javascript