1 /**
  2  * @fileoverview
  3  * <code>APE</code> provides core features, including namespacing and object creational aspects.
  4  * 
  5  * <h3>APE JavaScript Library</h3>
  6  * <p>
  7  * Released under Academic Free Licence 3.0.
  8  * </p>
  9  * 
 10  * @author Garrett Smith 
 11  */
 12 
 13 /** @name APE
 14  * @namespace */
 15 if(APE !== undefined) throw Error("APE is already defined.");
 16 var APE = {
 17     
 18     /**
 19      * @memberOf APE
 20      * @description Prototype inheritance.
 21      * @param {Object} subclass 
 22      * @param {Object} superclass
 23      * @param {Object} mixin If present, <var>mixin</var>'s own properties are copied to receiver 
 24      * using APE.mixin(subclass.prototoype, superclass.prototype).
 25      */
 26     extend : function(subclass, superclass, mixin) {
 27         if(arguments.length === 0) return;
 28         var f = arguments.callee, subp;
 29         f.prototype = superclass.prototype;
 30         subclass.prototype = subp = new f;
 31         if(typeof mixin == "object") 
 32             APE.mixin(subp, mixin);
 33         subp.constructor = subclass;
 34         return subclass;
 35     },
 36 
 37     /**
 38      * Shallow copy of properties; does not look up prototype chain. 
 39      * Copies all properties in s to r, using hasOwnProperty.
 40      * @param {Object} r the receiver of properties.
 41      * @param {Object} s the supplier of properties.
 42      * Accounts for JScript DontEnum bug for valueOf and toString.
 43      * @return {Object} r the receiver.
 44      */
 45     mixin : function(r, s) {
 46         var jscriptSkips = ['toString', 'valueOf'],
 47             prop,
 48             i = 0,
 49             skipped;
 50         for(prop in s) {
 51             if(s.hasOwnProperty(prop))
 52                 r[prop] = s[prop];
 53         }
 54         // JScript DontEnum bug.
 55         for( ; i < jscriptSkips.length; i++) {
 56             skipped = jscriptSkips[i];
 57             if(s.hasOwnProperty(skipped))
 58                 r[skipped] = s[skipped];
 59         }
 60         return r;
 61     },
 62 
 63     toString : function() { return "[APE JavaScript Library]"; },
 64 
 65     /** Creational method meant for being cross-cut.
 66      * Uses APE.newApply to create 
 67      * @param {HTMLElement} el An element. If el does not have 
 68      * an ID, then an ID will be automatically generated, based on the 
 69      * constructor's (this) identifier, or, If this is anonymous, "APE".
 70      * @requires {Object} an object to be attached to as a property.
 71      * @aspect 
 72      * @scope {Function} that accepts an HTMLElement for 
 73      * its first argument. 
 74      * APE.getByNode is intended to be bouund to a constructor function.
 75      * @return <code>{new this(el [,args...])}</code>
 76      */
 77     getByNode : function(el) {
 78         var id = el.id,
 79             fName;        
 80         if(!id) {
 81             if(!APE.getByNode._i) APE.getByNode._i = 0;
 82             fName = APE.getFunctionName(this);
 83             if(!fName) fName = "APE";
 84             id = el.id = fName+"_" + (APE.getByNode._i++);
 85         }
 86         if(!this.hasOwnProperty("instances")) this.instances = {};
 87         return this.instances[id] || (this.instances[id] = APE.newApply(this, arguments));       
 88     },
 89 
 90     /** Tries to get a name of a function object, returns "" if anonymous. 
 91      */
 92     getFunctionName : function(fun) {
 93         if(typeof fun.name == "string") return fun.name;
 94         var name = Function.prototype.toString.call(fun).match(/\s([a-z]+)\(/i);
 95         return name && name[1]||"";
 96     },
 97 
 98     /** Creational method meant for being cross-cut.
 99      * @param {HTMLElement} el An element that has an id.
100      * @requires {Object} an object to bind to.
101      * @aspect 
102      * @description <code>getById</code> must be assigned to a function constructor
103      * that accepts an HTMLElement's <code>id</code> for 
104      * its first argument. 
105      * @example <pre>
106      * function Slider(el, config){ }
107      * Slider.getById = APE.getById;
108      * </pre>
109      * This allows for implementations to use a factory method with the constructor.
110      * <pre>
111      * Slider.getById( "weight", 1 );
112      * </pre>
113      * Subsequent calls to:
114      * <pre>
115      * Slider.getById( "weight" );
116      * </pre>
117      * will return the same Slider instance.
118      * An <code>instances</code> property is added to the constructor object
119      * that <code>getById</code> is assigned to.
120      * @return <pre>new this(id [,args...])</pre>
121      */
122     getById : function(id) {
123         if(!this.hasOwnProperty("instances")) this.instances = {};
124         return this.instances[id] || (this.instances[id] = APE.newApply(this, arguments));       
125     },
126     
127     /** 
128      * @param {Function} fun constructor to be invoked.
129      * @param {Array} args arguments to pass to the constructor.
130      * Instantiates a constructor and uses apply().
131      */
132     newApply : function(fun, args) {
133         if(arguments.length === 0) return;
134         var f = arguments.callee, i;
135 
136         f.prototype = fun.prototype;// Copy prototype.
137         f.prototype.constructor = fun;
138 
139         i = new f;
140         fun.apply(i, args); // Apply the original constructor.
141         return i;
142     },
143 
144     /** Throws the error in a setTimeout 1ms.
145      *  Deferred errors are useful for Event Notification systems,
146      * Animation, and testing.
147      * @param {Error} error that occurred.
148      */
149     deferError : function(error) {        
150         setTimeout(function(){throw error;},1);
151     }
152 };
153 
154 (function(){
155 
156     APE.namespace = namespace;
157 
158     /** 
159      * @memberOf APE
160      * @description creates a namespace split on "."
161      * does <em>not</em> automatically add APE to the front of the chain, as YUI does.
162      * @param {String} s the namespace. "foo.bar" would create a namespace foo.bar, but only
163      * if that namespace did not exist.
164      * @return {Package} the namespace.
165      */
166     function namespace(s) {
167         var packages = s.split("."),
168             pkg = window,
169             hasOwnProperty = Object.prototype.hasOwnProperty,
170             qName = pkg.qualifiedName,
171             i = 0,
172             len = packages.length;
173             name;
174         for (; i < len; i++) {
175             name = packages[i];
176 
177             // Internet Explorer does not support 
178             // hasOwnProperty on things like window, so call Object.prototype.hasOwnProperty.
179             // Opera does not support the global object or [[Put]] properly (see below)
180             if(!hasOwnProperty.call(pkg, name)) {
181                 pkg[name] = new Package((qName||"APE")+"."+name);
182             }
183             pkg = pkg[name];
184         }
185 
186         return pkg;
187     }
188 
189     Package.prototype.toString = function(){
190         return"["+this.qualifiedName+"]";
191     };
192     
193     /* constructor Package
194      */
195     function Package(qualifiedName) {
196         this.qualifiedName = qualifiedName;
197     }
198 })();
199 
200 (function(){
201 /**@class 
202  * A safe patch to the Object object. This patch addresses a bug that only affects Opera.
203  * <strong>It does <em>not</em> affect any for-in loops in any browser</strong> (see tests). 
204  */
205 Object=window.Object;
206 
207 var O = Object.prototype, hasOwnProperty = O.hasOwnProperty;
208 if(typeof window != "undefined" && hasOwnProperty && !hasOwnProperty.call(window, "Object")) {
209 /** 
210  * @overrides Object.prototype.hasOwnProperty
211  * @method
212  * This is a conditional patch that affects some versions of Opera.
213  * It is perfectly safe to do this and does not affect enumeration.
214  */
215     Object.prototype.hasOwnProperty = function(p) {
216         if(this === window) return (p in this) && (O[p] !== this[p]);
217         return hasOwnProperty.call(this, p);
218     };
219 }
220 })();