Bubble.js: A 1.6K Solution to a Common Problem

One of the most common tasks in web development is event management. Our JavaScript code is usually listening to events dispatched by the DOM elements. 

This is how we get information from the user: That is, he or she clicks, types, interacts with our page and we need to know once this happen. Adding event listeners looks trivial but could be a tough process.

In this article, we will see a real case problem and its 1.6K solution.

The Problem

A friend of mine works as a junior developer. As such, he doesn't have a lot of experience with vanilla JavaScript; however, he has had to start using frameworks such as AngularJS and Ember without having the fundamental understanding of DOM-to-JavaScript relationship. During his time as a junior developer, he was put in charge of a small project: A single page campaign websites with almost no JavaScript involved. He faced a small but very interesting problem which ultimately lead me to write Bubble.js.

Imagine that we have a popup. A nicely styled <div> element:

Here is the code that we use to show a message:

We have another function hideMessage that changes the display property to none and hides the popup. The approach may work in the most generic case but still has some problems. 

For example, say we need to implement additional logic if the issues come in one by one. Let’s say that we have to add two buttons to the content of the popup - Yes and No.

So, how we will know that the user clicks on them? We have to add event listeners to every one of the links. 

For example:

The code above works during the first run. What if we need a new button, or even worse, what if we need a different type of button? That is, what if we were to continue using <a> elements but with different class names? We can't use the same addListeners function, and it's annoying to create a new method for every variation of popup.

Here are where problems become visible:

  • We have to add the listeners again and again. In fact, we have to do this every time when the HTML in the popup’s <div> is changed.
  • We could attach event listeners only if the content of the popup is updated. Only after the showMessage calling. We have to think about that all the time and sync the two processes.
  • The code that adds the listeners has one hard dependency - the popup variable. We need to call its querySelector function instead of document.querySelector. Otherwise, we may select a wrong element.
  • Once we change the logic in the message we have to change the selectors and probably the addEventListener calls. It is not DRY at all.

There must be a better way to do this.

Yes, there is a better approach. And no, the solution is not to use a framework.

Before to reveal the answer let’s talk a bit about the events in the DOM tree.

Understanding Event Handling

Events are an essential part of web development. They add interactivity to our applications and act as a bridge between the business logic and the user. Every DOM element can dispatch events. All we have to do is to subscribe for these events and process the received Event object.

There is a term event propagation that stands behind event bubbling and event capturing both of which are two ways of event handling in DOM. Let’s use the following markup and see the difference between them.

We will attach click event handlers to the both elements. However, because there are nested into each other, they both will receive the click event.

Once we press the link we see the following output in the console:

So, indeed the both elements receive the click event. First, the link and then the <div>. This is the bubbling effect. From the deepest possible element to its parents. There is a way to stop the bubbling. Every handler receives an event object that has stopPropagation method:

By using stopPropagation function, we indicate that the event should not be sent to the parents.

Sometimes we may need to reverse the order and have the event caught by the outer element. To achieve this, we have to use a third parameter in addEventListener. If we pass true as a value we will do event capturing. For example:

That is how our browser process the events when we interact with the page.

The Solution

Okay, so why did we spend a section of the article talking about bubbling and capturing We mentioned them because bubbling is the answer of our problems with the popup. We should set the event listeners not to the links but to the <div> directly.

By following this approach, we eliminate the issues listed in the beginning.

  • There is only one event listener and we are adding it once. No matter what we put inside the popup, the catching of the events will happen in their parent.
  • We are not bound to the additional content. In other words, we do not care when the showMessage is called. As long as the popup variable is alive we will catch the events.
  • Because we call addListeners once, we use the popup variable also once. We do not have to keep it or pass it between the methods.
  • Our code became flexible because we opted not care about the HTML passed to showMessage. We have access to the clicked anchor in that e.target points to the pressed element.

The above code is better than the one that we started with. However, still doesn’t function the same way. As we said, e.target points to the clicked <a> tag. So, we will use that to distinguish the Yes and No buttons.

We fetched the value of the class attribute and use it as a key. The different classes point to different callbacks.

However, it is not a good idea to use the class attribute. It is reserved for applying visual styles to the element, and its value may change at any time. As JavaScript developers, we should use data attributes.

Our code becomes a little bit better too. We can remove the quotes used in addListeners function:

The result could be seen in this JSBin.

Bubble.js

I applied the solution above in several projects so it made sense to wrap it a library. It’s called Bubble.js and it is available in GitHub. It is 1.6K file that does exactly what we did above.

Let’s transform our popup example to use Bubble.js. The first thing that we have to change is the used markup:

Instead of data-action we should use data-bubble-action.

Once we include bubble.min.js in our page, we have a global bubble function available. It accepts a DOM element selector and returns the library’s API. The on method is the one that adds the listeners:

There is also an alternative syntax:

By default, Bubble.js listens for click events, but there is an option to change that. Let’s add an input field and listens for its keyup event:

The JavaScript handler still receives the Event object. So, in this case we are able to show the text of the field:

Sometimes we need to catch not one but many events dispatched by the same element. data-bubble-action accepts multiple values separated by comma:

Find the final variant in a JSBin here.

Fallbacks

The solution provided in this article relies completely on the event bubbling. In some cases e.target may not point to the element that we need. 

For example:

If we place our mouse over "choose" and perform a click, the element that dispatches the event is not the <a> tag but the span element.

Summary

Admittedly, communication with the DOM is an essential part of our application development, but it is a common practice that we use frameworks just to bypass that communication. 

We do not like adding listeners again and again. We do not like debugging weird double-event-firing bugs. The truth is that if we know how the browser works, we are able to eliminate these problems.

Bubble.js is but one result of few hours reading and one hour coding - it's our 1.6K solution to one of the most common problems.

Tags:

Comments

Related Articles