Hôm nay cafedev chia sẻ cho ace cách tuỳ chỉnh, tạo sự kiện trong Javascript. Chúng ta không chỉ có thể chỉ định trình xử lý mà còn có thể tạo các sự kiện từ JavaScript.

Các sự kiện tùy chỉnh có thể được sử dụng để tạo “các thành phần đồ họa”. Ví dụ: một phần tử gốc của menu dựa trên JS của riêng chúng ta có thể kích hoạt các sự kiện cho biết điều gì xảy ra với menu: open(menu mở), select(một mục được chọn), v.v. Một code khác có thể lắng nghe các sự kiện và quan sát những gì đang xảy ra với menu.

Chúng ta không chỉ có thể tạo ra các sự kiện hoàn toàn mới mà chúng ta phát minh ra cho mục đích của riêng mình mà còn cả những sự kiện tích hợp sẵn, chẳng hạn như click, mousedownv.v. Điều đó có thể hữu ích cho việc kiểm tra tự động.

1. Công cụ tạo sự kiện

Các lớp sự kiện tích hợp tạo thành một hệ thống phân cấp, tương tự như các lớp phần tử DOM. Gốc là lớp Sự kiện được tích hợp sẵn .

Chúng ta có thể tạo ác đối tượng Event c như sau:

let event = new Event(type[, options]);

Tranh luận:

  • typeloại sự kiện, một chuỗi giống “click” hoặc tương tự của chúng ta “my-event”.

  • tùy chọn – đối tượng có hai thuộc tính tùy chọn:

    • bubbles: true/false- nếu true, thì sự kiện sủi bọt.
    • cancelable: true/false- nếu true, thì “hành động mặc định” có thể bị ngăn chặn. Sau đó, chúng ta sẽ xem ý nghĩa của nó đối với các sự kiện tùy chỉnh.
  • Theo mặc định cả hai đều sai: {bubbles: false, cancelable: false}.

2. DispatchEvent

Sau khi một đối tượng sự kiện được tạo, chúng ta nên “chạy” nó trên một phần tử bằng lệnh gọi elem.dispatchEvent(event).

Sau đó, trình xử lý phản ứng trên đó như thể đó là một sự kiện trình duyệt thông thường. Nếu sự kiện được tạo bằng sủi bọt cờ, thì sự kiện đó sẽ sủi bọt.

Trong ví dụ dưới đây, click sự kiện được bắt đầu bằng JavaScript. Trình xử lý hoạt động theo cách tương tự như khi nút được nhấp:

<button id="elem" onclick="alert('Click!');">Autoclick</button>

<script>
  let event = new Event("click");
  elem.dispatchEvent(event);
</script>

event.isTrusted

Có một cách để nói sự kiện người dùng “thực” từ sự kiện do tập lệnh tạo.

Thuộc tính event.isTrusted là true cho các sự kiện đến từ hành động sử dụng thực tế và false cho các sự kiện kịch bản tạo ra.

3. Ví dụ về sủi bọt

Chúng ta có thể tạo một sự kiện sôi nổi với tên “hello”và bắt kịp document nó.

Tất cả những gì chúng ta cần là đặt bubbles thành true:

<!--
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Group: https://www.facebook.com/groups/cafedev.vn/
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
Pinterest: https://www.pinterest.com/cafedevvn/
YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
-->

<h1 id="elem">Hello from the script!</h1>

<script>
  // catch on document...
  document.addEventListener("hello", function(event) { // (1)
    alert("Hello from " + event.target.tagName); // Hello from H1
  });

  // ...dispatch on elem!
  let event = new Event("hello", {bubbles: true}); // (2)
  elem.dispatchEvent(event);

  // the handler on document will activate and display the message.

</script>

Ghi chú:

  1. Chúng ta nên sử dụng addEventListener cho các sự kiện tùy chỉnh của mình, vì on<event>chỉ tồn tại cho các sự kiện được tích hợp sẵn, document.onhello không hoạt động.
  2. Phải thiết lập bubbles:true, nếu không sự kiện sẽ không nổi lên.

Cơ chế tạo bọt giống nhau đối với các sự kiện tích hợp sẵn ( click) và tùy chỉnh ( hello). Ngoài ra còn có các giai đoạn bắt và sủi bọt.

4. MouseEvent, KeyboardEvent và những thứ khác

Dưới đây là danh sách ngắn các lớp cho Sự kiện giao diện người dùng từ đặc tả Sự kiện giao diện người dùng :

  • UIEvent
  • FocusEvent
  • MouseEvent
  • WheelEvent
  • KeyboardEvent

Chúng ta nên sử dụng chúng thay vì new Event nếu chúng ta muốn tạo ra các sự kiện như vậy. Ví dụ new MouseEvent(“click”),.

Hàm tạo bên phải cho phép chỉ định các thuộc tính tiêu chuẩn cho kiểu sự kiện đó.

Thích clientX/clientY cho một sự kiện chuột:

let event = new MouseEvent("click", {
  bubbles: true,
  cancelable: true,
  clientX: 100,
  clientY: 100
});

alert(event.clientX); // 100

