1 // ==========================================================================
  2 // Project:   SproutCore Costello - Property Observing Library
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 /*global require, console */
  8 
  9 // These commands are used by the build tools to control load order.  On the
 10 // client side these are a no-op.
 11 if (!window.require) { window.require = function require() {}; }
 12 if (!window.sc_require) { window.sc_require = require; }
 13 if (!window.sc_resource) { window.sc_resource = function sc_resource() {}; }
 14 sc_require('license');
 15 
 16 // ........................................
 17 // GLOBAL CONSTANTS
 18 //
 19 // Most global constants should be defined inside of the SC namespace.
 20 // However the following two are useful enough and generally benign enough
 21 // to put into the global object.
 22 window.YES = true;
 23 window.NO = false;
 24 
 25 // prevent a console.log from blowing things up if we are on a browser that
 26 // does not support it
 27 if (typeof console === 'undefined') {
 28   window.console = {};
 29   console.log = console.info = console.warn = console.error = function () {};
 30 }
 31 
 32 window.SC = window.SC || {};
 33 window.SproutCore = window.SproutCore || SC;
 34 
 35 // ........................................
 36 // BOOTSTRAP
 37 //
 38 // The root namespace and some common utility methods are defined here. The
 39 // rest of the methods go into the mixin defined below.
 40 
 41 /**
 42   @version 1.11.0
 43   @namespace
 44 
 45   All SproutCore methods and functions are defined
 46   inside of this namespace.  You generally should not add new properties to
 47   this namespace as it may be overwritten by future versions of SproutCore.
 48 
 49   You can also use the shorthand "SC" instead of "SproutCore".
 50 
 51   SproutCore-runtime is a framework that provides core functions for SproutCore
 52   including cross-platform functions, support for property observing and
 53   objects.  It's focus is on small size and performance.  You can use this
 54   in place of or along-side other cross-platform libraries such as jQuery or
 55   Prototype.
 56 */
 57 SC = window.SC; // This is dumb but necessary for jsdoc to get it right
 58 
 59 SC.VERSION = '1.11.0';
 60 
 61 /**
 62   @private
 63 
 64   Adds properties to a target object. You must specify whether
 65   to overwrite a value for a property or not.
 66 
 67   Used as a base function for the wrapper functions SC.mixin and SC.supplement.
 68 
 69   @param {Boolean} overwrite if a target has a value for a property, this specifies
 70                   whether or not to overwrite that value with the copyied object's
 71                   property value.
 72   @param {Object} target the target object to extend
 73   @param {Object} properties one or more objects with properties to copy.
 74   @returns {Object} the target object.
 75   @static
 76 */
 77 SC._baseMixin = function (override, args) {
 78   // Copy reference to target object
 79   var target = args[0] || {},
 80       idx = 1,
 81       length = args.length,
 82       options, copy, key;
 83 
 84   // Handle case where we have only one item...extend SC
 85   if (length === 1) {
 86     target = this || {};
 87     idx = 0;
 88   }
 89 
 90   for (; idx < length; idx++) {
 91     if (!(options = args[idx])) continue;
 92     for (key in options) {
 93       if (!options.hasOwnProperty(key)) continue;
 94       copy = options[key];
 95       if (target === copy) continue; // prevent never-ending loop
 96       if (copy !== undefined && (override || (target[key] === undefined))) target[key] = copy;
 97     }
 98     // Manually copy toString() because some JS engines do not enumerate it
 99     // (such as IE8)
100     if (options.hasOwnProperty('toString')) target.toString = options.toString;
101   }
102 
103   return target;
104 };
105 
106 /**
107   Adds properties to a target object.
108 
109   Takes the root object and adds the attributes for any additional
110   arguments passed.
111 
112   @param {Object} target the target object to extend
113   @param {Object} properties one or more objects with properties to copy.
114   @returns {Object} the target object.
115   @static
116 */
117 SC.mixin = function () {
118   // Accessing `arguments.length` is just a Number and doesn't materialize the `arguments` object, which is costly.
119   // TODO: Add macro to build tools for this.
120   var args = new Array(arguments.length); // Array.prototype.slice.call(arguments)
121   for (var i = 0, len = args.length; i < len; i++) { args[i] = arguments[i]; }
122 
123   return SC._baseMixin(true, args);
124 };
125 
126 /**
127   Adds properties to a target object.  Unlike SC.mixin, however, if the target
128   already has a value for a property, it will not be overwritten.
129 
130   Takes the root object and adds the attributes for any additional
131   arguments passed.
132 
133   @param {Object} target the target object to extend
134   @param {Object} properties one or more objects with properties to copy.
135   @returns {Object} the target object.
136   @static
137 */
138 SC.supplement = function () {
139   // Accessing `arguments.length` is just a Number and doesn't materialize the `arguments` object which is costly.
140   // TODO: Add macro to build tools for this.
141   var args = new Array(arguments.length); // Array.prototype.slice.call(arguments)
142   for (var i = 0, len = args.length; i < len; i++) { args[i] = arguments[i]; }
143 
144   return SC._baseMixin(false, args);
145 };
146 
147 /**
148   Alternative to mixin.  Provided for compatibility with jQuery.
149   @function
150 */
151 SC.extend = SC.mixin;
152 
153 // ..........................................................
154 // CORE FUNCTIONS
155 //
156 // Enough with the bootstrap code.  Let's define some core functions
157 
158 SC.mixin(/** @scope window.SC.prototype */ {
159 
160   // ........................................
161   // GLOBAL CONSTANTS
162   //
163   T_ERROR:     'error',
164   T_OBJECT:    'object',
165   T_NULL:      'null',
166   T_CLASS:     'class',
167   T_HASH:      'hash',
168   T_FUNCTION:  'function',
169   T_UNDEFINED: 'undefined',
170   T_NUMBER:    'number',
171   T_BOOL:      'boolean',
172   T_ARRAY:     'array',
173   T_STRING:    'string',
174   T_DATE:      'date',
175   T_REGEXP:    'regexp',
176 
177   // ........................................
178   // TYPING & ARRAY MESSAGING
179   //
180 
181   /**
182     Returns a consistent type for the passed item.
183 
184     Use this instead of the built-in typeOf() to get the type of an item.
185     It will return the same result across all browsers and includes a bit
186     more detail.
187 
188     @param {Object} item the item to check
189     @returns {String} One of the following, depending on the type of the item<br>
190             SC.T_STRING: String primitive,<br>
191             SC.T_NUMBER: Number primitive,<br>
192             SC.T_BOOLEAN: Boolean primitive,<br>
193             SC.T_NULL: Null value,<br>
194             SC.T_UNDEFINED: Undefined value,<br>
195             SC.T_FUNCTION: A function,<br>
196             SC.T_DATE: Date primitive,<br>
197             SC.T_REGEXP: RegExp primitive,<br>
198             SC.T_ARRAY: An instance of Array,<br>
199             SC.T_CLASS: A SproutCore class (created using SC.Object.extend()),<br>
200             SC.T_OBJECT: A SproutCore object instance,<br>
201             SC.T_HASH: A JavaScript object not inheriting from SC.Object, <br>
202             SC.T_ERROR: A SproutCore SC.Error object <br>
203   */
204   typeOf: function (item) {
205     var nativeType = SC._nativeTypeOf(item);
206 
207     // Translate it into an SC type.
208     if (nativeType === "function") {
209       return item.isClass ? SC.T_CLASS : SC.T_FUNCTION;
210     } else if (nativeType === "object") {
211 
212       // Note: typeOf() may be called before SC.Error has had a chance to load
213       // so this code checks for the presence of SC.Error first just to make
214       // sure.  No error instance can exist before the class loads anyway so
215       // this is safe.
216       if (SC.Error && (item instanceof SC.Error)) {
217         return SC.T_ERROR;
218       } else if (item instanceof SC.Object) {
219         return SC.T_OBJECT;
220       } else {
221         return SC.T_HASH;
222       }
223     }
224 
225     return nativeType;
226   },
227   // Inlined from jQuery.type to avoid dependency.
228   _nativeTypeOf: function(item) {
229     if (item === undefined) return SC.T_UNDEFINED;
230     if (item === null) return SC.T_NULL;
231 
232     var nativeType = typeof item,
233         toString;
234     if (nativeType === "object" || nativeType === "function") {
235       toString = SC._nativeToString.call(item);
236       return SC._nativeTypeHash[toString] || "object";
237     } else {
238       return nativeType;
239     }
240   } ,
241   // Inlined from jQuery's class2type to avoid dependency.
242   _nativeTypeHash: {
243     "[object Boolean]": "boolean",
244     "[object Number]": "number",
245     "[object String]": "string",
246     "[object Function]": "function",
247     "[object Array]": "array",
248     "[object Date]": "date",
249     "[object RegExp]": "regexp",
250     "[object Object]": "object"
251   },
252   // Inlined from jQuery to avoid dependency.
253   _nativeToString: Object.prototype.toString,
254 
255   /**
256     Returns YES if the passed value is null or undefined.  This avoids errors
257     from JSLint complaining about use of ==, which can be technically
258     confusing.
259 
260     @param {Object} obj value to test
261     @returns {Boolean}
262   */
263   none: function (obj) {
264     /*jshint eqnull:true */
265     return obj == null;
266   },
267 
268   /**
269     Verifies that a value is either null or an empty string. Return false if
270     the object is not a string.
271 
272     @param {Object} obj value to test
273     @returns {Boolean}
274   */
275   empty: function (obj) {
276     return obj === null || obj === undefined || obj === '';
277   },
278 
279   /**
280     Returns YES if the passed object is an array or Array-like.
281 
282     SproutCore Array Protocol:
283     * the object has an objectAt property; or
284     * the object is a native Array; or
285     * the object is an Object, and has a length property
286 
287     Unlike SC.typeOf this method returns true even if the passed object is
288     not formally array but appears to be array-like (i.e. has a length
289     property, responds to .objectAt, etc.)
290 
291     @param {Object} obj the object to test
292     @returns {Boolean}
293   */
294   isArray: function (obj) {
295     if (!obj || obj.setInterval) { return false; }
296     if (Array.isArray && Array.isArray(obj)) { return true; }
297     if (obj.objectAt) { return true; }
298     if (obj.length !== undefined && SC._nativeTypeOf(obj) === "object") { return true; }
299 
300     return false;
301   },
302 
303   /**
304     Makes an object into an Array if it is not array or array-like already.
305     Unlike SC.A(), this method will not clone the object if it is already
306     an array.
307 
308     @param {Object} obj object to convert
309     @returns {Array} Actual array
310   */
311   makeArray: function (obj) {
312     return SC.isArray(obj) ? obj : SC.A(obj);
313   },
314 
315   /**
316     Converts the passed object to an Array.  If the object appears to be
317     array-like, a new array will be cloned from it.  Otherwise, a new array
318     will be created with the item itself as the only item in the array.
319 
320     @param {Object} object any enumerable or array-like object.
321     @returns {Array} Array of items
322   */
323   A: function (obj) {
324     // null or undefined -- fast path
325     if (obj === null || obj === undefined) return [];
326 
327     // primitive -- fast path
328     if (obj.slice instanceof Function) {
329       // do we have a string?
330       if (typeof(obj) === 'string') return [obj];
331       else return obj.slice();
332     }
333 
334     // enumerable -- fast path
335     if (obj.toArray) return obj.toArray();
336 
337     // if not array-like, then just wrap in array.
338     if (!SC.isArray(obj)) return [obj];
339 
340     // when all else fails, do a manual convert...
341     var ret = [], len = obj.length;
342     while (--len >= 0) ret[len] = obj[len];
343     return ret;
344   },
345 
346   //
347   // GUIDS & HASHES
348   //
349 
350   // Like jQuery.expando but without any risk of conflict
351   guidKey: "SproutCore" + (SC.VERSION + Math.random()).replace(/\D/g, ""),
352 
353   // Used for guid generation...
354   _guidPrefixes: {"number": "nu", "string": "st"},
355   _guidCaches:   {"number": {},   "string": {}},
356   _numberGuids: [],
357   _stringGuids: {},
358   _keyCache: {},
359   _uuid: 0,
360 
361   /**"
362     Returns a unique GUID for the object.  If the object does not yet have
363     a guid, one will be assigned to it.  You can call this on any object,
364     SC.Object-based or not, but be aware that it will add a _guid property.
365 
366     You can also use this method on DOM Element objects.
367 
368     @param {Object} obj any object, string, number, Element, or primitive
369     @returns {String} the unique guid for this instance.
370   */
371   guidFor: function (obj) {
372     var cache, ret,
373         type = typeof obj;
374 
375     // special cases where we don't want to add a key to object
376     if (obj === undefined) return "(undefined)";
377     if (obj === null) return "(null)";
378 
379     // Don't allow prototype changes to String etc. to change the guidFor
380     if (type === SC.T_NUMBER || type === SC.T_STRING) {
381       cache = this._guidCaches[type];
382       ret   = cache[obj];
383       if (!ret) {
384         ret        = "st" + (SC._uuid++);
385         cache[obj] = ret;
386       }
387       return ret;
388     } else if (type === SC.T_BOOL) {
389       return (obj) ? "(true)" : "(false)";
390     }
391 
392     var guidKey = this.guidKey;
393     if (obj[guidKey]) return obj[guidKey];
394 
395     // More special cases; not as common, so we check for them after the cache
396     // lookup
397     if (obj === Object) return '(Object)';
398     if (obj === Array) return '(Array)';
399 
400     return SC.generateGuid(obj, "sc");
401   },
402 
403   /**
404     Returns a key name that combines the named key + prefix.  This is more
405     efficient than simply combining strings because it uses a cache
406     internally for performance.
407 
408     @param {String} prefix the prefix to attach to the key
409     @param {String} key The key
410     @returns {String} result
411   */
412   keyFor: function (prefix, key) {
413     var ret, pcache = this._keyCache[prefix];
414     if (!pcache) pcache = this._keyCache[prefix] = {}; // get cache for prefix
415     ret = pcache[key];
416     if (!ret) ret = pcache[key] = prefix + '_' + key;
417     return ret;
418   },
419 
420   /**
421     Generates a new guid, optionally saving the guid to the object that you
422     pass in.  You will rarely need to use this method.  Instead you should
423     call SC.guidFor(obj), which return an existing guid if available.
424 
425     @param {Object} obj the object to assign the guid to
426     @param {String} prefix prefixes the generated guid
427     @returns {String} the guid
428   */
429   generateGuid: function (obj, prefix) {
430     var ret = (prefix + (SC._uuid++));
431     if (obj) obj[this.guidKey] = ret;
432     return ret;
433   },
434 
435   /**
436     Returns a unique hash code for the object. If the object implements
437     a hash() method, the value of that method will be returned. Otherwise,
438     this will return the same value as guidFor().
439 
440     If you pass multiple arguments, hashFor returns a string obtained by
441     concatenating the hash code of each argument.
442 
443     Unlike guidFor(), this method allows you to implement logic in your
444     code to cause two separate instances of the same object to be treated as
445     if they were equal for comparisons and other functions.
446 
447     <b>IMPORTANT</b>: If you implement a hash() method, it MUST NOT return a
448     number or a string that contains only a number. Typically hash codes
449     are strings that begin with a "%".
450 
451     @param {Object...} objects the object(s)
452     @returns {String} the hash code for this instance.
453   */
454   hashFor: function () {
455     var l = arguments.length,
456       h = '',
457       obj, f, i;
458 
459     for (i = 0; i < l; ++i) {
460       obj = arguments[i];
461       h += (obj && (f = obj.hash) && (typeof f === SC.T_FUNCTION)) ? f.call(obj) : this.guidFor(obj);
462     }
463 
464     return h === '' ? null : h;
465   },
466 
467   /**
468     This will compare the two object values using their hash codes.
469 
470     @param {Object} a first value to compare
471     @param {Object} b the second value to compare
472     @returns {Boolean} YES if the two have equal hash code values.
473 
474   */
475   isEqual: function (a, b) {
476     // QUESTION: is there a compelling performance reason to special-case
477     // undefined here?
478     return this.hashFor(a) === this.hashFor(b);
479   },
480 
481   /**
482    This will compare two javascript values of possibly different types.
483    It will tell you which one is greater than the other by returning
484    -1 if the first is smaller than the second,
485     0 if both are equal,
486     1 if the first is greater than the second.
487 
488    The order is calculated based on SC.ORDER_DEFINITION , if types are different.
489    In case they have the same type an appropriate comparison for this type is made.
490 
491    @param {Object} v first value to compare
492    @param {Object} w the second value to compare
493    @returns {NUMBER} -1 if v < w, 0 if v = w and 1 if v > w.
494 
495   */
496   compare: function (v, w) {
497     // Doing a '===' check is very cheap, so in the case of equality, checking
498     // this up-front is a big win.
499     if (v === w) return 0;
500 
501     var type1 = SC.typeOf(v);
502     var type2 = SC.typeOf(w);
503 
504     // If we haven't yet generated a reverse-mapping of SC.ORDER_DEFINITION,
505     // do so now.
506     var mapping = SC.ORDER_DEFINITION_MAPPING;
507     if (!mapping) {
508       var order = SC.ORDER_DEFINITION;
509       mapping = SC.ORDER_DEFINITION_MAPPING = {};
510       var idx, len;
511       for (idx = 0, len = order.length;  idx < len;  ++idx) {
512         mapping[order[idx]] = idx;
513       }
514 
515       // We no longer need SC.ORDER_DEFINITION.
516       delete SC.ORDER_DEFINITION;
517     }
518 
519     var type1Index = mapping[type1];
520     var type2Index = mapping[type2];
521 
522     if (type1Index < type2Index) return -1;
523     if (type1Index > type2Index) return 1;
524 
525     // ok - types are equal - so we have to check values now
526     switch (type1) {
527     case SC.T_BOOL:
528     case SC.T_NUMBER:
529       if (v < w) return -1;
530       if (v > w) return 1;
531       return 0;
532 
533     case SC.T_STRING:
534       var comp = v.localeCompare(w);
535       if (comp < 0) return -1;
536       if (comp > 0) return 1;
537       return 0;
538 
539     case SC.T_ARRAY:
540       var vLen = v.length;
541       var wLen = w.length;
542       var l = Math.min(vLen, wLen);
543       var r = 0;
544       var i = 0;
545       var thisFunc = arguments.callee;
546       while (r === 0 && i < l) {
547         r = thisFunc(v[i], w[i]);
548         i++;
549       }
550       if (r !== 0) return r;
551 
552       // all elements are equal now
553       // shorter array should be ordered first
554       if (vLen < wLen) return -1;
555       if (vLen > wLen) return 1;
556       // arrays are equal now
557       return 0;
558 
559     case SC.T_OBJECT:
560       if (v.constructor.isComparable === YES) return v.constructor.compare(v, w);
561       return 0;
562 
563     default:
564       return 0;
565     }
566   },
567 
568   // ..........................................................
569   // OBJECT MANAGEMENT
570   //
571 
572   /**
573     Empty function.  Useful for some operations.
574 
575     @returns {Object}
576   */
577   K: function () { return this; },
578 
579   /**
580     Empty array.  Useful for some optimizations.
581 
582     @type Array
583   */
584   EMPTY_ARRAY: [],
585 
586   /**
587     Empty hash.  Useful for some optimizations.
588 
589     @type Hash
590   */
591   EMPTY_HASH: {},
592 
593   /**
594     Empty range. Useful for some optimizations.
595 
596     @type Range
597   */
598   EMPTY_RANGE: {start: 0, length: 0},
599 
600   /**
601     Creates a new object with the passed object as its prototype.
602 
603     This method uses JavaScript's native inheritance method to create a new
604     object.
605 
606     You cannot use beget() to create new SC.Object-based objects, but you
607     can use it to beget Arrays, Hashes, Sets and objects you build yourself.
608     Note that when you beget() a new object, this method will also call the
609     didBeget() method on the object you passed in if it is defined.  You can
610     use this method to perform any other setup needed.
611 
612     In general, you will not use beget() often as SC.Object is much more
613     useful, but for certain rare algorithms, this method can be very useful.
614 
615     For more information on using beget(), see the section on beget() in
616     Crockford's JavaScript: The Good Parts.
617 
618     @param {Object} obj the object to beget
619     @returns {Object} the new object.
620   */
621   beget: function (obj) {
622     if (obj === null || obj === undefined) return null;
623     var K = SC.K;
624     K.prototype = obj;
625     var ret = new K();
626     K.prototype = null; // avoid leaks
627     if (typeof obj.didBeget === "function") ret = obj.didBeget(ret);
628     return ret;
629   },
630 
631   /**
632     Creates a clone of the passed object.  This function can take just about
633     any type of object and create a clone of it, including primitive values
634     (which are not actually cloned because they are immutable).
635 
636     If the passed object implements the clone() method, then this function
637     will simply call that method and return the result.
638 
639     @param {Object} object the object to clone
640     @param {Boolean} deep if true, a deep copy of the object is made
641     @returns {Object} the cloned object
642   */
643   copy: function (object, deep) {
644     var ret = object, idx;
645 
646     // fast paths
647     if (object) {
648       if (object.isCopyable) return object.copy(deep);
649       if (object.clone)      return object.clone();
650     }
651 
652     switch (SC._nativeTypeOf(object)) {
653     case "array":
654       ret = object.slice();
655 
656       if (deep) {
657         idx = ret.length;
658         while (idx--) { ret[idx] = SC.copy(ret[idx], true); }
659       }
660       break;
661 
662     case "object":
663       ret = {};
664       for (var key in object) { ret[key] = deep ? SC.copy(object[key], true) : object[key]; }
665     }
666 
667     return ret;
668   },
669 
670   /**
671     Returns a new object combining the values of all passed hashes.
672 
673     @param {Object...} object one or more objects
674     @returns {Object} new Object
675   */
676   merge: function () {
677     var ret = {}, len = arguments.length, idx;
678     for (idx = 0; idx < len; idx++) SC.mixin(ret, arguments[idx]);
679     return ret;
680   },
681 
682   /**
683     Returns all of the keys defined on an object or hash.  This is useful
684     when inspecting objects for debugging.
685 
686     @param {Object} obj The Object
687     @returns {Array} array of keys
688   */
689   keys: function (obj) {
690     var ret = [];
691     for (var key in obj) ret.push(key);
692     return ret;
693   },
694 
695   /**
696     Convenience method to inspect an object.  This method will attempt to
697     convert the object into a useful string description.
698 
699     @param {Object} obj The object you want to inspect.
700 
701     @returns {String} A description of the object
702   */
703   inspect: function (obj) {
704     var v, ret = [];
705     for (var key in obj) {
706       v = obj[key];
707       if (v === 'toString') continue; // ignore useless items
708       if (SC.typeOf(v) === SC.T_FUNCTION) v = "function () { ... }";
709       ret.push(key + ": " + v);
710     }
711     return "{ " + ret.join(", ") + " }";
712   },
713 
714   /**
715     Returns a tuple containing the object and key for the specified property
716     path.  If no object could be found to match the property path, then
717     returns null.
718 
719     This is the standard method used throughout SproutCore to resolve property
720     paths.
721 
722     @param {String} path the property path
723     @param {Object} root optional parameter specifying the place to start
724     @returns {Array} array with [object, property] if found or null
725   */
726   tupleForPropertyPath: function (path, root) {
727     /* jshint eqnull:true */
728     // if passed nothing, return nothing.
729     if (path == null) return null;
730 
731     // if the passed path is itself a tuple, return it
732     if (typeof path === "object" && (path instanceof Array)) return path;
733 
734     // find the key.  It is the last . or first *
735     var key;
736     var stopAt = path.indexOf('*');
737     if (stopAt < 0) stopAt = path.lastIndexOf('.');
738     key = (stopAt >= 0) ? path.slice(stopAt + 1) : path;
739 
740     // convert path to object.
741     var obj = this.objectForPropertyPath(path, root, stopAt);
742     return (obj && key) ? [obj, key] : null;
743   },
744 
745   /**
746     Finds the object for the passed path or array of path components.  This is
747     the standard method used in SproutCore to traverse object paths.
748 
749     @param {String} path the path
750     @param {Object} root optional root object.  window is used otherwise
751     @param {Integer} stopAt optional point to stop searching the path.
752     @returns {Object} the found object or undefined.
753   */
754   objectForPropertyPath: function (path, root, stopAt) {
755 
756     var loc, nextDotAt, key, max;
757 
758     if (!root) root = window;
759 
760     // faster method for strings
761     if (typeof path === "string") {
762       if (stopAt === undefined) stopAt = path.length;
763       loc = 0;
764       while ((root) && (loc < stopAt)) {
765         nextDotAt = path.indexOf('.', loc);
766         if ((nextDotAt < 0) || (nextDotAt > stopAt)) nextDotAt = stopAt;
767         key = path.slice(loc, nextDotAt);
768         root = root.get ? root.get(key) : root[key];
769         loc = nextDotAt + 1;
770       }
771       if (loc < stopAt) root = undefined; // hit a dead end. :(
772 
773     // older method using an array
774     } else {
775       loc = 0;
776       max = path.length;
777       key = null;
778 
779       while ((loc < max) && root) {
780         key = path[loc++];
781         if (key) root = (root.get) ? root.get(key) : root[key];
782       }
783       if (loc < max) root = undefined;
784     }
785 
786     return root;
787   },
788 
789   /**
790    Acts very similar to SC.objectForPropertyPath(), the only difference is
791    that it will throw an error when object can't be found.
792 
793     @param {String} path the path
794     @param {Object} root optional root object.  window is used otherwise
795     @param {Integer} stopAt optional point to stop searching the path.
796     @returns {Object} the found object or throws an error.
797   */
798   requiredObjectForPropertyPath: function (path, root, stopAt) {
799     var o = SC.objectForPropertyPath(path, root, stopAt);
800     if (!o) {
801       throw path + " could not be found";
802     }
803     return o;
804   }
805 
806 }); // end mixin
807 
808 /** @private Alias for SC.clone() */
809 SC.clone = SC.copy;
810 
811 /** @private Alias for SC.A() */
812 SC.$A = SC.A;
813 
814 /** @private Provided for compatibility with old HTML templates. */
815 SC.didLoad = SC.K;
816 
817 /** @private Used by SC.compare */
818 SC.ORDER_DEFINITION = [ SC.T_ERROR,
819                         SC.T_UNDEFINED,
820                         SC.T_NULL,
821                         SC.T_BOOL,
822                         SC.T_NUMBER,
823                         SC.T_STRING,
824                         SC.T_ARRAY,
825                         SC.T_HASH,
826                         SC.T_OBJECT,
827                         SC.T_FUNCTION,
828                         SC.T_CLASS ];
829