Created
January 5, 2011 21:36
-
-
Save nfarina/767062 to your computer and use it in GitHub Desktop.
A minimal jQuery-based JavaScript UI framework. In-progress and a bit raw.
This file contains 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
(function(){ // begin private function | |
var copyProperties = function(props, o, overwrite, wrapFunctions) { | |
var wrap = wrapFunctions ? wrapFunctionWithSelf : nowrapFunction; | |
if (props) { | |
for (key in props) if (overwrite || o[key] === undefined) { | |
var getter = props.__lookupGetter__(key); | |
var setter = props.__lookupSetter__(key); | |
var baseGetter = o.__lookupGetter__(key); | |
var baseSetter = o.__lookupSetter__(key); | |
if (getter) o.__defineGetter__(key,wrap(getter,o,key,true)); | |
if (setter) o.__defineSetter__(key,wrap(setter,o,key,false,true)); | |
// weird things happen when only one side of a subclassed property is added, so | |
// we'll make the other side if necessary. | |
if (getter && !setter && baseSetter) o.__defineSetter__(key,baseSetter); | |
if (setter && !getter && baseGetter) o.__defineGetter__(key,baseGetter); | |
if (!getter && !setter && props.hasOwnProperty(key)) | |
if (typeof props[key] === 'function') | |
o[key] = wrap(props[key],o,key); | |
else | |
o[key] = props[key]; | |
} | |
} | |
} | |
var nowrapFunction = function(func) { return func; } | |
// Management of the "base" function. "base" is a global function attached to window | |
// that you can call while one of your methods is executing, in order to call | |
// a function on your superclass with the correct context. We store the correct | |
// context in our private baseContext var. Since "base" and "baseContext" are global, | |
// we need to manage their visibility using a stack - so that if you call a method | |
// from inside another method, the current base and context are pushed and popped | |
// off a global stack as needed. | |
var baseContext; | |
var baseStack = []; | |
function pushBaseStack(wrapper, context) { | |
baseStack.push([wrapper,context]); | |
window.base = wrapper; | |
baseContext = context; | |
} | |
function popBaseStack() { | |
baseStack.pop(); | |
if (baseStack.length > 0) { | |
var array = baseStack[baseStack.length-1]; | |
window.base = array[0]; | |
baseContext = array[1]; | |
} | |
else { | |
delete window.base; | |
baseContext = null; | |
} | |
} | |
var wrapFunctionWithSelf = function(func,o,key,getter,setter) { | |
var baseWrapper; | |
if (getter) | |
baseWrapper = function() { return o.base.__lookupGetter__(key).apply(baseContext,arguments); }; | |
else if (setter) | |
baseWrapper = function() { return o.base.__lookupSetter__(key).apply(baseContext,arguments); }; | |
else | |
baseWrapper = function() { return o.base[key].apply(baseContext,arguments); }; | |
return function() { | |
pushBaseStack(baseWrapper,this); | |
var ret = func.apply(this,[this].concat(Array.prototype.slice.call(arguments))); | |
popBaseStack(); | |
return ret; | |
} | |
} | |
//Object.create should exist already, in ECMA5 or whatever | |
if (typeof Object.create !== 'function') { | |
Object.create = function (o) { | |
function F() {} | |
F.prototype = o; | |
return new F(); | |
}; | |
} | |
var ignoredWindowMembers = {} | |
for (var key in window) | |
ignoredWindowMembers[key] = true; | |
// Our ultimate prototype, has classish helper methods. | |
// The methods "prototypeName" and "prototypeWithName" only work if you define your | |
// objects on the global object ("window"). | |
Base = { | |
init: function() {}, | |
derive: function(props) { | |
var o = Object.create(this); | |
copyProperties(props,o,true,true); | |
//o.__base__ = this; | |
return o; | |
}, | |
create: function() { | |
var derived = this.derive(); | |
derived.init.apply(derived, arguments); | |
return derived; | |
}, | |
get base() { | |
return this.__proto__; | |
//return this.__base__; | |
}, | |
get baseName() { | |
// Slow but useful. TODO-SPEEDUP: cache result | |
for (var p = this; p; p = p.base) | |
for (var key in window) | |
if (!ignoredWindowMembers[key] && window[key] === p) return key; | |
}, | |
baseWithName: function(name) { | |
return window[name]; | |
}, | |
toString: function() { | |
return '[object ' + (this.baseName || "Object") + ']'; | |
}, | |
} | |
// Replace builtin Object.toString() function, so Webkit will call it when printing things to the console | |
var nativeToString = Object.prototype.toString; | |
Object.prototype.toString = function() { | |
if (typeof this === 'object') | |
return Base.toString.apply(this) | |
else | |
return nativeToString.apply(this); | |
} | |
// If jQuery is available, create the Widget class. | |
if (window.jQuery) { | |
// big dictionary of all known templates registered by Widget.addTemplates | |
var allTemplates = {}; | |
// Widget's true prototype is the jQuery.fn object, meaning that your Widget | |
// subclasses get automatic access to all jQuery functions like addClass(), etc. | |
// Base's own methods and properties are mixed in below. | |
Widget = Base.derive.call(jQuery.fn,{ | |
init: function(self, selector) { | |
if (selector === undefined) | |
selector = '<div>'; // default element | |
jQuery.fn.init.call(self,selector); | |
// add CSS class names to represent our full class hierarchy - TODO-SPEEDUP: cache result | |
for (var base = self.base; base !== Widget; base = base.base) | |
self.addClass(base.baseName); | |
// does there exist a template with an ID matching our class name? if so, consume it automatically | |
var template = self.getTemplate(self.baseName); | |
if (template) | |
self.applyTemplate(self.baseName); | |
self.widget(self); // backreference | |
}, | |
get element(self) { return self[0]; }, | |
addTemplates: function(cls, html) { | |
if (!html) throw "Widget.addTemplates(html) called without a proper html argument."; | |
$('<div>').html(html).children().each(function(i,template) { | |
if (template.tagName.toLowerCase() !== 'template') throw "Found an element <"+template.tagName+"> in a templates file, expected <template>."; | |
if (!template.id) throw "All <template> elements must have an id."; | |
allTemplates[template.id] = template; | |
}) | |
}, | |
getTemplate: function(cls,id) { | |
return allTemplates[id]; | |
}, | |
applyTemplate: function(self,id,target) { | |
var template = self.getTemplate(id); | |
if (!template) throw "Template with id '"+id+"' was not found."; | |
// add template contents to the target if specified, or self if not | |
target = target || self; | |
target.html($(template).html()); | |
// create instance vars for any named elements in the template for easy access. | |
target.find('[id!=""]').each(function(i,namedElement) { | |
self[namedElement.id] = $(namedElement); | |
}) | |
}, | |
addStylesheet: function(cls, css) { | |
if (!css) throw "Widget.addStylesheet(css) called without a proper css argument."; | |
//var link = $('<link rel="stylesheet" type="text/css">').attr('href','/media/mapping/mapping.css'); | |
//$('head').append(link); | |
$('<style>').text(css).appendTo($('head')); | |
}, | |
}) | |
// A jQuery plugin to get a DOM element's associated Widget, if any. | |
jQuery.fn.widget = function(target) { | |
return this.data('widget',target); | |
} | |
// A jQuery plugin to get the nearest parent that is a Widget. | |
jQuery.fn.parentWidget = function() { | |
for (var p = this.parent(); p.length; p = p.parent()) { | |
var w = p.widget(); | |
if (w) return w; | |
} | |
} | |
// A jQuery plugin to get the nearest parent that is a Widget of a particular type. | |
jQuery.fn.parentWidgetWithBase = function(base) { | |
if (base) | |
for (var w = this.parentWidget(); w; w = w.parentWidget()) | |
if (w && base.isPrototypeOf(w)) | |
return w; | |
} | |
// Like jQuery's .each() function, but instead of handling DOM elements it | |
// looks up the associated Widget on each using .widget(). | |
jQuery.fn.eachWidget = function(callback) { | |
var length = this.length, i=0; | |
for (var value = $(this[0]).widget(); | |
i < length && callback.call(value, i, value) !== false; value = $(this[++i]).widget() ) {} | |
} | |
// mixin members of Base to Widget | |
copyProperties(Base, Widget); | |
} | |
// This is pretty much critical for OO-style event delegates. | |
Function.prototype.bind = function(scope) { | |
var _function = this; | |
return function() { | |
return _function.apply(scope, arguments); | |
} | |
} | |
})(); // end private function |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment