JSFiddle - React, Tailwind, and code Playground
HTML
<script src="http://underscorejs.org/underscore-min.js"></script>
<script src="http://backbonejs.org/backbone-min.js"></script>
<script src="http://marionettejs.com/downloads/backbone.marionette.min.js"></script>
<div id="page"></div>
<script id="tab-layout" type="text/template">
<ul class="nav nav-tabs"></ul>
<div class="tab-contents"></div>
</script>
<script id="tab-label" type="text/template">
<%= label %>
</script>
<script id="tab-content" type="text/template">
<%= content %>
</script>
CSS
.tabbable {
border: 1px dotted red;
}
.nav-tabs {
border: 1px solid blue;
}
.tab-contents {
border: 1px solid green;
}
.tabbable > ul.nav-tabs > li {
border: 1px solid #ababab;
background-color: yellow;
}
.tabbable > div.tab-contents > div.tab-pane {
border: 1px solid #dedede;
}
JavaScript
var TabLayout = Backbone.Marionette.CompositeView.extend({
template: '#tab-layout',
className: 'tabbable',
itemView: Backbone.Marionette.ItemView.extend({
template: '#tab-content',
className: 'tab-pane'
}),
labelItemView: Backbone.Marionette.ItemView.extend({
template: '#tab-label',
tagName: 'li',
isLabel: true
}),
getLabelItemView: function(){
var itemView = this.options.labelItemView || this.labelItemView;
if (!itemView){
var err = new Error("A `labelItemView` must be specified");
err.name = "NoLabelItemViewError";
throw err;
}
return itemView;
},
// All these, methods are overrides of methods in
// Marionette.CompositeView / Marionette.CollectionView...
initChildViewStorage: function() {
this.children = {};
this.labelChildren = {};
},
appendHtml: function(collectionView, itemView, index) {
if (itemView.isLabel) {
collectionView.$('.nav-tabs').append(itemView.el);
} else {
collectionView.$('.tab-contents').append(itemView.el);
}
},
addChildView: function(item, collection, options) {
this.closeEmptyView();
var ItemView = this.getItemView();
var LabelItemView = this.getLabelItemView();
this.addItemView(item, ItemView, options.index);
this.addItemView(item, LabelItemView, options.index);
},
removeItemView: function(item, collection, options) {
var key = item.cid;
var view = this.children[key];
if (view){
this._clearView(view);
delete this.children[key];
}
var labelView = this.labelChildren[key];
if (labelView){
this._clearView(view);
delete this.labelChildren[key];
}
if (!this.collection || this.collection.length === 0){
this.showEmptyView();
}
...