Option name | Type | Description |
---|---|---|
root | object | Where to attach the Behold object. |
$ | object | jQuery lib, or api-compatible replacement (such as Zepto). |
_ | object | underscore/lodash lib. |
Copyright Christopher Keefer, 2014.
(function(root, $, _){
Our container function for the various elements of Behold, and static functions such as extend.
function Behold(){}
Recreate javascript inheritance (working similarly to backbone extend) to allow our views to be
extendable/inheritable.
Behold.extend = function(onProto, onStatic){
onProto = onProto || {};
onStatic = onStatic || {};
var parent = this,
child,
proxy;
For legacy support reasons, if the context (this) is Behold, then we instead recall extend
with the context set to Behold.View, allowing applications to continue using Behold.extend when
they want to extend Behold Views.
if (this === Behold) return Behold.extend.apply(Behold.View, arguments);
child = (onProto && onProto.hasOwnProperty('constructor')) ?
onProto.constructor : function(){ return parent.apply(this, arguments); };
_.extend(child, parent, onStatic);
proxy = function(){ this.constructor = child; };
proxy.prototype = parent.prototype;
child.prototype = new proxy;
_.extend(child.prototype, onProto);
child.__super__ = parent.prototype;
return child;
};
Option name | Type | Description |
---|---|---|
options | object= | Options to be passed to the view. Those with certain names will be set directly on the object for easy access - the others will live in this.options in the view. |
Our view function.
Behold.View = function(options){
this.cid = _.uniqueId('view');
this.options = options || {};
this.$ = $;
this._ = _;
this._setOptions();
this._setEl();
this.bindUI();
this.attachEvents();
this.initialize.apply(this, arguments);
};
Called if we're generating a new DOM structure rather than attaching to an existing structure.
Override this to change the way we attach our generated DOM - by default, we render the template
(if any) and make that the content of this element, and then attach this.el to the specified region.
Behold.View.prototype.attachElContent = function(){
var content = this.render();
this.$el.append(content);
this.region.empty().append(this.$el);
};
Renders the content for this view when we're not attaching to an existing DOM.
By default, we rely on a template being defined for the view, and use
underscores template function to render it, passing this.options in. If
no template is defined, we return an empty string.
Behold.View.prototype.render = function(){
var template = (this.template) ?
(typeof this.template === 'function') ? this.template :
_.template($(this.template).html()) : void 0;
if (template){
return template(this.options);
}
return '';
};
Option name | Type | Description |
---|---|---|
ui | object | The ui object to look in. |
key | string | The key to search for in the object. |
Get the full selector for a given key in the ui.
Behold.View.prototype.getUISelector = function(ui, key){
var el;
el = ui[key];
// If we're using our @ui sugar, keep looping through until we've got the full element string
while (el.indexOf('@ui.') === 0)
{
el = el.split(' ');
el = ui[el[0].substr(4)]+' '+el.slice(1).join(' ');
}
return el;
};
Bind this.ui (if set) as a lazy-mapping to the jQuery element references, to replace the css locator string that
previously occupied it. Cache retrieved jQuery element references to return after the first time the ui key has
been referenced. Save the original css strings in case we need to unbind or rebind in _uiBindings.
Behold.View.prototype.bindUI = function(){
var ui = _.extend({}, this._uiBindings || this.ui),
uiCache = (this._uiCache = {}),
$el = this.$el,
keys;
if (!ui || Object.getOwnPropertyNames(ui).length === 0) return this;
if (!this._uiBindings) this._uiBindings = _.extend({}, ui);
this.ui = {};
keys = Object.keys(ui);
keys.forEach(function(key){
var el = this.getUISelector(ui, key);
Object.defineProperty(this.ui, key, {
get:function(){
if (uiCache[key]){
return uiCache[key];
}
return (uiCache[key] = $el.find(el));
}
});
}, this);
return this;
};
Unbind all ui element references by deleting the entries and restoring this.ui to the contents of _uiBindings.
Behold.View.prototype.unBindUI = function(){
var ui = this.ui,
keys;
if (!ui || !this._uiBindings) return this;
keys = Object.keys(ui);
keys.forEach(function(key){
delete this.ui[key];
}, this);
this.ui = _.extend({}, this._uiBindings);
delete this._uiBindings;
return this;
};
Assign events based on this.events(if set) to map either this.ui element references (prefaced by
"@ui."), or else a bare css locator string. Namespace all events with '.dgbehold' plus the cid of this view.
Delegate events to the root element for efficiency. If the event key is prefaced with the 'capture'
indicator, the > symbol, bind to the specified element using event capturing, allowing us to 'delegate' to
the root element for events that don't bubble.
Behold.View.prototype.attachEvents = function(){
var that = this,
ui = this._uiBindings || this.ui,
events = this.events,
cid = this.cid,
$el = this.$el,
captures = this._capturingListeners = this._capturingListeners || {},
keys;
if (events)
{
keys = Object.keys(events);
keys.forEach(function(key){
var func = events[key],
sKey = key.split(' '),
el,
event;
func = (typeof func === 'function') ? func : that[func];
el = (sKey[1].indexOf('@ui.') === 0) ? that.getUISelector(ui, sKey[1].substr(4)) : sKey[1];
event = sKey[0]+'.dgbehold'+cid;
if (event[0] === '>'){
event = sKey[0].substr(1);
captures[key] = func.bind(that);
$el[0].addEventListener(event, captures[key], true);
return;
}
$el.on(event, el, func.bind(that));
});
}
return this;
};
Detach events attached by calling attachEvents.
Behold.View.prototype.detachEvents = function(){
var that = this,
$el = this.$el,
events = this.events,
cid = this.cid,
keys = Object.keys(events);
if (keys.length)
{
// Remove all delegated events
$el.off('.dgbehold'+cid);
// Remove all capturing events
keys.filter(function(key){
return key[0] === '>';
}).forEach(function(key){
var func = that._capturingListeners[key],
event;
event = key.split(' ')[0].substr(1);
$el[0].removeEventListener(event, func, true);
});
this._capturingListeners = {};
}
return this;
};
Remove the referenced element from the DOM.
Behold.View.prototype.remove = function(){
this.detachEvents();
this.$el.remove();
return this;
};
Option name | Type | Description |
---|---|---|
element | string,HTMLElement,jQuery | The element to set as the root of this view. |
skipEvents | boolean= | Whether to skip binding events. |
skipUnbinding | boolean= | Whether to skip unbinding events before binding new events. |
Set a new element as the referenced element for this view.
Behold.View.prototype.setElement = function(element, skipEvents, skipUnbinding){
if (this.$el && !skipUnbinding)
{
this.detachEvents();
this.unBindUI();
}
this.$el = (element instanceof $) ? element : $(element);
this.el = this.$el[0];
if (!skipEvents)
{
this.bindUI();
this.attachEvents();
}
return this;
};
An empty function by default, to be overridden by extending classes.
Behold.View.prototype.initialize = function(){};
Make Behold.View extensible.
Behold.View.extend = Behold.extend.bind(Behold.View);
Create a router to attach to a module that has a routes
object defined on it.
By default we use the module itself as the controller, but the module can also
define a controller
object that can contain the handler functions.
Behold.Router = function(routes, module){
this.controller = module.controller || module;
this.routes = routes;
this.handlers = this._parseRoutes();
window.addEventListener('popstate', function(event){
this._onChange(window.location.pathname, event.state || {});
}.bind(this));
};
// Cached Regexes for Routing
Behold.Router.optionalParam = /\((.*?)\)/g;
Behold.Router.namedParam = /(\(\?)?:\w+/g;
Behold.Router.splatParam = /\*\w+/g;
Behold.Router.escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g;
Option name | Type | Description |
---|---|---|
url | string | |
data | object= |
Navigate to the specified url, optionally passing the data object to the popstate event
both on navigation, and if we were to press the 'back' button to navigate away.
Behold.Router.navigate = function(url, data){
var event = new Event('popstate');
data = data || {};
window.history.pushState(data, document.title, url);
event.state = data;
window.dispatchEvent(event);
return this;
};
Application container for behold views.
Behold.Application = function(){
this.modules = {};
};
Call the initialize function on all registered modules when start is called.
Behold.Application.prototype.start = function(){
var modules = this.modules,
setupRoutes = [],
callInit = [],
module;
// Iterate through all routes and router setup, before iterating through again to
// call init, so that routers are ready to handle possible navigation called from
// initialize functions.
for (var name in modules)
{
if (modules.hasOwnProperty(name)){
module = modules[name];
if (module.routes)
{
setupRoutes.push(module);
}
if (module.initialize && typeof module.initialize === 'function')
{
callInit.push(module);
}
}
}
setupRoutes.forEach(function(module){
module._router = new Behold.Router(module.routes, module);
});
callInit.forEach(function(module){
module.initialize();
});
};
Option name | Type | Description |
---|---|---|
name | string | |
initializer | function= | |
arguments | ... | to be passed to the initializer |
Register a module, instantiating it and passing it the arguments specified.
Alternatively, if the initializer isn't provided, expect that we want a reference to the module
passed back to us. Will always return an object. Module declaration can be split amongst
multiple files.
Behold.Application.prototype.module = function(name, initializer){
var module = this.modules[name] = this.modules[name] || {},
args = [module, this, $, _].concat(Array.prototype.slice.call(arguments, 2));
if (!initializer) return this.modules[name];
initializer.apply(module, args);
return this.modules[name];
};
root.Behold = Behold;
})(window, jQuery, _);