I have a Marionette.CollectionView
which renders a list of ItemView
s. During render()
, I use the ItemView's model
to draw some SVG using Raphael.
Raphael requires that I specify a height and a width for its canvas, which I would normally grab from this.$el
. However, $el
(as an empty <div>
) has no dimensions until it is added to the DOM and CSS rules are applied to it, so I need to delay rendering until I know that the view is in the DOM.
The problem is that Marionette.CollectionView
doesn't add a child view to the DOM until after it has rendered. How can I override this behavior without re-implementing half of CollectionView
?
// Renders a single object.
var ItemView = Marionette.ItemView.extend({
template: "#item-view-template",
onRender: function() {
var svgEl = this.$el.find("div.svg-canvas");
// Raphael needs the element's width and height, which
// is 0 until this.$el is in the DOM.
var paper = Raphael(svgEl.get(0), svgEl.height(), svgEl.width());
// ... draw some SVG...
}
});
// Renders a collection of objects.
var ListView = Marionette.CollectionView.extend({
itemView: ItemView,
model: MyModel
});
// Main point of entry.
MyApp.show = function() {
var collection = new MyCollection();
var listView = new ListView({ collection: collection });
MyApp.mainRegion.show(listView);
collection.fetch();
};
onRender
won't handle your needs, as this method gets called when the view has been rendered - but does not guarantee that the view has been added to the DOM yet.
To do that, you'll need an onShow
method which will be called by the region when you show the view in the region. The problem is that the current implementation only calls onShow
on the view you directly pass in - the collection view in this case. So you would need to implement your onShow in a way that makes it call that method on all of the collection views' children.
Marionette.CollectionView.extend({
// ...
onShow: function(){
_.each(this.children, function(childView){
if (childView.onShow){ childView.onShow(); }
});
}
});
That should do it. When you call MyApp.mainRegion.show(listView)
it will call the onShow
method of the collection view, which will then call it on the children (if it's there).
Per discussion in the comments, an implementation that will guarantee the onShow
method of the child views is called, even after the onShow
of the parent view has been called and an item is added to the collection later:
ItemView = Marionette.ItemView.extend({
// ...
onShow: function(){
// I am guaranteed to be called from the CollectionView
// because the CollectionView registers me in a promise
}
});
CollectionView = Marionette.CollectionView.extend({
initialize: function(){
this.onShowCallbacks = new Marionette.Callbacks();
},
onShow: function(){
this.onShowCallbacks.run();
},
appendHtml: function(cv, iv){
cv.append(iv.el);
// promise to run 'onShow' if it exists
if (iv.hasOwnProperty("onShow")){
this.onShowCallbacks.add(iv.onShow);
}
}
});
Also available in this gist: https://gist.github.com/2872526
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With