Javascript

DOM in JS #7: Working with Events

DOM in JS 7 Working with Events

1. Mở đầu

Tương tác với DOM bằng event là một phần hết sức quan trọng khi làm web, bất kể một sự kiện nào từ load page, close page, click vào 1 phần tử trong page, … đều là một event. Và Dom hay với cả window (BOM – mình sẽ có một series về chủ đề này – hoặc ngay bây giờ bạn có thể research) đều cho chúng ta handler khi events occurred.

Vì events trong DOM là rất rộng lên mình xin phép sẽ chỉ nói những phần có thể hay gặp nhất thôi hoặc bạn cũng có thể research để hiểu thêm hoặc click vào đường link tham khảo bên dưới nhé.

2. Chi tiết

Event là gì?

Events ở đây bạn có thể hiểu là tất cả những hành động gì mà người dùng tương tác với trang web như là load page, close page, click element, scroll, … hoặc thậm chí là ngay cả khi tắt mạng đi bạn cũng có thể handle bằng Offline mode. v.v

Nhưng ở đây lại phát sinh thêm ra hai khái niệm mà mình nghĩ bạn nên hiểu đó là event bubblingevent capturing (Mình không biết dịch ra như thế nào). Bây giờ, bạn có thể xem code như hình bên dưới:

<body>
    <div id="container">
        <button id='btn'>Click Me!</button>
    </div>
</body>

Vậy có phải là khi bạn click vào button thì thẻ div cũng sẽ được click đúng không nào, tương tự với body. Nếu bạn chưa hiểu thì có thể F12 lên và insert đoạn HTML này vào và mở cửa sổ log lên để xem nhé.

<div id="container" onclick="console.log('div')">
    Click Me DIV!
    <br>
    <button id="btn" onclick="console.log('button')">Click Me BUTTON!</button>
</div>
// button
// div

Bạn sẽ nhận được log của div khi mà bạn chỉ nhấn vào button, tại sao vậy? Mình xin trả lời đó chính là tính chất event bubbling, bạn có thể xem hình dưới đây.

https://cdn.javascripttutorial.net/wp-content/uploads/2020/02/JavaScript-event-bubbling.png

Làm sao để tránh tình trạng khi mà click vào button mà sự kiện trên div vẫn hoạt động thì mình xin nói sau nhé. Còn bây giờ hãy đến với khái niệm capturing, bạn có thể hiểu nó ngược lại với Bubbling, bạn có thể tham khảo thêm tại đây. Đây là hai cách mà events được propagation trong DOM. Nhưng tại sao lại có propagation (sự lan truyền) này, đó là bởi ngay từ đầu bạn khi browser biên dịch code ra nó đã có rồi. Bạn có thể xem thêm hình minh họa.

https://cdn.javascripttutorial.net/wp-content/uploads/2020/02/JavaScript-DOM-Level-2-Event.png

Và để tránh propagation chúng ta sẽ có một số cách giải quyết khác nhau, bạn có thể xem thêm ở phần dưới mình sẽ nói rõ hơn nhé.

Có những cách nào để handle event?

Có 3 cách để gắn sự kiện cho một Node đó là dùng Event attributes, DOM Level 0 event, DOM Level 2 event, nhưng cho dù dùng cách nào thì bạn cũng có những thành phần chung nhất là event name, handler, đối tượng node bạn tương tác đến (Đối tượng this) và đối tượng mà event cho bạn khi handler execute.

Đối với Event attributes: Cách gắn này cho bạn sử dụng event attribute ngay trong element, từ đây bạn cũng có thể tham chiếu đến chính đối tượng bạn gọi đển hoặc đối tượng event mà sự kiện cho bạn. Bạn có thể sửa Body element của mình bằng cách chèn thêm 1 arttribute là onclick như bên dưới:

<body onclick="console.log('event', event);console.log('this', this)"></body>

// event MouseEvent {isTrusted: true, screenX: 224, screenY: 564, clientX: 224, clientY: 461, …}
// this <body onclick=​"console.log('event', event)​;​console.log('this', this)​">​…​</body>​

Nhưng vì là attributes của Node lên có thể sự kiện này có ở Node này mà không có ở node kia hay ngược lại, ví dụ như bạn không có onBlur, onChange, … trong div vậy.

Nhưng làm theo cách này sẽ có những nhược điểm mà bạn lên lưu tâm, đó là về tính maintainable và sẽ là thế nào nếu như phần HTML được load trước khi mà phần JS được load, như vậy thì có phải là user click vào phần HTML thì bạn sẽ nhận được lỗi đúng không và nó cũng không thể remove event sau khi handle.

Đối với loại DOM Level 0 event: Đây là cách rất hay để handler event vì nó đơn giản, không cân viết function hay code ở phần HTML như cách bên trên. Và nó cũng có thể xóa sự kiện đi khi mà bạn không còn muốn dùng đến nó nữa và tất nhiên bạn cũng có thể tham chiếu đến đối tượng event và đối tượng được handler. Bạn hãy cùng xem ví dụ bên dưới để có thể hiểu hơn nhé.

const bodyEl = document.querySelector('body');

const showLog = function(event) {console.log("event", event);console.log("this", this);};

bodyEl.onclick = showLog;

bodyEl.onclick = null; // Remove event after handle

Hãy gắn đoạn code này vào cửa sổ console của trình duyệt nhé, bạn sẽ nhận được kết quả như ví dụ bên trên.

Đối với loại DOM Level 2 event: Đây có lẽ là cách mạnh mẽ nhất, nó có thể cho bạn thêm, xóa events, thậm chí còn cho phép bạn control được cả event bubblingevent capturing cũng như cho phép bạn tương tác với đối tượng được handler và đối tượng event khi handle.

const showLog = function(event) {console.log("event", event);console.log("this", this);};
bodyEl.addEventListener('click', showLog);
bodyEl.removeEventListener('click', showLog); // remove event after handle

Bên trên là cách chúng ta thường dùng để gắn sự kiện cho một Element, nhưng addEventListener còn cho bạn một tham số nữa để có thể control event bubblingevent capturing. Mặc đinh, tham số này sẽ là false và sự kiện của bạn là bubbling, nhưng khi bạn set nó là true thì nó sẽ thành capturing (Nghĩa là nó sẽ handle từ thằng ngoài cùng to nhất rồi mới đến thằng bạn muốn handle). Cụ thể nhìn sẽ viết 1 ví dụ nhỏ nhỏ minh họa nhé.

<div id="div1" style="
    background-color: red;
    padding: 24px;
">
  div 1
  <div id="div2" style="
    background-color: green;
">
    div 2
  </div>
</div>

const div1 = document.querySelector("#div1");
const div2 = document.querySelector("#div2");

div1.addEventListener("click", function (event) {
  alert("you clicked on div 1");
}, true);

div2.addEventListener("click", function (event) {
  alert("you clicked on div 2");
}, false);

Như vậy, bạn hãy thử chèn đoạn code trên vào trang bạn đang mở, hay chính blogs này cũng được. Và hãy xem chuyện gì đang xảy ra khi mà bạn click vào thẻ div thứ 2, ^^ thằng div 1 sẽ được gọi trước.

Nhưng bay giờ hãy thử như thế này tiếp nhé:

<div id="div1" style="
    background-color: red;
    padding: 24px;
">
  div 1
  <div id="div2" style="
    background-color: green;
">
    div 2
  </div>
</div>

const div1 = document.querySelector("#div1");
const div2 = document.querySelector("#div2");

div1.addEventListener("click", function (event) {
  alert("you clicked on div 1");
}, false);

div2.addEventListener("click", function (event) {
  alert("you clicked on div 2");
}, false);

Khi bạn chạy code lên và click vào div2 thì nó vẫn chạy nhưng lại kéo theo cả event ở div1 cũng chạy và để custom cái tính năng này (Nó không phải lỗi đâu) bạn có thể dùng đển phương thức bên trong event object (Một tham số khi mà bạn gắn sự kiện vào Dom).

Nếu bạn muốn chỉ handle div2 thôi thì hãy call thêm stopPropagation, đây là một phương thức bên trong event object. Nó sẽ giúp dừng Propagation (Sự lan truyền).

div2.addEventListener("click", function (event) {
  alert("you clicked on div 2");
}, false);

Ở đây cũng có một phương thức nữa rất hay được dùng đó chính là preventDefault(), phương thức này sẽ cho bạn bỏ đi behavior default của 1 thẻ. Ví dụ như a element như ví dụ bên dưới:

<a id="aEl" href="https://chamdev.com/">Chamdev</a>
// Mặc định khi bạn click vào nó sẽ tự chuyển trang
// Nhưng khi bạn loại bỏ behavior này đi thì nó sẽ không còn được chuyển trang nữa.
const link = document.querySelector('aEl');

link.addEventListener('click',function(event) {
    console.log('clicked');
    event.preventDefault();
});

Sau đây là một số bảng mà bạn có thể tham khảo, những event hay dùng, nó còn rất nhiều nên mình có để thêm link ở phần tham khảo nhé.

Body events:

AttributeValueDescription
onloadscriptScript runs when a HTML document loads
onunloadscriptScript runs when a HTML document unloads

Form events:

AttributeValueDescription
onchangescriptScript runs when the element changes
onsubmitscriptScript runs when the form is submitted
onresetscriptScript runs when the form is reset
onselectscriptScript runs when the element is selected
onblurscriptScript runs when the element loses focus
onfocusscriptScript runs when the element gets focus

Keyboard events:

AttributeValueDescription
onkeydownscriptScript runs when key is pressed
onkeypressscriptScript runs when key is pressed and released
onkeyupscriptScript runs when key is released

Mouse events:

AttributeValueDescription
onclickscriptScript runs when a mouse click
ondblclickscriptScript runs when a mouse double-click
onmousedownscriptScript runs when mouse button is pressed
onmousemovescriptScript runs when mouse pointer moves
onmouseoutscriptScript runs when mouse pointer moves out of an element
onmouseoverscriptScript runs when mouse pointer moves over an element
onmouseupscriptScript runs when mouse button is released

Ngoài ra, cũng như create element, create attribute thì bạn cũng có thể create event của riêng bạn, bạn có thể tham khảo thêm ở đây nhé.

3. Kết luận

Mình đang giới thiệu để bạn hiểu hơn về DOM cũng như vai trò của nó để bạn có base vững hơn một chút để có thể fix những con bug khó. Tuy nhiên khi vào dự án thật có lẽ bạn sẽ ít dùng đển những cách viết thuần này vì đơn giản Framework hay library của JS nó trang bị đến tận răng cho chúng ta rồi. ^^

Tuy nhiên, khi đi phỏng vấn hoặc task nào cần đến việc viết để override phần nào đó trong library thì bạn vẫn cần nắm được DOM, vậy nên nắm được cơ bản vẫn là rất quan trọng.

Đến đây mình cũng xin tạm kết lại cái series dài đằng đẵng này lại nhé. phewwww

Cuối cùng, SEE YOU SOONNNN!

4. Tham khảo

# JavaScript Events
# JavaScript – Events
# HTML – Events References
# JavaScript Events
# JavaScript HTML DOM Events
# HTML DOM Events

Tagged , , ,
0 0 vote
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments