Convention-based, awesome model binding for Backbone.js
Inspired by Brad Phelan, Knockout.js' data-binding capabilities, and Brandon Satrom's work with Knockout.
It's easy to get up and running. You only need to have Backbone (including underscore.js - a requirement for Backbone) and jQuery in your page before including the backbone.modelbining plugin.
- Backbone.js v0.5.1 or higher
- jQuery v1.6.2 or higher
This is a plugin for Backbone.js and is built and tested against Backbone v0.5.1. It also uses jQuery to perform most of the bindng and manipulations, and is built and tested against v1.6.1. However, I am currently using this plugin in a production application with Backbone v0.3.3 and jQuery v1.5.1.
At this point, I make no guarantees of it working with any version of Backbone or jQuery, other than what it has been built and tested against. It works for me, so it may work for you with versions other than what is stated
Download the backbone.modelbinding.js
file from this github repository and copy it into
your javascripts folder. Add the needed <script>
tag to bring the plugin into any page
that wishes to use it. Be sure to include the modelbinding file after the backbone.js file.
The model binding code is executed with a call to Backbone.ModelBinding.bind(view)
. There are
several places that it can be called from, depending on your circumstances.
All of the element binding happens within the context of the view's el
, therefore you must
call the model binding code after your view's el
has been populated with the elements that
will be bound to.
If your view modifies the html contents of the view's el
in the render
method, you should
call the model binding after the modifications are made:
SomeView = Backbone.View.extend({
render: function(){
// ... render your form here
$(this.el).html("... some html and content goes here ... ");
// execute the model bindings
Backbone.ModelBinding.bind(this);
}
});
If, however, your view has an el
that represents an existing element in your html, and the
contents of the el
are not modified during a call to render
, then you can make the call to
the model binding code in the initializer or anywhere else.
<form id="some-form">
Name: <input id="name">
</form>
FormView = Backbone.View.extend({
el: "#some-form",
initialize: function(){
Backbone.ModelBinding.bind(this);
}
});
There is no requirement for the model binding code to be called from within a view directly. You can bind the view from external code, like this:
FormView = Backbone.View.extend({
el: "#some-form",
});
formView = new FormView();
Backbone.ModelBinding.bind(formView);
When your view has completed its work and is ready to be removed from the DOM, you not only
need to unbnd your view's events (handled through the view's remove
method, typically), you
also need to unbind the model events that are bound in the view.
Backbone.ModelBinding can unbind its own events through a simple call to
Backbone.ModelBinding.unbind(view)
. If you do not call this method when your view is being
closed / removed / cleaned up, then you may end up with memory leaks and zombie views that
are still responding to model change events.
FormView = Backbone.View.extend({
el: "#some-form",
initialize: function(){
Backbone.ModelBinding.bind(this);
},
close: function(){
this.remove();
this.unbind();
Backbone.ModelBinding.unbind(this);
}
});
Automatic bi-directional binding between your form input and your model.
The convention based binding requires no additional configuration or code in your
view, other than calling the Backbone.ModelBinding.bind(this);
as noted above.
With the conventions binding, your <input>
fields will be bound to the views model
by the id of the input.
For example:
// something.html
<input id='name'>
// something.js
SomeModel = Backbone.Model.extend();
SomeView = Backbone.View.extend({
render: function(){
// ... render your form here
// execute the defined bindings
Backbone.ModelBinding.bind(this);
}
});
model = new SomeModel();
view = new SomeView({model: model});
model.set({name: "some name"});
In this example, when model.set
is called to set the name, "some name" will appear
in the #name
input field. Similarly, when the #name
input field is changed, the
value entered into that field will be sent to the model's name
property.
Backbone.ModelBinding supports Knockout-style data-bind attributes on any arbitrary HTML element. These bindings will populate any attribute, the text, or HTML contents of an HTML element based on your configurations. This is particularly useful when a model that is being edited is also being displayed elsewhere on the screen.
To bind an element to a model's properties, add a data-bind
attribute to the element
and specify what should be updated with which model property using a elementAttr modelAttr
format. For example <span data-bind="text name">
will update the span's text with
the model's name property, when the model's name changes.
<form>
<input type="text" id="name">
</form>
Name: <span data-bind="text name">
SomeView = Backbone.View.extend({
// ...
render: function(){
// ...
Backbone.ModelBinding.bind(this);
}
});
someModel = new SomeModel();
someView = new SomeView({model: someModel});
In this example, the model's name
will be updated when you type into the text box
and then tab or click away from it (to fire the change event). When the model's name
property is updated, the data-bind
convention will pick up the change and set
the text of the span
to the model's name.
Multiple attributes can be specified for a single element's data-bind
by separating
each with a ;
(semi-colon). For example:
<form>
<input type="text" id="name">
</form>
Name: <span data-bind="text name; class name">
SomeView = Backbone.View.extend({
// ...
render: function(){
// ...
Backbone.ModelBinding.bind(this);
}
});
someModel = new SomeModel();
someView = new SomeView({model: someModel});
In this example, both the text and the css class will be updated when you change the name input. You can data-bind as many attributes as you need, in this manner.
There are several special cases for the data-bind attribute. These allow a little more functionality than just setting an attribute on an element.
- (default) - if you only specify the model property, defaults to the text of the html element
- text - replace the text contents of the element
- html - replace the html contents of the element
- enabled - enable or disable the html element
If you only specify the model's property in the data-bind attribute, then the data-bind
will bind the value of that model property to the text
of the html element.
<div data-bind="name"/>
See the document for data-bind text, below.
If you set the data-bind attribute to use text
, it will replace the text contents of the
html element instead of just setting an element attribute.
<div id="someDiv" data-bind="text someProperty"></div>
someModel.set({someProperty: "some value"});
If you set the data-bind attribute to use html
, it will replace the entire
inner html of the html element, instead of just setting an element attribute.
<div id="someDiv" data-bind="html someProperty"></div>
someModel.set({someProperty: "some value"});
This special case breaks the html element standard of using a disabled
attribute, specifically
to inver the logic used for enabling / disabling an element, to keep the data-bind attribute
clean and easy to read.
If you have a model with a property that indicates a negative state, such as invalid
, then you
can use a data-bind attribute of disabled
:
<button id="someButton" data-bind="disabled invalid"></div>
someModel.set({invalid: true});
However, some developers prefer to use positive state, such as isValid
. In this case, setting
the disabled attribute to the model's isValid property would result in the button being disabled
when the model is valid and enabled when the model is not valid. To correct this, a special case
has been added to enable and disable an element with enabled
.
<button id="someButton" data-bind="enabled isValid"></div>
someModel.set({isValid: false});
This will disable the button when the model is invalid and enable the button when the model is valid.
This allows you to specify that an element should be shown or hidden by setting the css of the element according to the value of the model properties specified.
<div data-bind="display isValid" />
someModel.set({isValid: false});
When the model's property is set to false, the HTML element's display
css will be set
to none
. When the model's property is set to true, the HTML element's display
css
will be set to block
.
hidden
This is the inverse of display
.
<div data-bind="hidden isValid" />
someModel.set({isValid: true});
When the model's property is set to false, the HTML element's display
css will be set
to block
. When the model's property is set to true, the HTML element's display
css
will be set to none
.
If a model's property is unset
, the data-bind may not update correctly when using text
or html
as the bound attribute of the element.
<div data-bind="text something"></div>
model.set({something: "whatever"});
model.unset("something");
The result will be a div with it's text set to "". this is handled through the data-bind's substitutions for undefined values. The default substitution is to replace an undefined value with an empty string. However, this can be per attribute:
<div data-bind="text something"></div>
<div data-bind="html something"></div>
Backbone.ModelBinding.Configuration.dataBindSubst({
text: "undefined. setting text to this",
html: " "
});
model.set({something: "whatever"});
model.unset("something");
The result of this example will be a div that displays "undefined. setting the text to this" and a div whose contents is a single space, instead of being empty.
The following form input types are supported by the form convention binder:
- text
- textarea
- password
- checkbox
- select
- radio button groups
Radio buttons are group are assumed to be grouped by the name
attribute of the
radio button items.
Select boxes will populate 2 separate fields into the model that they are bound to.
The standard #fieldid
will be populated with the selected value. An additional
{#fieldid}_text
will be populated with the text from the selected item. For example,
a selected option of
<select id='company'>
<option value="foo_bar">Foo Bar Widgets, Inc.</option>
...
</select>
will populate the company
property of the model with "foo_bar", and will populate
the company_text
property of the model with "Foo Bar Widgets, Inc."
There is no support for hidden fields at the moment, because there is no 'change' event that jQuery can listen to on a hidden field.
The convention binding system allows you to specify the attribute to use for the convention, by the input type. The default configuration is:
{
text: "id",
textarea: "id",
password: "id",
radio: "name",
checkbox: "id",
select: "id"
}
You can override this configuration and use any attribute you wish, by specifying any or all of these input types when you call the model binding. This is useful when you have field ids that do not match directly to the model properties.
The following will use use the class
attribute's value as the binding for all input field:
SomeView = Backbone.View.extend({
render: function(){
// ... some rendering here
Backbone.ModelBinding.bind(this, { all: "class" });
}
});
<input type="text" id="the_model_name" class="name">
If the same convention needs to be used throughout an application, and not just withing a single view, the configuration can be set at a global level:
Backbone.ModelBinding.Configuration.configureAllBindingAttributes({all: "class"});
The following will use a modelAttr
attribute value as the convention for text boxes, only.
SomeView = Backbone.View.extend({
render: function(){
// ... some rendering here
Backbone.ModelBinding.bind(this, { text: "modelAttr" });
}
});
<input type="text" id="the_model_name" modelAttr="name">
When this text box has it's value changed, the model's name
property will be populated with
the value instead of the_model_name
.
If the same convention needs to be used throughout an application, and not just withing a single view, the configuration can be set at a global level:
Backbone.ModelBinding.Configuration.configureBindingAttributes({text: "modelAttr"});
Now all text boxes will update the model property specified in the text box's modelAttr
.
The convention based bindings are pluggable. Each of the existing form input types can have it's convention replaced and you can add your own conventions for input types not currently handled, etc.
To replace a convention entirely, you need to supply a json document that has two pieces of
information: a jQuery selector string and an object with a bind
method. Place the convention
in the Backbone.ModelBinding.Conventions
and it will be picked up and executed. The bind
method receives three parameters: the jQuery selector you specified, the Backbone view, and
the model being bound.
You can replace the handler of an existing convention. For example, this will set the
value of a textbox called #name
to some text, instead of doing any real binding.
var nameSettingsHandler = {
bind: function(selector, view, model){
view.$("#name").val("a custom convention supplied this name");
}
};
Backbone.ModelBinding.Conventions.text.handler = nameSettingsHandler;
You can also create your own conventions that do just about anything you want. Here's an example
that modifies the contents of <p>
tags:
var PConvention = {
selector: "p",
handler: {
bind: function(selector, view, model){
view.$(selector).each(function(index){
var name = model.get("name");
$(this).html(name);
});
}
}
};
Backbone.ModelBinding.Conventions.paragraphs = PConvention;
This example will find all <p>
tags in the view and render the name
property from the model
into that paragraph, replacing all other text. Note that the name of the convention is set to
paragraphs
when added to the conventions. This name did not exist prior to this assignment, so
the convention was added. If you assign a convention to an existing name, you will replace that
convention.
The list of existing conventions includes:
- text
- password
- radio
- checkbox
- select
- textarea
- formbinding
- databind
For fully functional, bi-directional binding convention examples, check out the source code
to Backbone.ModelBinding in the backbone.modelbinding.js
file.
- Data-bind multiple attributes for a single element
- Default data-bind substitutaion to '' for all attributes
- Fixed a bug that prevented
<input>
elements with notype
attribute from being bound
- Fixed a bug in configuring the binding attribute for textarea elements
- Data-bind defaults to the html element's
text
if you only specify the model property:data-bind="name"
- Fixed issue with binding 1 / 0 to checkboxes (truthy / falsy values)
- Added data-bind attribute for setting an HTML element's
display
css - Added inserve of data-bind
display
as data-bindhidden
- Corrected issue with binding a model's property to a checkbox, when the property is false
- Data-bind substitutions - lets you replace "undefined" with another, set value, when using data-bind
- Corrected issue with
unset
ing a model property, in the data-bind convention
- Breaking Change: Changed the
Backbone.ModelBinding.call(view)
method signature toBackbone.ModelBinding.bind(view)
- Added ability to unbind model binding with
unbind
method, to prevent memory leaks and zombie forms - Added backbone.modelbinding.min.js to the repository, compiled with Google's Closure Compiler Service
- Updated the selectors used for the conventions. Text inputs are now found with "input:text", which should
select all text inputs, even without a
type='text'
attribute (though this seems to be buggy in jQuery v1.6.2) - Significant internal restructuring of code
- data-bind will bind the model's value immediately instead of waiting for the model's value to change
- support
enabled
functionality for data-bind:data-bind="enabled isValid"
- documented existing support for data-bind
disabled
:data-bind="disabled invalid"
- Fixes for 'falsey' value bindings
- Update the docs to include when and where to call the model bindings
- Making some global vars not global
- Configuration to easily set all binding attributes for all elements
- Fix for IE
- Making some global vars not global
- Added data-bind convention
- Added configuration options
- Conventions for all form input types
- Removed formBinding code
- Removed htmlBinding code
- Significant internal code cleanup and restructuring
Copyright (c) 2011 Derick Bailey, Muted Solutions, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.