vecnatechnologies / backbone-torso

A holistic approach to Backbone applications
http://vecnatechnologies.github.io/backbone-torso
Apache License 2.0
17 stars 20 forks source link

How do you communicate from a child item view to a parent list view? #142

Closed Earthstar closed 6 years ago

Earthstar commented 8 years ago

For example, the child item view has a button that, when pressed, should pop up a modal controlled by the parent view.

kentmw commented 8 years ago

Good question. This goes for pretty much any parent to many, many child views. The simplest way is to have an event bus to publish to.

var bus = new Torso.Events

You can namespace with the view type and the action that is being performed. You can also add the child view as a payload if you need the parent to handle who sent it.

myChildViewMethod: function() {
  bus.trigger('todoView:requestPopup', this)
}

If you want to use your own list view bus, you can pass a list view bus to the child views by overriding the generateChildArgs (which, should not be double as it is meant to be overridden)

  __generateChildArgs: function(model) {
    var args = Torso.ListView.prototype.__generateChildArgs.call(this, model);
    args.listBus = this.bus;
    return args;
  }

We could think about adding an automatic pub-sub system between the child views and the parent where you tell the parent to listen for specific events on every child it creates. You could roll your own version of this by extending ListView and creating "on" callbacks to listview's 'child-view-added' and 'child-view-removed' events.

Earthstar commented 8 years ago

Interesting. The context is that I had a very long, protracted discussion about whether to use events or callbacks for child -> parent communication, and that stuff like this should probably be added to the "cookbook."

Since Backbone/Torso uses events for communication, it would make sense to me if events fired on child item views bubble up to the parent list view, analogous to how events on models bubble up to the collection. Then, you wouldn't have to pass a 'bus' object around.

kentmw commented 8 years ago

Interesting, you're saying a child view's events would be republished by their parents?

Earthstar commented 8 years ago

Yes, so you'd be able to listen to events on the parent directly (although this might only make sense for List views?) What do you think? I'm trying to improve Torso's "developer ergonomics" so we don't have to pass arguments or override things all the time to get the behavior we want.

kentmw commented 8 years ago

I like that a parent might automatically listen to all events of a tracked or owned child and let the parent react to them, but I don't like the parent re-triggering events. Controlling events will be difficult and knowing the originating triggerer will become a problem. Name claches will also become an issue between parent and child and what is the point?

If the parent automatically listens to the "all" event during child registration, torso views could invoke a method with the name of the event, the triggerer, and the payload. That's nice, except the method you'd override would look pretty stupid:

function(childview, eventName, payload) {
  if (eventName = 'foo') {
    ...
  } else if (eventName = 'bar') {
   ...
  } // and so on
}

I guess you could go a step further, and for any parent, the programmer can register a set of events they care about, and during the child registration, only bind to those events. Just a short hand and fail-safe way of listening to children views.

kentmw commented 8 years ago

What are your thoughts on allowing a parent to register child views like this? If we place the logic at the register/unregister child views, then even a view with many child views would be able to listen without much effort.

  childEvents: {
    requestPopup: 'handlePopupRequest',
    anotherChildViewEventName: 'handler'
  },

  handlePopupRequest: function(childView, payload) {
    // do something
  },

  registerTrackedView: function(view, options) {
    var trackedViewsHash = this.__getTrackedViewsHash(options);
    trackedViewsHash[view.cid] = view;
    _.each(this.childEvents, function(eventCallback, eventName) {
      this.listenTo(view, eventName, _.bind(this[eventCallback], this, view, /*have to think how to get payload*/), this);
    }
    return view;
 },

 unregisterTrackedView: function(view, options) {
    var trackedViewsHash = this.__getTrackedViewsHash(options);
    delete trackedViewsHash[view.cid];
    this.stopListening(view);
    return view;
  },

The only thing I don't like about this is matching which child views you want to listen to.

If I had 10 child view A's and 10 child view B's, and I only wanted to listen to A's 'requestPopup', how would I target those views?

kentmw commented 8 years ago

I'm reopening because while there are ways to handle this communication, I agree that there could be a better way to open communication channels between list views and their children views, especially when there are multiple instances of the same list view (and therefore some identifier has to be passed in with the event). I think a dialog on this concept is worth it.

If possible, I'd like to limit:

  1. Needing to pass in values with the event to determine which object should care.
  2. The number of non-caring objects triggered by an event as long as it doesn't limit portability of the objects or add extra dependencies
kentmw commented 8 years ago

@mandragorn @Earthstar I'd like us to investigate scoped events buses. Like a pod-level bus. Keeping the independence of child views, but gives a more useful scope than only being application-level.

kentmw commented 6 years ago
this.on('item-view-added', (data) => {
  this.listenTo(data.view, 'my-event', this.doSomething);
});
this.on('item-view-removed', (data) => {
  this.stopListening(data.view, 'my-event', this.doSomething);
});