Menu Home

MessageChannel()

Hi everyone, and welcome to another exciting edition of Boring JavaScript! Today, we tackle channel messaging through the MessageChannel() API.

Don’t like to read? Then watch our video.

Same Bat Time, Same Bat Channel

Not to terribly long ago, communications between an HTML page and an iFrame, or two iFrames, or in later years Shared Workers, was possible, but was cumbersome and fraught with pitfalls that could lead to errors. Channel Messaging was created to facilitate the communications between all those different browsing contexts. Now it’s a simple matter of passing port numbers between the contexts and letting them message each other to their heart’s content.

It’s really that easy,

In our case, we’re going to have the main browser screen talk back and forth with an iFrame.

Let’s take a look at an example. First, here is our HTML for the main screen:

<main>
    <h2>Channel Message</h2>
    <div id="display-board">
        <section id="loading-dock">
            <iframe src="loading-dock/index.html" width="450" height="400"></iframe>
        </section>
        <section id="zoo">
            <h3>Virtuoid Zoo</h3>
            <table>
                <thead>
                    <tr><th>Type</th><th>Name</th><th>Action</th></tr>
                </thead>
                <tbody>

                </tbody>
            </table>
        </section>
    </div>
</main>

The iFrame in Line 5 will contain another app that represents our ‘loading dock’ for the animals in our zoo. The <table> above will list out all of the animals that get transferred from the loading dock (in the iFrame) to our zoo. Each one will have a ‘remove’ button that will transfer the animal back to the loading dock.

Here is the HTML for the iFrame:

<main>
    <h2>Loading Dock</h2>
    <label><span>Animal to add:</span><input id="animal-to-add" type="text" /><button type="button" id="add-animal">Add</button></label>
    <label><span>Animal to Transfer:</span><input readonly id="animal-to-transfer" type="text" /><button type="button" id="transfer-animal">Transfer</button></label>
    <table>
        <thead>
            <tr><th>Type</th><th>Name</th></tr>
        </thead>
        <tbody id="zoo-listing">
            <tr><td>Cat</td><td>Fluffy</td></tr>
            <tr><td>Dog</td><td>Fido</td></tr>
            <tr><td>Horse</td><td>Mr. Ed</td></tr>
            <tr><td>Cow</td><td>Betsy</td></tr>
            <tr><td>Coyote</td><td>Wile E.</td></tr>
            <tr><td>Road Runner</td><td>Beep Beep</td></tr>
            <tr><td>Dolphin</td><td>Flipper</td></tr>
            <tr><td>Whale</td><td>Moby Dick</td></tr>
            <tr><td>Lizard</td><td>Larry</td></tr>
        </tbody>
    </table>
</main>

We already have nine animals in the holding area at the loading dock (the table). Notice there are two buttons at the top: ‘Add’, which will add an animal to our loading dock holding area, and ‘Transfer’ which will transfer to the animal to the Zoo itself.

The idea will be to select an animal from the list to ‘Transfer’ to the zoo, and when the zoo keeper clicks on the ‘Remove’ button, it will put that animal on the ‘Add’ button to be added back into the holding area.

Let’s Shake on It.

Now the fun begins. It is very simple to setup a Messaging Channel between the main browser and the iFrame. The idea is this:

  1. Create the Messaging Channel. It will return two ports
  2. Send one port to the iFrame. Keep the other for yourself.
  3. Setup a listener to the ‘message’ event on each port.
  4. When you want to send data, use the ‘postMessage’ method.

And that’s it. It’s that easy. Let’s look at the code on the main browser side:

const loadingDockInterface = document.querySelector('#loading-dock iframe');
const zoo = document.querySelector('#zoo table tbody');

const loadingDockChannel = new MessageChannel();
const loadingDockChannelPort = loadingDockChannel.port1;

const transferAnimalToZoo = (event) => {
	const [ type, name ] = event.data.split(',');
	const tr = document.createElement('tr');
	tr.insertAdjacentHTML('beforeend', `<td>${type}</td>`);
	tr.insertAdjacentHTML('beforeend', `<td>${name}</td>`);
	tr.insertAdjacentHTML('beforeend', `<td><button>Remove</button></td>`);
	zoo.appendChild(tr);
}

const transferAnimalToLoadingDock = (event) => {
	if (event.target.tagName === 'BUTTON') {
		const tdName = event.target.parentElement.previousElementSibling;
		const tdType = tdName.previousElementSibling;
		const animalToTransfer = `${tdType.textContent},${tdName.textContent}`;
		loadingDockChannelPort.postMessage(animalToTransfer);
		tdName.parentElement.remove();
	}
}

