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 (andkeyup
for IE, since it doesn't fireinput
correctly) alongsidechange
andblur
events – this ensures instantaneous feedback for a slick experience.If you'd rather the updates only happened on
change
andblur
, pass inlazy: 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
andlazy
directives. Thelazy
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 lastinput
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 aselected
attribute, so we need to specify an initial value ofcolor
. 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' ]);