Created
February 4, 2013 20:21
-
-
Save uzikilon/4709422 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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