/** * 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; }