Announcing bindinator.js

Having recently set up bindinator.com, I am “officially” announcing my side-project Bind-O-Matic.js bindinator.js. It’s a small (currently 7kB gzipped and minified) Javascript library that is designed to make developing in vanilla javascript better in every way than using one or more frameworks. It embodies my current ideas about Javascript, Web, and UI development, and programming — for whatever that’s worth.

Also, I’m having a ton of fun hacking on it.

By way of “dogfooding”, I’m simultaneously building a skunkworks version of my main work project (which is an Electron-based desktop application) with it, adapting any code I can over to it, building b8r’s own demo environment, and slowly porting various other components and code snippets to it.

Above is my old galaxy generator, updated with a bunch of SVG goodness, and implemented using b8r (it was originally cobbled together using jQuery).

Why another framework?

I’ve worked with quite a few frameworks over the years, and in the end I like working in “vanilla” js (especially now that modern browsers have made jQuery pretty much unnecessary). Bindinator is intended to provide a minimal set of tools for making vanilla js development more:

  • productive
  • reusable
  • debuggable
  • maintainable
  • scalable

Without ruining the things that make vanilla js development as pleasant as it already is:

  • Leverage debugging tools
  • Leverage browser behavior (e.g. accessibility, semantic HTML)
  • Leverage browser strengths (e.g. let it parse and render HTML)
  • Be mindful of emerging ideas (e.g. semantic DOM, import)
  • Super fast debug cycle (no transpiling etc.) — see “leverage debugging tools”
  • Don’t require the developer to have to deal with different, conflicting models

The last point is actually key: pretty much every framework tries to abstract away the behavior of the browser (which, these days, is actually pretty reasonable) with some idealized behavior that the designer(s) of the framework come up with. The downside is that, like it or not, the browser is still there, so you (a) end up having to unlearn your existing, generally useful knowledge of how the browser works, (b) learn a new — probably worse — model, and then (c) reconcile the two when the abstraction inevitably leaks.

Being Productive

Bindinator is designed to make programmers and designers more (and separately) productive, decouple their activities, and be very easy to pick up.

To make something appear in a browser you need to create markup or maybe SVG. The easiest way to create markup or SVG that looks exactly like what you want is — surprise — to create what you want directly, not write a whole bunch of code that — if it works properly — will create what you want.

Guess what? Writing Javascript to create styled DOM nodes is slower, more error-prone, less exact, probably involves writing in pseudo-languages, adds compilation/transpilation steps, doesn’t leverage something the browser is really good at (parsing and rendering markup), and probably involves adding a mountain of dependencies to your code.

Bindinator lets you take HTML and bind it or turn it into reusable components without translating it into Javascript, some pseudo-language, a templating language, or transpilation. It also follows that a designer can style your markup.

Here’s a button:

<button class="awesome">Click Me</button>

Now it’s bound — asynchronously and by name.

<button class="awesome" data-event="click:reactor.selfDestruct">
  Click Me
</button>

When someone clicks on it, an object registered as “reactor” will have its “selfDestruct” property (presumably a function) called. If the controller object hasn’t been loaded, b8r’s event handler will store the event and replay it when the controller is registered.

Here’s an input:

<inpu type="range">

And now its value is bound to the fuel_rod_position of an object registered as “reactor”:

<input type="range" data-bind="value=reactor.fuel_rod_position">

And maybe we want to allow the user to edit the setting manually as well, so something like this:

<input type="range" data-bind="value=reactor.fuel_rod_position">
<input type="number" data-bind="value=reactor.fuel_rod_position">

…just works.

Suppose later we find ourselves wanting lots of sliders like this, so we want to turn it into a reusable component. We take that markup, and modify it slightly and add some setup to make it behave nicely:

<input type="range" data-bind="value=_component_.value">
<input type="number" data-bind="value=_component_.value">
<script>
 const slider = findOne('[type="range"]');
 slider.setAttribute('min', component.getAttribute('min') || 0);
 slider.setAttribute('max', component.getAttribute('max') || 10);
 register(data || {value: 0});
