Templated Event Bindings

I was just about to offer my thoughts and they were very similar. With “eventing” the parent now has to listen for the event. But I think I like that. This allows parent components to subscribe to “inserted type” events of the subcomponents that it cares about. This does create a coupling in the fact that unless bit-tabs is listening for bit-panel’s inserted event, it won’t know anything about them. However, on the flip side, calling “addPanel” from bit-panel requires bit-panel to know that there is (or should be) a parent component that exposes the “addPanel” interface. Either way, one component needs to be aware in some sense the other should be out there.

“addPanel” would still be on the viewModel keeping it easy to test. The only part in the “events” object is listening for the event and calling addPanel.

Here’s a quick example of walking the parent viewModels:

var findParentViewModelWithFn = function(el, fnName) {
    while(el.parent().length) {
        if(el.parent().viewModel && el.parent().viewModel()[fnName]) {
            return el.parent().viewModel();
        }
        el = el.parent();
    }
    return undefined;
};

On the subject of coupling, in the bit-tabs example and probably most others, the parent needs to know about the children but not the other way around. In this case, the parent is modifying the child’s state, so having knowledge of its interface is inevitable. What we can prevent is the child knowing about the parent.

With eventing, the child simply declares its interface and the parent merely needs to subscribe to it. You could say the same is happening with scope walking if you consider "has an ancestor with a method named addPanel" to be part of the child interface, and adding an addPanel method would be how you subscribe to the child, but that feels inverted to me.

One concern I have about scope walking is that you could find the wrong viewmodel with the right method name, especially considering that all viewmodel members are basically public which increases the opportunity for this. I think the odds of this happening would be substantially lower with eventing so you don’t have to make crazy unique method names to avoid conflict.

More thoughts…

One of the things I am also doing differently in some of my components that still interface directly with their ancestor viemodels, is that I decrease the child’s knowledge of the parent by having the initial registration method (e.g., addPanel) return the teardown function (e.g., bound removePanel) so the child only needs to know one method name.

Also, I mentioned this a bit previously, but I’ve started to move away from passing the child viewmodel. Instead, I try to pass an object containing just what the parent needs. With bit-tabs, you could simply pass this.viewModel.compute('active') as the entire child interface:

inserted: function() {
  var ctrl = this;
  this.element.trigger({
    type: 'bit-panel-inserted',
    panelApi: this.viewModel.compute('active'),
    makeTeardown: function(teardown) {
        ctrl.teardown = teardown;
    }
  });
}

Bit-tabs could then save panelApi from each event in the panels array and call those computes with true or false to change child states. You could also pass an entire object of methods and computes to save instead.

I’m ok with sending the entire child’s viewModel. It’s just a reference anyway, right? The parent only need worry about that parts of said viewModel that it cares about. However… in the absence of private properties on a viewModel, I think you suggestion might not be a bad one.

With eventing, you only have to worry about a parent conflict if there are more than one parent that is listening for that specific sub-component-inserted event. This seems much less likely to occur than for their to be a parent viewModel that exposes an “addPanel” method. Both are rather unlikely.