Using Backbone and Marionette, I've created a new layout that goes into the main content div on my page. The layout looks like this:
<div id='dash-sidebar'>
<div id='dash-profile'></div>
<div id='dash-nav'></div>
</div>
<div id='dash-content'></div>
The issue is that when I render the layout, Backbone automatically wraps it in a div before putting it into the main content div like this:
<div id='main-content'>
<div>
<div id='dash-sidebar'>
<div id='dash-profile'></div>
<div id='dash-nav'></div>
</div>
<div id='dash-content'></div>
</div>
</div>
I know that I can change the element with tagName, but is it possible to avoid wrapping the template altogether and just insert it directly into the main content div on the page?
Each Backbone View must be represented by a single element. Your first HTML block has two elements, which is why it cannot be represented by a view without first wrapping it in an outer div.
Could you refactor your Layout to include the main-content
area as well? Then the Layout's el
would correspond to the entire outer div.
Another thing to try would be using Backbone.View's setElement() method to override the creation of the outer div, and manually inject the HTML that you want for the element in a View. Something like:
onRender: function() {
this.setElement( /* the HTML you want for your layout */ );
}
I'm not sure how this would work if you passed in HTML that had two parent elements instead of just one, however.
EDIT3: Warning! This answer may be out of date. I received a comment that this answer no longer works and have not had the time to investigate (I personally do not use this method).
I like to use Twitter/Bootstrap as my UI library and was having some issues with table styling because of the default tag wrapping (specifically a <div>
between my <tbody>
and my <tr>
s).
After some digging, I found something in the Marionette docs on the Region
about how the View
attaches the el
. Backbone builds the el
from various View
attributes and keeps the built element up to date so it can be rendered at any time. So I figured, if view.el
is the parent, why not just use the HTML contents? Marionette also provides a way to create a custom Region
I was able to get everything running by creating a custom Region
with an overridden open
function. That way I can specify which regions I want to wrap with a tag and those that I do not. In the following example snippet, I create my custom non-wrapping Region
(NowrapRegion
) and use it in my Layout
(MyLayout
) for my <tbody>
(the views I pass in my real program create <tr>
s).
var NowrapRegion = Marionette.Region.extend({
open: function(view){
this.$el.html(view.$el.html());
}
});
var MyLayout = Marionette.Layout.extend({
template: templates.mylayout,
regions: {
foo: '#foo',
columns: '#columns', //thead
rows: { selector: '#rows', regionType: NowrapRegion } //tbody
}
});
BOOM! Wrapping when I want it and none when I don't.
EDIT: This seems to affect events
applied at the *View
level. I haven't looked into it yet, but be warned that they don't seem to be getting triggered.
Whether this is the "right" way to do it or not, I am not sure. I would welcome any feedback from @derick-bailey should he see this.
EDIT2: @chris-neale suggested the following, I have not verified this yet but it seems sound. Thanks!
Rather than copying the html in the view, using a deep clone on the
child nodes will maintain events and data.
var NowrapRegion = Marionette.Region.extend({
open: function(view){
view.$el.children().clone(true).appendTo(this.$el);
}
});
UPDATE:
Marionette.Layout is an extension of the Backbone view, so this is the normal behavior, see backbone documentation:
The "el" property references the DOM object created in the browser.
Every Backbone.js view has an "el" property, and if it not defined,
Backbone.js will construct its own, which is an empty div element.
So my previous answer had nothing to do with your problem, sorry.
Update:
Found an issue 546 in Backbone gitHub on this subject (wich was closed as wontfix), jashkenas posted this comment to explain why it is not easy to implement:
A large part of the advantage of using Backbone Views is the fact that
they have their element available at all times -- regardless of
whether a template has been rendered (many views have multiple
templates) -- regardless of wether the view is present in the DOM or
not.
This allows you to create and add views to the DOM, rendering later,
and be sure that all of your events are bound correctly (because
they're delegated from view.el).
If you can come up with a good strategy that allows a view to have
"complete" templates while preserving the above characteristics ...
then let's talk about it -- but it must allow the root element to
exist without having to render the contents of the template (which may
depend on data that might not arrive until later), and it must allow a
view to easily have multiple templates.
I wanted to achieve pretty much the same thing. I'd like to have all the HTML markup in my templates and let Backbone do the rest. So I wrote the following snippet which removes the extra 'div'.
First, you set the tagName to 'detect' and then after the first render you detect the tagName of the first element inside your view. Obviously this only works when you provide your own wrapper in your template.
// Single table row inside tbody
tableRowView = Backbone.Marionette.ItemView.extend({
tagName: 'detect',
template: function(data) {
return Handlebars.templates['tablerow/single'](data);
},
onRender: function() {
if (this.tagName == 'detect')
this.tagName = this.$el.children().first().prop('tagName').toLowerCase();
this.setElement( this.$el.children( this.tagName ) );
var $parent = this.$el.parent( this.tagName );
if ($parent.length) {
this.$el.detach();
this.$el.insertAfter($parent);
$parent.remove();
}
},
modelEvents: {
"change": "render"
}
});
Template:
<tr>
<td>{{artist}}</td>
<td>{{title}}</td>
<td>{{length}}</td>
</tr>
However, I've just played around with it a little - if anybody sees any problems that this approach could cause (performance, memory, zombies, etc) I'd be very open to learn about them.
BTW, this could probably easily packaged into a plugin.