Skip to content

Instantly share code, notes, and snippets.

@arendjr
Created November 23, 2013 14:40
Show Gist options
  • Save arendjr/7615292 to your computer and use it in GitHub Desktop.
Save arendjr/7615292 to your computer and use it in GitHub Desktop.
Custom View implementation
/**
* Base class for all views.
*
* @param context Application or View instance that serves as a context for this view. If a
* View instance is given, it is assumed to be the parent view.
* @param options Optional options object. May contain the following properties:
* $el - jQuery container containing the top-level element to be used by this
* view. If none is given, a new element is created for the view.
*/
function View(context, options) {
options = options || {};
// context may be an Application or View object.
var application = null, parent = null;
if (context instanceof View) {
application = context.application;
parent = context;
} else {
application = context;
}
if (!application) {
console.log("View instantiated without Application reference");
}
/**
* Reference to the parent view.
*/
this.parent = parent;
/**
* References to all the children of the view.
*/
this.children = [];
/**
* The options passed to the constructor (or an empty object if none were passed).
*/
this.options = options;
/**
* jQuery container containing the top-level element used by the view.
*/
this.$el = options.$el || $("<" + this.tagName + ">");
if (parent) {
this.reparent(parent);
}
this._events = [];
this.delegateEvents();
if (this.initialize) {
this.initialize(options);
}
}
View.extend = extend;
_.extend(View.prototype, {
/**
* Registers another view as a child of this view.
*
* @param child Another view.
*
* Child views automatically register themselves with their parent when the parent is passed
* as context to the View constructor of the child.
*/
addChild: function(child) {
this.children.push(child);
},
/**
* Attaches all listeners from the events map to the view's top-level element.
*
* All of the listeners specified by superclasses are automatically inherited by subclasses
* unless explicitly overwritten.
*/
delegateEvents: function() {
this.undelegateEvents();
var events = {};
var object = this;
while (object.constructor !== View) {
if (_.has(object.constructor.prototype, "events")) {
events = _.extend(_.clone(object.events), events);
}
object = object.constructor.__super__;
}
_.each(events, function(listener, event) {
if (typeof listener === "string") {
listener = _.bind(this[listener], this);
} else if (typeof listener === "function") {
listener = _.bind(listener, this);
}
var selector = "";
if (event.indexOf(" ") > 0) {
selector = event.split(" ").slice(1).join(" ");
event = event.split(" ", 1)[0];
}
if (selector) {
this.$el.on(event, selector, listener);
} else {
this.$el.on(event, listener);
}
this._events.push({ event: event, selector: selector, listener: listener });
}, this);
},
/**
* Map of DOM events to which the view listens.
*
* The keys of the map specify the events, with an optional space-separated selector. The
* values are the listeners, which may be specified as either a function object or the
* string name of a view method. Examples:
*
* "click": function(event) { console.log("clicked"); }
* "click button": "onButtonClicked"
*/
events: {},
/**
* Removes the view from the DOM.
*
* Also acts as a destructor by removing the view's children.
*/
remove: function() {
this.removeChildren();
this.$el.remove();
},
/**
* Removes a registered child from the list of children.
*
* @param child The child view.
*/
removeChild: function(child) {
child.remove();
this.children = _.without(this.children, child);
},
/**
* Removes all registered child views of this view.
*
* This method is called automatically when the view itself is removed.
*/
removeChildren: function() {
_.each(this.children, function(child) {
child.remove();
});
this.children = [];
},
/**
* Renders the view.
*
* Should return the view's $el property.
*/
render: function() {
return this.$el;
},
/**
* Registers the view with its parent.
*
* This is called automatically by the constructor and should normally not need to be called
* manually.
*/
reparent: function(parent) {
parent.addChild(this);
},
/**
* Sets a new element to use for the view.
*
* Automatically re-attaches all listeners from the events map to the new element.
*
* @param el The new element. May be either a DOM element or a jQuery container.
*/
setElement: function(el) {
this.undelegateEvents();
this.$el = (el.jquery ? el : $(el));
this.delegateEvents();
},
/**
* Tag name of the top-level element that's created when the view is instantiated.
*/
tagName: "div",
/**
* Detaches all listeners from the events map from the view's top-level element.
*/
undelegateEvents: function() {
_.each(this._events, function(event) {
if (event.selector) {
this.$el.off(event.event, event.selector, event.listener);
} else {
this.$el.off(event.event, event.listener);
}
}, this);
this._events = [];
},
/**
* Runs CSS queries against the DOM scoped within this view.
*/
$: function(selector) {
return this.$el.find(selector);
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment