Reputation: 361
I'll start with a clear question and explain on after :
How to properly encapsulate a model, contained in a CompositeView
, into an ItemView
My problem is simple, but I can't manage to get something good working.
I have a tree of Notes.
Each note is a Backbone.model and has a @descendants
collection,
I then have a Backbone.Collection to represent that tree (of which @descendants is an instance)
I setup a basic CollectionView
, with, as an itemView
, the CompositeView
. Everything rendered perfectly and I was quite happy about it. This working solution is shown in the first snippet of code!
Here comes the problem. Building on this, I kinda ran into this issue : Events fired for whole hierarchy which make sense for how my events where bound. At first, I simply added unique selectors (added data-guid to my html elements so I could get them by it) and it worked alright, although it was already getting "hacky". But other similar problem have risen so I decided I needed a solution for this. So, I told myslef, let's encapsulate each model in an ItemView and use the Composite view to recursively render them. But... This has not been for the best in all worlds...
Here is what I had in the beginning :
class Note.ModelView extends Marionette.CompositeView
template: "note/noteModel"
id: "note-item"
itemViewContainer: ".note-descendants"
ui:
noteContent: ".noteContent"
events:
"keypress .noteContent": "createNote"
"click .destroy": "deleteNote"
# ...
initialize: ->
@collection = @model.descendants
# ...
class Note.CollectionView extends Marionette.CollectionView
id: "note-list"
itemView: Note.ModelView
initialize: ->
@listenTo @collection, "sort", @render
Now I transferred every thing that belongs to rendering the Model in a new ItemView
class Note.ModelView extends Marionette.ItemView
template: "note/noteModel"
ui:
noteContent: ".noteContent"
events: ->
guid = @model.get 'guid'
events = {}
events["keypress #noteContent#{guid}"] = "createNote"
events["blur #noteContent#{guid}"] = "updateNote"
events["click #destroy#{guid}"] = @triggerEvent 'deleteNote'
initialize: ->
@bindKeyboardShortcuts()
@listenTo @model, "change:created_at", @setCursor
class Note.TreeView extends Marionette.CompositeView
template: "note/parentNote"
itemView: Note.ModelView
initialize: ->
@collection = @model.descendants
@listenTo @collection, "sort", @render
_.bindAll @, "renderItem"
renderItem: (model) ->
if model.get('parent_id') is 'root'
itemView = new Note.ModelView model: model
itemView.render()
@$el.append itemView.el
onRender: ->
@collection.each this.renderItem
@renderItem @model
appendHtml: (collectionView, itemView) ->
@model.descendants.each (note) =>
iv = new Note.ModelView model: note
iv.render()
@.$('.note-descendants').append iv.el
class Note.CollectionView extends Marionette.CollectionView
id: "note-list"
itemView: Note.TreeView
initialize: ->
@listenTo @collection, "sort", @render
and the noteMode template (parentNote is just an empty template right now)
<button class="btn btn-danger btn-xs untab">UN</button>
<button class="btn btn-success btn-xs tab">TAB</button>
<div class="noteContent">{{{indent}}}{{{title}}}</div>
<button class="destroy"></button>
<div class="note-descendants"></div> # That is where I'm putting the children notes
So this is almost working. But it's hacky and.. well it still doesn't quite work. I've been reading through all documentations, and I've read those two links Derick Bailey on nested structure and CompositeView and David Sulc on Nested Views but I still feel I'm missing some important details.
What I'm looking for, as an answer, is any hint or any common way to manage this with Marionette. I'm really looking for something clean since this is going to be one of the angular piece on which the app will be built.
Maybe also I'm searching in the wrong place? Anything will be welcome!! Thank you very much
I wish you all a very nice night. Thank you for your time.
Upvotes: 4
Views: 2497
Reputation: 163
I'm going to explain to you, in different words, what Derick Bailey already stated in the blog post you referenced.
Views
You need two views to make this work.
The first, the "mother view", is the outer most view which contains the recursive structure as its child nodes. This may either be a Marionette.CollectionView or a Marionette.CompositeView.
The second view is your recursive view, meaning it needs to be a Marionette.CompositeView. You make a Marionette.CompositeView recursive, by NOT specifiying an "itemView" property. In lack of an itemView property, a Marionette.CompositeView will use ITSELF to render the models of the collection it was given.
Collection
You need a collection that is either a Backbone.Collection of nested Backbone.Collections, or simply a Backbone.Collection which contains a deeply nested hash. In the latter case, you need not forget to convert the simple hashes to Backbone.Collections when handing them to a Marionette.CompositeView (as done below in the RecursiveView's initialize() method).
Putting it all together
var RecursiveView = Marionette.CompositeView.extend({
template: '#someTemplate',
initialize: function () {
this.collection = this.model.get('children')
}
});
var MotherView = Marionette.CollectionView.extend({
itemView: RecursiveView,
collection: new Backbone.Collection([{key: 'value', children: [{}, {}, {}]}])
});
Problem: Event Bubbling
Naturally, DOM events will now bubble up, so this code
var RecursiveView = Marionette.CompositeView.extend({
events: {
'click button': someCallback
}
});
will be a catch-all for the events of the view it references as well as all its descendants.
The easiest solution to this problem is the use of the direct descendant css selectr >
, e.g.
var RecursiveView = Marionette.CompositeView.extend({
events: {
'click >button': someCallback
}
});
If that isn't enough, one effective solution would be to prevent the automatic event bubbling, e.g.
var RecursiveView = Marionette.CompositeView.extend({
events: {
'click button': function (event) {
event.stopPropagation();
// or event.stopImmediatePropagation() depending on your use case
// cf. http://stackoverflow.com/a/5299841/899586 and
}
}
});
Upvotes: 3