Two-way binding

edit this page

By default, a Ractive instance will update its internal model based on user input, if you have <input>, <textarea> or <select> elements.

If that's unhelpful for your app, you can disable it by passing twoway: false as an initialisation option.

<input> elements

Two-way binding works with <input> elements of any type, including the new HTML5 types such as color or email. In most cases, you just add a mustache as the value attribute - this must be a single, standard interpolator mustache, like this:

<input placeholder='Type your name' value='{{user.name}}'>

In the example above, any changes the user makes to the input will be reflected in the user.name property.

Checkboxes

Checkboxes are used to flag a property as true or false. Rather than binding the value attribute, we bind the checked attribute:

<label>
    <input type='checkbox' checked='{{on}}'>
    The lights are {{ on ? 'on' : 'off' }}
</label>

(True HTML nerds will recognise that boolean attributes such as checked don't have a value, as such - they either exist on an element or they don't. This makes life unnecessarily difficult; Ractive treats dynamic (i.e., controlled by a mustache) boolean attributes as present if the mustache is true, absent if false.)

Checkboxes may also be bound by the name attribute, which will result in the value specified in the name attribute containing an array of selected checkbox values.

{{#colors}}
  <label>
    <input type='checkbox' name='{{selected}}' value='{{.}}' />{{.}}
  </label>
{{/}}

If colors is an array, ['Red', 'Yellow', 'Green', 'Blue'], then checking the first box will result in selected becoming ['Red']. Checking the third box will result in selected becoming ['Red', 'Green']. The array may also be updated from code using set or any of the array modification methods available to Ractive, and the checkboxes will be updated to reflect the contents of the array in the Ractive viewmodel.

Note that checkboxes may be bound by either checked or name but not both.

Radios

Radio buttons can be controlled in the same way as checkboxes. However the chances are, if you're using radio buttons, you have a set of mutually exclusive options to choose between. In this case, if you set the name attribute on a radio input to e.g. {{someValue}}, someValue will reflect the value of the currently checked option:

<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 currently selected color is <span style='color: {{color}};'>{{color}}</span></p>

In the example above, the value of color will initially be 'red' (because that input is checked in the template), and will update as and when the user selects another option.

<textarea> elements

Textareas work exactly the same as standard text inputs - slap a dynamic value attribute on them, and you're done:

<textarea value='{{content}}'></textarea>

<select> elements

Drop-down select menus are good when you have many options to choose from. They're straightforward to use in Ractive:

<select value='{{selectedCountry}}'>
    <option selected disabled>Select a country</option>
    {{#countries}}
        <option value='{{id}}'>{{name}}</option>
    {{/countries}}
</select>
ractive = new Ractive({
    el: myContainer,
    template: myTemplate,
    data: {
        countries: [
            { id: 'AFG', name: 'Afghanistan' },
            { id: 'ALB', name: 'Albania' },
            { id: 'DZA', name: 'Algeria' },
            { id: 'ASM', name: 'American Samoa' }
            // and so on...
        ]
    }
});

In the example above, no country will initially be selected. If the user were to select Afghanistan from the list, the value of selectedCountry would change to 'AFG'.

contenteditable

Ractive can two-way data bind contenteditable elements.

var ractive = new Ractive({
    el: myContainer,
    template: myTemplate,
    data: {
        content: 'Edit me'
    }
});

ractive.observe('content', function(newValue, oldValue, keypath) {
    //newValue will contain the new content of the div
});

myTemplate

<div contenteditable="true" value="{{content}}"></div>

This will be rendered as

<div contenteditable="true">Edit Me</div>

As the user edits the data the content will update, and observe will fire.

There are a few caveats that you need to know. Despite the value binding with only two curly braces {{content}} (double curly braces are required for two way data binding) the returned content may or may not be a string of HTML. If a user presses enter after most browsers will insert a <br />. The content would be set to Edit Me<br/>. Additionally anything you set to content would be rendered as HTML and not escaped despite the lack of triple curly braces({{{content}}}).

ractive.set('content', '<ul><li>Edit Me</li></ul>');

This would actually render an editable unordered list.

Be aware that not all browsers actually operate the same way when it comes to interacting with contenteditable. HTML generated by one browser may differ from another.

Ambiguous references

A mustache used in a two-way binding context must have an unambiguous reference. Consider the following:

{{#foo}}
    <input value='{{bar}}'>
{{/foo}}
ractive = new Ractive({
    el: myContainer,
    template: myTemplate,
    data: { foo: {} }
});

Ractive must decide, straight away, what keypath to bind the input's value attribute to. It will first see if it can resolve the reference ('bar') given the current context stack (which includes a single context - 'foo'). It can't, so it is forced to make an assumption - that the 'bar' reference should resolve to the 'bar' keypath.

But this might not be what you want - maybe you did want it to resolve to 'foo.bar'.

You have two options - either use a restricted reference, i.e. <input value='{{.bar}}'>, or ensure that foo has a bar property (even if the initial value is undefined).

Lazy updates

Under the hood, Ractive always binds to the change event (and the click event in IE, in the case of checkboxes, due to an IE bug). By default, it will also bind to the input event, which means the model is updated instantly when the user enters data (the change event fires when a change is 'committed by the user'). In some situations, that's not what you want - if you want to only bind to change, pass in lazy: true as an initialisation option.

Observing changes

To use user input elsewhere in your app, you'll need to observe it using ractive.observe():

<input placeholder='Type your name' value='{{user.name}}'>
ractive = new Ractive({
    el: myContainer,
    template: myTemplate
});

ractive.observe( 'user.name', function ( newValue ) {
    app.user.name = newValue;
    triggerSomeBehaviour();
});