I\'m looking at the Angry Cats Backbone/Marionette tutorial posts here
http://davidsulc.com/blog/2012/04/15/a-simple-backbone-marionette-tutorial/
http://dav
2014/02/18 — updated to accommodate the improvements noted by @vaughan and @Thom-Nichols in the comments
In many of my itemView/layouts I do this:
var Layout = Backbone.Marionette.Layout.extend({
...
onRender: function () {
// Get rid of that pesky wrapping-div.
// Assumes 1 child element present in template.
this.$el = this.$el.children();
// Unwrap the element to prevent infinitely
// nesting elements during re-render.
this.$el.unwrap();
this.setElement(this.$el);
}
...
});
The above code only works when the wrapper div contains a single element, which is how I design my templates.
In your case .children()
will return <tr class="angry_cat">
, so this should work perfect.
I agree, it does keep the templates much cleaner.
One thing to note:
This technique does not force only 1 child element. It blindly grabs .children()
so if you've incorrectly built the template to return more than one element, like the first template example with 3 <td>
elements, it won't work well.
It requires your template to return a single element, as you have in the second template with the root <tr>
element.
Of course it could be written to test for this if need be.
Here is a working example for the curious: http://codepen.io/somethingkindawierd/pen/txnpE
For IE9+ you could just use firstElementChild and childElementCount:
var Layout = Backbone.Marionette.LayoutView.extend({
...
onRender: function () {
if (this.el.childElementCount == 1) {
this.setElement(this.el.firstElementChild);
}
}
...
});
There is a good reason why Marionette automatically inserts the wrapper DIV. It's only when your template consists of just one element when you can drop it. Hence the test for number of child elements.
Another option is to use the attachElContent method present in every Marionette view. Its default implementation means re-renders of the view will overwrite the root element's inner HTML. This ultimately gives rise to the infinite nesting mentioned in bejonbee's answer.
If you would rather not overwrite onRender and/or require a pure-JS solution, the following code might be just what you want:
var Layout = Backbone.Marionette.LayoutView.extend({
...
attachElContent: function (html) {
var parentEl = this.el.parentElement;
var oldEl;
//View already attached to the DOM => re-render case => prevents
//recursive nesting by considering template's top element as the
//view's when re-rendering
if (parentEl) {
oldEl = this.el;
this.setElement(html); //gets new element from parsed html
parentEl.replaceChild(this.el, oldEl); //updates the dom with the new element
return this;
//View hasn't been attached to the DOM yet => first render
// => gets rid of wrapper DIV if only one child
} else {
Marionette.ItemView.prototype.attachElContent.call(this, html);
if (this.el.childElementCount == 1) {
this.setElement(this.el.firstElementChild);
}
return this;
}
}
...
});
Note that for re-rendering to work, the code assumes a template with a single child that contains all markup.
This solution works for re-rendering. You need to override render
.
onRender
tricks won't work for re-render. They will cause nesting on every re-render.
BM.ItemView::render = ->
@isClosed = false
@triggerMethod "before:render", this
@triggerMethod "item:before:render", this
data = @serializeData()
data = @mixinTemplateHelpers(data)
template = @getTemplate()
html = Marionette.Renderer.render(template, data)
#@$el.html html
$newEl = $ html
@$el.replaceWith $newEl
@setElement $newEl
@bindUIElements()
@triggerMethod "render", this
@triggerMethod "item:rendered", this
this
While I'm sure there's a way to hack the internals of render
to get it to behave the way you'd like, taking this approach means you'll be fighting the conventions of Backbone and Marionette through the whole development process. ItemView
needs to have an associated $el
, and by convention, it's a div
unless you specify a tagName
.
I empathize -- especially in the case of Layouts and Regions, it appears to be impossible to stop Backbone from generating an extra element. I'd recommend accepting the convention while you learn the rest of the framework and only then deciding if it's worth hacking render
to behave differently (or to just choose a different framework).
Wouldn't it be cleaner to use vanilla JS instead of jQuery to accomplish this?
var Layout = Backbone.Marionette.LayoutView.extend({
...
onRender: function () {
this.setElement(this.el.innerHTML);
}
...
});