Pothibo

ObserveJS

JavaScript is growing at an incredible rate from all fronts. People are releasing frameworks at an amazing rate. And if you think it isn't enough, browsers are also pushing new APIs for you to build new things. I don't know about you, but I can't keep up.

Generally, I hate magic in frameworks. I think it just brings a lot of unexpected things in an environment that is already full of unexpections. Browser APIs is the zoo. Some browser support XYZ while other only support X and Z. And for that reason, I loved jQuery. It didn't try to be clever, it just tried — and succeeded — to make our life easier. Today, things are different. Many of the things that jQuery brought merged back into the ECMAStandard, like querySelector, event listeners, etc.

Cross browser support is not a done deal yet but it seems to be headed that way. As a result, people put more and more pressure on JavaScript by designing highly dynamic websites. I guess this is the reason why all those MVCs have come into existence. A natural evolution, if you will.

But with React, it seems there's an invisible hand that is pushing back on the MVC. They claim it's a Model<->View structure. Are we dropping the controller on the front end? Sure feels that way. And seeing how popular React is getting, I think I'm not the only one who thinks that way.

Nonetheless, I think that React is a middle step towards something else. I think we're gradually going back to an object oriented based on DOM elements. Wait, what?

Yes, I think that slowly, people will realize that JavaScript land is purely an extension of the DOM. JavaScript's only job is to manipulate the DOM. It listens to it, mutates it, creates it and deletes it. Everything else is indirectly motivated by your need to do one of those 4 things.

With that point of view, I created ObserveJS. Its simplicity will baffle you. You may even look at it thinking that it's either stupid or useless. But I have been using it for a year now and let me tell you that it's simplicity is what makes it so awesome to work with. Much like jQuery, ObserveJS doesn't try to be clever, it just makes your life easier.

Also, and more importantly, I didn't want to force myself to use paradigms. I didn't want to be slowed by new paradigms & concepts. I wanted something I could snap on my project and keep business as usual.

Here's how I did it.

Prelude

Let's pretend, for a minute, that you want to build a popup. Here's the list of features you want to implement:

  1. Popup should be on top of everything else (duh);
  2. A gray overlay should be rendered behind the popup;
  3. Prevent scrolling so user deals with the popup;
  4. Hitting escape or clicking outside the popup dismisses it.

Now that you know what I'm building, let's dig in.

Bind it and forget about it

ObserveJS works by declaring classes in JavaScript and then binding it to a label. That label can be anything you want as long as it's unique — it will raise an exception if there's a collision. In the context of the dialog & overlay I'm building, here's how I would do it. Keep in mind that I'm using CoffeeScript here but JavaScript is equally good.

ObserveJS.bind 'Overlay', class loaded: =>
ObserveJS.bind 'Popup', class loaded: =>

This is how I bind two anonymous class to two different labels: Overlay and Popup. Right now, they do nothing. What I want to do is to have those two classes load when their matching DOM element is found on the DOM. This is where the label becomes important: it's purpose is to identify elements that you want to bind to the classes. That's why the method is called bind. It literally binds a JavaScript class to a DOM element.

So, how do you bind those classes to the DOM?

<div as='Overlay'> <div as='Popup'> <h2>Hello world!</h2> </div> </div>

Now, when this piece of HTML makes it to the DOM, ObserveJS will instantiate the class automatically and call loaded which I implemented above. This is the only mandatory method. And the cool thing is that you can add the overlay and popup whenever you want. ObserveJS will pick it up as soon as it appears on the DOM using DOM Mutation Observers.

But I'm still missing features. I'm going to start with the CSS which should implement feature #1 and #2.

div.overlay { display: flex; justify-content: center; align-items: center; min-height: 100vh; width: 100vw; position: absolute; top: 0; left: 0; background: rgba(0,0,0,0.5); z-index: 5000; } main.overlayed { position: fixed; } div.popup { display: flex; flex-direction: column; border-radius: 2px; max-width: 90vw; width: 600px; background: #DDE4EA; box-shadow: 0 0 3px #1a3642; z-index: 101; }

And now, I'm going to edit the JavaScript classes to implement feature #3 and #4:

@on 'click', @clicked @on 'keyup', document, @escaped @y = window.scrollY main = document.body.querySelector('main') main.classList.add('overlayed') main.style.top = "-#{@y}px" document.body.scrollTop = 0 clicked: (e) => if e.target == @element() @remove() escaped: (e) => if e.keyCode == 27 @remove() remove: (e) => main = document.body.querySelector('main') main.classList.remove('overlayed') @element().remove() window.scrollTo(0, @y) main.style.top = null @scrollTop = null
loaded: => # Nothing to do here, everything is handled in the overlay

This is all you need to make it work. Don't believe me? Try it out.

Now if that isn't awesome, I don't know what is. However, you may have looked at the code above and noticed @on() and @element() and wondered where they come from.

It's ObserveJS that applies those two methods to every object that is created by it. @element() returns the element that you labelled and whom your class is bound to.

On the other hand, @on() is a helper method that wraps addEventListener() to your method defaulting to @element(). So if you want to listen to an event from a child of your element or the element itself, you can use the first flavor of method. If you need to specify an element, you can look at the example to understand how to do it.

DOM Mutation Observer

A few seconds ago, I mentioned that ObserveJS uses mutation observers to watch for changes on the DOM and instantiate classes. In its most basic form, the observer looks for changes and query the tree subset for any matching element.

outdated: (mutations) -> mutations.forEach (mutation) => matched = mutation.target.querySelectorAll("[as]") @findAndInstantiateClassFor(matched)

That means you don't have to deal with page load statuses. You bind the object to a label, apply that label to any element you want and done! As soon as it gets on the page, ObserveJS picks it up and loads it for you.

It also means that if you are using turbolinks, you don't have to deal with any loading events. Before, you had to listen for DOMContentReady, page:load and maybe other events too. Now, you have to listen to zero event. Zip. Nada. Fire and forget.

While I believe this makes web development even easier than before, I believe there's more to it than mere functionality. It blurs the line between the DOM and the JavaScript environment.

Objectified environment

The DOM is essentially object oriented. And even though JavaScript doesn't have classes (yet), most people build their JavaScript components in that fashion. So, if DOM are objects and JavaScript are objects, why not joint the two together?

Prototype.js did that ages ago. While I believe it was a very good idea, I also agree that it had issues. But those issues belongs to an another era.

Binding JavaScript classes to DOM elements feel natural to me. Before that, I never knew where to store my objects. Now, I don't even need to think about that. I don't have to think about the life cycle anymore. I can focus on what I'm trying to build.

Looking at the future

The future is bright for the web. There's a lot of work involved in many areas and one thing that I am really looking forward is the web components because it will make ObserveJS even more relevant than it is for me today. Web components will allow you to custom elements that inhibit different behaviors that are going to be implemented in JavaScript.

DOM elements with life cycle callbacks. That does look like ObserveJS, doesn't it? It may or may not replace ObserveJS in the future. And it doesn't matter.

Because today, I'm using ObserveJS and I get to use it today in my applications. It makes my code easier to maintain, and easier to read. Give it a try.

Get more ideas like this to your inbox

You will never receive spam, ever.