Xin lưu ý: hàm tạo chung Event không cho phép điều đó.

Hãy thử:

<!--
Cafedev.vn - Kênh thông tin IT hàng đầu Việt Nam
@author cafedevn
Contact: cafedevn@gmail.com
Fanpage: https://www.facebook.com/cafedevn
Group: https://www.facebook.com/groups/cafedev.vn/
Instagram: https://instagram.com/cafedevn
Twitter: https://twitter.com/CafedeVn
Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
Pinterest: https://www.pinterest.com/cafedevvn/
YouTube: https://www.youtube.com/channel/UCE7zpY_SlHGEgo67pHxqIoA/
-->

let event = new Event("click", {
  bubbles: true, // only bubbles and cancelable
  cancelable: true, // work in the Event constructor
  clientX: 100,
  clientY: 100
});

alert(event.clientX); // undefined, the unknown property is ignored!

Về mặt kỹ thuật, chúng ta có thể giải quyết vấn đề đó bằng cách chỉ định trực tiếp event.clientX=100 sau khi tạo. Vì vậy, đó là một vấn đề thuận tiện và tuân theo các quy tắc. Các sự kiện do trình duyệt tạo luôn có đúng loại.

Danh sách đầy đủ các thuộc tính cho các sự kiện giao diện người dùng khác nhau có trong đặc tả, chẳng hạn như MouseEvent .

5. Sự kiện tùy chỉnh

Đối với riêng chúng ta, các loại sự kiện hoàn toàn mới như “hello” chúng ta nên sử dụng new CustomEvent. Về mặt kỹ thuật CustomEvent cũng giống như Event, với một ngoại lệ.

Trong đối số thứ hai (đối tượng), chúng ta có thể thêm một thuộc tính bổ sung detail cho bất kỳ thông tin tùy chỉnh nào mà chúng ta muốn chuyển với sự kiện.

Ví dụ:

<h1 id="elem">Hello for John!</h1>

<script>
  // additional details come with the event to the handler
  elem.addEventListener("hello", function(event) {
    alert(event.detail.name);
  });

  elem.dispatchEvent(new CustomEvent("hello", {
    detail: { name: "John" }
  }));
</script>

Các detail tỉnh có thể có bất kỳ dữ liệu. Về mặt kỹ thuật, chúng ta có thể sống mà không có, bởi vì chúng ta có thể gán bất kỳ thuộc tính nào vào một  đối tượng new Event thông thường sau khi tạo ra nó. Nhưng CustomEvent cung cấp trường đặc biệt detail để nó tránh xung đột với các thuộc tính sự kiện khác.

Bên cạnh đó, lớp sự kiện mô tả “loại sự kiện”, và nếu sự kiện là tùy chỉnh, thì chúng ta nên sử dụng CustomEvent chỉ để hiểu rõ nó là gì.

6. event.preventDefault ()

Nhiều sự kiện trình duyệt có “hành động mặc định”, chẳng hạn như điều hướng đến một liên kết, bắt đầu lựa chọn, v.v.

Đối với các sự kiện mới, tùy chỉnh, chắc chắn không có hành động trình duyệt mặc định nào, nhưng mã gửi sự kiện như vậy có thể có kế hoạch riêng về những việc cần làm sau khi kích hoạt sự kiện.

Bằng cách gọi event.preventDefault(), một trình xử lý sự kiện có thể gửi tín hiệu rằng những hành động đó nên được hủy bỏ.

Trong trường hợp đó, cuộc gọi để elem.dispatchEvent(event) trở lại false. Và code đã gửi nó biết rằng nó không nên tiếp tục.

Hãy xem một ví dụ thực tế – một con thỏ đang ẩn nấp (có thể là một menu đóng hoặc một cái gì đó khác).

Bên dưới, bạn có thể thấy một #rabbit và hàm hide() điều khiển sự kiện “hide” trên đó, để cho tất cả các bên quan tâm biết rằng con thỏ sẽ ẩn.

Bất kỳ trình xử lý nào cũng có thể lắng nghe sự kiện đó rabbit.addEventListener(‘hide’,…) và nếu cần, hãy hủy hành động bằng cách sử dụng event.preventDefault(). Sau đó, con thỏ sẽ không biến mất:

<pre id="rabbit">
  |\   /|
   \|_|/
   /. .\
  =\_Y_/=
   {>o<}
</pre>
<button onclick="hide()">Hide()</button>

<script>
  function hide() {
    let event = new CustomEvent("hide", {
      cancelable: true // without that flag preventDefault doesn't work
    });
    if (!rabbit.dispatchEvent(event)) {
      alert('The action was prevented by a handler');
    } else {
      rabbit.hidden = true;
    }
  }

  rabbit.addEventListener('hide', function(event) {
    if (confirm("Call preventDefault?")) {
      event.preventDefault();
    }
  });
</script>

Xin lưu ý: sự kiện phải có cờ cancelable: true, nếu không cuộc gọi event.preventDefault() sẽ bị bỏ qua.

7. Sự kiện-trong-sự kiện là đồng bộ

Thông thường các sự kiện được xử lý trong một hàng đợi. Đó là: nếu trình duyệt đang xử lý onclick và một sự kiện mới xảy ra, ví dụ như chuột được di chuyển, thì việc mousemove xử lý nó được xếp hàng đợi, các trình xử lý tương ứng sẽ được gọi sau khi onclick xử lý xong.

Ngoại lệ đáng chú ý là khi một sự kiện được bắt đầu từ bên trong một sự kiện khác, ví dụ như sử dụng dispatchEvent. Các sự kiện như vậy được xử lý ngay lập tức: các trình xử lý sự kiện mới được gọi, và sau đó xử lý sự kiện hiện tại được tiếp tục.

Ví dụ: trong đoạn code dưới đây, sự kiện menu-open được kích hoạt trong onclick.

Nó được xử lý ngay lập tức mà không cần đợi trình xử lý onlick kết thúc:

<button id="menu">Menu (click me)</button>

<script>
  menu.onclick = function() {
    alert(1);

    menu.dispatchEvent(new CustomEvent("menu-open", {
      bubbles: true
    }));

    alert(2);
  };

  // triggers between 1 and 2
  document.addEventListener('menu-open', () => alert('nested'));
</script>

Thứ tự đầu ra là: 1 → lồng nhau → 2.

Xin lưu ý rằng sự kiện lồng nhau menu-open được bắt trên document. Việc truyền và xử lý sự kiện lồng nhau kết thúc trước khi quá trình quay trở lại mã bên ngoài ( onclick).

Đó không chỉ là về dispatchEvent, có những trường hợp khác. Nếu một trình xử lý sự kiện gọi các phương thức kích hoạt các sự kiện khác – chúng được xử lý quá đồng bộ, theo kiểu lồng nhau.

Hãy nói rằng chúng ta không thích nó. Trước tiên onclick, chúng ta muốn được xử lý hoàn toàn, độc lập với menu-open hoặc bất kỳ sự kiện lồng ghép nào khác.

Sau đó, chúng ta có thể đặt dispatchEvent(hoặc một lệnh gọi kích hoạt sự kiện khác) vào cuối onclick hoặc, có thể tốt hơn, đặt nó trong khoảng thời gian không trễ setTimeout:

<button id="menu">Menu (click me)</button>

<script>
  menu.onclick = function() {
    alert(1);

    setTimeout(() => menu.dispatchEvent(new CustomEvent("menu-open", {
      bubbles: true
    })));

    alert(2);
  };

  document.addEventListener('menu-open', () => alert('nested'));
</script>

Bây giờ dispatchEvent chạy không đồng bộ sau khi quá trình thực thi mã hiện tại kết thúc, bao gồm mouse.onclick, do đó, các trình xử lý sự kiện hoàn toàn riêng biệt.

Thứ tự đầu ra trở thành: 1 → 2 → lồng vào nhau.

8. Tóm lược

Để tạo một sự kiện từ code, trước tiên chúng ta cần tạo một đối tượng sự kiện.

Hàm tạo chung Event(name, options)chấp nhận một tên sự kiện tùy ý và đối tượng  options có hai thuộc tính:

  • bubbles: true nếu sự kiện sẽ bong bóng.
  • cancelable: true nếu event.preventDefault() làm việc.

Các hàm tạo khác của các sự kiện gốc như MouseEvent, KeyboardEventv.v. chấp nhận các thuộc tính cụ thể cho loại sự kiện đó. Ví dụ, clientX đối với các sự kiện chuột.

Đối với các sự kiện tùy chỉnh, chúng ta nên sử dụng hàm tạo CustomEvent. Nó có một tùy chọn bổ sung có tên detail, chúng ta nên gán dữ liệu sự kiện cụ thể cho nó. Sau đó, tất cả các trình xử lý có thể truy cập nó dưới dạng event.detail.

Mặc dù có khả năng kỹ thuật để tạo ra các sự kiện trình duyệt như click hoặc keydown, chúng ta nên sử dụng hết sức cẩn thận.

Chúng ta không nên tạo các sự kiện trình duyệt vì đó là một cách khó chạy các trình xử lý. Đó là một kiến ​​trúc tồi trong hầu hết thời gian.

Các sự kiện gốc có thể được tạo:

  • Là một cuộc tấn công bẩn thỉu để làm cho các thư viện của bên thứ 3 hoạt động theo cách cần thiết, nếu họ không cung cấp các phương tiện tương tác khác.
  • Đối với thử nghiệm tự động, hãy “nhấp vào nút” trong tập lệnh và xem liệu giao diện có phản ứng chính xác hay không.

Các sự kiện tùy chỉnh với tên riêng của chúng ta thường được tạo cho mục đích kiến ​​trúc, để báo hiệu những gì xảy ra bên trong menu, thanh trượt, băng chuyền, v.v.

Nguồn và tài liệu tiếng anh tham khảo:

Full series tự học Javascript từ cơ bản tới nâng cao tại đây nha.

Nếu bạn thấy hay và hữu ích, bạn có thể tham gia các kênh sau của cafedev để nhận được nhiều hơn nữa:

Chào thân ái và quyết thắng!

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!