Binding

Binding is a concise and powerful way to hook up certain components to automatically change values on other objects or even other components. An example is in order to explain.

Say you have a model object that contain various properties you want to use in some kind of application. For this example, we want to draw a circle with a certain radius, line width and fill color. The model might look like this:

const model = {
  radius: 50,
  lineWidth: 1,
  fillColor: "#f00",
};

And say you have a couple of HSliders and a ColorPicker that you want to adjust those values with. You’d probaly add a default handler which changed the model property like so:

const panel = new Panel(document.body, 20, 20, 200, 200);
new HSlider(panel, 20, 20, "Radius", model.radius, 0, 100, event => model.radius = event.target.value);
new HSlider(panel, 20, 60, "Line Width", model.lineWidth, 0, 100, event => model.lineWidth = event.target.value);
new ColorPicker(panel, 20, 100, "Color", model.fillColor, event => model.fillColor = event.target.value);

Not too bad, but binding makes it a bit easier. You just call bind passing in the object you want to affect, and which property of that object you want to set. The property name needs to be a string. It looks like this:

const panel = new Panel(document.body, 20, 20, 200, 200);
new HSlider(panel, 20, 20, "Radius", model.radius, 0, 100)
  .bind(model, "radius");
new HSlider(panel, 20, 60, "Line Width", model.lineWidth, 0, 100)
  .bind(model, "lineWidth");
new ColorPicker(panel, 20, 100, "Color", model.fillColor)
  .bind(model, "fillColor");

A bit more concise, and a whole lot more readable.

Presumably, you also have some kind of function that updates a drawing when the model is changed. Assuming your drawing function is called render, you might try to add that as a default handler, like so:

new ColorPicker(panel, 20, 100, "Color", model.fillColor, render)
  .bind(model, "fillColor");

The problem with this strategy though, is that the default handler will trigger first and do the render, and after that, the bind will change the model property. So your model will always be one change out of date. A solution to this is to add a hander after the bind with the addHandler method, like so:

new ColorPicker(panel, 20, 100, "Color", model.fillColor)
  .bind(model, "fillColor")
  .addHandler(render);

Now the events will be run in the expected order.

Here’s this demo running live:

And the source

Binding components to other components

You can also bind the output of one component to the input of another, like so:

const pb = new ProgressBar(panel, 20, 20, 0, 100);

new HSlider(panel, 20, 60, "Progress", 0, 0, 100)
  bind(pb, "progress");

Now when you change the HSlider, it will update the progress bar’s progress value. Here’s a bit of an insane demo showing a bunch of examples:

All of the numeric components in the top left are bound to each other. Change any one of them changes the rest.

On the bottom left, the text input, label and text area are similarly bound to each other.

The text input on the top right is bound to the color picker’s label. And the color picker’s output, along with the two check boxes are bound to the bottom label’s color, bold and italic properties.

The code is here. Although it’s fairly wordy, you can imagine how rough it would be using handlers and events.