</script>

This is probably the least self-explanatory step. The script tag of a component executes in a private context where there are some useful local variables:

component is the element into which the component is loaded; find and findOne are syntax sugar for component.querySelector and component.querySelectorAll (converted to a proper array) respectively, and register is syntax sugar for registering the specified object as having the component’s unique id.

And save it as “slider-numeric.component.html”. We can invoke it thus:

<span 
  data-component="slider-numeric"
  data-bind="component(value)=reactor.fuel_rod_position"
></span>

And load it asynchronously thus:

const {component} = require('b8r');
component('slider-numeric');

To understand exactly what goes on under the hood, we can look at the resulting markup in (for example) the Chrome debugger:

Chrome debugger view of a simple b8r component

Some things to note: data-component-id is human-readable and tells you what kind of component it is. The binding mechanism (change and input event handlers) is explicit and self-documented in the DOM, and the binding has become concrete (_component_ has been replaced with the id of that component’s instance). No special debugging tools required.

Code Reuse

Bindinator makes it easy to separate presentation (and presentation logic) from business logic, making each individually reusable with little effort. Components are easily constructed from pieces of markup, making “componentization” much like ordinary refactoring.

A bindinator component looks like this:

<style>
  /* style rules go here */
</style>
<div>
  <!-- markup goes here -->
</div>
<script>
  /* component logic goes here */
</script>

All the parts are optional. E.g. a component need not have any actual

When a component is loaded, the HTML is rendered into DOM nodes, the script is converted into the body of a function, and the style sheet is inserted into the document head. When a component is instanced, the DOM elements are cloned and the factory function is executed in a private context.

Debugging

Bindinator is designed to have an incredibly short debug cycle, to add as little cognitive overhead as possible, and work well with debugging tools.

To put it another way, it’s designed not to slow down the debug cycle you’d have if you weren’t using it. Bindinator requires no transpilation, templating languages, parallel DOM implementations, it’s designed to leverage your existing knowledge of the browser’s behavior rather than subvert and complicate it, and if you inspect or debug code written with bindinator you’ll discover the markup and code you wrote where you expect to. You’ll be able to see what’s going on by looking at the DOM.

Maintenance

If you’re productive, write reusable (and hence DRY) code, and your code is easier to debug, your codebase is likely to be maintainable.

Scale

Bindinator is designed to make code scalable:

Code reuse is easy because views are cleanly separated from business logic.

Code is smaller because bindinator is small, bindinator code is small, and code reuse leads to less code being written, served, and executed.

Bindinator is designed for asynchrony, making optimization processes (like finessing when things are served, when they are loaded, and so forth) easy to employ without worrying about breaking stuff.

Core Concepts

Bindinator’s core concepts are event and data binding (built on the observation that data-binding is really just event-binding, assuming that changes to bound objects generate events) and object registration (named objects with properties accessed by path).

Bindinator provides a bunch of convenient toTargets — DOM properties to which you might want to write a value, in particular value, text, attr, style, class, and so forth. In most cases bindings are self-explanatory, e.g.

data-bind="style(fontFamily)=userPrefs.uiFont"

There are fewer fromTargets (value, text, and checked) which update bound properties based on user changes — for more complex cases you can always bind to methods by name and path.

Components are simply snippets of web content that get inserted the way you’d want and expect them to, with some syntax sugar for allowing each snippet to be bound to a uniquely named instance object.

And, finally, b8r provides a small number of convenience methods (which it needs to do what it does) to make it easier to work with ajax (json, jsonp), the DOM, and events.

The Future

I’m still working on implementing literate programming (allowing the programmer to mix documentation, examples, and tests into source code), providing b8r-specific lint tools, building out the standard control library (although mostly vanilla HTML elements work just fine), and adding more tests in general. I’m tracking progress publicly using Trello.