const initializeLoadingDock = (event) => {
	loadingDockChannelPort.addEventListener('message', transferAnimalToZoo);
	loadingDockChannelPort.start();
	loadingDockInterface.contentWindow.postMessage('init', '*', [loadingDockChannel.port2]);
};
loadingDockInterface.addEventListener('load', initializeLoadingDock);
zoo.addEventListener('click', transferAnimalToLoadingDock);

The important lines are:

  1. Line 4 – Creates the Messaging Channel, and creates two ports – ‘port1’ and ‘port2’
  2. Line 5 – We’re keeping ‘port1’
  3. Line 31 – We setup a listener on the iFrame to know when it has loaded. When it is done, we call…
  4. Line 28 – We setup an event listener on our ‘port1’ to listen for messages coming in from the other port.
  5. Line 29 – We send a message to the iFrame telling it that we are sending it an array of ports (in this case, only one port – ‘port2’) it can use. You can look up the details to the ‘contentWindow.postMessage’ method on the MDN docs. Now the iFrame know what port to use.
  6. Line 32 – We setup a click listener on the zoo element so that when we click on a ‘Remove’ button, we can act upon that.
  7. Line 21 – When we know what animal to remove, we use ‘postMessage()’ to our port number (port1). This sends the message to the iFrame.

And that’s it from our side. We basically setup an event listener on ‘port1’ to listen for messages from ‘port2’, use ‘postMessage()’ on ‘port1’ to send messages to ‘port2’

And we reverse the process on the iFrame side:

let iFramePort = null;
const animalToAdd = document.getElementById('animal-to-add');
const addAnimal = document.getElementById('add-animal');
const animalToTransfer = document.getElementById('animal-to-transfer');
const transferAnimal = document.getElementById('transfer-animal');
const zooListing = document.getElementById('zoo-listing');

let transferAnimalTableElement = null;

const setDisplayForIFrame = (event) => {
	switch (event.data) {
		case 'init':
			iFramePort = event.ports[0];
			iFramePort.addEventListener('message', receiveAnAnimal);
			document.body.classList.add('iframe');
			iFramePort.start();
			break;
		default:
			break;
	}
}

const addTheAnimal = (event) => {
	const [type, name] = animalToAdd.value.split(',');
	zooListing.insertAdjacentHTML('beforeend', `<tr><td>${type}</td><td>${name}</td></tr>`);
	animalToAdd.value = '';
}

const transferTheAnimal = (event) => {
	if (animalToTransfer.value) {
		iFramePort.postMessage(animalToTransfer.value);
		transferAnimalTableElement.remove();
		transferAnimalTableElement = null;
		animalToTransfer.value = "";
	}
}

const receiveAnAnimal = (event) => {
	animalToAdd.value = event.data;
}

const addAnimalToTransferInput = (event) => {
	transferAnimalTableElement = event.target.parentElement;
	const transferAnimalText = `${transferAnimalTableElement.firstChild.textContent},${transferAnimalTableElement.firstChild.nextSibling.textContent}`;
	animalToTransfer.value = transferAnimalText;
}

window.addEventListener('message', setDisplayForIFrame);
addAnimal.addEventListener('click', addTheAnimal);
transferAnimal.addEventListener('click', transferTheAnimal);
zooListing.addEventListener('click', addAnimalToTransferInput);

It looks like a lot, but most of it is DOM manipulation. The important steps are:

  • Line 48 – Sets up a listener to the ‘contextWindow.postMessage’ message from the main browser. Once received, it goes to…
  • Line 13 – We save the port number the main browser window sent us.
  • Line 14 – We setup an event listener on the port for any messages coming from the main browser,
  • Line 31 – When we click on the ‘Transfer’ button, the value inside of the input box is sent to the main browser via the ‘postMessage’ method.
  • Line 39 – This is the code that executes when we get a message from the main browser. Not much there.

Everything else is pretty much DOM manipulation code. The key here is that the iFrame received the port from the main browser app, and that allows them to communicate to each other through a simple event listener and a single ‘postMessage’ method.

And – seriously – that’s it. Create the channel, give the ports to the two processes that need it, and tell them to postMessage() and addEventListeners. Then message to your heart’s content.

The Video

We have a great video that show the above code in action. Check it out!

Shameless Plug

You can find us everywhere on the web!

Check out all our videos at: https://www.boringjavascript.com

Check out everything at: https://www.thevirtuoid.com

Facebook: https://www.facebook.com/TheVirtuoid

Twitter: https://twitter.com/TheVirtuoid

YouTube: https://www.youtube.com/channel/UCKZ7CV6fI7xlh7zIE9TWqgw

Categories: Boring JavaScript Javascript

Tagged as:

thevirtuoid

Web Tinkerer. No, not like Tinkerbell.

Creator of the game Virtuoid. Boring JavaScript. Visit us at thevirtuoid.com

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: