I have three different ways to initialize and render a view and its subviews, and each one of them has different problems. I'm curious to know if there is a better way that solves all of the problems:
Scenario One:
Initialize the children in the parent's initialize function. This way, not everything gets stuck in render so that there is less blocking on rendering.
initialize : function () {
//parent init stuff
this.child = new Child();
},
render : function () {
this.$el.html(this.template());
this.child.render().appendTo(this.$('.container-placeholder');
}
The problems:
-
The biggest problem is that calling render on the parent for a second time will remove all of the childs event bindings. (This is because of how jQuery's
$.html()
works.) This could be mitigated by callingthis.child.delegateEvents().render().appendTo(this.$el);
instead, but then the first, and the most often case, you're doing more work unnecessarily. -
By appending the children, you force the render function to have knowledge of the parents DOM structure so that you get the ordering you want. Which means changing a template might require updating a view's render function.
Scenario Two:
Initialize the children in the parent's initialize()
still, but instead of appending, use setElement().delegateEvents()
to set the child to an element in the parents template.
initialize : function () {
//parent init stuff
this.child = new Child();
},
render : function () {
this.$el.html(this.template());
this.child.setElement(this.$('.placeholder-element')).delegateEvents().render();
}
Problems:
- This makes the
delegateEvents()
necessary now, which is a slight negative over it only being necessary on subsequent calls in the first scenario.
Scenario Three:
Initialize the children in the parent's render()
method instead.
initialize : function () {
//parent init stuff
},
render : function () {
this.$el.html(this.template());
this.child = new Child();
this.child.appendTo($.('.container-placeholder').render();
}
Problems:
-
This means that the render function now has to be tied down with all of the initialization logic as well.
-
If I edit the state of one of the child views, and then call render on the parent, a completely new child will be made and all of its current state will be lost. Which also seems like it could get dicey for memory leaks.
Really curious to get your guys' take on this. Which scenario would you use? or is there a fourth magical one that solves all of these problems?
Have you ever kept track of a rendered state for a View? Say a renderedBefore
flag? Seems really janky.
Best Answer
This is a great question. Backbone is great because of the lack of assumptions it makes, but it does mean you have to (decide how to) implement things like this yourself. After looking through my own stuff, I find that I (kind of) use a mix of scenario 1 and scenario 2. I don't think a 4th magical scenario exists because, simply enough, everything you do in scenario 1 & 2 must be done.
I think it'd be easiest to explain how I like to handle it with an example. Say I have this simple page broken into the specified views:
Say the HTML is, after being rendered, something like this:
Hopefully it's pretty obvious how the HTML matches up with the diagram.
The
ParentView
holds 2 child views,InfoView
andPhoneListView
as well as a few extra divs, one of which,#name
, needs to be set at some point.PhoneListView
holds child views of its own, an array ofPhoneView
entries.So on to your actual question. I handle initialization and rendering differently based on the view type. I break my views into two types,
Parent
views andChild
views.The difference between them is simple,
Parent
views hold child views whileChild
views do not. So in my example,ParentView
andPhoneListView
areParent
views, whileInfoView
and thePhoneView
entries areChild
views.Like I mentioned before, the biggest difference between these two categories is when they're allowed to render. In a perfect world, I want
Parent
views to only ever render once. It is up to their child views to handle any re-rendering when the model(s) change.Child
views, on the other hand, I allow to re-render anytime they need since they don't have any other views relying upon them.In a little more detail, for
Parent
views I like myinitialize
functions to do a few things:InfoView
would be assigned#info
).Step 1 is pretty self explanatory.
Step 2, the rendering, is done so that any elements the child views rely on already exist before I try to assign them. By doing this, I know all child
events
will be correctly set, and I can re-render their blocks as many times as I want without worrying about having to re-delegate anything. I do not actuallyrender
any child views here, I allow them to do that within their owninitialization
.Steps 3 and 4 are actually handled at the same time as I pass
el
in while creating the child view. I like to pass an element in here as I feel the parent should determine where in its own view the child is allowed to put its content.For rendering, I try to keep it pretty simple for
Parent
views. I want therender
function to do nothing more than render the parent view. No event delegation, no rendering of child views, nothing. Just a simple render.Sometimes this doesn't always work though. For instance in my example above, the
#name
element will need to be updated any time the name within the model changes. However, this block is part of theParentView
template and not handled by a dedicatedChild
view, so I work around that. I will create some sort ofsubRender
function that only replaces the content of the#name
element, and not have to trash the whole#parent
element. This may seem like a hack, but I've really found it works better than having to worry about re-rendering the whole DOM and reattaching elements and such. If I really wanted to make it clean, I'd create a newChild
view (similar to theInfoView
) that would handle the#name
block.Now for
Child
views, theinitialization
is pretty similar toParent
views, just without the creation of any furtherChild
views. So:Child
view rendering is also very simple, just render and set the content of myel
. Again, no messing with delegation or anything like that.Here is some example code of what my
ParentView
may look like:You can see my implementation of
subRender
here. By having changes bound tosubRender
instead ofrender
, I don't have to worry about blasting away and rebuilding the whole block.Here's example code for the
InfoView
block:The binds are the important part here. By binding to my model, I never have to worry about manually calling
render
myself. If the model changes, this block will re-render itself without affecting any other views.The
PhoneListView
will be similar to theParentView
, you'll just need a little more logic in both yourinitialization
andrender
functions to handle collections. How you handle the collection is really up to you, but you'll at least need to be listening to the collection events and deciding how you want to render (append/remove, or just re-render the whole block). I personally like to append new views and remove old ones, not re-render the whole view.The
PhoneView
will be almost identical to theInfoView
, only listening to the model changes it cares about.Hopefully this has helped a little, please let me know if anything is confusing or not detailed enough.