gmac / backbone.epoxy

Declarative data binding and computed models for Backbone
http://epoxyjs.org
MIT License
614 stars 89 forks source link

Forcing recompute of a computed property #25

Closed jpurcell001 closed 11 years ago

jpurcell001 commented 11 years ago

Is there any way to force the recompute of a computed property on a model or view, outside of the change dependency graph? An example use case is a model that has a nested collection; we'd like to have a computed property that tracks the length of the collection, and therefore needs to react to add/remove/reset events on the underlying collection. Another example is a computed on a view that tracks elapsed time, and would react based on a setTimeout in the view.

Haven't though through the interface but maybe as simple as model.recompute("computedPropertyName"); that would force the computed property value to be re-evaluated, and trigger any associated change events (and update the cached value) if it resulted in a change.

So you could do things like

this.listenTo(this.subcollection, "add remove reset", function() {
   this.recompute("subcollectionLength");
});

I know it can be done to some degree with a separate viewModel but sometimes that feels like overkill.

gmac commented 11 years ago

Hey, good question. I definitely see the rationale here for manually recomputing a binding, although I'm not sure that this feature harmonizes with the guiding principles of the base library. I'd generally prefer to have developers rethink their application architecture to avoid manually-built hooks, rather than turning to a .recompute method in situations where they don't know what else to do. In other words: I don't want an API like this to turn into an easy yet inappropriate out. I'll take this into consideration; feel free to post some use cases in code. I'll definitely consider them.

Either way – what you're proposing IS extremely simple to add. If you want to add it into your project right now, just extend the base Epoxy install with this:

Backbone.Epoxy.Model.prototype.recompute = function(attribute) {
   if (this.hasComputed(attribute)) {
      return this.c()[ attribute ].get(true);
   }
   return null;
};
jpurcell001 commented 11 years ago

Thanks - that snippet is helpful. How would you do the same thing for an Epoxy View that is using computeds? (which is where a lot of this sort of thing tends to live)

gmac commented 11 years ago

Good question, and the answer is a little odd: you don't. View computeds are simply routers; they don't trigger their own events. When you bind to a view computed, you're actually binding to the model fields accessed within the view computed. When the underlying model values change, a binding updates pass through the computed view function for rendering. The system is still dependent on managed model data to trigger updates though.

Going back to your original question about binding the length of a collection: don't forget about binding filters, this is one of their main uses: data-bind="text:length($collection)".