Data binding to the DOM is just one of those things that’s damn handy when you’ve got it and super laborious when you don’t. The problem is that it usually comes at a price of a hefty framework (hefty can apply to byte-size, but more often: the learning curve to use said framework).

So, as any good re-inventer of wheels, I wrote my own two-way data binding library, partly to experiment, partly to solve existing needs in my own projects - weighing in at < 2K compressed.

I present (cleverly named): [bind.js](https://github.com/remy/bind.js)

[I’ve published 38 videos for new developers, designers, UX, UI, product owners and anyone who needs to conquer the command line today.](https://training.leftlogic.com/buy/terminal/cli2?coupon=BLOG\&utm_source=blog\&utm_medium=banner\&utm_campaign=remysharp-discount)

Demo time[](#demo-time)

Below is an [interactive demo](http://rem.jsbin.com/nepuda/edit?console,output) of bind in action. As you change the state of the form, you’ll see the object update on the right (a JSON.stringify output).

Example usage[](#example-usage)

The concept behind the API is fairly simple: take an object bind it to functions and/selectors given a particular mapping.

[For example](https://jsbin.com/remutu/edit?js,console,output), to bind a score and player name to the DOM:

var user = Bind({
  name: '[new user]',
  game: { score: 0 }
}, {
  'game.score': 'span.score',
  name: 'input[name="username"]'
})

// in the game
user.game.score += 25;

…​and the HTML updates take care of themselves. Then the user enters their name in the input field, it’ll update the user object.

The mapping value is flexible too, and can be:

  • A string representing a selector

  • A function which receives the new value

  • An object that supports any of the following properties: dom, callback and transform

With an object as the value of the mapping, it allows you to do a transform on your data before it lands in the DOM. This is obviously useful for things like updating [list elements](https://jsbin.com/nemubo/1/edit?js,output):

var data = Bind({
  cats: ['nap', 'sam', 'prince']
}, {
  cats: {
    dom: '#cats',
    transform: function (value) {
      return '<li>I had a cat called <em>' + this.safe(value) + '</em></li>';
    }
  },
  'cats.0': {
    dom: '#first-cat',
    transform: function (value) {
      return 'My first cat was ' + this.safe(value);
    }
  }
});

Inside the transform function also has a helper, this.safe() which will escape your content safe for HTML.

Object.observe?[](#objectobserve)

Nope. I’m not using it, which is also why there’s some limitations (property deletion being the main/only one).

Why not? Mostly for a larger platform of support. This library supports IE9 upwards (and thus all other browsers) and includes feature detection.

I also tried an Object.observe polyfill early on, but didn’t have much success (though I don’t recall what the issues were). I also fancies the code challenge :)

HTML decorators?[](#html-decorators)

Nope (again). I was recently debugging some Knockout code, and found myself struggling as I realised that the actual binding was happening in the HTML, and the manipulation was happening in the separate JavaScript file.

So no, there’s no data-* support, intentionally. All the code lives in one place: in the JavaScript.

I personally like that my data binding is all in one place. If you’re not so keen, there’s always Angular, Knockout and the like.

Nice to haves[](#nice-to-haves)

I’ve started opening a few [issues](https://github.com/remy/bind/issues) on things I’d like, but they currently include:

  • Root object mapping support (i.e. to be able to hook a callback on anything changing)

  • Glob support, i.e. me.*.name will fire callbacks matching any path starting with me and ending with name

  • Test to test every individual form element (currently it’s a bit of a mishmash)

Feel free to [raise an issue](https://github.com/remy/bind/issues) or feature request or let me know if you’d use this even!

Published 2-Jun 2015 under #code. [Edit this post](https://github.com/remy/remysharp.com/blob/main/public/blog/bind.md)

Comments

Lock Thread

Login

Add Comment[M ↓   Markdown]()

[Upvotes]()[Newest]()[Oldest]()

Paul Hodel

0 points

2 years ago

LemonadeJS is another micro-library (4K), with no dependencies worth looking at. https://lemonadejs.net https://github.com/lemonadejs/lemonadejs

0 points

3 years ago

Thanks for your nice simple tool. I used it as a core component for my chess FEN editor: https://shaack.com/projekte/cm-fen-editor/ and it works great. It’s a nice alternative for oversized frameworks…​ like…​ you know.

![](/images/no-user.svg)

Ollie Williams

-1 points

6 years ago

Have you thought of remaking this with ES6 proxies?

![](/images/no-user.svg)

josip haboić

0 points

7 years ago

Useful, gonna try it now :)

![](/images/no-user.svg)

Aurelio De Rosa

0 points

7 years ago

Worth mentioning that Object.observe() has been deprecated and it’ll be removed soon in all browsers.

![](/images/no-user.svg)

Juan Pastás

0 points

7 years ago

hmmm, why they do such things…​

![](/images/no-user.svg)

Stu

0 points

8 years ago

The demo dies after a few seconds with

"ReferenceError: Bind is not defined

at nepuda.js:1:2060"

![](/images/no-user.svg)

rem

0 points

8 years ago

Try opening the bin directly.

![](/images/no-user.svg)

Stu

0 points

8 years ago

Exactly the same thing happens - ah, it was privacy badger blocking [cdn.rawgit.com](http://cdn.rawgit.com)

![](/images/no-user.svg)

Andres C. Rodriguez

0 points

8 years ago

Quick question: if I understand correctly Angular does the MODEL to VIEW way using a polling mechanism every 100ms. I looked through the code to see if how you were doing it but was not able to figure it out. Could you comment on your technique here? Do you need special accessors (setter/getter)? Thanks for your answer. Keep up the good work.

![](/images/no-user.svg)

CodySCarlson

0 points

6 years ago

Any comment on the "technique here"? Very curious. Also, Bind.js looks like just what I’ve been looking for!

![](/images/no-user.svg)

Poster

0 points

8 years ago

Works awesome, except for TEXTAREA binding.

![](/images/no-user.svg)

rem

1 point

7 years ago

Long time to reply to this, but I fixed textarea support a while back. Thank you!

![](/images/no-user.svg)

Penuel

1 point

6 years ago

Thanks for the great bind.js library. However as others pointed out it still does not work with textarea. I tried it in my project and your demo bin, but it does not work. Do you have an example for it? Thanks once again.

![](/images/no-user.svg)

csev

0 points

6 years ago

In case it takes a while to get an answer - this was my workaround using jQuery

var data = Bind(…​);

$(document).on("change","textarea.two-way",function(){\ data.me.description = $(this).val(); // Trigger onchange\ });

I wanted to do a lot of textareas :)

![](/images/no-user.svg)

csev

0 points

6 years ago

I see the commit where you added textarea support but cannot make it work no matter what I try. A simple textarea example added to the form elements example would be super useful.

![](/images/no-user.svg)

vitmalina

0 points

8 years ago

I am curious, how does it compare to http://rivetsjs.com, which seems to be just a data binding library too.

![](/images/no-user.svg)

Ben Styles

0 points

8 years ago

Rivets contributor here – I think the main difference lies in how you declare your bindings. In Rivets, you declare the bindings in your template (or directly in the dom). Logic stays in your javascript, but you are explicitly marking up your view with bindings to your model. This library, conversely, keeps all binding declarations in your javascript, as Remy says himself above – this is his explicit aim, to keep binding decorators out of the html view. Rivets is also larger, more mature, with a really cool system for swapping out almost all its core processes (change-observers, binding transformers, etc) with your own implementations. But one thing Rivets can’t do at the moment is build out a brand new model from the view, rather than the other way around. I’d be interested to hear from Remy if this is possible with Bind.js

![](/images/no-user.svg)

rem

0 points

8 years ago

Hi Ben - thanks for taking the time to reply.

Can you explain (maybe in pseudo code) what you’d expect from "build out a brand new model from the view"?

![](/images/no-user.svg)

Ben Styles

0 points

8 years ago

Of course. I will do it in "rivets pseudo code" as that’s what I know.

Currently, you declare bindings in the html like

<p rv-text="product.price"></p>

When you call rivets.bind(view, model), rivets takes your object

var model = { product: { price: '123' } }

And sets the textContent in the p tag to 123. But if you are compiling static html views (e.g. using Jekyll or Roots or some other static site generator), it makes sense to put that data in the dom at compile time. But you still want the binding to be dynamic. Rivets cannot, currently, parse the html for values already rendered and turn that into a javascript model object.

Could you do this with Bind.js?

![](/images/no-user.svg)

rem

1 point

8 years ago

Ah, interesting. Well, you couldn’t with 0.4.0, but you can now with 1.0.1 :) (though there was only one release, and I wanted the version on 1.x rather than 0.x).

If you want to set the value from the DOM into the Bind.js object, then you do (for example):

<p class="price">123</p>

In JS:

\`\`var model = new Bind({\ price: null,\ }, {\ price: '.price',\ });

console.assert(model.price === '123');`\`

Fairly [straight forward](https://github.com/remy/bind.js/commit/fa736bbbdb9b5a7fc8243ebcbfe81231fd6b6d32#diff-8c3cba4d9eb16ca73c7ce483e4575241) change - it just needs the model to have a null value to indicate that the value should come from the DOM (which I’ll add to the README).

![](/images/no-user.svg)

Mircea Militaru

0 points

8 years ago

On Safari is the first thing you do is a click on the progress know you get a nice browser crash. Can’t tell why

![](/images/no-user.svg)

rem

0 points

8 years ago

I’ve just tried Safari on the desktop (Yosemite) and it doesn’t crash for me. Could you file an issue? Also, the progress element isn’t clickable, so I’m guessing you hit the range element instead?

![](/images/no-user.svg)

hexx

0 points

8 years ago

nice, IE8 doesn’t work because of ES5 (would shim work?)

![](/images/no-user.svg)

rem

0 points

8 years ago

That’s right - it’s possible to add polyfills, but it wasn’t a particular aim of the project (to keep it "simple"…​since the binding code was actually quite tricky!)

[Commento](https://commento.io)