Skip to content

Instantly share code, notes, and snippets.

@uzikilon
Created February 4, 2013 20:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save uzikilon/4709422 to your computer and use it in GitHub Desktop.
Save uzikilon/4709422 to your computer and use it in GitHub Desktop.
// Backbone.ContainerView 0.0.1
// (c) 2013 Uzi Kilon, Okta Inc.
// Backbone.ContainerView may be freely distributed under the MIT license.
// For all details and documentation:
// https://github.com/uzikilon/Backbone.ContainerView
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['underscore', 'backbone'], function (_, Backbone) {
// Use global variables if the locals are undefined.
return factory(_ || root._, Backbone || root.Backbone);
});
}
else {
// RequireJS isn't being used.
// Assume underscore and backbone are loaded in <script> tags
root.Backbone.ContainerView = factory(root._, root.Backbone);
}
}(this, function (_, Backbone) {
var ContainerView = Backbone.View.extend({
constructor: function () {
// init per-instance children collection
this._children = {};
this._rendered = false;
Backbone.View.prototype.constructor.apply(this, arguments);
},
/*jshint maxstatements:11 maxcomplexity:4 */
/**
* Add a child view to the container
*
* @param view the view
* @param selector selectrot on current template to add to
* @param bubbleEvents rarther the view should bubble up evenmts to parent (default false)
*/
add: function (view, selector, bubbleEvents) {
// prevent dups
if ( this._children[view.cid] ) {
throw 'Duplicate view added';
}
// when a child view remove itself:
// * stop listening to events
// * remove from child views collection
// * call the default remove handler
var self = this, originalRemove = view.remove;
view.remove = function () {
self.stopListening(view);
delete self._children[view.cid];
originalRemove.apply(view, arguments);
};
// make the view responsible for adding itself to the parent:
// * register the selector in the closure
// * register a reference the parent in the closure
view._addToContainer = (function (selector) {
return function () {
if (selector && self.$(selector).length != 1) {
throw new Error('Invalid selector: ' + selector);
}
var $el = selector ? self.$(selector) : self.$el;
$el.append(this.render().el);
};
}).call(view, selector);
// if flag to bubble events is set
// proxy all child view events
if (bubbleEvents) {
this.listenTo(view, 'all', function () {
this.trigger.apply(this, arguments);
});
}
// add to the dom if `render` has been called
if ( this._rendered ) {
view.render()._addToContainer();
}
// add view to child views collection
this._children[view.cid] = view;
return this;
},
/**
* @Override
* Render self template and all child views
*/
render: function () {
this._rendered = true;
// first render tempalte to el
if (this.template) {
this.$el.html(this.template);
}
// append children to container
this.each(function(view) {
view.render()._addToContainer();
}, this);
return this;
},
/**
* Remove all children from container
*/
empty: function () {
this.each(function (view) {
view.remove();
});
return this;
},
/**
* @Override
* Make sure we remove children wheh
* removing parent (cleanup / gc)
*/
remove: function () {
this.empty();
Backbone.View.prototype.remove.apply(this, arguments);
return this;
},
/**
* Run a method on container and all child views
*/
proxy: function (method, args) {
/*jshint maxcomplexity:3 */
this.each(function (child) {
if (child instanceof ContainerView) {
// if child is a container view, proxy thright
child.proxy.apply(child, [method, args]);
}
else if (child[method]) {
// if method exists on child, run it
// fail gracefuly if it doesnt exeists
child[method].apply(child, args);
}
});
// run on the container view as well
if (this[method]) {
this[method].apply(this, args);
}
return this;
}
});
// Mixin some underscore collection methods
// Code borrowed from Backbone.js source
var methods = ['forEach', 'each', 'map', 'find', 'detect', 'filter',
'select', 'reject', 'every', 'all', 'some', 'any', 'include',
'contains', 'invoke', 'toArray', 'first', 'initial', 'rest',
'last', 'without', 'isEmpty', 'pluck', 'size'];
_.each(methods, function (method) {
ContainerView.prototype[method] = function () {
var views = _.toArray(this._children);
var args = _.toArray(arguments);
args.unshift(views);
return _[method].apply(_, args);
};
}, this);
return ContainerView;
}));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment