/**
* DUI: The Digg User Interface Library
*
* Copyright (c) 2008-2009, Digg, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* - Neither the name of the Digg, Inc. nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @module DUI
* @author Micah Snyder <micah@digg.com>
* @description The Digg User Interface Library
* @version 0.0.5
* @link http://code.google.com/p/digg
*
*/
/* Add Array.prototype.indexOf -- Guess which major browser doesn't support it natively yet? */
[].indexOf || (Array.prototype.indexOf = function(v, n){
n = (n == null) ? 0 : n; var m = this.length;
for(var i = n; i < m; i++) {
if(this[i] == v) return i;
}
return -1;
});
(function($) {
/* Create our top-level namespace */
DUI = {};
/**
* @class Class Class creation and management for use with jQuery. Class is a singleton that handles static and dynamic classes, as well as namespaces
*/
DUI.Class = {
/**
* @var {Array} _dontEnum Internal array of keys to omit when looking through a class' properties. Once the real DontEnum bit is writable we won't have to deal with this.
*/
_dontEnum: ['_ident', '_dontEnum', 'create', 'namespace', 'ns', 'supers', 'sup', 'init', 'each'],
/**
* @function create Make a class! Do work son, do work
* @param {optional Object} methods Any number of objects can be passed in as arguments to be added to the class upon creation
* @param {optional Boolean} static If the last argument is Boolean, it will be treated as the static flag. Defaults to false (dynamic)
*/
create: function() {
//Set _this to DUI.Class
var _this = this;
//Figure out if we're creating a static or dynamic class
var s = (arguments.length > 0 && //if we have arguments...
arguments[arguments.length - 1].constructor == Boolean) ? //...and the last one is Boolean...
arguments[arguments.length - 1] : //...then it's the static flag...
false; //...otherwise default to a dynamic class
//Static: Object, dynamic: Function
var c = s ? {} : function() {
this.init.apply(this, arguments);
}
//All of our classes have these in common
var methods = {
_ident: {
library: "DUI.Class",
version: "0.0.5",
dynamic: true
},
//_dontEnum should exist in our classes as well
_dontEnum: this._dontEnum,
//A basic namespace container to pass objects through
ns: [],
//A container to hold one level of overwritten methods
supers: {},
//A constructor
init: function() {},
//Our namespace function
namespace:function(ns) {
//Don't add nothing
if (!ns) return null;
//Set _this to the current class, not the DUI.Class lib itself
var _this = this;
//Handle ['ns1', 'ns2'... 'nsN'] format
if(ns.constructor == Array) {
//Call namespace normally for each array item...
$.each(ns, function() {
_this.namespace.apply(_this, [this]);
});
//...then get out of this call to namespace
return;
//Handle {'ns': contents} format
} else if(ns.constructor == Object) {
//Loop through the object passed to namespace
for(var key in ns) {
//Only operate on Objects and Functions
if([Object, Function].indexOf(ns[key].constructor) > -1) {
//In case this.ns has been deleted
if(!this.ns) this.ns = [];
//Copy the namespace into an array holder
this.ns[key] = ns[key];
//Apply namespace, this will be caught by the ['ns1', 'ns2'... 'nsN'] format above
this.namespace.apply(this, [key]);
}
}
//We're done with namespace for now
return;
}
//Note: [{'ns': contents}, {'ns2': contents2}... {'nsN': contentsN}] is inherently handled by the above two cases
var levels = ns.split(".");
/* Dynamic classes are Functions, so we'll extend their prototype.
Static classes are Objects, so we'll extend them directly */
var nsobj = this.prototype ? this.prototype : this;
$.each(levels, function() {
/* When adding a namespace check to see, in order:
* 1) Does the ns exist in our ns passthrough object?
* 2) Does the ns already exist in our class
* 3) Does the ns exist as a global var?
* NOTE: Support for this was added so that you can namespace classes
* into other classes, i.e. MyContainer.namespace('MyUtilClass'). this
* behaviour is dangerously greedy though, so it may be removed.
* 4) If none of the above, make a new static class
*/
nsobj[this] = _this.ns[this] || nsobj[this] || window[this] || DUI.Class.create(true);
/* If our parent and child are both dynamic classes, copy the child out of Parent.prototype and into Parent.
* It seems weird at first, but this allows you to instantiate a dynamic sub-class without instantiating
* its parent, e.g. var baz = new Foo.Bar();
*/
if(_this.prototype && DUI.isClass(nsobj[this]) && nsobj[this].prototype) {
_this[this] = nsobj[this];
}
//Remove our temp passthrough if it exists
delete _this.ns[this];
//Move one level deeper for the next iteration
nsobj = nsobj[this];
});
//TODO: Do we really need to return this? It's not that useful anymore
return nsobj;
},
/* Create exists inside classes too. neat huh?
* Usage differs slightly: MyClass.create('MySubClass', { myMethod: function() }); */
create: function() {
//Turn arguments into a regular Array
var args = Array.prototype.slice.call(arguments);
//Pull the name of the new class out
var name = args.shift();
//Create a new class with the rest of the arguments
var temp = DUI.Class.create.apply(DUI.Class, args);
//Load our new class into the {name: class} format to pass it into namespace()
var ns = {};
ns[name] = temp;
//Put the new class into the current one
this.namespace(ns);
},
//Iterate over a class' members, omitting built-ins
each: function(cb) {
if(!$.isFunction(cb)) {
throw new Error('DUI.Class.each must be called with a function as its first argument.');
}
//Set _this to the current class, not the DUI.Class lib itself
var _this = this;
$.each(this, function(key) {
if(_this._dontEnum.indexOf(key) != -1) return;
cb.apply(this, [key, this]);
});
},
//Call the super of a method
sup: function() {
try {
var caller = this.sup.caller.name;
this.supers[caller].apply(this, arguments);
} catch(noSuper) {
return false;
}
}
}
//Static classes don't need a constructor
s ? delete methods.init : null;
//...nor should they be identified as dynamic classes
s ? methods._ident.dynamic = false : null;
/* Put default methods into the class before anything else,
* so that they'll be overwritten by the user-specified ones */
$.extend(c, methods);
/* Second copy of methods for dynamic classes: They get our
* common utils in their class definition AND their prototype */
if(!s) $.extend(c.prototype, methods);
//Static: extend the Object, Dynamic: extend the prototype
var extendee = s ? c : c.prototype;
//Loop through arguments. If they're the right type, tack them on
$.each(arguments, function() {
//Either we're passing in an object full of methods, or the prototype of an existing class
if(this.constructor == Object || typeof this.init != undefined) {
/* Here we're going per-property instead of doing $.extend(extendee, this) so that
* we overwrite each property instead of the whole namespace. Also: we omit the 'namespace'
* helper method that DUI.Class tacks on, as there's no point in storing it as a super */
for(i in this) {
/* If a property is a function (other than our built-in helpers) and it already exists
* in the class, save it as a super. note that this only saves the last occurrence */
if($.isFunction(extendee[i]) && _this._dontEnum.indexOf(i) == -1) {
//since Function.name is almost never set for us, do it manually
this[i].name = extendee[i].name = i;
//throw the existing function into this.supers before it's overwritten
extendee.supers[i] = extendee[i];
}
//Special case! If 'dontEnum' is passed in as an array, add its contents to DUI.Class._dontEnum
if(i == 'dontEnum' && this[i].constructor == Array) {
extendee._dontEnum = $.merge(extendee._dontEnum, this[i]);
}
//extend the current property into our class
extendee[i] = this[i];
}
}
});
//Shiny new class, ready to go
return c;
}
};
/* Turn DUI into a static class whose contents are DUI.
Now you can use DUI's tools on DUI itself, i.e. DUI.create('Foo');
I'm pretty sure this won't melt the known universe, but caveat emptor. */
DUI = DUI.Class.create(DUI, true);
})(jQuery);
//Simple check so see if the object passed in is a DUI Class
DUI.isClass = function(check, type)
{
type = type || false;
try {
if(check._ident.library == 'DUI.Class') {
if((type == 'dynamic' && !check._ident.dynamic)
|| (type == 'static' && check._ident.dynamic)) {
return false;
}
return true;
}
} catch(noIdentUhOh) {
return false;
}
return false;
}