+14

Giao tiếp giữa 2 tab trình duyệt với JavaScript

Cách đây mấy hôm mình có nghĩ đến một vấn đề khá thú vị khi đang làm việc, đó là làm thế nào để lấy được giá trị của một form được submit ở một tab khác. Đây là một việc cực kì đơn giản khi thao tác trên một tab, nhưng đối với hai, ba hay nhiều tab nữa thì sao ? Bài viết hnay mình sẽ giải thích và hướng dẫn một số cách để làm được việc này với JavaScript

Giao tiếp giữa 2 tab là gì ?

Giao tiếp giữa 2 tab là việc có thể gửi và nhận data (messages) giữa nhiều tab, window, iframe (gọi chung là các instance)

Khi nào thì sử dụng

Trước khi mình đi vào hướng dẫn thì mình nghĩ mình nên đưa ra một vài case để ứng dụng việc giao tiếp giữa các tab. Đôi khi người dùng sẽ phải mở nhiều tab/window của trang web của bạn, và việc giúp ngừoi dùng có trải nghiệm tốt nhất là việc rất quan trọng. Ví dụ như chức năng bật tắt Dark Mode, nếu dùng việc giao tiếp này thì bạn có thể thay đổi giao diện cho tất cả các instance đang mở mà không cần phải refresh trang.

Hay việc submit form trong iFrame và lấy giá trị của form ấy ở bên ngoài mà ko cần phải refresh.

Hạn chế

Việc này có một vài hạn chế, vì nó chỉ có thể dùng đối với các domain trên cùng một origin.

  • Không dùng được giữa HTTP và HTTPS.
  • Không dùng được giữa các host khác nhau.
  • Không dùng được giữa các port khác nhau.

Hướng dẫn

Hiện tại mình thấy có 2 cách thường dùng và hiệu quả nhất là sử dụng localStorageBroadcastChannel API

localStorage - cách cũ mà hiệu quả

Để bắt được sự kiện gửi message ở một tab sang tab khác, bạn chỉ cần bind event storage cho tất cả các tab:

window.addEventListener("storage", message_receive);

Function message_receive sẽ được gọi mỗi khi set giá trị cho localStorage ở tất cả các tab. Object event listener đã chứa luôn data vừa set cho localStorage, do đó bạn không cần phải parse object localStorage nữa. Việc này khá tiện khi bạn có thể reset giá trị ngay sau khi set, do dó data được dọn dẹp sạch sẽ và không để lại một dấu vết nào :v . Phía dưới là các hàm xử lý việc gửi và nhận message từ một form :

// gửi message
//
function message_broadcast(message) {
  var message = document.getElementById('input').value; // lấy giá trị của form

  localStorage.setItem('broadcast', JSON.stringify(message)); // lưu giá trị của form vào localStorage dưới key 'broadcast'
  localStorage.removeItem('broadcast'); // xoá giá trị của key 'broadcast' trong localStorage để dọn dẹp dữ liệu
};

// nhận message
//
function message_receive(e) {
  if (e.key == 'broadcast') {
    var message = JSON.parse(e.newValue); // chỉ check với key là 'broadcast'
  }

  if (!message) return; // nếu message rỗng thì bỏ qua

  // Ở đây bạn có thể xử lý message đã nhận.
  // bạn có thể gửi object dạng { 'title': 'tiêu đề', 'message': 'nội dung' }
  alert(message);
  // vân vân.
  // mây mây.
};

Và một chiếc form nho nhỏ:

<input type="text" id="input" placeholder="Nội dung" />
<button onclick="message_broadcast()">Gửi</button>

Tada!

Một vài hạn chế của việc sử dụng localStorage

  • Bạn không thế lưu trực tiếp Object vào local storage / session storage mà phải stringify object ấy. Do đó bạn sẽ luôn phải parse giá trị trước khi sử dụng. Mình cho điều này là không tối ưu lắm.
  • Event sẽ không được trigger nếu giá trị của nó không đổi hoặc được update thành một giá trị giống hệt giá trị ban đầu. Để xử lý vấn đề này thì bạn có thể sử dụng Date.now() để các instance khác luôn bắt được sự kiện.
{'message': 'Hello World!', 'uid': Date.now()}

Ngoài các hạn chế ra thì sử dụng localStorage có một ưu điểm là được support trên nhiều browser.

BroadcastChannel API

Gửi dữ liệu

Để gửi dữ liệu sang tab khác, đầu tiên chúng ta cần tạo một instance BroadcastChannel.

const channel = new BroadcastChannel('myChannel');

'myChannel' ở đây chính là tên của channel mà chúng ta đã subscribe. Sau khi subscribe tới một channel thì bạn có thể gửi và nhận message từ channel ấy.

Để gửi dữ liệu, chúng ta sẽ làm như sau:

channel.postMessage('Hello World!');

Chúng ta có thể gửi được nhiều kiểu Object với postMessage:

// array
channel.postMessage(['Chó', 'Mèo', 'Lợn', 'Gà']);

// object
channel.postMessage({ name: "Đạt", age: 23 });

// blob
channel.postMessage(new Blob(['Hello World!'], {
    type: "text/plain"
}));

Nhận dữ liệu

Để nhận dữ liệu chúng ta cùng subcribe vào channel 'myChannel' và bắt sự kiện message của channel:

const channel = new BroadcastChannel('myChannel'); //subcribe vào channel myChannel, cùng channel với lúc gửi

channel.addEventListener("message", function(e) {
  alert(e.data);
});

Code hoàn chỉnh của chúng ta:

<script>
    const channel = new BroadcastChannel('myChannel');

    channel.addEventListener('message', function(e) {
      alert(e.data);
    });

    function message_broadcast() {
      var message = document.getElementById('input').value;

      channel.postMessage(message);
    };
</script>

<input type="text" id="input" placeholder="Nội dung" />
<button onclick="message_broadcast()">Gửi</button>

Voila!

Ưu/nhược điểm

Không như sử dụng localStorage, sử dụng BroadcastChannel giúp bạn post được full object, mảng, và các kiểu con đà điểu khác.

Tuy nhiên cách thứ 2 có thể hơi phức tạp hơn chút và có phần hơi overkill tuỳ theo mục đích sử dụng.

BroadcastChannel API cũng không được support rộng cho lắm. So với local storage (~96%), và BroadcastChannel chỉ hơn 78%. Chrome và Firefox đều hỗ trợ, tuy nhiên IE, Safari và Edge đều không hỗ trợ. (Chromium Edge vẫn hỗ trợ API này).

Một vài Usecase khác

  • Có thể dùng để báo với user rằng họ đang edit ở một tab/window khác, hoặc dùng để sync data ở nhiều tab với nhau.
  • Phần nội dung cần được phân quyền/ bị khoá sẽ được mở khi login ở một tab khác, giúp tất cả instance đều được đồng bộ.
  • Thay ảnh đại diện.
  • Giao tiếp giữa các iframe - iframe, parent - iframe, iframe - parent.
  • Thay đổi giao diện/setting của trang web và cần đồng bộ với tất cả các tab khác mà không cần refresh thủ công.

Cảm ơn các bạn đã đọc bài (bow)


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.