gmac / backbone.epoxy

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

Purpose of viewMap #105

Closed frank-weindel closed 9 years ago

frank-weindel commented 9 years ago

I'm looking to create my own version of the collection binding, to do this I'm cloning a lot of the code in Epoxy's standard collection binding. I noticed there is a variable called viewMap that is local to the module which is being saved off and restored on every set. What is the purpose of this? I can't access this variable within my own binding. Looking at its uses, I only see places where things are pushed onto it but never where its used. Can this be ignored?

gmac commented 9 years ago

viewMap is one of the magic internal variables that powers automatic dependency mapping. Basically, when you create a new object, Epoxy keeps an internal ledger of everything that object talks to so that bindings to those dependencies can automatically be configured. viewMap is that internal ledger for views.

The reason viewMap does a hand-off within the collection binding is because new View objects are being created and configured synchronously within that operation. Those new views may need to map their own dependencies, so the parent viewMap is shelved, and a new viewMap is introduced for each new child view constructed. At the end of the operation, the parent-level viewMap is restored. It may seem odd that this viewMap is not more object-oriented, but this is very much by design. Epoxy is heavily exploiting the benefit of library-level scoping to have objects magically interconnect with one another.

frank-weindel commented 9 years ago

Thanks @gmac. How would you suggest that I implement my own collection binding in this case? Right now I have an implementation (which is mostly copied from yours but with changes to work with a special kind of Backbone.View, and some other features) that seems to be working, but without the lines pertaining to viewMap. I'm not sure what could go wrong here in the future that I need to be careful about. I'd really like to have this kind of flexibility to with epoxy.

gmac commented 9 years ago

No prob @frank-weindel, glad you're finding Epoxy useful. For building your own collection binding, I would retain that viewMap implementation, given that is the secret sauce that makes Epoxy able to automatically map and bind dependencies. Without that, you're losing a lot of Epoxy's core value. Now, to leverage that encapsulated viewMap property, you would need to include your new collection binding within the scope of Epoxy's closure... so you'd be authoring directly within the library source. If you create your own fork and just add in your version of the collection handler definition, you should still be able to pull in core library updates as they become available.

frank-weindel commented 9 years ago

Hey. I'm still a little unclear what it does exactly. My collection binding seems to be working fine without swapping the viewMap, and the itemViews are also views with Epoxy mixed-in. When I expose the viewMap to the window for testing in a simple epoxy application it is null. Maintaining a forked repo would be a solution, but I'd really like to keep this implementation separate from that repo for a number of reasons.

Here is my binding handler implementation if you'd like to see what I'm doing. https://gist.github.com/frank-weindel/f4283db10c9b17a4e46b

gmac commented 9 years ago

Not to run the Epoxy test suite up the flagpole (it's kind of a mess and needs a big refactor), but the circumstance that the viewMap handoff addresses is this one: https://github.com/gmac/backbone.epoxy/blob/master/test.js#L896-L907

Basically... viewMap is a ledger recording all dependencies that an object references while configuring its bindings. The viewMap array is defined when an object starts binding, and is nullified when it finishes; hence the reason you normally see viewMap set as null. This system works great: binding configuration is a synchronous operation, therefore all objects can safely report themselves into a single library-level variable that the current binding object then uses to configure itself with.

The one exception here is the collection binding, which has a sub-routine: a collection binding builds sub-views. Now, each of these subviews wants to bind to its own set of dependencies independently from the parent object which is still working on binding the collection. There was originally a major bug here: given that there's only one library-level dependency map, a collection binding would pick up the dependencies of all of it's child views, and each child view would pick up all of the dependencies of those views that came before it within the collection.

Hence, that viewMap fancy footwork was introduced: a collection binding stashes its own dependency map before starting the sub-routine of building child views. When it's allowed each child to bind itself independently, then it restores its own dependency map and proceeds with binding itself.

frank-weindel commented 9 years ago

Why not make viewMap an object level property? Wouldn't we avoid that complication with virtually no detriment?

gmac commented 9 years ago

That assumes objects know about one another, which they don't necessarily do. It's a function of Epoxy at the library level to handle disparate object linkages.

frank-weindel commented 9 years ago

When you say "objects know about one another" what exactly do you mean? I think of an Epoxy View as having any number of bindings on any number of possible binding targets. Usually those bindings and their relationship with their binding targets are pretty well contained in that View. A collection binding is special because it has the potential to spawn child Epoxy "Item Views". Those Item Views typically only end up with a Model and the bindings to properties on that model. Again all of that is contained in a view. Where is the potential crossover that can occur between multiple views potentially sharing the same bindings and binding sources? Sorry I'm a bit confused and am trying to understand what is going on. Maybe theres a way of using Epoxy that exhibits this issue clearly that I'm not aware of.