There is also a model binding plugin to Backbone: https://github.com/derickbailey/backbone.modelbinding
There is also a model binding plugin to Backbone: https://github.com/derickbailey/backbone.modelbinding
The model contains the "truth" -- the current state of the app. Views should be bound to models and automatically update when the model changes.
But when the view changes, the reverse is not the case. You don't want every checkbox click, radio button twiddle, and keystroke to be causing your model to change ... thereby emitting changes which may cause other pieces of your app to re-render, changes which may cause Ajax requests to persist the model state to the server, and so on.
Instead, most apps have a "save" button for some changes, a mouse click for others, or drag and drop, or a gesture, or an "undo" link.
It's much more convenient and flexible to allow your app to react to DOM events as it needs to, instead of having specific model attributes hard-bound to concrete DOM elements. ... that said, if the latter is your cup of tea, there's a plugin for that.
It was immediately obvious to me, the downsides you suggested. In my simple case it was an append interface, not an edit interface, so I stubbornly worked around it by creating orphaned models that are bound to my user editable view, and then get added into the app focused collection once submitted.
Hmm, really that sounds like poor application design than anything else.
I mean to me in an MVC binding the View to the Model bot ways is critical. The Model should be smart enough to track changes and only persist them on specific request.
And your app code should say "hey Model, I am going to query the server now - are you saved?" or "Hey - I have this new data timestamped X, or do you have something more recent?".
One of them is that the data often isn't the same. The view displays a computed value from the model, and the model should receive the "real" value upon a change. For example:
<span data-value="<%= feed.get('kind') %>">
<%= feed.entitle() %>
</span>
... becomes ...
<span data-value="article">
NYT Article
</span>
When the DOM element is chosen, the model's value should be "article", not "NYT Article" ... and perhaps the change should happen immediately, and perhaps the change should happen only after a "Save" button is clicked.With concrete two-way binding, you may spend more time configuring all of your DOM elements, and configuring when your models should be considered "changed", and when they shouldn't -- than you gain by tying them together in the first place.
Having just finished a pretty complex "application" of this sort (which cannibalized Knockout to get what I needed...) my view is that most stuff does not need any sort of filtering. Or if it does (and the big example I could give here would be timestamps) a lot of the filtering can be standardised.
Backbone, at least as I practiced it, has a model that very closely corresponds to what goes over the wire. You observe stuff on that, you react to events, the event observers react and update the model and as a result the model is sent over ajax.
Amber, again as I practice it, has a model that corresponds to the complete application state of a particular component of the page. If you have a checkbox that doesn't 1-1 match with something you'd send to your server, you create an attribute on your model purely to track the state of that checkbox and wire up observers to update the appropriate "real" attributes you're sending to the server. This is not immediately apparent when looking at the two frameworks but I find the difference to be crucial.
I started using backbone the day you open sourced it since I had a personal version of more or less the same thing that was twice the code and less elegant. I found that it was fantastic as long as you could do idempotent DOM updates (i.e. innerHTML) but got lost as soon as I wanted to start adding sub components that needed to be initialized and I was back to dealing with a lot of event twiddling and dom manipulation for updating the view. I believe the split in state between the DOM and application model causes this complexity which is avoided in Amber by having all client side application state in one place. I think I could apply the same ideas in Backbone but I haven't tried since switching over.
I think that I'd tend to agree with your interpretation -- you write:
> [In Amber] If you have a checkbox that doesn't 1-1
> match with something you'd send to your server, you
> create an attribute on your model purely to track the
> state of that checkbox ...
That sort of approach would be against the Backbone "party line". A large part of the point is to have your canonical state for a given resource in one place: the model. If you now have both "model" and a "view-model" for the same resource, one of which has been adopted to be more checkbox-y, just so that its attributes correspond more closely with the DOM ... it would be a shame.I do not. I plan on writing a blog post on this topic within the month that'll have examples. I'll ping you on twitter when I post it.
In the meantime, the Sproutcore todos app has a simple example of this:
https://github.com/sproutcore/todos/blob/master/js/app.js#L2...
The allAreDone propery. It's not something you'd have in the canonical data you'd send to/from the server but exists as a property so it can be observed and so the template bindings can manipulate it.
> If you now have both "model" and a "view-model" for the same resource, one of which has been adopted to be more checkbox-y, just so that its attributes correspond more closely with the DOM ... it would be a shame.
The model is more checkboxy but the checkboxy bits are initialized from the canonical bits and your sync equivalent has to strip them back off. This is annoying but less annoying than DOM twiddling.
(It seems there's an overwhelming consensus forming around issues like this - which FaxJs addresses at the core.)
So at least in that respect, SproutCore 1.x and Backbone are in full agreement.
In Batman we really want to make this other object a reality and have been scheming about "draft" versions of models for quite a while, but still haven't quite nailed down how to do it when things like associated objects enter into the mix.
One could think of it as a Presenter but with data flowing the other way through the abstraction.
This is key. If the UI is linked, checkbox by checkbox, field by field, to the model, then we've created the brittle situation where the user is, in effect, directly manipulating the "business objects". Such a scheme ends up imposing (at least) implicit design constraints when modeling the app (steering the developer away from the best or most logical decisions when they would make the rigidly linked view layer unwieldy), and similarly shackling the UX design to the model setup. Which makes for a worse app. These should be related but separate concerns; a smart user experience will require some logic, marshalling, and abstraction.
Disclaimer: I developed Synapse.
App.Post = SC.Object.extend({
fooBinding: SC.oneWay("Bar.baz")
}) this.model.bind("change", this.render);
... but you can be as fine-grained about it as you like.Another difference I suppose would be binding classnames and attributes to your model may require several lines of jQuery to manipulate, vs I believe in amber.js its done by naming convention and an options object (to use different class names).
Meanwhile the view gets to have really rich two-way interaction with the model, which is a big win for keeping presentation logic and model logic separate.
As an example: whenever the value of one field alters the available choices for another field, it's better to capture that logic in the model. Then you can write a view that focuses only on how to display choices, not which choices need to be displayed.
so if you have a model with some changing attributes you can write your view like this:
class View extends Backbone.View
template: (api) ->
new dynamictemplate.Template schema:'html', ->
@div class:'item', ->
@text 'default'
api.bind 'type', (type) =>
@attr 'class', type # changing the class to the new type
api.bind 'content', (content) =>
@text content
@end()
render: () ->
api = {}
_.extend(api, Backbone.Events)
@model.bind 'change:type', (model, type) ->
api.trigger 'type', type
@model.bind 'change:content', (model, content) ->
api.trigger 'content', content
tpl = @template(api)
tpl.on 'end', -> # when the rendering is done
@el = tpl.jquery # fresh build dom element by jquery
$('body').append(@el) # add it to the dom
if you want to use this you have to run render only once.
now every time the model changes the event handlers in the templates get triggered and updating it too, but what happens is that jquery sets the attribute class or the text of the div.if you don't like to write _all_ your templates as functions i'm currently writing a tool for this library where you can use an already working html file to mask the functions, so you have to write only a subset of the resulting design but getting the full output:
what you wants as a result (which is your mask):
<div class='item'>
default
</div>
what you 'only' have to wite: new Template schema:'html', ->
@$div ->
api.bind 'content', (content) =>
@text content
now if you emit the 'content' of 'api' the text of <div class='item'> gets updated by jquery. note that you don't have to write the class attribute because the tag name is already matching, but the result will have the class='item' attribute.i really don't know if this solves the actual problem or but i really would love to see others thinking about how to solve it (or help me getting on with this lib :P).
i allready have some ideas about writing a debug tool where every html tag that you write as function gets an special border which highlights when you change the properties like setting text and attribute or by adding new tags (look at the demo where i add tags after the templates is rendered).
[0] https://github.com/dodo/node-dynamictemplate
ps: i hate using the data-* attribute to hook models to the dom because thats first totally ugly and second the designer doesn't want to touch it (and shouldn't! because it is _not_ part of the design).