UNPKG

41.5 kBJavaScriptView Raw
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
10if (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.
14var require = require || function require() { } ;
15var sc_require = sc_require || require;
16var sc_resource = sc_resource || function sc_resource() {};
17
18var 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.
28var YES = true ;
29var NO = false ;
30
31// prevent a console.log from blowing things up if we are on a browser that
32// does not support it
33if (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*/
63var SC = global.SC || {} ;
64var SproutCore = SproutCore || SC ;
65
66SC.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*/
84SC._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*/
122SC.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*/
140SC.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
151SC.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() */
772SC.clone = SC.copy ;
773
774/** @private Alias for SC.A() */
775SC.$A = SC.A;
776
777/** @private Provided for compatibility with old HTML templates. */
778SC.didLoad = SC.K ;
779
780/** @private Used by SC.compare */
781SC.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
798SC.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*/
1016String.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*/
1036String.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*/
1049String.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//
1061if (!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*/
1080SC.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*/
1088SC.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*/
1097SC.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*/
1105SC.DROP_ANY = 0x07 ;
1106
1107
1108/**
1109 This variable is here to make the tab focus behavior work like safari's.
1110*/
1111SC.SAFARI_FOCUS_BEHAVIOR = YES;
1112
1113SC.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
1186SC.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.
1220SC._mapDisplayNamesUseHashForSeenTypes = ['object', 'number', 'boolean', 'array', 'string', 'function', 'class', 'undefined', 'error']; // 'hash' causes problems
1221
1222SC.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};