Two-way Binding

HTML forms are a very important part of many web applications. Most data-binding in a template only goes in one direction - from the model to the view, but form elements provide a point at which the view could update the model. To that end, Ractive.js provides special bindings for form elements that go both ways - from the model to the view and from the view to the model - or two-way bindings.

Step 1

The 'Hello, world!' of two-way data binding looks like this:

<label>
  Enter your name:
  <input value='{{name}}'>
</label>

<p>Hello, {{name}}!</p>

Update the template and re-render it, then type your name in the box.

Internally, we're binding to input events (and keyup for IE, since it doesn't fire input correctly) alongside change and blur events – this ensures instantaneous feedback for a slick experience.

If you'd rather the updates only happened on change and blur, pass in lazy: true as an initialisation option.

If you'd rather disable two-way binding altogether, you can do so with twoway: false.

You can also control two-way bindings and laziness on a per-element basis using the twoway and lazy directives. The lazy directive may be boolean, or if you'd like to get updates without a blur, you can pass it a number of milliseconds to wait after the last input event to trigger the update.

That's a cute demo, but it doesn't have much real world use. In all likelihood we want to do something with the data when it changes. For that, we use ractive.observe():

ractive.observe( 'name', function ( newValue, oldValue ) {
  doSomethingWith( newValue );
});

Step 2

You can control whether checkboxes are checked or not like so:

<label>
  <input type='checkbox' checked='{{checked}}'>
  {{#if checked}}checked!{{else}}not checked{{/if}}
</label>

Update the template and try toggling the checkbox.

If you have a group of radio buttons, whose values are mutually exclusive, you can do this:

<label><input type='radio' name='{{color}}' value='red' checked> red</label>
<label><input type='radio' name='{{color}}' value='green'> green</label>
<label><input type='radio' name='{{color}}' value='blue'> blue</label>
<p>The selected colour is <span style='color: {{color}};'>{{color}}</span>.</p>

Here, because we've set the name attribute to {{color}}, the value of color is set to the value attribute of whichever radio button is currently checked. (If you need to read that sentence a couple of times, I don't blame you.) Notice that the value is initialised to red, because that option is initially checked.

Add name='{{color}}' to each of the options in the template and run the code.

Front-end über nerds will notice that this isn't how these attributes normally work. For example, a checkbox with checked='false' is the same as one with checked='true', because it's a boolean attribute which either exists on the element or doesn't – its value is completely irrelevant.

Furthermore, once you've interacted with a checkbox, its checked attribute becomes irrelevant! You can only change the value programmatically by doing element.checked = true rather than element.setAttribute( 'checked' ).

Combine all that with cross-browser quirks (e.g. IE8 and below only fire change events on blur), and we're in some seriously confusing territory.

So Ractive.js makes no apology for using checked='{{checked}}' to mean 'checked if checked is true, unchecked if it's false'. We're bringing sanity to the process of gathering user input.

Needless to say, you can continue to interact with the values programmatically:

ractive.set( 'checked', true );
ractive.set( 'color', 'green' );

This is as good a time as any to introduce the ractive.toggle() method:

ractive.toggle( 'checked' );

// Equivalent to:
//   var checked = ractive.get( 'checked' );
//   ractive.set( 'checked', !checked );

Step 3

As well as <input> elements (and <textarea>s, which work similarly), two-way binding works with <select> menus. Let's replace the radio group from the previous step with a <select>:

<select value='{{color}}'>
  <option>red</option>
  <option>green</option>
  <option selected>blue</option>
</select>

I apologise to my fellow Brits, and other English-speaking non-Americans, for the repeated use of color instead of colour. Occupational hazard.

Re-render the ractive. Notice that once again, the data is initialised to the value of the selected <option> – in this case, blue. (You can explicity set a value attribute, but if you don't, the text content of the <option> is used instead.)

That's good, but we can go one better – rather than hard-coding our colours into the template, let's do it properly:

<select value='{{color}}'>
  {{#each colors}}
    <option>{{this}}</option>
  {{/each}}
</select>

We haven't seen {{this}} before – it simply means 'the current context'. Previously, whenever we've used lists, they've been lists of objects, so we've been able to use a property of the object (like {{name}}). Using this allows us to use lists of primitives (in this case, strings) instead.

If you prefer, you can use {{.}} instead of {{this}}.

You can also set up an alias to this for a slightly more human friendly template in many circumstances. Within {{#each users as user}}...{{/each}}, user will resolve to the current iteration. You can still use index names too e.g. {{#each users as user: i}}...{{/each}}.

And add some data to our view:

var ractive = Ractive({
  el: output,
  template: template,
  data: {
    colors: [ 'red', 'green', 'blue' ],
    color: 'green'
  }
});

The template no longer has an <option> with a selected attribute, so we need to specify an initial value of color. Execute this code. For extra credit, add more colours:

ractive.push( 'colors', 'purple' );

Step 4

In some situations you need to make it possible to select several values simultaneously. HTML has us covered – we use the multiple attribute with a <select>.

Unfortunately that's as helpful as it gets – selectElement.value returns the value of the most recently selected option, which is just mad, frankly. In almost all cases, if you're using a select element with the multiple attribute, what you really want is an array of the selected values. This is what Ractive.js provides.

Try adding the multiple attribute to the template:

<select value='{{selectedColors}}' multiple>

Execute, then try making multiple selections. And, of course, it works the other way round:

ractive.set( 'selectedColors', [ 'green', 'purple' ]);