1 | // ==========================================================================
|
2 | // Project: SproutCore - JavaScript Application Framework
|
3 | // Copyright: ©2006-2011 Strobe Inc. and contributors.
|
4 | // Portions ©2008-2010 Apple Inc. All rights reserved.
|
5 | // License: Licensed under MIT license (see license.js)
|
6 | // ==========================================================================
|
7 | /*globals global */
|
8 |
|
9 | // Makes us compatible with Node.js
|
10 | if (window.global === undefined) window.global = window;
|
11 |
|
12 | // These commands are used by the build tools to control load order. On the
|
13 | // client side these are a no-op.
|
14 | var require = require || function require() { } ;
|
15 | var sc_require = sc_require || require;
|
16 | var sc_resource = sc_resource || function sc_resource() {};
|
17 |
|
18 | var sc_assert = function(assertion, msg) {
|
19 | if (!assertion) throw msg || "sc_assert()";
|
20 | };
|
21 |
|
22 | // ........................................
|
23 | // GLOBAL CONSTANTS
|
24 | //
|
25 | // Most global constants should be defined inside of the SC namespace.
|
26 | // However the following two are useful enough and generally benign enough
|
27 | // to put into the global object.
|
28 | var YES = true ;
|
29 | var NO = false ;
|
30 |
|
31 | // prevent a console.log from blowing things up if we are on a browser that
|
32 | // does not support it
|
33 | if (typeof console === 'undefined') {
|
34 | window.console = {} ;
|
35 | console.log = console.info = console.warn = console.error = function(){};
|
36 | }
|
37 |
|
38 | // ........................................
|
39 | // BOOTSTRAP
|
40 | //
|
41 | // The root namespace and some common utility methods are defined here. The
|
42 | // rest of the methods go into the mixin defined below.
|
43 |
|
44 | /**
|
45 | @version 1.4.5
|
46 | @namespace
|
47 |
|
48 | The SproutCore namespace. All SproutCore methods and functions are defined
|
49 | inside of this namespace. You generally should not add new properties to
|
50 | this namespace as it may be overwritten by future versions of SproutCore.
|
51 |
|
52 | You can also use the shorthand "SC" instead of "SproutCore".
|
53 |
|
54 | SproutCore-Base is a framework that provides core functions for SproutCore
|
55 | including cross-platform functions, support for property observing and
|
56 | objects. It's focus is on small size and performance. You can use this
|
57 | in place of or along-side other cross-platform libraries such as jQuery or
|
58 | Prototype.
|
59 |
|
60 | The core Base framework is based on the jQuery API with a number of
|
61 | performance optimizations.
|
62 | */
|
63 | var SC = global.SC || {} ;
|
64 | var SproutCore = SproutCore || SC ;
|
65 |
|
66 | SC.VERSION = '1.4.5';
|
67 |
|
68 | /**
|
69 | @private
|
70 |
|
71 | Adds properties to a target object. You must specify whether
|
72 | to overwrite a value for a property or not.
|
73 |
|
74 | Used as a base function for the wrapper functions SC.mixin and SC.supplement.
|
75 |
|
76 | @param overwrite {Boolean} if a target has a value for a property, this specifies
|
77 | whether or not to overwrite that value with the copyied object's
|
78 | property value.
|
79 | @param target {Object} the target object to extend
|
80 | @param properties {Object} one or more objects with properties to copy.
|
81 | @returns {Object} the target object.
|
82 | @static
|
83 | */
|
84 | SC._baseMixin = function (override) {
|
85 | var args = Array.prototype.slice.call(arguments, 1), src,
|
86 | // copy reference to target object
|
87 | target = args[0] || {},
|
88 | idx = 1,
|
89 | length = args.length ,
|
90 | options, copy , key;
|
91 |
|
92 | // Handle case where we have only one item...extend SC
|
93 | if (length === 1) {
|
94 | target = this || {};
|
95 | idx=0;
|
96 | }
|
97 |
|
98 | for ( ; idx < length; idx++ ) {
|
99 | if (!(options = args[idx])) continue ;
|
100 | for(key in options) {
|
101 | if (!options.hasOwnProperty(key)) continue ;
|
102 | copy = options[key] ;
|
103 | if (target===copy) continue ; // prevent never-ending loop
|
104 | if (copy !== undefined && ( override || (target[key] === undefined) )) target[key] = copy ;
|
105 | }
|
106 | }
|
107 |
|
108 | return target;
|
109 | } ;
|
110 |
|
111 | /**
|
112 | Adds properties to a target object.
|
113 |
|
114 | Takes the root object and adds the attributes for any additional
|
115 | arguments passed.
|
116 |
|
117 | @param target {Object} the target object to extend
|
118 | @param properties {Object} one or more objects with properties to copy.
|
119 | @returns {Object} the target object.
|
120 | @static
|
121 | */
|
122 | SC.mixin = function() {
|
123 | var args = Array.prototype.slice.call(arguments);
|
124 | args.unshift(true);
|
125 | return SC._baseMixin.apply(this, args);
|
126 | } ;
|
127 |
|
128 | /**
|
129 | Adds properties to a target object. Unlike SC.mixin, however, if the target
|
130 | already has a value for a property, it will not be overwritten.
|
131 |
|
132 | Takes the root object and adds the attributes for any additional
|
133 | arguments passed.
|
134 |
|
135 | @param target {Object} the target object to extend
|
136 | @param properties {Object} one or more objects with properties to copy.
|
137 | @returns {Object} the target object.
|
138 | @static
|
139 | */
|
140 | SC.supplement = function() {
|
141 | var args = Array.prototype.slice.call(arguments);
|
142 | args.unshift(false);
|
143 | return SC._baseMixin.apply(this, args);
|
144 | } ;
|
145 |
|
146 | // ..........................................................
|
147 | // CORE FUNCTIONS
|
148 | //
|
149 | // Enough with the bootstrap code. Let's define some core functions
|
150 |
|
151 | SC.mixin(/** @scope SC */ {
|
152 |
|
153 | // ........................................
|
154 | // GLOBAL CONSTANTS
|
155 | //
|
156 | T_ERROR: 'error',
|
157 | T_OBJECT: 'object',
|
158 | T_NULL: 'null',
|
159 | T_CLASS: 'class',
|
160 | T_HASH: 'hash',
|
161 | T_FUNCTION: 'function',
|
162 | T_UNDEFINED: 'undefined',
|
163 | T_NUMBER: 'number',
|
164 | T_BOOL: 'boolean',
|
165 | T_ARRAY: 'array',
|
166 | T_STRING: 'string',
|
167 |
|
168 | // ........................................
|
169 | // TYPING & ARRAY MESSAGING
|
170 | //
|
171 |
|
172 | /**
|
173 | Returns a consistant type for the passed item.
|
174 |
|
175 | Use this instead of the built-in typeOf() to get the type of an item.
|
176 | It will return the same result across all browsers and includes a bit
|
177 | more detail. Here is what will be returned:
|
178 |
|
179 | | Return Value Constant | Meaning |
|
180 | | SC.T_STRING | String primitive |
|
181 | | SC.T_NUMBER | Number primitive |
|
182 | | SC.T_BOOLEAN | Boolean primitive |
|
183 | | SC.T_NULL | Null value |
|
184 | | SC.T_UNDEFINED | Undefined value |
|
185 | | SC.T_FUNCTION | A function |
|
186 | | SC.T_ARRAY | An instance of Array |
|
187 | | SC.T_CLASS | A SproutCore class (created using SC.Object.extend()) |
|
188 | | SC.T_OBJECT | A SproutCore object instance |
|
189 | | SC.T_HASH | A JavaScript object not inheriting from SC.Object |
|
190 |
|
191 | @param item {Object} the item to check
|
192 | @returns {String} the type
|
193 | */
|
194 | typeOf: function(item) {
|
195 | if (item === undefined) return SC.T_UNDEFINED ;
|
196 | if (item === null) return SC.T_NULL ;
|
197 | var ret = typeof(item) ;
|
198 | if (ret == "object") {
|
199 | if (item instanceof Array) {
|
200 | ret = SC.T_ARRAY ;
|
201 | } else if (item instanceof Function) {
|
202 | ret = item.isClass ? SC.T_CLASS : SC.T_FUNCTION ;
|
203 |
|
204 | // NB: typeOf() may be called before SC.Error has had a chance to load
|
205 | // so this code checks for the presence of SC.Error first just to make
|
206 | // sure. No error instance can exist before the class loads anyway so
|
207 | // this is safe.
|
208 | } else if (SC.Error && (item instanceof SC.Error)) {
|
209 | ret = SC.T_ERROR ;
|
210 | } else if (item instanceof SC.Object) {
|
211 | ret = SC.T_OBJECT ;
|
212 | } else ret = SC.T_HASH ;
|
213 | } else if (ret === SC.T_FUNCTION) ret = (item.isClass) ? SC.T_CLASS : SC.T_FUNCTION;
|
214 | return ret ;
|
215 | },
|
216 |
|
217 | /**
|
218 | Returns YES if the passed value is null or undefined. This avoids errors
|
219 | from JSLint complaining about use of ==, which can be technically
|
220 | confusing.
|
221 |
|
222 | @param {Object} obj value to test
|
223 | @returns {Boolean}
|
224 | */
|
225 | none: function(obj) {
|
226 | return obj===null || obj===undefined;
|
227 | },
|
228 |
|
229 | /**
|
230 | Verifies that a value is either null or an empty string. Return false if
|
231 | the object is not a string.
|
232 |
|
233 | @param {Object} obj value to test
|
234 | @returns {Boolean}
|
235 | */
|
236 | empty: function(obj) {
|
237 | return obj===null || obj===undefined || obj==='';
|
238 | },
|
239 |
|
240 | /**
|
241 | Returns YES if the passed object is an array or array-like. Instances
|
242 | of the NodeList class return NO.
|
243 |
|
244 | Unlike SC.typeOf this method returns true even if the passed object is
|
245 | not formally array but appears to be array-like (i.e. has a length
|
246 | property, responds to .objectAt, etc.)
|
247 |
|
248 | @param obj {Object} the object to test
|
249 | @returns {Boolean}
|
250 | */
|
251 | isArray: function(obj) {
|
252 | if (obj && obj.objectAt) return YES ; // fast path
|
253 |
|
254 | var len = (obj ? obj.length : null), type = typeof obj;
|
255 | return !((len === undefined) || (len === null) || (obj instanceof Function) || (type === "string") || obj.setInterval);
|
256 | },
|
257 |
|
258 | /**
|
259 | Makes an object into an Array if it is not array or array-like already.
|
260 | Unlike SC.A(), this method will not clone the object if it is already
|
261 | an array.
|
262 |
|
263 | @param {Object} obj object to convert
|
264 | @returns {Array} Actual array
|
265 | */
|
266 | makeArray: function(obj) {
|
267 | return SC.isArray(obj) ? obj : SC.A(obj);
|
268 | },
|
269 |
|
270 | /**
|
271 | Converts the passed object to an Array. If the object appears to be
|
272 | array-like, a new array will be cloned from it. Otherwise, a new array
|
273 | will be created with the item itself as the only item in the array.
|
274 |
|
275 | @param object {Object} any enumerable or array-like object.
|
276 | @returns {Array} Array of items
|
277 | */
|
278 | A: function(obj) {
|
279 | // null or undefined -- fast path
|
280 | if (obj === null || obj === undefined) return [] ;
|
281 |
|
282 | // primitive -- fast path
|
283 | if (obj.slice instanceof Function) {
|
284 | // do we have a string?
|
285 | if (typeof(obj) === 'string') return [obj] ;
|
286 | else return obj.slice() ;
|
287 | }
|
288 |
|
289 | // enumerable -- fast path
|
290 | if (obj.toArray) return obj.toArray() ;
|
291 |
|
292 | // if not array-like, then just wrap in array.
|
293 | if (!SC.isArray(obj)) return [obj];
|
294 |
|
295 | // when all else fails, do a manual convert...
|
296 | var ret = [], len = obj.length;
|
297 | while(--len >= 0) ret[len] = obj[len];
|
298 | return ret ;
|
299 | },
|
300 |
|
301 | // ..........................................................
|
302 | // GUIDS & HASHES
|
303 | //
|
304 |
|
305 | guidKey: "_sc_guid_" + new Date().getTime(),
|
306 |
|
307 | // Used for guid generation...
|
308 | _nextGUID: 0, _numberGuids: [], _stringGuids: {}, _keyCache: {},
|
309 |
|
310 | /**
|
311 | Returns a unique GUID for the object. If the object does not yet have
|
312 | a guid, one will be assigned to it. You can call this on any object,
|
313 | SC.Object-based or not, but be aware that it will add a _guid property.
|
314 |
|
315 | You can also use this method on DOM Element objects.
|
316 |
|
317 | @param obj {Object} any object, string, number, Element, or primitive
|
318 | @returns {String} the unique guid for this instance.
|
319 | */
|
320 | guidFor: function(obj) {
|
321 |
|
322 | // special cases where we don't want to add a key to object
|
323 | if (obj === undefined) return "(undefined)";
|
324 | if (obj === null) return '(null)';
|
325 |
|
326 | var guidKey = this.guidKey;
|
327 | if (obj[guidKey]) return obj[guidKey];
|
328 |
|
329 | // More special cases; not as common, so we check for them after the cache
|
330 | // lookup
|
331 | if (obj === Object) return '(Object)';
|
332 | if (obj === Array) return '(Array)';
|
333 |
|
334 | var cache, ret;
|
335 |
|
336 | switch(typeof obj) {
|
337 | case SC.T_NUMBER:
|
338 | cache = this._numberGuids;
|
339 | ret = cache[obj];
|
340 | if (!ret) {
|
341 | ret = "nu" + obj;
|
342 | cache[obj] = ret;
|
343 | }
|
344 | return ret;
|
345 |
|
346 | case SC.T_STRING:
|
347 | cache = this._stringGuids;
|
348 | ret = cache[obj];
|
349 | if (!ret) {
|
350 | ret = "st" + obj;
|
351 | cache[obj] = ret;
|
352 | }
|
353 | return ret;
|
354 |
|
355 | case SC.T_BOOL:
|
356 | return (obj) ? "(true)" : "(false)" ;
|
357 | default:
|
358 | return SC.generateGuid(obj);
|
359 | }
|
360 | },
|
361 |
|
362 | /**
|
363 | Returns a key name that combines the named key + prefix. This is more
|
364 | efficient than simply combining strings because it uses a cache
|
365 | internally for performance.
|
366 |
|
367 | @param {String} prefix the prefix to attach to the key
|
368 | @param {String} key key
|
369 | @returns {String} result
|
370 | */
|
371 | keyFor: function(prefix, key) {
|
372 | var ret, pcache = this._keyCache[prefix];
|
373 | if (!pcache) pcache = this._keyCache[prefix] = {}; // get cache for prefix
|
374 | ret = pcache[key];
|
375 | if (!ret) ret = pcache[key] = prefix + '_' + key ;
|
376 | return ret ;
|
377 | },
|
378 |
|
379 | /**
|
380 | Generates a new guid, optionally saving the guid to the object that you
|
381 | pass in. You will rarely need to use this method. Instead you should
|
382 | call SC.guidFor(obj), which return an existing guid if available.
|
383 |
|
384 | @param {Object} obj the object to assign the guid to
|
385 | @returns {String} the guid
|
386 | */
|
387 | generateGuid: function(obj) {
|
388 | var ret = ("sc" + (this._nextGUID++));
|
389 | if (obj) obj[this.guidKey] = ret ;
|
390 | return ret ;
|
391 | },
|
392 |
|
393 | /**
|
394 | Returns a unique hash code for the object. If the object implements
|
395 | a hash() method, the value of that method will be returned. Otherwise,
|
396 | this will return the same value as guidFor().
|
397 |
|
398 | If you pass multiple arguments, hashFor returns a string obtained by
|
399 | concatenating the hash code of each argument.
|
400 |
|
401 | Unlike guidFor(), this method allows you to implement logic in your
|
402 | code to cause two separate instances of the same object to be treated as
|
403 | if they were equal for comparisons and other functions.
|
404 |
|
405 | IMPORTANT: If you implement a hash() method, it MUST NOT return a
|
406 | number or a string that contains only a number. Typically hash codes
|
407 | are strings that begin with a "%".
|
408 |
|
409 | @param obj {Object} the object(s)
|
410 | @returns {String} the hash code for this instance.
|
411 | */
|
412 | hashFor: function() {
|
413 | var len = arguments.length,
|
414 | h = '',
|
415 | obj, f, i;
|
416 |
|
417 | for (i = 0; i < len; ++i) {
|
418 | obj = arguments[i];
|
419 | h += (obj && (f = obj.hash) && (typeof f === SC.T_FUNCTION)) ? f.call(obj) : this.guidFor(obj);
|
420 | }
|
421 |
|
422 | return h === '' ? null : h;
|
423 | },
|
424 |
|
425 | /**
|
426 | This will compare the two object values using their hash codes.
|
427 |
|
428 | @param a {Object} first value to compare
|
429 | @param b {Object} the second value to compare
|
430 | @returns {Boolean} YES if the two have equal hash code values.
|
431 |
|
432 | */
|
433 | isEqual: function(a,b) {
|
434 | // shortcut a few places.
|
435 | if (a === null) {
|
436 | return b === null ;
|
437 | } else if (a === undefined) {
|
438 | return b === undefined ;
|
439 |
|
440 | // finally, check their hash-codes
|
441 | } else return this.hashFor(a) === this.hashFor(b) ;
|
442 | },
|
443 |
|
444 |
|
445 | /**
|
446 | This will compare two javascript values of possibly different types.
|
447 | It will tell you which one is greater than the other by returning
|
448 | -1 if the first is smaller than the second,
|
449 | 0 if both are equal,
|
450 | 1 if the first is greater than the second.
|
451 |
|
452 | The order is calculated based on SC.ORDER_DEFINITION , if types are different.
|
453 | In case they have the same type an appropriate comparison for this type is made.
|
454 |
|
455 | @param v {Object} first value to compare
|
456 | @param w {Object} the second value to compare
|
457 | @returns {NUMBER} -1 if v < w, 0 if v = w and 1 if v > w.
|
458 |
|
459 | */
|
460 | compare: function (v, w) {
|
461 | // Doing a '===' check is very cheap, so in the case of equality, checking
|
462 | // this up-front is a big win.
|
463 | if (v === w) return 0;
|
464 |
|
465 | var type1 = SC.typeOf(v);
|
466 | var type2 = SC.typeOf(w);
|
467 |
|
468 | // If we haven't yet generated a reverse-mapping of SC.ORDER_DEFINITION,
|
469 | // do so now.
|
470 | var mapping = SC.ORDER_DEFINITION_MAPPING;
|
471 | if (!mapping) {
|
472 | var order = SC.ORDER_DEFINITION;
|
473 | mapping = SC.ORDER_DEFINITION_MAPPING = {};
|
474 | var idx, len;
|
475 | for (idx = 0, len = order.length; idx < len; ++idx) {
|
476 | mapping[order[idx]] = idx;
|
477 | }
|
478 |
|
479 | // We no longer need SC.ORDER_DEFINITION.
|
480 | delete SC.ORDER_DEFINITION;
|
481 | }
|
482 |
|
483 | var type1Index = mapping[type1];
|
484 | var type2Index = mapping[type2];
|
485 |
|
486 | if (type1Index < type2Index) return -1;
|
487 | if (type1Index > type2Index) return 1;
|
488 |
|
489 | // ok - types are equal - so we have to check values now
|
490 | switch (type1) {
|
491 | case SC.T_BOOL:
|
492 | case SC.T_NUMBER:
|
493 | if (v<w) return -1;
|
494 | if (v>w) return 1;
|
495 | return 0;
|
496 |
|
497 | case SC.T_STRING:
|
498 | var comp = v.localeCompare(w);
|
499 | if (comp<0) return -1;
|
500 | if (comp>0) return 1;
|
501 | return 0;
|
502 |
|
503 | case SC.T_ARRAY:
|
504 | var vLen = v.length;
|
505 | var wLen = w.length;
|
506 | var l = Math.min(vLen, wLen);
|
507 | var r = 0;
|
508 | var i = 0;
|
509 | var thisFunc = arguments.callee;
|
510 | while (r===0 && i < l) {
|
511 | r = thisFunc(v[i],w[i]);
|
512 | i++;
|
513 | }
|
514 | if (r !== 0) return r;
|
515 |
|
516 | // all elements are equal now
|
517 | // shorter array should be ordered first
|
518 | if (vLen < wLen) return -1;
|
519 | if (vLen > wLen) return 1;
|
520 | // arrays are equal now
|
521 | return 0;
|
522 |
|
523 | case SC.T_OBJECT:
|
524 | if (v.constructor.isComparable === YES) return v.constructor.compare(v, w);
|
525 | return 0;
|
526 |
|
527 | default:
|
528 | return 0;
|
529 | }
|
530 | },
|
531 |
|
532 | // ..........................................................
|
533 | // OBJECT MANAGEMENT
|
534 |
|
535 | /**
|
536 | Empty function. Useful for some operations.
|
537 |
|
538 | @returns {Object}
|
539 | */
|
540 | K: function() { return this; },
|
541 |
|
542 | /**
|
543 | Empty array. Useful for some optimizations.
|
544 |
|
545 | @property {Array}
|
546 | */
|
547 | EMPTY_ARRAY: [],
|
548 |
|
549 | /**
|
550 | Empty hash. Useful for some optimizations.
|
551 |
|
552 | @property {Hash}
|
553 | */
|
554 | EMPTY_HASH: {},
|
555 |
|
556 | /**
|
557 | Empty range. Useful for some optimizations.
|
558 |
|
559 | @property {Range}
|
560 | */
|
561 | EMPTY_RANGE: {start: 0, length: 0},
|
562 |
|
563 | /**
|
564 | Creates a new object with the passed object as its prototype.
|
565 |
|
566 | This method uses JavaScript's native inheritence method to create a new
|
567 | object.
|
568 |
|
569 | You cannot use beget() to create new SC.Object-based objects, but you
|
570 | can use it to beget Arrays, Hashes, Sets and objects you build yourself.
|
571 | Note that when you beget() a new object, this method will also call the
|
572 | didBeget() method on the object you passed in if it is defined. You can
|
573 | use this method to perform any other setup needed.
|
574 |
|
575 | In general, you will not use beget() often as SC.Object is much more
|
576 | useful, but for certain rare algorithms, this method can be very useful.
|
577 |
|
578 | For more information on using beget(), see the section on beget() in
|
579 | Crockford's JavaScript: The Good Parts.
|
580 |
|
581 | @param obj {Object} the object to beget
|
582 | @returns {Object} the new object.
|
583 | */
|
584 | beget: function(obj) {
|
585 | if (obj === null || obj === undefined) return null ;
|
586 | var K = SC.K; K.prototype = obj ;
|
587 | var ret = new K();
|
588 | K.prototype = null ; // avoid leaks
|
589 | if (typeof obj.didBeget === "function") ret = obj.didBeget(ret);
|
590 | return ret ;
|
591 | },
|
592 |
|
593 | /**
|
594 | Creates a clone of the passed object. This function can take just about
|
595 | any type of object and create a clone of it, including primitive values
|
596 | (which are not actually cloned because they are immutable).
|
597 |
|
598 | If the passed object implements the clone() method, then this function
|
599 | will simply call that method and return the result.
|
600 |
|
601 | @param object {Object} the object to clone
|
602 | @param deep {Boolean} if true, a deep copy of the object is made
|
603 | @returns {Object} the cloned object
|
604 | */
|
605 | copy: function(object, deep) {
|
606 | var ret = object, idx ;
|
607 |
|
608 | // fast path
|
609 | if (object) {
|
610 | if (object.isCopyable) return object.copy(deep);
|
611 | if (object.clone && SC.typeOf(object.clone) === SC.T_FUNCTION) return object.clone();
|
612 | }
|
613 |
|
614 | switch (SC.typeOf(object)) {
|
615 | case SC.T_ARRAY:
|
616 | ret = object.slice() ;
|
617 | if (deep) {
|
618 | idx = ret.length;
|
619 | while (idx--) ret[idx] = SC.copy(ret[idx], true);
|
620 | }
|
621 | break ;
|
622 |
|
623 | case SC.T_HASH:
|
624 | case SC.T_OBJECT:
|
625 | ret = {};
|
626 | for (var key in object) ret[key] = deep ? SC.copy(object[key], true) : object[key];
|
627 | break ;
|
628 | }
|
629 |
|
630 | return ret ;
|
631 | },
|
632 |
|
633 | /**
|
634 | Returns a new object combining the values of all passed hashes.
|
635 |
|
636 | @param object {Object} one or more objects
|
637 | @returns {Object} new Object
|
638 | */
|
639 | merge: function() {
|
640 | var ret = {}, len = arguments.length, idx;
|
641 | for(idx=0;idx<len;idx++) SC.mixin(ret, arguments[idx]);
|
642 | return ret ;
|
643 | },
|
644 |
|
645 | /**
|
646 | Returns all of the keys defined on an object or hash. This is useful
|
647 | when inspecting objects for debugging.
|
648 |
|
649 | @param {Object} obj
|
650 | @returns {Array} array of keys
|
651 | */
|
652 | keys: function(obj) {
|
653 | var ret = [];
|
654 | for(var key in obj) ret.push(key);
|
655 | return ret;
|
656 | },
|
657 |
|
658 | /**
|
659 | Convenience method to inspect an object. This method will attempt to
|
660 | convert the object into a useful string description.
|
661 | */
|
662 | inspect: function(obj) {
|
663 | var v, ret = [] ;
|
664 | for(var key in obj) {
|
665 | v = obj[key] ;
|
666 | if (v === 'toString') continue ; // ignore useless items
|
667 | if (SC.typeOf(v) === SC.T_FUNCTION) v = "function() { ... }" ;
|
668 | ret.push(key + ": " + v) ;
|
669 | }
|
670 | return "{" + ret.join(" , ") + "}" ;
|
671 | },
|
672 |
|
673 | /**
|
674 | Returns a tuple containing the object and key for the specified property
|
675 | path. If no object could be found to match the property path, then
|
676 | returns null.
|
677 |
|
678 | This is the standard method used throughout SproutCore to resolve property
|
679 | paths.
|
680 |
|
681 | @param path {String} the property path
|
682 | @param root {Object} optional parameter specifying the place to start
|
683 | @returns {Array} array with [object, property] if found or null
|
684 | */
|
685 | tupleForPropertyPath: function(path, root) {
|
686 |
|
687 | // if the passed path is itself a tuple, return it
|
688 | if (typeof path === "object" && (path instanceof Array)) return path ;
|
689 |
|
690 | // find the key. It is the last . or first *
|
691 | var key ;
|
692 | var stopAt = path.indexOf('*') ;
|
693 | if (stopAt < 0) stopAt = path.lastIndexOf('.') ;
|
694 | key = (stopAt >= 0) ? path.slice(stopAt+1) : path ;
|
695 |
|
696 | // convert path to object.
|
697 | var obj = this.objectForPropertyPath(path, root, stopAt) ;
|
698 | return (obj && key) ? [obj,key] : null ;
|
699 | },
|
700 |
|
701 | /**
|
702 | Finds the object for the passed path or array of path components. This is
|
703 | the standard method used in SproutCore to traverse object paths.
|
704 |
|
705 | @param path {String} the path
|
706 | @param root {Object} optional root object. window is used otherwise
|
707 | @param stopAt {Integer} optional point to stop searching the path.
|
708 | @returns {Object} the found object or undefined.
|
709 | */
|
710 | objectForPropertyPath: function(path, root, stopAt) {
|
711 |
|
712 | var loc, nextDotAt, key, max ;
|
713 |
|
714 | if (!root) root = global ;
|
715 |
|
716 | // faster method for strings
|
717 | if (SC.typeOf(path) === SC.T_STRING) {
|
718 | if (stopAt === undefined) stopAt = path.length ;
|
719 | loc = 0 ;
|
720 | while((root) && (loc < stopAt)) {
|
721 | nextDotAt = path.indexOf('.', loc) ;
|
722 | if ((nextDotAt < 0) || (nextDotAt > stopAt)) nextDotAt = stopAt;
|
723 | key = path.slice(loc, nextDotAt);
|
724 | root = root.get ? root.get(key) : root[key] ;
|
725 | loc = nextDotAt+1;
|
726 | }
|
727 | if (loc < stopAt) root = undefined; // hit a dead end. :(
|
728 |
|
729 | // older method using an array
|
730 | } else {
|
731 |
|
732 | loc = 0; max = path.length; key = null;
|
733 | while((loc < max) && root) {
|
734 | key = path[loc++];
|
735 | if (key) root = (root.get) ? root.get(key) : root[key] ;
|
736 | }
|
737 | if (loc < max) root = undefined ;
|
738 | }
|
739 |
|
740 | return root ;
|
741 | },
|
742 |
|
743 |
|
744 | // ..........................................................
|
745 | // LOCALIZATION SUPPORT
|
746 | //
|
747 |
|
748 | /**
|
749 | Known loc strings
|
750 |
|
751 | @property {Hash}
|
752 | */
|
753 | STRINGS: {},
|
754 |
|
755 | /**
|
756 | This is a simplified handler for installing a bunch of strings. This
|
757 | ignores the language name and simply applies the passed strings hash.
|
758 |
|
759 | @param {String} lang the language the strings are for
|
760 | @param {Hash} strings hash of strings
|
761 | @returns {SC} receiver
|
762 | */
|
763 | stringsFor: function(lang, strings) {
|
764 | SC.mixin(SC.STRINGS, strings);
|
765 | return this ;
|
766 | }
|
767 |
|
768 |
|
769 | }); // end mixin
|
770 |
|
771 | /** @private Aliasn for SC.clone() */
|
772 | SC.clone = SC.copy ;
|
773 |
|
774 | /** @private Alias for SC.A() */
|
775 | SC.$A = SC.A;
|
776 |
|
777 | /** @private Provided for compatibility with old HTML templates. */
|
778 | SC.didLoad = SC.K ;
|
779 |
|
780 | /** @private Used by SC.compare */
|
781 | SC.ORDER_DEFINITION = [ SC.T_ERROR,
|
782 | SC.T_UNDEFINED,
|
783 | SC.T_NULL,
|
784 | SC.T_BOOL,
|
785 | SC.T_NUMBER,
|
786 | SC.T_STRING,
|
787 | SC.T_ARRAY,
|
788 | SC.T_HASH,
|
789 | SC.T_OBJECT,
|
790 | SC.T_FUNCTION,
|
791 | SC.T_CLASS ];
|
792 |
|
793 |
|
794 | // ........................................
|
795 | // FUNCTION ENHANCEMENTS
|
796 | //
|
797 |
|
798 | SC.mixin(Function.prototype,
|
799 | /** @lends Function.prototype */ {
|
800 |
|
801 | /**
|
802 | Indicates that the function should be treated as a computed property.
|
803 |
|
804 | Computed properties are methods that you want to treat as if they were
|
805 | static properties. When you use get() or set() on a computed property,
|
806 | the object will call the property method and return its value instead of
|
807 | returning the method itself. This makes it easy to create "virtual
|
808 | properties" that are computed dynamically from other properties.
|
809 |
|
810 | Consider the following example:
|
811 |
|
812 | {{{
|
813 | contact = SC.Object.create({
|
814 |
|
815 | firstName: "Charles",
|
816 | lastName: "Jolley",
|
817 |
|
818 | // This is a computed property!
|
819 | fullName: function() {
|
820 | return this.getEach('firstName','lastName').compact().join(' ') ;
|
821 | }.property('firstName', 'lastName'),
|
822 |
|
823 | // this is not
|
824 | getFullName: function() {
|
825 | return this.getEach('firstName','lastName').compact().join(' ') ;
|
826 | }
|
827 | });
|
828 |
|
829 | contact.get('firstName') ;
|
830 | --> "Charles"
|
831 |
|
832 | contact.get('fullName') ;
|
833 | --> "Charles Jolley"
|
834 |
|
835 | contact.get('getFullName') ;
|
836 | --> function()
|
837 | }}}
|
838 |
|
839 | Note that when you get the fullName property, SproutCore will call the
|
840 | fullName() function and return its value whereas when you get() a property
|
841 | that contains a regular method (such as getFullName above), then the
|
842 | function itself will be returned instead.
|
843 |
|
844 | h2. Using Dependent Keys
|
845 |
|
846 | Computed properties are often computed dynamically from other member
|
847 | properties. Whenever those properties change, you need to notify any
|
848 | object that is observing the computed property that the computed property
|
849 | has changed also. We call these properties the computed property is based
|
850 | upon "dependent keys".
|
851 |
|
852 | For example, in the contact object above, the fullName property depends on
|
853 | the firstName and lastName property. If either property value changes,
|
854 | any observer watching the fullName property will need to be notified as
|
855 | well.
|
856 |
|
857 | You inform SproutCore of these dependent keys by passing the key names
|
858 | as parameters to the property() function. Whenever the value of any key
|
859 | you name here changes, the computed property will be marked as changed
|
860 | also.
|
861 |
|
862 | You should always register dependent keys for computed properties to
|
863 | ensure they update.
|
864 |
|
865 | h2. Using Computed Properties as Setters
|
866 |
|
867 | Computed properties can be used to modify the state of an object as well
|
868 | as to return a value. Unlike many other key-value system, you use the
|
869 | same method to both get and set values on a computed property. To
|
870 | write a setter, simply declare two extra parameters: key and value.
|
871 |
|
872 | Whenever your property function is called as a setter, the value
|
873 | parameter will be set. Whenever your property is called as a getter the
|
874 | value parameter will be undefined.
|
875 |
|
876 | For example, the following object will split any full name that you set
|
877 | into a first name and last name components and save them.
|
878 |
|
879 | {{{
|
880 | contact = SC.Object.create({
|
881 |
|
882 | fullName: function(key, value) {
|
883 | if (value !== undefined) {
|
884 | var parts = value.split(' ') ;
|
885 | this.beginPropertyChanges()
|
886 | .set('firstName', parts[0])
|
887 | .set('lastName', parts[1])
|
888 | .endPropertyChanges() ;
|
889 | }
|
890 | return this.getEach('firstName', 'lastName').compact().join(' ');
|
891 | }.property('firstName','lastName')
|
892 |
|
893 | }) ;
|
894 |
|
895 | }}}
|
896 |
|
897 | h2. Why Use The Same Method for Getters and Setters?
|
898 |
|
899 | Most property-based frameworks expect you to write two methods for each
|
900 | property but SproutCore only uses one. We do this because most of the time
|
901 | when you write a setter is is basically a getter plus some extra work.
|
902 | There is little added benefit in writing both methods when you can
|
903 | conditionally exclude part of it. This helps to keep your code more
|
904 | compact and easier to maintain.
|
905 |
|
906 | @param dependentKeys {String...} optional set of dependent keys
|
907 | @returns {Function} the declared function instance
|
908 | */
|
909 | property: function() {
|
910 | this.dependentKeys = SC.$A(arguments) ;
|
911 | var guid = SC.guidFor(this) ;
|
912 | this.cacheKey = "__cache__" + guid ;
|
913 | this.lastSetValueKey = "__lastValue__" + guid ;
|
914 | this.isProperty = YES ;
|
915 | return this ;
|
916 | },
|
917 |
|
918 | /**
|
919 | You can call this method on a computed property to indicate that the
|
920 | property is cacheable (or not cacheable). By default all computed
|
921 | properties are not cached. Enabling this feature will allow SproutCore
|
922 | to cache the return value of your computed property and to use that
|
923 | value until one of your dependent properties changes or until you
|
924 | invoke propertyDidChange() and name the computed property itself.
|
925 |
|
926 | If you do not specify this option, computed properties are assumed to be
|
927 | not cacheable.
|
928 |
|
929 | @param {Boolean} aFlag optionally indicate cacheable or no, default YES
|
930 | @returns {Function} reciever
|
931 | */
|
932 | cacheable: function(aFlag) {
|
933 | this.isProperty = YES ; // also make a property just in case
|
934 | if (!this.dependentKeys) this.dependentKeys = [] ;
|
935 | this.isCacheable = (aFlag === undefined) ? YES : aFlag ;
|
936 | return this ;
|
937 | },
|
938 |
|
939 | /**
|
940 | Indicates that the computed property is volatile. Normally SproutCore
|
941 | assumes that your computed property is idempotent. That is, calling
|
942 | set() on your property more than once with the same value has the same
|
943 | effect as calling it only once.
|
944 |
|
945 | All non-computed properties are idempotent and normally you should make
|
946 | your computed properties behave the same way. However, if you need to
|
947 | make your property change its return value everytime your method is
|
948 | called, you may chain this to your property to make it volatile.
|
949 |
|
950 | If you do not specify this option, properties are assumed to be
|
951 | non-volatile.
|
952 |
|
953 | @param {Boolean} aFlag optionally indicate state, default to YES
|
954 | @returns {Function} receiver
|
955 | */
|
956 | idempotent: function(aFlag) {
|
957 | this.isProperty = YES; // also make a property just in case
|
958 | if (!this.dependentKeys) this.dependentKeys = [] ;
|
959 | this.isVolatile = (aFlag === undefined) ? YES : aFlag ;
|
960 | return this ;
|
961 | },
|
962 |
|
963 | /**
|
964 | Declare that a function should observe an object at the named path. Note
|
965 | that the path is used only to construct the observation one time.
|
966 |
|
967 | @returns {Function} receiver
|
968 | */
|
969 | observes: function(propertyPaths) {
|
970 | // sort property paths into local paths (i.e just a property name) and
|
971 | // full paths (i.e. those with a . or * in them)
|
972 | var loc = arguments.length, local = null, paths = null ;
|
973 | while(--loc >= 0) {
|
974 | var path = arguments[loc] ;
|
975 | // local
|
976 | if ((path.indexOf('.')<0) && (path.indexOf('*')<0)) {
|
977 | if (!local) local = this.localPropertyPaths = [] ;
|
978 | local.push(path);
|
979 |
|
980 | // regular
|
981 | } else {
|
982 | if (!paths) paths = this.propertyPaths = [] ;
|
983 | paths.push(path) ;
|
984 | }
|
985 | }
|
986 | return this ;
|
987 | }
|
988 |
|
989 | });
|
990 |
|
991 | // ..........................................................
|
992 | // STRING ENHANCEMENT
|
993 | //
|
994 |
|
995 | // Interpolate string. looks for %@ or %@1; to control the order of params.
|
996 | /**
|
997 | Apply formatting options to the string. This will look for occurrences
|
998 | of %@ in your string and substitute them with the arguments you pass into
|
999 | this method. If you want to control the specific order of replacement,
|
1000 | you can add a number after the key as well to indicate which argument
|
1001 | you want to insert.
|
1002 |
|
1003 | Ordered insertions are most useful when building loc strings where values
|
1004 | you need to insert may appear in different orders.
|
1005 |
|
1006 | h3. Examples
|
1007 |
|
1008 | {{{
|
1009 | "Hello %@ %@".fmt('John', 'Doe') => "Hello John Doe"
|
1010 | "Hello %@2, %@1".fmt('John', 'Doe') => "Hello Doe, John"
|
1011 | }}}
|
1012 |
|
1013 | @param args {Object...} optional arguments
|
1014 | @returns {String} formatted string
|
1015 | */
|
1016 | String.prototype.fmt = function() {
|
1017 | // first, replace any ORDERED replacements.
|
1018 | var args = arguments,
|
1019 | idx = 0; // the current index for non-numerical replacements
|
1020 | return this.replace(/%@([0-9]+)?/g, function(s, argIndex) {
|
1021 | argIndex = (argIndex) ? parseInt(argIndex,0)-1 : idx++ ;
|
1022 | s =args[argIndex];
|
1023 | return ((s===null) ? '(null)' : (s===undefined) ? '' : s).toString();
|
1024 | }) ;
|
1025 | };
|
1026 |
|
1027 | /**
|
1028 | Localizes the string. This will look up the reciever string as a key
|
1029 | in the current Strings hash. If the key matches, the loc'd value will be
|
1030 | used. The resulting string will also be passed through fmt() to insert
|
1031 | any variables.
|
1032 |
|
1033 | @param args {Object...} optional arguments to interpolate also
|
1034 | @returns {String} the localized and formatted string.
|
1035 | */
|
1036 | String.prototype.loc = function() {
|
1037 | var str = SC.STRINGS[this] || this;
|
1038 | return str.fmt.apply(str,arguments) ;
|
1039 | };
|
1040 |
|
1041 |
|
1042 |
|
1043 | /**
|
1044 | Splits the string into words, separated by spaces. Empty strings are
|
1045 | removed from the results.
|
1046 |
|
1047 | @returns {Array} an array of non-empty strings
|
1048 | */
|
1049 | String.prototype.w = function() {
|
1050 | var ary = [], ary2 = this.split(' '), len = ary2.length, str, idx=0;
|
1051 | for (idx=0; idx<len; ++idx) {
|
1052 | str = ary2[idx] ;
|
1053 | if (str.length !== 0) ary.push(str) ; // skip empty strings
|
1054 | }
|
1055 | return ary ;
|
1056 | };
|
1057 |
|
1058 | //
|
1059 | // DATE ENHANCEMENT
|
1060 | //
|
1061 | if (!Date.now) {
|
1062 | Date.now = function() {
|
1063 | return new Date().getTime() ;
|
1064 | };
|
1065 | }
|
1066 |
|
1067 | // ==========================================================================
|
1068 | // Project: SproutCore - JavaScript Application Framework
|
1069 | // Copyright: ©2006-2011 Strobe Inc. and contributors.
|
1070 | // Portions ©2008-2010 Apple Inc. All rights reserved.
|
1071 | // License: Licensed under MIT license (see license.js)
|
1072 | // ==========================================================================
|
1073 |
|
1074 | /**
|
1075 | Indicates that the collection view expects to accept a drop ON the specified
|
1076 | item.
|
1077 |
|
1078 | @property {Number}
|
1079 | */
|
1080 | SC.DROP_ON = 0x01 ;
|
1081 |
|
1082 | /**
|
1083 | Indicates that the collection view expects to accept a drop BEFORE the
|
1084 | specified item.
|
1085 |
|
1086 | @property {Number}
|
1087 | */
|
1088 | SC.DROP_BEFORE = 0x02 ;
|
1089 |
|
1090 | /**
|
1091 | Indicates that the collection view expects to accept a drop AFTER the
|
1092 | specified item. This is treated just like SC.DROP_BEFORE is most views
|
1093 | except for tree lists.
|
1094 |
|
1095 | @property {Number}
|
1096 | */
|
1097 | SC.DROP_AFTER = 0x04 ;
|
1098 |
|
1099 | /**
|
1100 | Indicates that the collection view want's to know which operations would
|
1101 | be allowed for either drop operation.
|
1102 |
|
1103 | @property {Number}
|
1104 | */
|
1105 | SC.DROP_ANY = 0x07 ;
|
1106 |
|
1107 |
|
1108 | /**
|
1109 | This variable is here to make the tab focus behavior work like safari's.
|
1110 | */
|
1111 | SC.SAFARI_FOCUS_BEHAVIOR = YES;
|
1112 |
|
1113 | SC.mixin(/** @lends SC */ {
|
1114 |
|
1115 | /**
|
1116 | Reads or writes data from a global cache. You can use this facility to
|
1117 | store information about an object without actually adding properties to
|
1118 | the object itself. This is needed especially when working with DOM,
|
1119 | which can leak easily in IE.
|
1120 |
|
1121 | To read data, simply pass in the reference element (used as a key) and
|
1122 | the name of the value to read. To write, also include the data.
|
1123 |
|
1124 | You can also just pass an object to retrieve the entire cache.
|
1125 |
|
1126 | @param elem {Object} An object or Element to use as scope
|
1127 | @param name {String} Optional name of the value to read/write
|
1128 | @param data {Object} Optional data. If passed, write.
|
1129 | @returns {Object} the value of the named data
|
1130 | */
|
1131 | data: function(elem, name, data) {
|
1132 | elem = (elem === window) ? "@window" : elem ;
|
1133 | var hash = SC.hashFor(elem) ; // get the hash key
|
1134 |
|
1135 | // Generate the data cache if needed
|
1136 | var cache = SC._data_cache ;
|
1137 | if (!cache) SC._data_cache = cache = {} ;
|
1138 |
|
1139 | // Now get cache for element
|
1140 | var elemCache = cache[hash] ;
|
1141 | if (name && !elemCache) cache[hash] = elemCache = {} ;
|
1142 |
|
1143 | // Write data if provided
|
1144 | if (elemCache && (data !== undefined)) elemCache[name] = data ;
|
1145 |
|
1146 | return (name) ? elemCache[name] : elemCache ;
|
1147 | },
|
1148 |
|
1149 | /**
|
1150 | Removes data from the global cache. This is used throughout the
|
1151 | framework to hold data without creating memory leaks.
|
1152 |
|
1153 | You can remove either a single item on the cache or all of the cached
|
1154 | data for an object.
|
1155 |
|
1156 | @param elem {Object} An object or Element to use as scope
|
1157 | @param name {String} optional name to remove.
|
1158 | @returns {Object} the value or cache that was removed
|
1159 | */
|
1160 | removeData: function(elem, name) {
|
1161 | elem = (elem === window) ? "@window" : elem ;
|
1162 | var hash = SC.hashFor(elem) ;
|
1163 |
|
1164 | // return undefined if no cache is defined
|
1165 | var cache = SC._data_cache ;
|
1166 | if (!cache) return undefined ;
|
1167 |
|
1168 | // return undefined if the elem cache is undefined
|
1169 | var elemCache = cache[hash] ;
|
1170 | if (!elemCache) return undefined;
|
1171 |
|
1172 | // get the return value
|
1173 | var ret = (name) ? elemCache[name] : elemCache ;
|
1174 |
|
1175 | // and delete as appropriate
|
1176 | if (name) {
|
1177 | delete elemCache[name] ;
|
1178 | } else {
|
1179 | delete cache[hash] ;
|
1180 | }
|
1181 |
|
1182 | return ret ;
|
1183 | }
|
1184 | }) ;
|
1185 |
|
1186 | SC.mixin(Function.prototype, /** @scope Function.prototype */ {
|
1187 | /**
|
1188 | Creates a timer that will execute the function after a specified
|
1189 | period of time.
|
1190 |
|
1191 | If you pass an optional set of arguments, the arguments will be passed
|
1192 | to the function as well. Otherwise the function should have the
|
1193 | signature:
|
1194 |
|
1195 | {{{
|
1196 | function functionName(timer)
|
1197 | }}}
|
1198 |
|
1199 | @param target {Object} optional target object to use as this
|
1200 | @param interval {Number} the time to wait, in msec
|
1201 | @returns {SC.Timer} scheduled timer
|
1202 | */
|
1203 | invokeLater: function(target, interval) {
|
1204 | if (interval === undefined) interval = 1 ;
|
1205 | var f = this;
|
1206 | if (arguments.length > 2) {
|
1207 | var args = SC.$A(arguments).slice(2,arguments.length);
|
1208 | args.unshift(target);
|
1209 | // f = f.bind.apply(f, args) ;
|
1210 | var that = this, func = f ;
|
1211 | f = function() { return func.apply(that, args.slice(1)); } ;
|
1212 | }
|
1213 | return SC.Timer.schedule({ target: target, action: f, interval: interval });
|
1214 | }
|
1215 |
|
1216 | });
|
1217 |
|
1218 | // Note: We won't use SC.T_* here because those constants might not yet be
|
1219 | // defined.
|
1220 | SC._mapDisplayNamesUseHashForSeenTypes = ['object', 'number', 'boolean', 'array', 'string', 'function', 'class', 'undefined', 'error']; // 'hash' causes problems
|
1221 |
|
1222 | SC.mapDisplayNames = function(obj, level, path, seenHash, seenArray) {
|
1223 | if (!(/webkit/).test(navigator.userAgent.toLowerCase())) return;
|
1224 |
|
1225 | // Lazily instantiate the hash of types we'll use a hash for the "have we
|
1226 | // seen this before?" structure. (Some types are not safe to put in a hash
|
1227 | // in this manner, so we'll use the hash for its algorithmic advantage when
|
1228 | // possible, but fall back to an array using indexOf() when necessary.)
|
1229 | if (!SC._mapDisplayNamesUseHashForSeenTypesHash) {
|
1230 | var types = SC._mapDisplayNamesUseHashForSeenTypes ;
|
1231 | var typesHash = {} ;
|
1232 | var len = types.length ;
|
1233 | for (var i = 0; i < len; ++i) {
|
1234 | var type = types[i] ;
|
1235 | typesHash[type] = true ;
|
1236 | }
|
1237 | SC._mapDisplayNamesUseHashForSeenTypesHash = typesHash ;
|
1238 | }
|
1239 |
|
1240 |
|
1241 | if (obj === undefined) obj = window ;
|
1242 | if (level === undefined) level = 0 ;
|
1243 | if (path === undefined) path = [] ;
|
1244 | if (seenHash === undefined) seenHash = {} ;
|
1245 | if (seenArray === undefined) seenArray = [] ;
|
1246 |
|
1247 | if (level > 5) return ;
|
1248 |
|
1249 | var useHash = !!SC._mapDisplayNamesUseHashForSeenTypesHash[SC.typeOf(obj)] ;
|
1250 |
|
1251 | var hash;
|
1252 | var arrayToCheck;
|
1253 | if (useHash) {
|
1254 | hash = SC.hashFor(obj) ;
|
1255 | arrayToCheck = seenHash[hash];
|
1256 | }
|
1257 | else {
|
1258 | arrayToCheck = seenArray;
|
1259 | }
|
1260 |
|
1261 | if (arrayToCheck && arrayToCheck.indexOf(obj) !== -1) return ;
|
1262 |
|
1263 | if (arrayToCheck) {
|
1264 | arrayToCheck.push(obj) ;
|
1265 | }
|
1266 | else if (useHash) {
|
1267 | seenHash[hash] = [obj] ;
|
1268 | }
|
1269 |
|
1270 | var loc = path.length, str, val, t;
|
1271 | path[loc] = '';
|
1272 |
|
1273 | for(var key in obj) {
|
1274 | if (obj.hasOwnProperty && !obj.hasOwnProperty(key)) continue ;
|
1275 | if (!isNaN(Number(key))) continue ; // skip array indexes
|
1276 | if (key === "constructor") continue ;
|
1277 | if (key === "superclass") continue ;
|
1278 | if (key === "document") continue ;
|
1279 |
|
1280 | // Avoid TypeError's in WebKit based browsers
|
1281 | if (obj.type && obj.type === 'file') {
|
1282 | if (key === 'selectionStart' || key === 'selectionEnd') continue;
|
1283 | }
|
1284 |
|
1285 | try{
|
1286 | val = obj[key];
|
1287 | }catch(e){
|
1288 | //This object might be special this get called when an app
|
1289 | // using webView adds an static C object to JS.
|
1290 | continue;
|
1291 | }
|
1292 | if (key === "SproutCore") key = "SC";
|
1293 | t = SC.typeOf(val);
|
1294 | if (t === SC.T_FUNCTION) {
|
1295 | if (!val.displayName) { // only name the first time it is encountered
|
1296 | path[loc] = key ;
|
1297 | str = path.join('.').replace('.prototype.', '#');
|
1298 | val.displayName = str;
|
1299 | }
|
1300 |
|
1301 | // handle constructor-style
|
1302 | if (val.prototype) {
|
1303 | path.push("prototype");
|
1304 | SC.mapDisplayNames(val.prototype, level+1, path, seenHash, seenArray);
|
1305 | path.pop();
|
1306 | }
|
1307 |
|
1308 | } else if (t === SC.T_CLASS) {
|
1309 | path[loc] = key ;
|
1310 | SC.mapDisplayNames(val, level+1, path, seenHash, seenArray);
|
1311 |
|
1312 | } else if ((key.indexOf('_')!==0) && (t===SC.T_OBJECT || t===SC.T_HASH)) {
|
1313 | path[loc] = key ;
|
1314 | SC.mapDisplayNames(val, level+1, path, seenHash, seenArray);
|
1315 | }
|
1316 | }
|
1317 |
|
1318 | path.pop();
|
1319 | };
|