Skip to content

Instantly share code, notes, and snippets.

@mauritslamers
Created May 19, 2015 11:48
Show Gist options
  • Select an option

  • Save mauritslamers/f4e2e4e598141744d18f to your computer and use it in GitHub Desktop.

Select an option

Save mauritslamers/f4e2e4e598141744d18f to your computer and use it in GitHub Desktop.
node_modules/tern/bin/condense --name Sproutcore scruntime.js > sproutcore_tern.json
/* start of original file: frameworks/sproutcore/frameworks/bootstrap.js*/
/* start of original file: frameworks/sproutcore/frameworks/bootstrap/core.js*/
// ==========================================================================
// Project: SproutCore - JavaScript Application Framework
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
/* end of original file: frameworks/sproutcore/frameworks/bootstrap/core.js*/
/* start of original file: frameworks/sproutcore/frameworks/bootstrap/system/browser.js*/
// ==========================================================================
// Project: SproutCore - JavaScript Application Framework
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
var SC = SC || { MODULE_INFO: {}, LAZY_INSTANTIATION: {} };
SC._detectBrowser = function(rawUserAgent, language) {
if (rawUserAgent === undefined) rawUserAgent = navigator.userAgent;
if (language === undefined) language = navigator.language || navigator.browserLanguage;
var userAgent = rawUserAgent.toLowerCase(),
// Gibberish at the end is to determine when the browser version is done
version = (userAgent.match( /.*(?:rv|chrome|webkit|opera|ie)[\/: ](.+?)([ \);]|$)/ ) || [])[1],
webkitVersion = (userAgent.match( /webkit\/(.+?) / ) || [])[1];
var browser = {
version: version,
safari: /webkit/.test(userAgent) ? webkitVersion : 0,
opera: /opera/.test(userAgent) ? version : 0,
msie: /msie/.test(userAgent) && !/opera/.test(userAgent) ? version : 0,
mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test(userAgent) ? version : 0,
mobileSafari: /apple.*mobile.*safari/.test(userAgent) ? version : 0,
chrome: /chrome/.test( userAgent ) ? version : 0,
windows: !!/windows/.test(userAgent),
mac: !!/macintosh/.test(userAgent) || (/mac os x/.test(userAgent) && !/like mac os x/.test(userAgent)),
language: language.split('-', 1)[0]
};
browser.current = browser.msie ? 'msie' : browser.mozilla ? 'mozilla' : browser.chrome ? 'chrome' : browser.safari ? 'safari' : browser.opera ? 'opera' : 'unknown' ;
return browser ;
};
SC.browser = SC._detectBrowser();
/* end of original file: frameworks/sproutcore/frameworks/bootstrap/system/browser.js*/
/* start of original file: frameworks/sproutcore/frameworks/bootstrap/system/bench.js*/
// ==========================================================================
// Project: SproutCore - JavaScript Application Framework
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
/*global SC_benchmarkPreloadEvents*/
// sc_require("system/browser")
if (typeof SC_benchmarkPreloadEvents !== "undefined") {
SC.benchmarkPreloadEvents = SC_benchmarkPreloadEvents;
SC_benchmarkPreloadEvents = undefined;
} else {
SC.benchmarkPreloadEvents = { headStart: new Date().getTime() };
}
/* end of original file: frameworks/sproutcore/frameworks/bootstrap/system/bench.js*/
/* start of original file: frameworks/sproutcore/frameworks/bootstrap/system/loader.js*/
// ==========================================================================
// Project: SproutCore - JavaScript Application Framework
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
// sc_require("system/browser");
SC.setupBodyClassNames = function() {
var el = document.body ;
if (!el) return ;
var browser, platform, shadows, borderRad, classNames, style;
browser = SC.browser.current ;
platform = SC.browser.windows ? 'windows' : SC.browser.mac ? 'mac' : 'other-platform' ;
style = document.documentElement.style;
shadows = (style.MozBoxShadow !== undefined) ||
(style.webkitBoxShadow !== undefined) ||
(style.oBoxShadow !== undefined) ||
(style.boxShadow !== undefined);
borderRad = (style.MozBorderRadius !== undefined) ||
(style.webkitBorderRadius !== undefined) ||
(style.oBorderRadius !== undefined) ||
(style.borderRadius !== undefined);
classNames = el.className ? el.className.split(' ') : [] ;
if(shadows) classNames.push('box-shadow');
if(borderRad) classNames.push('border-rad');
classNames.push(browser) ;
if (browser === 'chrome') classNames.push('safari');
classNames.push(platform) ;
if (parseInt(SC.browser.msie,0)==7) classNames.push('ie7') ;
if (SC.browser.mobileSafari) classNames.push('mobile-safari') ;
if ('createTouch' in document) classNames.push('touch');
el.className = classNames.join(' ') ;
} ;
/* end of original file: frameworks/sproutcore/frameworks/bootstrap/system/loader.js*/
/* end of original file: frameworks/sproutcore/frameworks/bootstrap.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/license.js*/
/**
* @license
* ==========================================================================
* SproutCore Costello -- Property Observing Library
* Copyright ©2006-2010, Sprout Systems, Inc. and contributors.
* Portions copyright ©2008-2010 Apple Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* For more information about SproutCore, visit http://www.sproutcore.com
*
* ==========================================================================
*/
/* end of original file: frameworks/sproutcore/frameworks/runtime/license.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/core.js*/
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
/*global NodeList */
// These commands are used by the build tools to control load order. On the
// client side these are a no-op.
var require = require || function require() { } ;
var sc_require = sc_require || require;
var sc_resource = sc_resource || function sc_resource() {};
sc_require('license') ;
// ........................................
// GLOBAL CONSTANTS
//
// Most global constants should be defined inside of the SC namespace.
// However the following two are useful enough and generally benign enough
// to put into the global object.
var YES = true ;
var NO = false ;
// prevent a console.log from blowing things up if we are on a browser that
// does not support it
if (typeof console === 'undefined') {
window.console = {} ;
console.log = console.info = console.warn = console.error = function(){};
}
// ........................................
// BOOTSTRAP
//
// The root namespace and some common utility methods are defined here. The
// rest of the methods go into the mixin defined below.
/**
@namespace
The SproutCore namespace. All SproutCore methods and functions are defined
inside of this namespace. You generally should not add new properties to
this namespace as it may be overwritten by future versions of SproutCore.
You can also use the shorthand "SC" instead of "SproutCore".
SproutCore-Base is a framework that provides core functions for SproutCore
including cross-platform functions, support for property observing and
objects. It's focus is on small size and performance. You can use this
in place of or along-side other cross-platform libraries such as jQuery or
Prototype.
The core Base framework is based on the jQuery API with a number of
performance optimizations.
*/
var SC = SC || {} ;
var SproutCore = SproutCore || SC ;
/**
@private
Adds properties to a target object. You must specify whether
to overwrite a value for a property or not.
Used as a base function for the wrapper functions SC.mixin and SC.supplement.
@param overwrite {Boolean} if a target has a value for a property, this specifies
whether or not to overwrite that value with the copyied object's
property value.
@param target {Object} the target object to extend
@param properties {Object} one or more objects with properties to copy.
@returns {Object} the target object.
@static
*/
SC._baseMixin = function (override) {
var args = Array.prototype.slice.call(arguments, 1), src,
// copy reference to target object
target = args[0] || {},
idx = 1,
length = args.length ,
options, copy , key;
// Handle case where we have only one item...extend SC
if (length === 1) {
target = this || {};
idx=0;
}
for ( ; idx < length; idx++ ) {
if (!(options = args[idx])) continue ;
for(key in options) {
if (!options.hasOwnProperty(key)) continue ;
copy = options[key] ;
if (target===copy) continue ; // prevent never-ending loop
if (copy !== undefined && ( override || (target[key] === undefined) )) target[key] = copy ;
}
}
return target;
} ;
/**
Adds properties to a target object.
Takes the root object and adds the attributes for any additional
arguments passed.
@param target {Object} the target object to extend
@param properties {Object} one or more objects with properties to copy.
@returns {Object} the target object.
@static
*/
SC.mixin = function() {
var args = Array.prototype.slice.call(arguments);
args.unshift(true);
return SC._baseMixin.apply(this, args);
} ;
/**
Adds properties to a target object. Unlike SC.mixin, however, if the target
already has a value for a property, it will not be overwritten.
Takes the root object and adds the attributes for any additional
arguments passed.
@param target {Object} the target object to extend
@param properties {Object} one or more objects with properties to copy.
@returns {Object} the target object.
@static
*/
SC.supplement = function() {
var args = Array.prototype.slice.call(arguments);
args.unshift(false);
return SC._baseMixin.apply(this, args);
} ;
/**
Alternative to mixin. Provided for compatibility with jQuery.
@function
*/
SC.extend = SC.mixin ;
// ..........................................................
// CORE FUNCTIONS
//
// Enough with the bootstrap code. Let's define some core functions
SC.mixin(/** @scope SC */ {
// ........................................
// GLOBAL CONSTANTS
//
T_ERROR: 'error',
T_OBJECT: 'object',
T_NULL: 'null',
T_CLASS: 'class',
T_HASH: 'hash',
T_FUNCTION: 'function',
T_UNDEFINED: 'undefined',
T_NUMBER: 'number',
T_BOOL: 'boolean',
T_ARRAY: 'array',
T_STRING: 'string',
// ........................................
// TYPING & ARRAY MESSAGING
//
/**
Returns a consistant type for the passed item.
Use this instead of the built-in typeOf() to get the type of an item.
It will return the same result across all browsers and includes a bit
more detail. Here is what will be returned:
| Return Value Constant | Meaning |
| SC.T_STRING | String primitive |
| SC.T_NUMBER | Number primitive |
| SC.T_BOOLEAN | Boolean primitive |
| SC.T_NULL | Null value |
| SC.T_UNDEFINED | Undefined value |
| SC.T_FUNCTION | A function |
| SC.T_ARRAY | An instance of Array |
| SC.T_CLASS | A SproutCore class (created using SC.Object.extend()) |
| SC.T_OBJECT | A SproutCore object instance |
| SC.T_HASH | A JavaScript object not inheriting from SC.Object |
@param item {Object} the item to check
@returns {String} the type
*/
typeOf: function(item) {
if (item === undefined) return SC.T_UNDEFINED ;
if (item === null) return SC.T_NULL ;
var nativeType = jQuery.type(item);
if (nativeType === "function") {
return item.isClass ? SC.T_CLASS : SC.T_FUNCTION
} else if (nativeType === "object") {
if (item.isError) {
return SC.T_ERROR ;
} else if (item.isObject) {
return SC.T_OBJECT ;
} else {
return SC.T_HASH ;
}
}
return nativeType ;
},
/**
Returns YES if the passed value is null or undefined. This avoids errors
from JSLint complaining about use of ==, which can be technically
confusing.
@param {Object} obj value to test
@returns {Boolean}
*/
none: function(obj) {
return obj == null;
},
/**
Verifies that a value is either null or an empty string. Return false if
the object is not a string.
@param {Object} obj value to test
@returns {Boolean}
*/
empty: function(obj) {
return obj === null || obj === undefined || obj === '';
},
/**
Returns YES if the passed object is an array or Array-like.
SproutCore Array Protocol:
* the object has an objectAt property; or
* the object is a native Array; or
* the object is an Object, and has a length property
Unlike SC.typeOf this method returns true even if the passed object is
not formally array but appears to be array-like (i.e. has a length
property, responds to .objectAt, etc.)
@param obj {Object} the object to test
@returns {Boolean}
*/
isArray: function(obj) {
var type;
if ( !obj || obj.setInterval ) {
return false;
} else if ( obj.objectAt ) {
return true ;
} else if ( obj.length && jQuery.type(obj) === "object" ) {
return true
}
return false;
},
/**
Makes an object into an Array if it is not array or array-like already.
Unlike SC.A(), this method will not clone the object if it is already
an array.
@param {Object} obj object to convert
@returns {Array} Actual array
*/
makeArray: function(obj) {
return SC.isArray(obj) ? obj : SC.A(obj);
},
/**
Converts the passed object to an Array. If the object appears to be
array-like, a new array will be cloned from it. Otherwise, a new array
will be created with the item itself as the only item in the array.
@param object {Object} any enumerable or array-like object.
@returns {Array} Array of items
*/
A: function(obj) {
// null or undefined -- fast path
if ( obj === null || obj === undefined ) return [] ;
// primitive -- fast path
if ( obj.slice instanceof Function ) {
// do we have a string?
if ( typeof(obj) === 'string' ) return [obj] ;
else return obj.slice() ;
}
// enumerable -- fast path
if (obj.toArray) return obj.toArray() ;
// if not array-like, then just wrap in array.
if (!SC.isArray(obj)) return [obj];
// when all else fails, do a manual convert...
var ret = [], len = obj.length;
while(--len >= 0) ret[len] = obj[len];
return ret ;
},
//
// GUIDS & HASHES
//
guidKey: jQuery.expando,
// Used for guid generation...
_guidPrefixes: {"number": "nu", "string": "st"},
_guidCaches: {"number": {}, "string": {}},
_numberGuids: [], _stringGuids: {}, _keyCache: {},
/**"
Returns a unique GUID for the object. If the object does not yet have
a guid, one will be assigned to it. You can call this on any object,
SC.Object-based or not, but be aware that it will add a _guid property.
You can also use this method on DOM Element objects.
@param obj {Object} any object, string, number, Element, or primitive
@returns {String} the unique guid for this instance.
*/
guidFor: function(obj) {
// special cases where we don't want to add a key to object
if (obj === undefined) return "(undefined)";
if (obj === null) return "(null)";
var type = typeof obj;
// Don't allow prototype changes to String etc. to change the guidFor
if (type === SC.T_NUMBER || type === SC.T_STRING) {
cache = this._guidCaches[type];
ret = cache[obj];
if(!ret) {
ret = "st" + (jQuery.uuid++);
cache[obj] = ret;
}
return ret;
} else if (type === SC.T_BOOL) {
return (obj) ? "(true)" : "(false)"
}
var guidKey = this.guidKey;
if (obj[guidKey]) return obj[guidKey];
// More special cases; not as common, so we check for them after the cache
// lookup
if (obj === Object) return '(Object)';
if (obj === Array) return '(Array)';
var cache, ret;
var type = typeof obj;
return SC.generateGuid(obj, "sc");
},
/**
Returns a key name that combines the named key + prefix. This is more
efficient than simply combining strings because it uses a cache
internally for performance.
@param {String} prefix the prefix to attach to the key
@param {String} key key
@returns {String} result
*/
keyFor: function(prefix, key) {
var ret, pcache = this._keyCache[prefix];
if (!pcache) pcache = this._keyCache[prefix] = {}; // get cache for prefix
ret = pcache[key];
if (!ret) ret = pcache[key] = prefix + '_' + key ;
return ret ;
},
/**
Generates a new guid, optionally saving the guid to the object that you
pass in. You will rarely need to use this method. Instead you should
call SC.guidFor(obj), which return an existing guid if available.
@param {Object} obj the object to assign the guid to
@returns {String} the guid
*/
generateGuid: function(obj, prefix) {
var ret = (prefix + (jQuery.uuid++));
if (obj) obj[this.guidKey] = ret ;
return ret ;
},
/**
Returns a unique hash code for the object. If the object implements
a hash() method, the value of that method will be returned. Otherwise,
this will return the same value as guidFor().
If you pass multiple arguments, hashFor returns a string obtained by
concatenating the hash code of each argument.
Unlike guidFor(), this method allows you to implement logic in your
code to cause two separate instances of the same object to be treated as
if they were equal for comparisons and other functions.
IMPORTANT: If you implement a hash() method, it MUST NOT return a
number or a string that contains only a number. Typically hash codes
are strings that begin with a "%".
@param obj {Object} the object(s)
@returns {String} the hash code for this instance.
*/
hashFor: function() {
var l = arguments.length,
h = '',
obj, f, i;
for (i=0 ; i<l; ++i) {
obj = arguments[i];
h += (obj && (f = obj.hash) && (typeof f === SC.T_FUNCTION)) ? f.call(obj) : this.guidFor(obj);
}
return h === '' ? null : h;
},
/**
This will compare the two object values using their hash codes.
@param a {Object} first value to compare
@param b {Object} the second value to compare
@returns {Boolean} YES if the two have equal hash code values.
*/
isEqual: function(a,b) {
// QUESTION: is there a compelling performance reason to special-case
// undefined here?
return this.hashFor(a) === this.hashFor(b) ;
},
/**
This will compare two javascript values of possibly different types.
It will tell you which one is greater than the other by returning
-1 if the first is smaller than the second,
0 if both are equal,
1 if the first is greater than the second.
The order is calculated based on SC.ORDER_DEFINITION , if types are different.
In case they have the same type an appropriate comparison for this type is made.
@param v {Object} first value to compare
@param w {Object} the second value to compare
@returns {NUMBER} -1 if v < w, 0 if v = w and 1 if v > w.
*/
compare: function (v, w) {
// Doing a '===' check is very cheap, so in the case of equality, checking
// this up-front is a big win.
if (v === w) return 0;
var type1 = SC.typeOf(v);
var type2 = SC.typeOf(w);
// If we haven't yet generated a reverse-mapping of SC.ORDER_DEFINITION,
// do so now.
var mapping = SC.ORDER_DEFINITION_MAPPING;
if (!mapping) {
var order = SC.ORDER_DEFINITION;
mapping = SC.ORDER_DEFINITION_MAPPING = {};
var idx, len;
for (idx = 0, len = order.length; idx < len; ++idx) {
mapping[order[idx]] = idx;
}
// We no longer need SC.ORDER_DEFINITION.
delete SC.ORDER_DEFINITION;
}
var type1Index = mapping[type1];
var type2Index = mapping[type2];
if (type1Index < type2Index) return -1;
if (type1Index > type2Index) return 1;
// ok - types are equal - so we have to check values now
switch (type1) {
case SC.T_BOOL:
case SC.T_NUMBER:
if (v<w) return -1;
if (v>w) return 1;
return 0;
case SC.T_STRING:
var comp = v.localeCompare(w);
if (comp<0) return -1;
if (comp>0) return 1;
return 0;
case SC.T_ARRAY:
var vLen = v.length;
var wLen = w.length;
var l = Math.min(vLen, wLen);
var r = 0;
var i = 0;
var thisFunc = arguments.callee;
while (r===0 && i < l) {
r = thisFunc(v[i],w[i]);
i++;
}
if (r !== 0) return r;
// all elements are equal now
// shorter array should be ordered first
if (vLen < wLen) return -1;
if (vLen > wLen) return 1;
// arrays are equal now
return 0;
case SC.T_OBJECT:
if (v.constructor.isComparable === YES) return v.constructor.compare(v, w);
return 0;
default:
return 0;
}
},
// ..........................................................
// OBJECT MANAGEMENT
//
/**
Empty function. Useful for some operations.
@returns {Object}
*/
K: function() { return this; },
/**
Empty array. Useful for some optimizations.
@property {Array}
*/
EMPTY_ARRAY: [],
/**
Empty hash. Useful for some optimizations.
@property {Hash}
*/
EMPTY_HASH: {},
/**
Empty range. Useful for some optimizations.
@property {Range}
*/
EMPTY_RANGE: {start: 0, length: 0},
/**
Creates a new object with the passed object as its prototype.
This method uses JavaScript's native inheritence method to create a new
object.
You cannot use beget() to create new SC.Object-based objects, but you
can use it to beget Arrays, Hashes, Sets and objects you build yourself.
Note that when you beget() a new object, this method will also call the
didBeget() method on the object you passed in if it is defined. You can
use this method to perform any other setup needed.
In general, you will not use beget() often as SC.Object is much more
useful, but for certain rare algorithms, this method can be very useful.
For more information on using beget(), see the section on beget() in
Crockford's JavaScript: The Good Parts.
@param obj {Object} the object to beget
@returns {Object} the new object.
*/
beget: function(obj) {
if (obj === null || obj === undefined) return null ;
var K = SC.K; K.prototype = obj ;
var ret = new K();
K.prototype = null ; // avoid leaks
if (typeof obj.didBeget === "function") ret = obj.didBeget(ret);
return ret ;
},
/**
Creates a clone of the passed object. This function can take just about
any type of object and create a clone of it, including primitive values
(which are not actually cloned because they are immutable).
If the passed object implements the clone() method, then this function
will simply call that method and return the result.
@param object {Object} the object to clone
@param deep {Boolean} if true, a deep copy of the object is made
@returns {Object} the cloned object
*/
copy: function(object, deep) {
var ret = object, idx ;
// fast paths
if ( object ) {
if ( object.isCopyable ) return object.copy( deep );
if ( object.clone ) return object.clone();
}
switch ( jQuery.type(object) ) {
case "array":
ret = object.slice();
if ( deep ) {
idx = ret.length;
while ( idx-- ) { ret[idx] = SC.copy( ret[idx], true ); }
break ;
}
case "object":
ret = {} ;
for(var key in object) { ret[key] = deep ? SC.copy(object[key], true) : object[key] ; }
}
return ret ;
},
/**
Returns a new object combining the values of all passed hashes.
@param object {Object} one or more objects
@returns {Object} new Object
*/
merge: function() {
var ret = {}, len = arguments.length, idx;
for(idx=0; idx<len; idx++) SC.mixin(ret, arguments[idx]);
return ret ;
},
/**
Returns all of the keys defined on an object or hash. This is useful
when inspecting objects for debugging.
@param {Object} obj
@returns {Array} array of keys
*/
keys: function(obj) {
var ret = [];
for(var key in obj) ret.push(key);
return ret;
},
/**
Convenience method to inspect an object. This method will attempt to
convert the object into a useful string description.
*/
inspect: function(obj) {
var v, ret = [] ;
for(var key in obj) {
v = obj[key] ;
if (v === 'toString') continue ; // ignore useless items
if (SC.typeOf(v) === SC.T_FUNCTION) v = "function() { ... }" ;
ret.push(key + ": " + v) ;
}
return "{" + ret.join(" , ") + "}" ;
},
/**
Returns a tuple containing the object and key for the specified property
path. If no object could be found to match the property path, then
returns null.
This is the standard method used throughout SproutCore to resolve property
paths.
@param path {String} the property path
@param root {Object} optional parameter specifying the place to start
@returns {Array} array with [object, property] if found or null
*/
tupleForPropertyPath: function(path, root) {
// if the passed path is itself a tuple, return it
if (typeof path === "object" && (path instanceof Array)) return path ;
// find the key. It is the last . or first *
var key ;
var stopAt = path.indexOf('*') ;
if (stopAt < 0) stopAt = path.lastIndexOf('.') ;
key = (stopAt >= 0) ? path.slice(stopAt+1) : path ;
// convert path to object.
var obj = this.objectForPropertyPath(path, root, stopAt) ;
return (obj && key) ? [obj,key] : null ;
},
/**
Finds the object for the passed path or array of path components. This is
the standard method used in SproutCore to traverse object paths.
@param path {String} the path
@param root {Object} optional root object. window is used otherwise
@param stopAt {Integer} optional point to stop searching the path.
@returns {Object} the found object or undefined.
*/
objectForPropertyPath: function(path, root, stopAt) {
var loc, nextDotAt, key, max ;
if (!root) root = window ;
// faster method for strings
if (SC.typeOf(path) === SC.T_STRING) {
if (stopAt === undefined) stopAt = path.length ;
loc = 0 ;
while((root) && (loc < stopAt)) {
nextDotAt = path.indexOf('.', loc) ;
if ((nextDotAt < 0) || (nextDotAt > stopAt)) nextDotAt = stopAt;
key = path.slice(loc, nextDotAt);
root = root.get ? root.get(key) : root[key] ;
loc = nextDotAt+1;
}
if (loc < stopAt) root = undefined; // hit a dead end. :(
// older method using an array
} else {
loc = 0; max = path.length; key = null;
while((loc < max) && root) {
key = path[loc++];
if (key) root = (root.get) ? root.get(key) : root[key] ;
}
if (loc < max) root = undefined ;
}
return root ;
},
// ..........................................................
// LOCALIZATION SUPPORT
//
/**
Known loc strings
@property {Hash}
*/
STRINGS: {},
/**
This is a simplified handler for installing a bunch of strings. This
ignores the language name and simply applies the passed strings hash.
@param {String} lang the language the strings are for
@param {Hash} strings hash of strings
@returns {SC} receiver
*/
stringsFor: function(lang, strings) {
SC.mixin(SC.STRINGS, strings);
return this ;
}
}); // end mixin
/** @private Aliasn for SC.clone() */
SC.clone = SC.copy ;
/** @private Alias for SC.A() */
SC.$A = SC.A;
/** @private Provided for compatibility with old HTML templates. */
SC.didLoad = SC.K ;
/** @private Used by SC.compare */
SC.ORDER_DEFINITION = [ SC.T_ERROR,
SC.T_UNDEFINED,
SC.T_NULL,
SC.T_BOOL,
SC.T_NUMBER,
SC.T_STRING,
SC.T_ARRAY,
SC.T_HASH,
SC.T_OBJECT,
SC.T_FUNCTION,
SC.T_CLASS ];
// ........................................
// FUNCTION ENHANCEMENTS
//
SC.Function = {
property: function(fn, keys) {
fn.dependentKeys = SC.$A(keys) ;
var guid = SC.guidFor(fn) ;
fn.cacheKey = "__cache__" + guid ;
fn.lastSetValueKey = "__lastValue__" + guid ;
fn.isProperty = true ;
return fn ;
},
cacheable: function(fn, aFlag) {
fn.isProperty = true ; // also make a property just in case
if (!fn.dependentKeys) fn.dependentKeys = [] ;
fn.isCacheable = (aFlag === undefined) ? true : aFlag ;
return fn ;
},
idempotent: function(fn, aFlag) {
fn.isProperty = true; // also make a property just in case
if (!fn.dependentKeys) this.dependentKeys = [] ;
fn.isVolatile = (aFlag === undefined) ? true : aFlag ;
return fn ;
},
observes: function(fn, propertyPaths) {
// sort property paths into local paths (i.e just a property name) and
// full paths (i.e. those with a . or * in them)
var loc = propertyPaths.length, local = null, paths = null ;
while(--loc >= 0) {
var path = propertyPaths[loc] ;
// local
if ((path.indexOf('.')<0) && (path.indexOf('*')<0)) {
if (!local) local = fn.localPropertyPaths = [] ;
local.push(path);
// regular
} else {
if (!paths) paths = fn.propertyPaths = [] ;
paths.push(path) ;
}
}
return fn ;
}
}
SC.mixin(Function.prototype,
/** @lends Function.prototype */ {
/**
Indicates that the function should be treated as a computed property.
Computed properties are methods that you want to treat as if they were
static properties. When you use get() or set() on a computed property,
the object will call the property method and return its value instead of
returning the method itself. This makes it easy to create "virtual
properties" that are computed dynamically from other properties.
Consider the following example:
{{{
contact = SC.Object.create({
firstName: "Charles",
lastName: "Jolley",
// This is a computed property!
fullName: function() {
return this.getEach('firstName','lastName').compact().join(' ') ;
}.property('firstName', 'lastName'),
// this is not
getFullName: function() {
return this.getEach('firstName','lastName').compact().join(' ') ;
}
});
contact.get('firstName') ;
--> "Charles"
contact.get('fullName') ;
--> "Charles Jolley"
contact.get('getFullName') ;
--> function()
}}}
Note that when you get the fullName property, SproutCore will call the
fullName() function and return its value whereas when you get() a property
that contains a regular method (such as getFullName above), then the
function itself will be returned instead.
h2. Using Dependent Keys
Computed properties are often computed dynamically from other member
properties. Whenever those properties change, you need to notify any
object that is observing the computed property that the computed property
has changed also. We call these properties the computed property is based
upon "dependent keys".
For example, in the contact object above, the fullName property depends on
the firstName and lastName property. If either property value changes,
any observer watching the fullName property will need to be notified as
well.
You inform SproutCore of these dependent keys by passing the key names
as parameters to the property() function. Whenever the value of any key
you name here changes, the computed property will be marked as changed
also.
You should always register dependent keys for computed properties to
ensure they update.
h2. Using Computed Properties as Setters
Computed properties can be used to modify the state of an object as well
as to return a value. Unlike many other key-value system, you use the
same method to both get and set values on a computed property. To
write a setter, simply declare two extra parameters: key and value.
Whenever your property function is called as a setter, the value
parameter will be set. Whenever your property is called as a getter the
value parameter will be undefined.
For example, the following object will split any full name that you set
into a first name and last name components and save them.
{{{
contact = SC.Object.create({
fullName: function(key, value) {
if (value !== undefined) {
var parts = value.split(' ') ;
this.beginPropertyChanges()
.set('firstName', parts[0])
.set('lastName', parts[1])
.endPropertyChanges() ;
}
return this.getEach('firstName', 'lastName').compact().join(' ');
}.property('firstName','lastName')
}) ;
}}}
h2. Why Use The Same Method for Getters and Setters?
Most property-based frameworks expect you to write two methods for each
property but SproutCore only uses one. We do this because most of the time
when you write a setter is is basically a getter plus some extra work.
There is little added benefit in writing both methods when you can
conditionally exclude part of it. This helps to keep your code more
compact and easier to maintain.
@param dependentKeys {String...} optional set of dependent keys
@returns {Function} the declared function instance
*/
property: function() {
return SC.Function.property(this, arguments)
},
/**
You can call this method on a computed property to indicate that the
property is cacheable (or not cacheable). By default all computed
properties are not cached. Enabling this feature will allow SproutCore
to cache the return value of your computed property and to use that
value until one of your dependent properties changes or until you
invoke propertyDidChange() and name the computed property itself.
If you do not specify this option, computed properties are assumed to be
not cacheable.
@param {Boolean} aFlag optionally indicate cacheable or no, default YES
@returns {Function} reciever
*/
cacheable: function(aFlag) {
return SC.Function.cacheable(this, aFlag);
},
/**
Indicates that the computed property is volatile. Normally SproutCore
assumes that your computed property is idempotent. That is, calling
set() on your property more than once with the same value has the same
effect as calling it only once.
All non-computed properties are idempotent and normally you should make
your computed properties behave the same way. However, if you need to
make your property change its return value everytime your method is
called, you may chain this to your property to make it volatile.
If you do not specify this option, properties are assumed to be
non-volatile.
@param {Boolean} aFlag optionally indicate state, default to YES
@returns {Function} receiver
*/
idempotent: function(aFlag) {
return SC.Function.idempotent(this, aFlag)
},
/**
Declare that a function should observe an object at the named path. Note
that the path is used only to construct the observation one time.
@returns {Function} receiver
*/
observes: function(propertyPaths) {
return SC.Function.observes(this, arguments);
}
});
SC.CoreString = {
fmt: function(str, formats) {
// first, replace any ORDERED replacements.
var idx = 0; // the current index for non-numerical replacements
return str.replace(/%@([0-9]+)?/g, function(s, argIndex) {
argIndex = (argIndex) ? parseInt(argIndex,0) - 1 : idx++ ;
s = formats[argIndex];
return ((s === null) ? '(null)' : (s === undefined) ? '' : s).toString();
}) ;
},
loc: function(str, formats) {
var str = SC.STRINGS[str] || str;
return SC.String.fmt(str, arguments) ;
},
w: function(str) {
var ary = [], ary2 = str.split(' '), len = ary2.length, string, idx=0;
for (idx=0; idx<len; ++idx) {
string = ary2[idx] ;
if (string.length !== 0) ary.push(string) ; // skip empty strings
}
return ary ;
}
}
SC.mixin(String.prototype,
/** @lends Function.prototype */ {
// ..........................................................
// STRING ENHANCEMENT
//
// Interpolate string. looks for %@ or %@1; to control the order of params.
/**
Apply formatting options to the string. This will look for occurrences
of %@ in your string and substitute them with the arguments you pass into
this method. If you want to control the specific order of replacement,
you can add a number after the key as well to indicate which argument
you want to insert.
Ordered insertions are most useful when building loc strings where values
you need to insert may appear in different orders.
h3. Examples
{{{
"Hello %@ %@".fmt('John', 'Doe') => "Hello John Doe"
"Hello %@2, %@1".fmt('John', 'Doe') => "Hello Doe, John"
}}}
@param args {Object...} optional arguments
@returns {String} formatted string
*/
fmt: function() {
return SC.CoreString.fmt(this, arguments);
},
/**
Localizes the string. This will look up the reciever string as a key
in the current Strings hash. If the key matches, the loc'd value will be
used. The resulting string will also be passed through fmt() to insert
any variables.
@param args {Object...} optional arguments to interpolate also
@returns {String} the localized and formatted string.
*/
loc: function() {
return SC.CoreString.loc(this, arguments);
},
/**
Splits the string into words, separated by spaces. Empty strings are
removed from the results.
@returns {Array} an array of non-empty strings
*/
w: function() {
return SC.CoreString.w(this);
}
});
//
// DATE ENHANCEMENT
//
if (!Date.now) {
Date.now = function() {
return new Date().getTime() ;
};
}
/* end of original file: frameworks/sproutcore/frameworks/runtime/core.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/private/observer_set.js*/
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
// ........................................................................
// ObserverSet
//
/**
@namespace
This private class is used to store information about obversers on a
particular key. Note that this object is not observable. You create new
instances by calling SC.beget(SC.ObserverSet) ;
@since SproutCore 1.0
*/
SC.ObserverSet = {
/**
Adds the named target/method observer to the set. The method must be
a function, not a string.
Note that in debugging mode only, this method is overridden to also record
the name of the object and function that resulted in the target/method
being added.
*/
add: function(target, method, context) {
var targetGuid = SC.guidFor(target), methodGuid = SC.guidFor(method);
var targets = this._members, members = this.members;
// get the set of methods
var indexes = targets[targetGuid];
if ( !indexes ) indexes = targets[targetGuid] = {};
if (indexes[methodGuid] === undefined) indexes[methodGuid] = members.length;
else return;
members.push([target, method, context]);
},
/**
removes the named target/method observer from the set. If this is the
last method for the named target, then the number of targets will also
be reduced.
returns YES if the items was removed, NO if it was not found.
*/
remove: function(target, method) {
var targetGuid = SC.guidFor(target), methodGuid = SC.guidFor(method);
var indexes = this._members[targetGuid], members = this.members;
if( !indexes ) return false;
var index = indexes[methodGuid];
if ( index === undefined) return false;
if (index !== members.length - 1) {
var entry = (members[index] = members[members.length - 1]);
this._members[SC.guidFor(entry[0])][SC.guidFor(entry[1])] = index;
}
members.pop();
delete this._members[targetGuid][methodGuid];
return true;
},
/**
Invokes the target/method pairs in the receiver. Used by SC.RunLoop
Note: does not support context
*/
invokeMethods: function() {
var members = this.members, member;
for( var i=0, l=members.length; i<l; i++ ) {
member = members[i];
// method.call(target);
member[1].call(member[0]);
}
},
/**
Returns a new instance of the set with the contents cloned.
*/
clone: function() {
var newSet = SC.ObserverSet.create(), memberArray = this.members;
newSet._members = SC.clone(this._members);
var newMembers = newSet.members;
for( var i=0, l=memberArray.length; i<l; i++ ) {
newMembers[i] = SC.clone(memberArray[i]);
newMembers[i].length = 3;
}
return newSet;
},
/**
Creates a new instance of the observer set.
*/
create: function() {
return new SC.ObserverSet.constructor();
},
getMembers: function() {
return this.members.slice(0);
},
constructor: function() {
this._members = {};
this.members = [];
}
} ;
SC.ObserverSet.constructor.prototype = SC.ObserverSet;
SC.ObserverSet.slice = SC.ObserverSet.clone ;
/* end of original file: frameworks/sproutcore/frameworks/runtime/private/observer_set.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/mixins/observable.js*/
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
require('private/observer_set') ;
/*globals logChange */
/**
Set to YES to have all observing activity logged to the console. This
should be used for debugging only.
@property {Boolean}
*/
SC.LOG_OBSERVERS = NO ;
/**
@namespace
Key-Value-Observing (KVO) simply allows one object to observe changes to a
property on another object. It is one of the fundamental ways that models,
controllers and views communicate with each other in a SproutCore
application. Any object that has this module applied to it can be used in
KVO-operations.
This module is applied automatically to all objects that inherit from
SC.Object, which includes most objects bundled with the SproutCore
framework. You will not generally apply this module to classes yourself,
but you will use the features provided by this module frequently, so it is
important to understand how to use it.
h2. Enabling Key Value Observing
With KVO, you can write functions that will be called automatically whenever
a property on a particular object changes. You can use this feature to
reduce the amount of "glue code" that you often write to tie the various
parts of your application together.
To use KVO, just use the KVO-aware methods get() and set() to access
properties instead of accessing properties directly. Instead of writing:
{{{
var aName = contact.firstName ;
contact.firstName = 'Charles' ;
}}}
use:
{{{
var aName = contact.get('firstName') ;
contact.set('firstName', 'Charles') ;
}}}
get() and set() work just like the normal "dot operators" provided by
JavaScript but they provide you with much more power, including not only
observing but computed properties as well.
h2. Observing Property Changes
You typically observe property changes simply by adding the observes()
call to the end of your method declarations in classes that you write. For
example:
{{{
SC.Object.create({
valueObserver: function() {
// Executes whenever the "Value" property changes
}.observes('value')
}) ;
}}}
Although this is the most common way to add an observer, this capability is
actually built into the SC.Object class on top of two methods defined in
this mixin called addObserver() and removeObserver(). You can use these two
methods to add and remove observers yourself if you need to do so at run
time.
To add an observer for a property, just call:
{{{
object.addObserver('propertyKey', targetObject, targetAction) ;
}}}
This will call the 'targetAction' method on the targetObject to be called
whenever the value of the propertyKey changes.
h2. Observer Parameters
An observer function typically does not need to accept any parameters,
however you can accept certain arguments when writing generic observers.
An observer function can have the following arguments:
{{{
propertyObserver(target, key, value, revision) ;
}}}
- *target* - This is the object whose value changed. Usually this.
- *key* - The key of the value that changed
- *value* - this property is no longer used. It will always be null
- *revision* - this is the revision of the target object
h2. Implementing Manual Change Notifications
Sometimes you may want to control the rate at which notifications for
a property are delivered, for example by checking first to make sure
that the value has changed.
To do this, you need to implement a computed property for the property
you want to change and override automaticallyNotifiesObserversFor().
The example below will only notify if the "balance" property value actually
changes:
{{{
automaticallyNotifiesObserversFor: function(key) {
return (key === 'balance') ? NO : arguments.callee.base.apply(this,arguments) ;
},
balance: function(key, value) {
var balance = this._balance ;
if ((value !== undefined) && (balance !== value)) {
this.propertyWillChange(key) ;
balance = this._balance = value ;
this.propertyDidChange(key) ;
}
return balance ;
}
}}}
h1. Implementation Details
Internally, SproutCore keeps track of observable information by adding a
number of properties to the object adopting the observable. All of these
properties begin with "_kvo_" to separate them from the rest of your object.
@static
@since SproutCore 1.0
*/
SC.Observable = {
/**
Walk like that ol' duck
@property {Boolean}
*/
isObservable: YES,
/**
Determines whether observers should be automatically notified of changes
to a key.
If you are manually implementing change notifications for a property, you
can override this method to return NO for properties you do not want the
observing system to automatically notify for.
The default implementation always returns YES.
@param key {String} the key that is changing
@returns {Boolean} YES if automatic notification should occur.
*/
automaticallyNotifiesObserversFor: function(key) {
return YES;
},
// ..........................................
// PROPERTIES
//
// Use these methods to get/set properties. This will handle observing
// notifications as well as allowing you to define functions that can be
// used as properties.
/**
Retrieves the value of key from the object.
This method is generally very similar to using object[key] or object.key,
however it supports both computed properties and the unknownProperty
handler.
*Computed Properties*
Computed properties are methods defined with the property() modifier
declared at the end, such as:
{{{
fullName: function() {
return this.getEach('firstName', 'lastName').compact().join(' ');
}.property('firstName', 'lastName')
}}}
When you call get() on a computed property, the property function will be
called and the return value will be returned instead of the function
itself.
*Unknown Properties*
Likewise, if you try to call get() on a property whose values is
undefined, the unknownProperty() method will be called on the object.
If this method reutrns any value other than undefined, it will be returned
instead. This allows you to implement "virtual" properties that are
not defined upfront.
@param key {String} the property to retrieve
@returns {Object} the property value or undefined.
*/
get: function(key) {
var ret = this[key], cache ;
if (ret === undefined) {
return this.unknownProperty(key) ;
} else if (ret && ret.isProperty) {
if (ret.isCacheable) {
cache = this._kvo_cache ;
if (!cache) cache = this._kvo_cache = {};
return (cache[ret.cacheKey] !== undefined) ? cache[ret.cacheKey] : (cache[ret.cacheKey] = ret.call(this,key)) ;
} else return ret.call(this,key);
} else return ret ;
},
/**
Sets the key equal to value.
This method is generally very similar to calling object[key] = value or
object.key = value, except that it provides support for computed
properties, the unknownProperty() method and property observers.
*Computed Properties*
If you try to set a value on a key that has a computed property handler
defined (see the get() method for an example), then set() will call
that method, passing both the value and key instead of simply changing
the value itself. This is useful for those times when you need to
implement a property that is composed of one or more member
properties.
*Unknown Properties*
If you try to set a value on a key that is undefined in the target
object, then the unknownProperty() handler will be called instead. This
gives you an opportunity to implement complex "virtual" properties that
are not predefined on the obejct. If unknownProperty() returns
undefined, then set() will simply set the value on the object.
*Property Observers*
In addition to changing the property, set() will also register a
property change with the object. Unless you have placed this call
inside of a beginPropertyChanges() and endPropertyChanges(), any "local"
observers (i.e. observer methods declared on the same object), will be
called immediately. Any "remote" observers (i.e. observer methods
declared on another object) will be placed in a queue and called at a
later time in a coelesced manner.
*Chaining*
In addition to property changes, set() returns the value of the object
itself so you can do chaining like this:
{{{
record.set('firstName', 'Charles').set('lastName', 'Jolley');
}}}
@param key {String|Hash} the property to set
@param value {Object} the value to set or null.
@returns {SC.Observable}
*/
set: function(key, value) {
var func = this[key],
notify = this.automaticallyNotifiesObserversFor(key),
ret = value,
cachedep, cache, idx, dfunc ;
if(value === undefined && SC.typeOf(key) === SC.T_HASH) {
var hash = key;
for(key in hash) {
if (!hash.hasOwnProperty(key)) continue;
this.set(key, hash[key]);
}
return this;
}
// if there are any dependent keys and they use caching, then clear the
// cache. (If we're notifying, then propertyDidChange will do this for
// us.)
if (!notify && this._kvo_cacheable && (cache = this._kvo_cache)) {
// lookup the cached dependents for this key. if undefined, compute.
// note that if cachdep is set to null is means we figure out it has no
// cached dependencies already. this is different from undefined.
cachedep = this._kvo_cachedep;
if (!cachedep || (cachedep = cachedep[key])===undefined) {
cachedep = this._kvo_computeCachedDependentsFor(key);
}
if (cachedep) {
idx = cachedep.length;
while(--idx>=0) {
dfunc = cachedep[idx];
cache[dfunc.cacheKey] = cache[dfunc.lastSetValueKey] = undefined;
}
}
}
// set the value.
if (func && func.isProperty) {
cache = this._kvo_cache;
if (func.isVolatile || !cache || (cache[func.lastSetValueKey] !== value)) {
if (!cache) cache = this._kvo_cache = {};
cache[func.lastSetValueKey] = value ;
if (notify) this.propertyWillChange(key) ;
ret = func.call(this,key,value) ;
// update cached value
if (func.isCacheable) cache[func.cacheKey] = ret ;
if (notify) this.propertyDidChange(key, ret, YES) ;
}
} else if (func === undefined) {
if (notify) this.propertyWillChange(key) ;
this.unknownProperty(key,value) ;
if (notify) this.propertyDidChange(key, ret) ;
} else {
if (this[key] !== value) {
if (notify) this.propertyWillChange(key) ;
ret = this[key] = value ;
if (notify) this.propertyDidChange(key, ret) ;
}
}
return this ;
},
/**
Called whenever you try to get or set an undefined property.
This is a generic property handler. If you define it, it will be called
when the named property is not yet set in the object. The default does
nothing.
@param key {String} the key that was requested
@param value {Object} The value if called as a setter, undefined if called as a getter.
@returns {Object} The new value for key.
*/
unknownProperty: function(key,value) {
if (!(value === undefined)) { this[key] = value; }
return value ;
},
/**
Begins a grouping of property changes.
You can use this method to group property changes so that notifications
will not be sent until the changes are finished. If you plan to make a
large number of changes to an object at one time, you should call this
method at the beginning of the changes to suspend change notifications.
When you are done making changes, all endPropertyChanges() to allow
notification to resume.
@returns {SC.Observable}
*/
beginPropertyChanges: function() {
this._kvo_changeLevel = (this._kvo_changeLevel || 0) + 1;
return this;
},
/**
Ends a grouping of property changes.
You can use this method to group property changes so that notifications
will not be sent until the changes are finished. If you plan to make a
large number of changes to an object at one time, you should call
beginPropertyChanges() at the beginning of the changes to suspend change
notifications. When you are done making changes, call this method to allow
notification to resume.
@returns {SC.Observable}
*/
endPropertyChanges: function() {
this._kvo_changeLevel = (this._kvo_changeLevel || 1) - 1 ;
var level = this._kvo_changeLevel, changes = this._kvo_changes;
if ((level<=0) && changes && (changes.length>0) && !SC.Observers.isObservingSuspended) {
this._notifyPropertyObservers() ;
}
return this ;
},
/**
Notify the observer system that a property is about to change.
Sometimes you need to change a value directly or indirectly without
actually calling get() or set() on it. In this case, you can use this
method and propertyDidChange() instead. Calling these two methods
together will notify all observers that the property has potentially
changed value.
Note that you must always call propertyWillChange and propertyDidChange as
a pair. If you do not, it may get the property change groups out of order
and cause notifications to be delivered more often than you would like.
@param key {String} The property key that is about to change.
@returns {SC.Observable}
*/
propertyWillChange: function(key) {
return this ;
},
/**
Notify the observer system that a property has just changed.
Sometimes you need to change a value directly or indirectly without
actually calling get() or set() on it. In this case, you can use this
method and propertyWillChange() instead. Calling these two methods
together will notify all observers that the property has potentially
changed value.
Note that you must always call propertyWillChange and propertyDidChange as
a pair. If you do not, it may get the property change groups out of order
and cause notifications to be delivered more often than you would like.
@param key {String} The property key that has just changed.
@param value {Object} The new value of the key. May be null.
@returns {SC.Observable}
*/
propertyDidChange: function(key,value, _keepCache) {
this._kvo_revision = (this._kvo_revision || 0) + 1;
var level = this._kvo_changeLevel || 0,
cachedep, idx, dfunc, cache, func,
log = SC.LOG_OBSERVERS && !(this.LOG_OBSERVING===NO);
if (cache = this._kvo_cache) {
// clear any cached value
if (!_keepCache) {
func = this[key] ;
if (func && func.isProperty) {
cache[func.cacheKey] = cache[func.lastSetValueKey] = undefined ;
}
}
if (this._kvo_cacheable) {
// if there are any dependent keys and they use caching, then clear the
// cache. This is the same code as is in set. It is inlined for perf.
cachedep = this._kvo_cachedep;
if (!cachedep || (cachedep = cachedep[key])===undefined) {
cachedep = this._kvo_computeCachedDependentsFor(key);
}
if (cachedep) {
idx = cachedep.length;
while(--idx>=0) {
dfunc = cachedep[idx];
cache[dfunc.cacheKey] = cache[dfunc.lastSetValueKey] = undefined;
}
}
}
}
// save in the change set if queuing changes
var suspended = SC.Observers.isObservingSuspended;
if ((level > 0) || suspended) {
var changes = this._kvo_changes ;
if (!changes) changes = this._kvo_changes = SC.CoreSet.create() ;
changes.add(key) ;
if (suspended) {
if (log) console.log("%@%@: will not notify observers because observing is suspended".fmt(SC.KVO_SPACES,this));
SC.Observers.objectHasPendingChanges(this) ;
}
// otherwise notify property observers immediately
} else this._notifyPropertyObservers(key) ;
return this ;
},
// ..........................................
// DEPENDENT KEYS
//
/**
Use this to indicate that one key changes if other keys it depends on
change. Pass the key that is dependent and additional keys it depends
upon. You can either pass the additional keys inline as arguments or
in a single array.
You generally do not call this method, but instead pass dependent keys to
your property() method when you declare a computed property.
You can call this method during your init to register the keys that should
trigger a change notification for your computed properties.
@param {String} key the dependent key
@param {Array|String} dependentKeys one or more dependent keys
@returns {Object} this
*/
registerDependentKey: function(key, dependentKeys) {
var dependents = this._kvo_dependents,
func = this[key],
keys, idx, lim, dep, queue;
// normalize input.
if (typeof dependentKeys === "object" && (dependentKeys instanceof Array)) {
keys = dependentKeys;
lim = 0;
} else {
keys = arguments;
lim = 1;
}
idx = keys.length;
// define dependents if not defined already.
if (!dependents) this._kvo_dependents = dependents = {} ;
// for each key, build array of dependents, add this key...
// note that we ignore the first argument since it is the key...
while(--idx >= lim) {
dep = keys[idx] ;
// add dependent key to dependents array of key it depends on
queue = dependents[dep] ;
if (!queue) queue = dependents[dep] = [] ;
queue.push(key) ;
}
},
/** @private
Helper method used by computeCachedDependents. Just loops over the
array of dependent keys. If the passed function is cacheable, it will
be added to the queue. Also, recursively call on each keys dependent
keys.
@param {Array} queue the queue to add functions to
@param {Array} keys the array of dependent keys for this key
@param {Hash} dependents the _kvo_dependents cache
@param {SC.Set} seen already seen keys
@returns {void}
*/
_kvo_addCachedDependents: function(queue, keys, dependents, seen) {
var idx = keys.length,
func, key, deps ;
while(--idx >= 0) {
key = keys[idx];
seen.add(key);
// if the value for this key is a computed property, then add it to the
// set if it is cacheable, and process any of its dependent keys also.
func = this[key];
if (func && (func instanceof Function) && func.isProperty) {
if (func.isCacheable) queue.push(func); // handle this func
if ((deps = dependents[key]) && deps.length>0) { // and any dependents
this._kvo_addCachedDependents(queue, deps, dependents, seen);
}
}
}
},
/** @private
Called by set() whenever it needs to determine which cached dependent
keys to clear. Recursively searches dependent keys to determine all
cached property direcly or indirectly affected.
The return value is also saved for future reference
@param {String} key the key to compute
@returns {Array}
*/
_kvo_computeCachedDependentsFor: function(key) {
var cached = this._kvo_cachedep,
dependents = this._kvo_dependents,
keys = dependents ? dependents[key] : null,
queue, seen ;
if (!cached) cached = this._kvo_cachedep = {};
// if there are no dependent keys, then just set and return null to avoid
// this mess again.
if (!keys || keys.length===0) return cached[key] = null;
// there are dependent keys, so we need to do the work to find out if
// any of them or their dependent keys are cached.
queue = cached[key] = [];
seen = SC._TMP_SEEN_SET = (SC._TMP_SEEN_SET || SC.CoreSet.create());
seen.add(key);
this._kvo_addCachedDependents(queue, keys, dependents, seen);
seen.clear(); // reset
if (queue.length === 0) queue = cached[key] = null ; // turns out nothing
return queue ;
},
// ..........................................
// OBSERVERS
//
_kvo_for: function(kvoKey, type) {
var ret = this[kvoKey] ;
if (!this._kvo_cloned) this._kvo_cloned = {} ;
// if the item does not exist, create it. Unless type is passed,
// assume array.
if (!ret) {
ret = this[kvoKey] = (type === undefined) ? [] : type.create();
this._kvo_cloned[kvoKey] = YES ;
// if item does exist but has not been cloned, then clone it. Note
// that all types must implement copy().0
} else if (!this._kvo_cloned[kvoKey]) {
ret = this[kvoKey] = ret.copy();
this._kvo_cloned[kvoKey] = YES;
}
return ret ;
},
/**
Adds an observer on a property.
This is the core method used to register an observer for a property.
Once you call this method, anytime the key's value is set, your observer
will be notified. Note that the observers are triggered anytime the
value is set, regardless of whether it has actually changed. Your
observer should be prepared to handle that.
You can also pass an optional context parameter to this method. The
context will be passed to your observer method whenever it is triggered.
Note that if you add the same target/method pair on a key multiple times
with different context parameters, your observer will only be called once
with the last context you passed.
h2. Observer Methods
Observer methods you pass should generally have the following signature if
you do not pass a "context" parameter:
{{{
fooDidChange: function(sender, key, value, rev);
}}}
The sender is the object that changed. The key is the property that
changes. The value property is currently reserved and unused. The rev
is the last property revision of the object when it changed, which you can
use to detect if the key value has really changed or not.
If you pass a "context" parameter, the context will be passed before the
revision like so:
{{{
fooDidChange: function(sender, key, value, context, rev);
}}}
Usually you will not need the value, context or revision parameters at
the end. In this case, it is common to write observer methods that take
only a sender and key value as parameters or, if you aren't interested in
any of these values, to write an observer that has no parameters at all.
@param key {String} the key to observer
@param target {Object} the target object to invoke
@param method {String|Function} the method to invoke.
@param context {Object} optional context
@returns {SC.Object} self
*/
addObserver: function(key, target, method, context) {
var kvoKey, chain, chains, observers;
// normalize. if a function is passed to target, make it the method.
if (method === undefined) {
method = target; target = this ;
}
if (!target) target = this ;
if (typeof method === "string") method = target[method] ;
if (!method) throw "You must pass a method to addObserver()" ;
// Normalize key...
key = key.toString() ;
if (key.indexOf('.') >= 0) {
// create the chain and save it for later so we can tear it down if
// needed.
chain = SC._ChainObserver.createChain(this, key, target, method, context);
chain.masterTarget = target;
chain.masterMethod = method ;
// Save in set for chain observers.
this._kvo_for(SC.keyFor('_kvo_chains', key)).push(chain);
// Create observers if needed...
} else {
// Special case to support reduced properties. If the property
// key begins with '@' and its value is unknown, then try to get its
// value. This will configure the dependent keys if needed.
if ((this[key] === undefined) && (key.indexOf('@') === 0)) {
this.get(key) ;
}
if (target === this) target = null ; // use null for observers only.
kvoKey = SC.keyFor('_kvo_observers', key);
this._kvo_for(kvoKey, SC.ObserverSet).add(target, method, context);
this._kvo_for('_kvo_observed_keys', SC.CoreSet).add(key) ;
}
if (this.didAddObserver) this.didAddObserver(key, target, method);
return this;
},
/**
Remove an observer you have previously registered on this object. Pass
the same key, target, and method you passed to addObserver() and your
target will no longer receive notifications.
@returns {SC.Observable} reciever
*/
removeObserver: function(key, target, method) {
var kvoKey, chains, chain, observers, idx ;
// normalize. if a function is passed to target, make it the method.
if (method === undefined) {
method = target; target = this ;
}
if (!target) target = this ;
if (typeof method === "string") method = target[method] ;
if (!method) throw "You must pass a method to removeObserver()" ;
// if the key contains a '.', this is a chained observer.
key = key.toString() ;
if (key.indexOf('.') >= 0) {
// try to find matching chains
kvoKey = SC.keyFor('_kvo_chains', key);
if (chains = this[kvoKey]) {
// if chains have not been cloned yet, do so now.
chains = this._kvo_for(kvoKey) ;
// remove any chains
idx = chains.length;
while(--idx >= 0) {
chain = chains[idx];
if (chain && (chain.masterTarget===target) && (chain.masterMethod===method)) {
chains[idx] = chain.destroyChain() ;
}
}
}
// otherwise, just like a normal observer.
} else {
if (target === this) target = null ; // use null for observers only.
kvoKey = SC.keyFor('_kvo_observers', key) ;
if (observers = this[kvoKey]) {
// if observers have not been cloned yet, do so now
observers = this._kvo_for(kvoKey) ;
observers.remove(target, method) ;
if (observers.getMembers().length === 0) {
this._kvo_for('_kvo_observed_keys', SC.CoreSet).remove(key);
}
}
}
if (this.didRemoveObserver) this.didRemoveObserver(key, target, method);
return this;
},
/**
Returns YES if the object currently has observers registered for a
particular key. You can use this method to potentially defer performing
an expensive action until someone begins observing a particular property
on the object.
@param {String} key key to check
@returns {Boolean}
*/
hasObserverFor: function(key) {
SC.Observers.flush(this) ; // hookup as many observers as possible.
var observers = this[SC.keyFor('_kvo_observers', key)],
locals = this[SC.keyFor('_kvo_local', key)],
members ;
if (locals && locals.length>0) return YES ;
if (observers && observers.getMembers().length > 0) return YES ;
return NO ;
},
/**
This method will register any observers and computed properties saved on
the object. Normally you do not need to call this method youself. It
is invoked automatically just before property notifications are sent and
from the init() method of SC.Object. You may choose to call this
from your own initialization method if you are using SC.Observable in
a non-SC.Object-based object.
This method looks for several private variables, which you can setup,
to initialize:
- _observers: this should contain an array of key names for observers
you need to configure.
- _bindings: this should contain an array of key names that configure
bindings.
- _properties: this should contain an array of key names for computed
properties.
@returns {Object} this
*/
initObservable: function() {
if (this._observableInited) return ;
this._observableInited = YES ;
var loc, keys, key, value, observer, propertyPaths, propertyPathsLength,
len, ploc, path, dotIndex, root, propertyKey, keysLen;
// Loop through observer functions and register them
if (keys = this._observers) {
len = keys.length ;
for(loc=0;loc<len;loc++) {
key = keys[loc]; observer = this[key] ;
propertyPaths = observer.propertyPaths ;
propertyPathsLength = (propertyPaths) ? propertyPaths.length : 0 ;
for(ploc=0;ploc<propertyPathsLength;ploc++) {
path = propertyPaths[ploc] ;
dotIndex = path.indexOf('.') ;
// handle most common case, observing a local property
if (dotIndex < 0) {
this.addObserver(path, this, observer) ;
// next most common case, use a chained observer
} else if (path.indexOf('*') === 0) {
this.addObserver(path.slice(1), this, observer) ;
// otherwise register the observer in the observers queue. This
// will add the observer now or later when the named path becomes
// available.
} else {
root = null ;
// handle special cases for observers that look to the local root
if (dotIndex === 0) {
root = this; path = path.slice(1) ;
} else if (dotIndex===4 && path.slice(0,5) === 'this.') {
root = this; path = path.slice(5) ;
} else if (dotIndex<0 && path.length===4 && path === 'this') {
root = this; path = '';
}
SC.Observers.addObserver(path, this, observer, root);
}
}
}
}
// Add Bindings
this.bindings = []; // will be filled in by the bind() method.
if (keys = this._bindings) {
for(loc=0, keysLen = keys.length; loc < keysLen;loc++) {
// get propertyKey
key = keys[loc] ; value = this[key] ;
propertyKey = key.slice(0,-7) ; // contentBinding => content
this[key] = this.bind(propertyKey, value) ;
}
}
// Add Properties
if (keys = this._properties) {
for(loc=0, keysLen = keys.length; loc<keysLen;loc++) {
key = keys[loc];
if (value = this[key]) {
// activate cacheable only if needed for perf reasons
if (value.isCacheable) this._kvo_cacheable = YES;
// register dependent keys
if (value.dependentKeys && (value.dependentKeys.length>0)) {
this.registerDependentKey(key, value.dependentKeys) ;
}
}
}
}
},
// ..........................................
// NOTIFICATION
//
/**
Returns an array with all of the observers registered for the specified
key. This is intended for debugging purposes only. You generally do not
want to rely on this method for production code.
@params key {String} the key to evaluate
@returns {Array} array of Observer objects, describing the observer.
*/
observersForKey: function(key) {
var observers = this._kvo_for('_kvo_observers', key) ;
return observers.getMembers() ;
},
// this private method actually notifies the observers for any keys in the
// observer queue. If you pass a key it will be added to the queue.
_notifyPropertyObservers: function(key) {
if (!this._observableInited) this.initObservable() ;
SC.Observers.flush(this) ; // hookup as many observers as possible.
var log = SC.LOG_OBSERVERS && !(this.LOG_OBSERVING===NO),
observers, changes, dependents, starObservers, idx, keys, rev,
members, membersLength, member, memberLoc, target, method, loc, func,
context, spaces, cache ;
if (log) {
spaces = SC.KVO_SPACES = (SC.KVO_SPACES || '') + ' ';
console.log('%@%@: notifying observers after change to key "%@"'.fmt(spaces, this, key));
}
// Get any starObservers -- they will be notified of all changes.
starObservers = this['_kvo_observers_*'] ;
// prevent notifications from being sent until complete
this._kvo_changeLevel = (this._kvo_changeLevel || 0) + 1;
// keep sending notifications as long as there are changes
while(((changes = this._kvo_changes) && (changes.length > 0)) || key) {
// increment revision
rev = ++this.propertyRevision ;
// save the current set of changes and swap out the kvo_changes so that
// any set() calls by observers will be saved in a new set.
if (!changes) changes = SC.CoreSet.create() ;
this._kvo_changes = null ;
// Add the passed key to the changes set. If a '*' was passed, then
// add all keys in the observers to the set...
// once finished, clear the key so the loop will end.
if (key === '*') {
changes.add('*') ;
changes.addEach(this._kvo_for('_kvo_observed_keys', SC.CoreSet));
} else if (key) changes.add(key) ;
// Now go through the set and add all dependent keys...
if (dependents = this._kvo_dependents) {
// NOTE: each time we loop, we check the changes length, this
// way any dependent keys added to the set will also be evaluated...
for(idx=0;idx<changes.length;idx++) {
key = changes[idx] ;
keys = dependents[key] ;
// for each dependent key, add to set of changes. Also, if key
// value is a cacheable property, clear the cached value...
if (keys && (loc = keys.length)) {
if (log) {
console.log("%@...including dependent keys for %@: %@".fmt(spaces, key, keys));
}
cache = this._kvo_cache;
if (!cache) cache = this._kvo_cache = {};
while(--loc >= 0) {
changes.add(key = keys[loc]);
if (func = this[key]) {
this[func.cacheKey] = undefined;
cache[func.cacheKey] = cache[func.lastSetValueKey] = undefined;
} // if (func=)
} // while (--loc)
} // if (keys &&
} // for(idx...
} // if (dependents...)
// now iterate through all changed keys and notify observers.
while(changes.length > 0) {
key = changes.pop() ; // the changed key
// find any observers and notify them...
observers = this[SC.keyFor('_kvo_observers', key)];
if (observers) {
// We need to clone the 'members' structure here in case any of the
// observers we're about to notify happen to remove observers for
// this key, which would mutate the structure underneath us.
// (Cloning it rather than mutating gives us a clear policy: if you
// were registered as an observer at the time notification begins,
// you will be notified, regardless of whether you're removed as an
// observer during that round of notification. Similarly, if you're
// added as an observer during the notification round by another
// observer, you will not be notified until the next time.)
members = SC.clone(observers.getMembers()) ;
membersLength = members.length ;
for(memberLoc=0;memberLoc < membersLength; memberLoc++) {
member = members[memberLoc] ;
if (member[3] === rev) continue ; // skip notified items.
if(!member[1]) console.log(member);
target = member[0] || this;
method = member[1] ;
context = member[2];
member[3] = rev;
if (log) console.log('%@...firing observer on %@ for key "%@"'.fmt(spaces, target, key));
if (context !== undefined) {
method.call(target, this, key, null, context, rev);
} else {
method.call(target, this, key, null, rev) ;
}
}
}
// look for local observers. Local observers are added by SC.Object
// as an optimization to avoid having to add observers for every
// instance when you are just observing your local object.
members = this[SC.keyFor('_kvo_local', key)];
if (members) {
// Note: Since, unlike above, we don't expect local observers to be
// removed in general, we will not clone 'members'.
membersLength = members.length ;
for(memberLoc=0;memberLoc<membersLength;memberLoc++) {
member = members[memberLoc];
method = this[member] ; // try to find observer function
if (method) {
if (log) console.log('%@...firing local observer %@.%@ for key "%@"'.fmt(spaces, this, member, key));
method.call(this, this, key, null, rev);
}
}
}
// if there are starObservers, do the same thing for them
if (starObservers && key !== '*') {
// We clone the structure per the justification, above, for regular
// observers.
members = SC.clone(starObservers.getMembers()) ;
membersLength = members.length ;
for(memberLoc=0;memberLoc < membersLength; memberLoc++) {
member = members[memberLoc] ;
target = member[0] || this;
method = member[1] ;
context = member[2] ;
if (log) console.log('%@...firing * observer on %@ for key "%@"'.fmt(spaces, target, key));
if (context !== undefined) {
method.call(target, this, key, null, context, rev);
} else {
method.call(target, this, key, null, rev) ;
}
}
}
// if there is a default property observer, call that also
if (this.propertyObserver) {
if (log) console.log('%@...firing %@.propertyObserver for key "%@"'.fmt(spaces, this, key));
this.propertyObserver(this, key, null, rev);
}
} // while(changes.length>0)
// changes set should be empty. release it for reuse
if (changes) changes.destroy() ;
// key is no longer needed; clear it to avoid infinite loops
key = null ;
} // while (changes)
// done with loop, reduce change level so that future sets can resume
this._kvo_changeLevel = (this._kvo_changeLevel || 1) - 1;
if (log) SC.KVO_SPACES = spaces.slice(0, -2);
return YES ; // finished successfully
},
// ..........................................
// BINDINGS
//
/**
Manually add a new binding to an object. This is the same as doing
the more familiar propertyBinding: 'property.path' approach.
@param {String} toKey the key to bind to
@param {Object} target target or property path to bind from
@param {String|Function} method method for target to bind from
@returns {SC.Binding} new binding instance
*/
bind: function(toKey, target, method) {
var binding , pathType;
// normalize...
if (method !== undefined) target = [target, method];
// if a string or array (i.e. tuple) is passed, convert this into a
// binding. If a binding default was provided, use that.
pathType = typeof target;
if (pathType === "string" || (pathType === "object" && (target instanceof Array))) {
binding = this[toKey + 'BindingDefault'] || SC.Binding;
binding = binding.beget().from(target) ;
} else binding = target ;
// finish configuring the binding and then connect it.
binding = binding.to(toKey, this).connect() ;
this.bindings.push(binding) ;
return binding ;
},
/**
didChangeFor allows you to determine if a property has changed since the
last time the method was called. You must pass a unique context as the
first parameter (so didChangeFor can identify which method is calling it),
followed by a list of keys that should be checked for changes.
For example, in your render method you might pass the following context:
if (this.didChangeFor('render','height','width')) {
// Only render if changed
}
In your view's update method, you might instead pass 'update':
if (this.didChangeFor('update', 'height', 'width')) {
// Only update height and width properties
}
This method works by comparing property revision counts. Every time a
property changes, an internal counter is incremented. When didChangeFor is
invoked, the current revision count of the property is compared to the
revision count from the last time this method was called.
@param {String|Object} context a unique identifier
@param {String…} propertyNames one or more property names
*/
didChangeFor: function(context) {
var valueCache, revisionCache, seenValues, seenRevisions, ret,
currentRevision, idx, key, value;
context = SC.hashFor(context) ; // get a hash key we can use in caches.
// setup caches...
valueCache = this._kvo_didChange_valueCache ;
if (!valueCache) valueCache = this._kvo_didChange_valueCache = {};
revisionCache = this._kvo_didChange_revisionCache;
if (!revisionCache) revisionCache=this._kvo_didChange_revisionCache={};
// get the cache of values and revisions already seen in this context
seenValues = valueCache[context] || {} ;
seenRevisions = revisionCache[context] || {} ;
// prepare too loop!
ret = false ;
currentRevision = this._kvo_revision || 0 ;
idx = arguments.length ;
while(--idx >= 1) { // NB: loop only to 1 to ignore context arg.
key = arguments[idx];
// has the kvo revision changed since the last time we did this?
if (seenRevisions[key] != currentRevision) {
// yes, check the value with the last seen value
value = this.get(key) ;
if (seenValues[key] !== value) {
ret = true ; // did change!
seenValues[key] = value;
}
}
seenRevisions[key] = currentRevision;
}
valueCache[context] = seenValues ;
revisionCache[context] = seenRevisions ;
return ret ;
},
/**
Sets the property only if the passed value is different from the
current value. Depending on how expensive a get() is on this property,
this may be more efficient.
NOTE: By default, the set() method will not set the value unless it has
changed. However, this check can skipped by setting .property().indempotent(NO)
setIfChanged() may be useful in this case.
@param key {String|Hash} the key to change
@param value {Object} the value to change
@returns {SC.Observable}
*/
setIfChanged: function(key, value) {
if(value === undefined && SC.typeOf(key) === SC.T_HASH) {
var hash = key;
for(key in hash) {
if (!hash.hasOwnProperty(key)) continue;
this.setIfChanged(key, hash[key]);
}
return this;
}
return (this.get(key) !== value) ? this.set(key, value) : this ;
},
/**
Navigates the property path, returning the value at that point.
If any object in the path is undefined, returns undefined.
*/
getPath: function(path) {
var tuple = SC.tupleForPropertyPath(path, this) ;
if (tuple === null || tuple[0] === null) return undefined ;
return tuple[0].get(tuple[1]) ;
},
/**
Navigates the property path, finally setting the value.
@param path {String} the property path to set
@param value {Object} the value to set
@returns {SC.Observable}
*/
setPath: function(path, value) {
if (path.indexOf('.') >= 0) {
var tuple = SC.tupleForPropertyPath(path, this) ;
if (!tuple || !tuple[0]) return null ;
tuple[0].set(tuple[1], value) ;
} else this.set(path, value) ; // shortcut
return this;
},
/**
Navigates the property path, finally setting the value but only if
the value does not match the current value. This will avoid sending
unecessary change notifications.
@param path {String} the property path to set
@param value {Object} the value to set
@returns {Object} this
*/
setPathIfChanged: function(path, value) {
if (path.indexOf('.') >= 0) {
var tuple = SC.tupleForPropertyPath(path, this) ;
if (!tuple || !tuple[0]) return null ;
if (tuple[0].get(tuple[1]) !== value) {
tuple[0].set(tuple[1], value) ;
}
} else this.setIfChanged(path, value) ; // shortcut
return this;
},
/**
Convenience method to get an array of properties.
Pass in multiple property keys or an array of property keys. This
method uses getPath() so you can also pass key paths.
@returns {Array} Values of property keys.
*/
getEach: function() {
var keys = SC.A(arguments),
ret = [], idx, idxLen;
for(idx=0, idxLen = keys.length; idx < idxLen;idx++) {
ret[ret.length] = this.getPath(keys[idx]);
}
return ret ;
},
/**
Increments the value of a property.
@param key {String} property name
@param increment {Number} the amount to increment (optional)
@returns {Number} new value of property
*/
incrementProperty: function(key,increment) {
if (!increment) increment = 1;
this.set(key,(this.get(key) || 0)+increment);
return this.get(key) ;
},
/**
Decrements the value of a property.
@param key {String} property name
@param increment {Number} the amount to decrement (optional)
@returns {Number} new value of property
*/
decrementProperty: function(key,increment) {
if (!increment) increment = 1;
this.set(key,(this.get(key) || 0) - increment) ;
return this.get(key) ;
},
/**
Inverts a property. Property should be a bool.
@param key {String} property name
@param value {Object} optional parameter for "true" value
@param alt {Object} optional parameter for "false" value
@returns {Object} new value
*/
toggleProperty: function(key,value,alt) {
if (value === undefined) value = true ;
if (alt === undefined) alt = false ;
value = (this.get(key) == value) ? alt : value ;
this.set(key,value);
return this.get(key) ;
},
/**
Convenience method to call propertyWillChange/propertyDidChange.
Sometimes you need to notify observers that a property has changed value
without actually changing this value. In those cases, you can use this
method as a convenience instead of calling propertyWillChange() and
propertyDidChange().
@param key {String} The property key that has just changed.
@param value {Object} The new value of the key. May be null.
@returns {SC.Observable}
*/
notifyPropertyChange: function(key, value) {
this.propertyWillChange(key) ;
this.propertyDidChange(key, value) ;
return this;
},
/**
Notifies all of observers of a property changes.
Sometimes when you make a major update to your object, it is cheaper to
simply notify all observers that their property might have changed than
to figure out specifically which properties actually did change.
In those cases, you can simply call this method to notify all property
observers immediately. Note that this ignores property groups.
@returns {SC.Observable}
*/
allPropertiesDidChange: function() {
this._kvo_cache = null; //clear cached props
this._notifyPropertyObservers('*') ;
return this ;
},
addProbe: function(key) { this.addObserver(key,SC.logChange); },
removeProbe: function(key) { this.removeObserver(key,SC.logChange); },
/**
Logs the named properties to the console.
@param {String...} propertyNames one or more property names
*/
logProperty: function() {
var props = SC.$A(arguments),
prop, propsLen, idx;
for(idx=0, propsLen = props.length; idx<propsLen; idx++) {
prop = props[idx] ;
console.log('%@:%@: '.fmt(SC.guidFor(this), prop), this.get(prop)) ;
}
},
propertyRevision: 1
} ;
/** @private used by addProbe/removeProbe */
SC.logChange = function logChange(target, key, value) {
console.log("CHANGE: %@[%@] =>".fmt(target, key), target.get(key));
};
/**
Retrieves a property from an object, using get() if the
object implements SC.Observable.
@param {Object} object the object to query
@param {String} key the property to retrieve
*/
SC.mixin(SC, {
get: function(object, key) {
if (!object) return undefined;
if (key === undefined) return this[object];
if (object.get) return object.get(key);
return object[key];
}
});
// Make all Array's observable
SC.mixin(Array.prototype, SC.Observable) ;
/* end of original file: frameworks/sproutcore/frameworks/runtime/mixins/observable.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/system/enumerator.js*/
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
/**
@class
An object that iterates over all of the values in an object.
An instance of this object is returned everytime you call the
enumerator() method on an object that implements the SC.Enumerable mixin.
Once you create an enumerator instance, you can call nextObject() on it
until you can iterated through the entire collection. Once you have
exhausted the enumerator, you can reuse it if you want by calling reset().
@since SproutCore 1.0
*/
SC.Enumerator = function(enumerableObject) {
this.enumerable = enumerableObject ;
this.reset() ;
return this ;
} ;
SC.Enumerator.prototype = {
/**
Returns the next object in the enumeration or undefined when complete.
@returns {Object} the next object or undefined
*/
nextObject: function() {
var index = this._index ;
var len = this._length;
if (index >= len) return undefined ; // nothing to do
// get the value
var ret = this.enumerable.nextObject(index, this._previousObject, this._context) ;
this._previousObject = ret ;
this._index = index + 1 ;
if (index >= len) {
this._context = SC.Enumerator._pushContext(this._context);
}
return ret ;
},
/**
Resets the enumerator to the beginning. This is a nice way to reuse
an existing enumerator.
@returns {Object} this
*/
reset: function() {
var e = this.enumerable ;
if (!e) throw SC.$error("Enumerator has been destroyed");
this._length = e.get ? e.get('length') : e.length ;
var len = this._length;
this._index = 0;
this._previousObject = null ;
this._context = (len > 0) ? SC.Enumerator._popContext() : null;
},
/**
Releases the enumerators enumerable object. You cannot use this object
anymore. This is not often needed but it is useful when you need to
make sure memory gets cleared.
@returns {Object} null
*/
destroy: function() {
this.enumerable = this._length = this._index = this._previousObject = this._context = null;
}
} ;
/**
Use this method to manually create a new Enumerator object. Usually you
will not access this method directly but instead call enumerator() on the
item you want to enumerate.
@param {SC.Enumerable} enumerableObject enumerable object.
@returns {SC.Enumerator} the enumerator
*/
SC.Enumerator.create = function(enumerableObject) {
return new SC.Enumerator(enumerableObject) ;
};
// Private context caching methods. This avoids recreating lots of context
// objects.
SC.Enumerator._popContext = function() {
var ret = this._contextCache ? this._contextCache.pop() : null ;
return ret || {} ;
} ;
SC.Enumerator._pushContext = function(context) {
this._contextCache = this._contextCache || [] ;
var cache = this._contextCache;
cache.push(context);
return null ;
};
/* end of original file: frameworks/sproutcore/frameworks/runtime/system/enumerator.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/mixins/enumerable.js*/
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
require('core') ;
require('system/enumerator');
/*globals Prototype */
/**
@namespace
This mixin defines the common interface implemented by enumerable objects
in SproutCore. Most of these methods follow the standard Array iteration
API defined up to JavaScript 1.8 (excluding language-specific features that
cannot be emulated in older versions of JavaScript).
This mixin is applied automatically to the Array class on page load, so you
can use any of these methods on simple arrays. If Array already implements
one of these methods, the mixin will not override them.
h3. Writing Your Own Enumerable
To make your own custom class enumerable, you need two items:
1. You must have a length property. This property should change whenever
the number of items in your enumerable object changes. If you using this
with an SC.Object subclass, you should be sure to change the length
property using set().
2. If you must implement nextObject(). See documentation.
Once you have these two methods implement, apply the SC.Enumerable mixin
to your class and you will be able to enumerate the contents of your object
like any other collection.
h3. Using SproutCore Enumeration with Other Libraries
Many other libraries provide some kind of iterator or enumeration like
facility. This is often where the most common API conflicts occur.
SproutCore's API is designed to be as friendly as possible with other
libraries by implementing only methods that mostly correspond to the
JavaScript 1.8 API.
@since SproutCore 1.0
*/
SC.Enumerable = {
/**
Walk like a duck.
@property {Boolean}
*/
isEnumerable: YES,
/**
Implement this method to make your class enumerable.
This method will be call repeatedly during enumeration. The index value
will always begin with 0 and increment monotonically. You don't have to
rely on the index value to determine what object to return, but you should
always check the value and start from the beginning when you see the
requested index is 0.
The previousObject is the object that was returned from the last call
to nextObject for the current iteration. This is a useful way to
manage iteration if you are tracing a linked list, for example.
Finally the context paramter will always contain a hash you can use as
a "scratchpad" to maintain any other state you need in order to iterate
properly. The context object is reused and is not reset between
iterations so make sure you setup the context with a fresh state whenever
the index parameter is 0.
Generally iterators will continue to call nextObject until the index
reaches the your current length-1. If you run out of data before this
time for some reason, you should simply return undefined.
The default impementation of this method simply looks up the index.
This works great on any Array-like objects.
@param index {Number} the current index of the iteration
@param previousObject {Object} the value returned by the last call to nextObject.
@param context {Object} a context object you can use to maintain state.
@returns {Object} the next object in the iteration or undefined
*/
nextObject: function(index, previousObject, context) {
return this.objectAt ? this.objectAt(index) : this[index] ;
},
/**
Helper method returns the first object from a collection. This is usually
used by bindings and other parts of the framework to extract a single
object if the enumerable contains only one item.
If you override this method, you should implement it so that it will
always return the same value each time it is called. If your enumerable
contains only one object, this method should always return that object.
If your enumerable is empty, this method should return undefined.
@returns {Object} the object or undefined
*/
firstObject: function() {
if (this.get('length')===0) return undefined ;
if (this.objectAt) return this.objectAt(0); // support arrays out of box
// handle generic enumerables
var context = SC.Enumerator._popContext(), ret;
ret = this.nextObject(0, null, context);
context = SC.Enumerator._pushContext(context);
return ret ;
}.property(),
/**
Helper method returns the last object from a collection.
@returns {Object} the object or undefined
*/
lastObject: function() {
var len = this.get('length');
if (len===0) return undefined ;
if (this.objectAt) return this.objectAt(len-1); // support arrays out of box
}.property(),
/**
Returns a new enumerator for this object. See SC.Enumerator for
documentation on how to use this object. Enumeration is an alternative
to using one of the other iterators described here.
@returns {SC.Enumerator} an enumerator for the receiver
*/
enumerator: function() { return SC.Enumerator.create(this); },
/**
Iterates through the enumerable, calling the passed function on each
item. This method corresponds to the forEach() method defined in
JavaScript 1.6.
The callback method you provide should have the following signature (all
parameters are optional):
{{{
function(item, index, enumerable) ;
}}}
- *item* is the current item in the iteration.
- *index* is the current index in the iteration
- *enumerable* is the enumerable object itself.
Note that in addition to a callback, you can also pass an optional target
object that will be set as "this" on the context. This is a good way
to give your iterator function access to the current object.
@params callback {Function} the callback to execute
@params target {Object} the target object to use
@returns {Object} this
*/
forEach: function(callback, target) {
if (typeof callback !== "function") throw new TypeError() ;
var len = this.get ? this.get('length') : this.length ;
if (target === undefined) target = null;
var last = null ;
var context = SC.Enumerator._popContext();
for(var idx=0;idx<len;idx++) {
var next = this.nextObject(idx, last, context) ;
callback.call(target, next, idx, this);
last = next ;
}
last = null ;
context = SC.Enumerator._pushContext(context);
return this ;
},
/**
Retrieves the named value on each member object. This is more efficient
than using one of the wrapper methods defined here. Objects that
implement SC.Observable will use the get() method, otherwise the property
will be accessed directly.
@param {String} key the key to retrieve
@returns {Array} extracted values
*/
getEach: function(key) {
return this.map(function(next) {
return next ? (next.get ? next.get(key) : next[key]) : null;
}, this);
},
/**
Sets the value on the named property for each member. This is more
efficient than using other methods defined on this helper. If the object
implements SC.Observable, the value will be changed to set(), otherwise
it will be set directly. null objects are skipped.
@param {String} key the key to set
@param {Object} value the object to set
@returns {Object} receiver
*/
setEach: function(key, value) {
this.forEach(function(next) {
if (next) {
if (next.set) next.set(key, value) ;
else next[key] = value ;
}
}, this);
return this ;
},
/**
Maps all of the items in the enumeration to another value, returning
a new array. This method corresponds to map() defined in JavaScript 1.6.
The callback method you provide should have the following signature (all
parameters are optional):
{{{
function(item, index, enumerable) ;
}}}
- *item* is the current item in the iteration.
- *index* is the current index in the iteration
- *enumerable* is the enumerable object itself.
It should return the mapped value.
Note that in addition to a callback, you can also pass an optional target
object that will be set as "this" on the context. This is a good way
to give your iterator function access to the current object.
@params callback {Function} the callback to execute
@params target {Object} the target object to use
@returns {Array} The mapped array.
*/
map: function(callback, target) {
if (typeof callback !== "function") throw new TypeError() ;
var len = this.get ? this.get('length') : this.length ;
if (target === undefined) target = null;
var ret = [];
var last = null ;
var context = SC.Enumerator._popContext();
for(var idx=0;idx<len;idx++) {
var next = this.nextObject(idx, last, context) ;
ret[idx] = callback.call(target, next, idx, this) ;
last = next ;
}
last = null ;
context = SC.Enumerator._pushContext(context);
return ret ;
},
/**
Similar to map, this specialized function returns the value of the named
property on all items in the enumeration.
@params key {String} name of the property
@returns {Array} The mapped array.
*/
mapProperty: function(key) {
return this.map(function(next) {
return next ? (next.get ? next.get(key) : next[key]) : null;
});
},
/**
Returns an array with all of the items in the enumeration that the passed
function returns YES for. This method corresponds to filter() defined in
JavaScript 1.6.
The callback method you provide should have the following signature (all
parameters are optional):
{{{
function(item, index, enumerable) ;
}}}
- *item* is the current item in the iteration.
- *index* is the current index in the iteration
- *enumerable* is the enumerable object itself.
It should return the YES to include the item in the results, NO otherwise.
Note that in addition to a callback, you can also pass an optional target
object that will be set as "this" on the context. This is a good way
to give your iterator function access to the current object.
@params callback {Function} the callback to execute
@params target {Object} the target object to use
@returns {Array} A filtered array.
*/
filter: function(callback, target) {
if (typeof callback !== "function") throw new TypeError() ;
var len = this.get ? this.get('length') : this.length ;
if (target === undefined) target = null;
var ret = [];
var last = null ;
var context = SC.Enumerator._popContext();
for(var idx=0;idx<len;idx++) {
var next = this.nextObject(idx, last, context) ;
if(callback.call(target, next, idx, this)) ret.push(next) ;
last = next ;
}
last = null ;
context = SC.Enumerator._pushContext(context);
return ret ;
},
/**
Returns an array sorted by the value of the passed key parameters.
null objects will be sorted first. You can pass either an array of keys
or multiple parameters which will act as key names
@param {String} key one or more key names
@returns {Array}
*/
sortProperty: function(key) {
var keys = (typeof key === SC.T_STRING) ? arguments : key,
len = keys.length,
src;
// get the src array to sort
if (this instanceof Array) src = this;
else {
src = [];
this.forEach(function(i) { src.push(i); });
}
if (!src) return [];
return src.sort(function(a,b) {
var idx, key, aValue, bValue, ret = 0;
for(idx=0;ret===0 && idx<len;idx++) {
key = keys[idx];
aValue = a ? (a.get ? a.get(key) : a[key]) : null;
bValue = b ? (b.get ? b.get(key) : b[key]) : null;
ret = SC.compare(aValue, bValue);
}
return ret ;
});
},
/**
Returns an array with just the items with the matched property. You
can pass an optional second argument with the target value. Otherwise
this will match any property that evaluates to true.
@params key {String} the property to test
@param value {String} optional value to test against.
@returns {Array} filtered array
*/
filterProperty: function(key, value) {
var len = this.get ? this.get('length') : this.length ;
var ret = [];
var last = null ;
var context = SC.Enumerator._popContext();
for(var idx=0;idx<len;idx++) {
var next = this.nextObject(idx, last, context) ;
var cur = next ? (next.get ? next.get(key) : next[key]) : null;
var matched = (value === undefined) ? !!cur : SC.isEqual(cur, value);
if (matched) ret.push(next) ;
last = next ;
}
last = null ;
context = SC.Enumerator._pushContext(context);
return ret ;
},
/**
Returns the first item in the array for which the callback returns YES.
This method works similar to the filter() method defined in JavaScript 1.6
except that it will stop working on the array once a match is found.
The callback method you provide should have the following signature (all
parameters are optional):
{{{
function(item, index, enumerable) ;
}}}
- *item* is the current item in the iteration.
- *index* is the current index in the iteration
- *enumerable* is the enumerable object itself.
It should return the YES to include the item in the results, NO otherwise.
Note that in addition to a callback, you can also pass an optional target
object that will be set as "this" on the context. This is a good way
to give your iterator function access to the current object.
@params callback {Function} the callback to execute
@params target {Object} the target object to use
@returns {Object} Found item or null.
*/
find: function(callback, target) {
var len = this.get ? this.get('length') : this.length ;
if (target === undefined) target = null;
var last = null, next, found = NO, ret = null ;
var context = SC.Enumerator._popContext();
for(var idx=0;idx<len && !found;idx++) {
next = this.nextObject(idx, last, context) ;
if (found = callback.call(target, next, idx, this)) ret = next ;
last = next ;
}
next = last = null ;
context = SC.Enumerator._pushContext(context);
return ret ;
},
/**
Returns an the first item with a property matching the passed value. You
can pass an optional second argument with the target value. Otherwise
this will match any property that evaluates to true.
This method works much like the more generic find() method.
@params key {String} the property to test
@param value {String} optional value to test against.
@returns {Object} found item or null
*/
findProperty: function(key, value) {
var len = this.get ? this.get('length') : this.length ;
var found = NO, ret = null, last = null, next, cur ;
var context = SC.Enumerator._popContext();
for(var idx=0;idx<len && !found;idx++) {
next = this.nextObject(idx, last, context) ;
cur = next ? (next.get ? next.get(key) : next[key]) : null;
found = (value === undefined) ? !!cur : SC.isEqual(cur, value);
if (found) ret = next ;
last = next ;
}
last = next = null ;
context = SC.Enumerator._pushContext(context);
return ret ;
},
/**
Returns YES if the passed function returns YES for every item in the
enumeration. This corresponds with the every() method in JavaScript 1.6.
The callback method you provide should have the following signature (all
parameters are optional):
{{{
function(item, index, enumerable) ;
}}}
- *item* is the current item in the iteration.
- *index* is the current index in the iteration
- *enumerable* is the enumerable object itself.
It should return the YES or NO.
Note that in addition to a callback, you can also pass an optional target
object that will be set as "this" on the context. This is a good way
to give your iterator function access to the current object.
h4. Example Usage
{{{
if (people.every(isEngineer)) { Paychecks.addBigBonus(); }
}}}
@params callback {Function} the callback to execute
@params target {Object} the target object to use
@returns {Boolean}
*/
every: function(callback, target) {
if (typeof callback !== "function") throw new TypeError() ;
var len = this.get ? this.get('length') : this.length ;
if (target === undefined) target = null;
var ret = YES;
var last = null ;
var context = SC.Enumerator._popContext();
for(var idx=0;ret && (idx<len);idx++) {
var next = this.nextObject(idx, last, context) ;
if(!callback.call(target, next, idx, this)) ret = NO ;
last = next ;
}
last = null ;
context = SC.Enumerator._pushContext(context);
return ret ;
},
/**
Returns YES if the passed property resolves to true for all items in the
enumerable. This method is often simpler/faster than using a callback.
@params key {String} the property to test
@param value {String} optional value to test against.
@returns {Array} filtered array
*/
everyProperty: function(key, value) {
var len = this.get ? this.get('length') : this.length ;
var ret = YES;
var last = null ;
var context = SC.Enumerator._popContext();
for(var idx=0;ret && (idx<len);idx++) {
var next = this.nextObject(idx, last, context) ;
var cur = next ? (next.get ? next.get(key) : next[key]) : null;
ret = (value === undefined) ? !!cur : SC.isEqual(cur, value);
last = next ;
}
last = null ;
context = SC.Enumerator._pushContext(context);
return ret ;
},
/**
Returns YES if the passed function returns true for any item in the
enumeration. This corresponds with the every() method in JavaScript 1.6.
The callback method you provide should have the following signature (all
parameters are optional):
{{{
function(item, index, enumerable) ;
}}}
- *item* is the current item in the iteration.
- *index* is the current index in the iteration
- *enumerable* is the enumerable object itself.
It should return the YES to include the item in the results, NO otherwise.
Note that in addition to a callback, you can also pass an optional target
object that will be set as "this" on the context. This is a good way
to give your iterator function access to the current object.
h4. Usage Example
{{{
if (people.some(isManager)) { Paychecks.addBiggerBonus(); }
}}}
@params callback {Function} the callback to execute
@params target {Object} the target object to use
@returns {Array} A filtered array.
*/
some: function(callback, target) {
if (typeof callback !== "function") throw new TypeError() ;
var len = this.get ? this.get('length') : this.length ;
if (target === undefined) target = null;
var ret = NO;
var last = null ;
var context = SC.Enumerator._popContext();
for(var idx=0;(!ret) && (idx<len);idx++) {
var next = this.nextObject(idx, last, context) ;
if(callback.call(target, next, idx, this)) ret = YES ;
last = next ;
}
last = null ;
context = SC.Enumerator._pushContext(context);
return ret ;
},
/**
Returns YES if the passed property resolves to true for any item in the
enumerable. This method is often simpler/faster than using a callback.
@params key {String} the property to test
@param value {String} optional value to test against.
@returns {Boolean} YES
*/
someProperty: function(key, value) {
var len = this.get ? this.get('length') : this.length ;
var ret = NO;
var last = null ;
var context = SC.Enumerator._popContext();
for(var idx=0; !ret && (idx<len); idx++) {
var next = this.nextObject(idx, last, context) ;
var cur = next ? (next.get ? next.get(key) : next[key]) : null;
ret = (value === undefined) ? !!cur : SC.isEqual(cur, value);
last = next ;
}
last = null ;
context = SC.Enumerator._pushContext(context);
return ret ; // return the invert
},
/**
This will combine the values of the enumerator into a single value. It
is a useful way to collect a summary value from an enumeration. This
corresponds to the reduce() method defined in JavaScript 1.8.
The callback method you provide should have the following signature (all
parameters are optional):
{{{
function(previousValue, item, index, enumerable) ;
}}}
- *previousValue* is the value returned by the last call to the iterator.
- *item* is the current item in the iteration.
- *index* is the current index in the iteration
- *enumerable* is the enumerable object itself.
Return the new cumulative value.
In addition to the callback you can also pass an initialValue. An error
will be raised if you do not pass an initial value and the enumerator is
empty.
Note that unlike the other methods, this method does not allow you to
pass a target object to set as this for the callback. It's part of the
spec. Sorry.
@params callback {Function} the callback to execute
@params initialValue {Object} initial value for the reduce
@params reducerProperty {String} internal use only. May not be available.
@returns {Array} A filtered array.
*/
reduce: function(callback, initialValue, reducerProperty) {
if (typeof callback !== "function") throw new TypeError() ;
var len = this.get ? this.get('length') : this.length ;
// no value to return if no initial value & empty
if (len===0 && initialValue === undefined) throw new TypeError();
var ret = initialValue;
var last = null ;
var context = SC.Enumerator._popContext();
for(var idx=0;idx<len;idx++) {
var next = this.nextObject(idx, last, context) ;
// while ret is still undefined, just set the first value we get as ret.
// this is not the ideal behavior actually but it matches the FireFox
// implementation... :(
if (next !== null) {
if (ret === undefined) {
ret = next ;
} else {
ret = callback.call(null, ret, next, idx, this, reducerProperty);
}
}
last = next ;
}
last = null ;
context = SC.Enumerator._pushContext(context);
// uh oh...we never found a value!
if (ret === undefined) throw new TypeError() ;
return ret ;
},
/**
Invokes the named method on every object in the receiver that
implements it. This method corresponds to the implementation in
Prototype 1.6.
@param methodName {String} the name of the method
@param args {Object...} optional arguments to pass as well.
@returns {Array} return values from calling invoke.
*/
invoke: function(methodName) {
var len = this.get ? this.get('length') : this.length ;
if (len <= 0) return [] ; // nothing to invoke....
var idx;
// collect the arguments
var args = [] ;
var alen = arguments.length ;
if (alen > 1) {
for(idx=1;idx<alen;idx++) args.push(arguments[idx]) ;
}
// call invoke
var ret = [] ;
var last = null ;
var context = SC.Enumerator._popContext();
for(idx=0;idx<len;idx++) {
var next = this.nextObject(idx, last, context) ;
var method = next ? next[methodName] : null ;
if (method) ret[idx] = method.apply(next, args) ;
last = next ;
}
last = null ;
context = SC.Enumerator._pushContext(context);
return ret ;
},
/**
Invokes the passed method and optional arguments on the receiver elements
as long as the methods return value matches the target value. This is
a useful way to attempt to apply changes to a collection of objects unless
or until one fails.
@param targetValue {Object} the target return value
@param methodName {String} the name of the method
@param args {Object...} optional arguments to pass as well.
@returns {Array} return values from calling invoke.
*/
invokeWhile: function(targetValue, methodName) {
var len = this.get ? this.get('length') : this.length ;
if (len <= 0) return null; // nothing to invoke....
var idx;
// collect the arguments
var args = [] ;
var alen = arguments.length ;
if (alen > 2) {
for(idx=2;idx<alen;idx++) args.push(arguments[idx]) ;
}
// call invoke
var ret = targetValue ;
var last = null ;
var context = SC.Enumerator._popContext();
for(idx=0;(ret === targetValue) && (idx<len);idx++) {
var next = this.nextObject(idx, last, context) ;
var method = next ? next[methodName] : null ;
if (method) ret = method.apply(next, args) ;
last = next ;
}
last = null ;
context = SC.Enumerator._pushContext(context);
return ret ;
},
/**
Simply converts the enumerable into a genuine array. The order, of
course, is not gauranteed. Corresponds to the method implemented by
Prototype.
@returns {Array} the enumerable as an array.
*/
toArray: function() {
var ret = [];
this.forEach(function(o) { ret.push(o); }, this);
return ret ;
},
/**
Converts an enumerable into a matrix, with inner arrays grouped based
on a particular property of the elements of the enumerable.
@params key {String} the property to test
@returns {Array} matrix of arrays
*/
groupBy: function(key){
var len = this.get ? this.get('length') : this.length,
ret = [],
last = null,
context = SC.Enumerator._popContext(),
grouped = [],
keyValues = [],
idx, next, cur;
for(idx=0;idx<len;idx++) {
next = this.nextObject(idx, last, context) ;
cur = next ? (next.get ? next.get(key) : next[key]) : null;
if(SC.none(grouped[cur])) {
grouped[cur] = []; keyValues.push(cur);
}
grouped[cur].push(next);
last = next;
}
last = null;
context = SC.Enumerator._pushContext(context);
for(idx=0,len=keyValues.length; idx < len; idx++){
ret.push(grouped[keyValues[idx]]);
}
return ret ;
}
} ;
// Build in a separate function to avoid unintential leaks through closures...
SC._buildReducerFor = function(reducerKey, reducerProperty) {
return function(key, value) {
var reducer = this[reducerKey] ;
if (SC.typeOf(reducer) !== SC.T_FUNCTION) {
return this.unknownProperty ? this.unknownProperty(key, value) : null;
} else {
// Invoke the reduce method defined in enumerable instead of using the
// one implemented in the receiver. The receiver might be a native
// implementation that does not support reducerProperty.
var ret = SC.Enumerable.reduce.call(this, reducer, null, reducerProperty) ;
return ret ;
}
}.property('[]') ;
};
SC.Reducers = /** @lends SC.Enumerable */ {
/**
This property will trigger anytime the enumerable's content changes.
You can observe this property to be notified of changes to the enumerables
content.
For plain enumerables, this property is read only. SC.Array overrides
this method.
@property {SC.Array}
*/
'[]': function(key, value) { return this ; }.property(),
/**
Invoke this method when the contents of your enumerable has changed.
This will notify any observers watching for content changes. If your are
implementing an ordered enumerable (such as an array), also pass the
start and end values where the content changed so that it can be used to
notify range observers.
@param {Number} start optional start offset for the content change
@param {Number} length optional length of change
@returns {Object} receiver
*/
enumerableContentDidChange: function(start, length) {
this.notifyPropertyChange('[]') ;
return this ;
},
/**
Call this method from your unknownProperty() handler to implement
automatic reduced properties. A reduced property is a property that
collects its contents dynamically from your array contents. Reduced
properties always begin with "@". Getting this property will call
reduce() on your array with the function matching the key name as the
processor.
The return value of this will be either the return value from the
reduced property or undefined, which means this key is not a reduced
property. You can call this at the top of your unknownProperty handler
like so:
{{{
unknownProperty: function(key, value) {
var ret = this.handleReduceProperty(key, value) ;
if (ret === undefined) {
// process like normal
}
}
}}}
@param {String} key
the reduce property key
@param {Object} value
a value or undefined.
@param {Boolean} generateProperty
only set to false if you do not want an optimized computed property
handler generated for this. Not common.
@returns {Object} the reduced property or undefined
*/
reducedProperty: function(key, value, generateProperty) {
if (!key || key.charAt(0) !== '@') return undefined ; // not a reduced property
// get the reducer key and the reducer
var matches = key.match(/^@([^(]*)(\(([^)]*)\))?$/) ;
if (!matches || matches.length < 2) return undefined ; // no match
var reducerKey = matches[1]; // = 'max' if key = '@max(balance)'
var reducerProperty = matches[3] ; // = 'balance' if key = '@max(balance)'
reducerKey = "reduce" + reducerKey.slice(0,1).toUpperCase() + reducerKey.slice(1);
var reducer = this[reducerKey] ;
// if there is no reduce function defined for this key, then we can't
// build a reducer for it.
if (SC.typeOf(reducer) !== SC.T_FUNCTION) return undefined;
// if we can't generate the property, just run reduce
if (generateProperty === NO) {
return SC.Enumerable.reduce.call(this, reducer, null, reducerProperty) ;
}
// ok, found the reducer. Let's build the computed property and install
var func = SC._buildReducerFor(reducerKey, reducerProperty);
var p = this.constructor.prototype ;
if (p) {
p[key] = func ;
// add the function to the properties array so that new instances
// will have their dependent key registered.
var props = p._properties || [] ;
props.push(key) ;
p._properties = props ;
this.registerDependentKey(key, '[]') ;
}
// and reduce anyway...
return SC.Enumerable.reduce.call(this, reducer, null, reducerProperty) ;
},
/**
Reducer for @max reduced property.
*/
reduceMax: function(previousValue, item, index, e, reducerProperty) {
if (reducerProperty && item) {
item = item.get ? item.get(reducerProperty) : item[reducerProperty];
}
if (previousValue === null) return item ;
return (item > previousValue) ? item : previousValue ;
},
/**
Reducer for @maxObject reduced property.
*/
reduceMaxObject: function(previousItem, item, index, e, reducerProperty) {
// get the value for both the previous and current item. If no
// reducerProperty was supplied, use the items themselves.
var previousValue = previousItem, itemValue = item ;
if (reducerProperty) {
if (item) {
itemValue = item.get ? item.get(reducerProperty) : item[reducerProperty] ;
}
if (previousItem) {
previousValue = previousItem.get ? previousItem.get(reducerProperty) : previousItem[reducerProperty] ;
}
}
if (previousValue === null) return item ;
return (itemValue > previousValue) ? item : previousItem ;
},
/**
Reducer for @min reduced property.
*/
reduceMin: function(previousValue, item, index, e, reducerProperty) {
if (reducerProperty && item) {
item = item.get ? item.get(reducerProperty) : item[reducerProperty];
}
if (previousValue === null) return item ;
return (item < previousValue) ? item : previousValue ;
},
/**
Reducer for @maxObject reduced property.
*/
reduceMinObject: function(previousItem, item, index, e, reducerProperty) {
// get the value for both the previous and current item. If no
// reducerProperty was supplied, use the items themselves.
var previousValue = previousItem, itemValue = item ;
if (reducerProperty) {
if (item) {
itemValue = item.get ? item.get(reducerProperty) : item[reducerProperty] ;
}
if (previousItem) {
previousValue = previousItem.get ? previousItem.get(reducerProperty) : previousItem[reducerProperty] ;
}
}
if (previousValue === null) return item ;
return (itemValue < previousValue) ? item : previousItem ;
},
/**
Reducer for @average reduced property.
*/
reduceAverage: function(previousValue, item, index, e, reducerProperty) {
if (reducerProperty && item) {
item = item.get ? item.get(reducerProperty) : item[reducerProperty];
}
var ret = (previousValue || 0) + item ;
var len = e.get ? e.get('length') : e.length;
if (index >= len-1) ret = ret / len; //avg after last item.
return ret ;
},
/**
Reducer for @sum reduced property.
*/
reduceSum: function(previousValue, item, index, e, reducerProperty) {
if (reducerProperty && item) {
item = item.get ? item.get(reducerProperty) : item[reducerProperty];
}
return (previousValue === null) ? item : previousValue + item ;
},
reduceStddev: function(previousValue,item,index, e, reducerProperty){
// get mean first:
var mean = e.get('@average');
var dev,ret;
if(reducerProperty && item){
item = item.get ? item.get(reducerProperty) : item[reducerProperty];
}
var len = e.get ? e.get('length') : e.length;
dev = item - mean;
dev = dev*dev;
ret = (previousValue === null)? { mean: mean, devsum: 0 }: previousValue;
ret.devsum += dev;
if(index >= len-1) ret = Math.sqrt(ret.devsum/len);
return ret;
},
reduceStddevsample: function(previousValue,item,index, e, reducerProperty){
// get mean first:
var mean = e.get('@average');
var dev,ret;
if(reducerProperty && item){
item = item.get ? item.get(reducerProperty) : item[reducerProperty];
}
var len = e.get ? e.get('length') : e.length;
dev = item - mean;
dev = dev*dev;
ret = (previousValue === null)? { mean: mean, devsum: 0 }: previousValue;
ret.devsum += dev;
if(index >= len-1) ret = Math.sqrt(ret.devsum/len-1);
return ret;
}
} ;
// Apply reducers...
SC.mixin(SC.Enumerable, SC.Reducers) ;
SC.mixin(Array.prototype, SC.Reducers) ;
Array.prototype.isEnumerable = YES ;
// ......................................................
// ARRAY SUPPORT
//
// Implement the same enhancements on Array. We use specialized methods
// because working with arrays are so common.
(function() {
// These methods will be applied even if they already exist b/c we do it
// better.
var alwaysMixin = {
// this is supported so you can get an enumerator. The rest of the
// methods do not use this just to squeeze every last ounce of perf as
// possible.
nextObject: SC.Enumerable.nextObject,
enumerator: SC.Enumerable.enumerator,
firstObject: SC.Enumerable.firstObject,
lastObject: SC.Enumerable.lastObject,
sortProperty: SC.Enumerable.sortProperty,
// see above...
mapProperty: function(key) {
var len = this.length ;
var ret = [];
for(var idx=0;idx<len;idx++) {
var next = this[idx] ;
ret[idx] = next ? (next.get ? next.get(key) : next[key]) : null;
}
return ret ;
},
filterProperty: function(key, value) {
var len = this.length ;
var ret = [];
for(var idx=0;idx<len;idx++) {
var next = this[idx] ;
var cur = next ? (next.get ? next.get(key) : next[key]) : null;
var matched = (value === undefined) ? !!cur : SC.isEqual(cur, value);
if (matched) ret.push(next) ;
}
return ret ;
},
//returns a matrix
groupBy: function(key) {
var len = this.length,
ret = [],
grouped = [],
keyValues = [],
idx, next, cur;
for(idx=0;idx<len;idx++) {
next = this[idx] ;
cur = next ? (next.get ? next.get(key) : next[key]) : null;
if(SC.none(grouped[cur])){ grouped[cur] = []; keyValues.push(cur); }
grouped[cur].push(next);
}
for(idx=0,len=keyValues.length; idx < len; idx++){
ret.push(grouped[keyValues[idx]]);
}
return ret ;
},
find: function(callback, target) {
if (typeof callback !== "function") throw new TypeError() ;
var len = this.length ;
if (target === undefined) target = null;
var next, ret = null, found = NO;
for(var idx=0;idx<len && !found;idx++) {
next = this[idx] ;
if(found = callback.call(target, next, idx, this)) ret = next ;
}
next = null;
return ret ;
},
findProperty: function(key, value) {
var len = this.length ;
var next, cur, found=NO, ret=null;
for(var idx=0;idx<len && !found;idx++) {
cur = (next=this[idx]) ? (next.get ? next.get(key): next[key]):null;
found = (value === undefined) ? !!cur : SC.isEqual(cur, value);
if (found) ret = next ;
}
next=null;
return ret ;
},
everyProperty: function(key, value) {
var len = this.length ;
var ret = YES;
for(var idx=0;ret && (idx<len);idx++) {
var next = this[idx] ;
var cur = next ? (next.get ? next.get(key) : next[key]) : null;
ret = (value === undefined) ? !!cur : SC.isEqual(cur, value);
}
return ret ;
},
someProperty: function(key, value) {
var len = this.length ;
var ret = NO;
for(var idx=0; !ret && (idx<len); idx++) {
var next = this[idx] ;
var cur = next ? (next.get ? next.get(key) : next[key]) : null;
ret = (value === undefined) ? !!cur : SC.isEqual(cur, value);
}
return ret ; // return the invert
},
invoke: function(methodName) {
var len = this.length ;
if (len <= 0) return [] ; // nothing to invoke....
var idx;
// collect the arguments
var args = [] ;
var alen = arguments.length ;
if (alen > 1) {
for(idx=1;idx<alen;idx++) args.push(arguments[idx]) ;
}
// call invoke
var ret = [] ;
for(idx=0;idx<len;idx++) {
var next = this[idx] ;
var method = next ? next[methodName] : null ;
if (method) ret[idx] = method.apply(next, args) ;
}
return ret ;
},
invokeWhile: function(targetValue, methodName) {
var len = this.length ;
if (len <= 0) return null ; // nothing to invoke....
var idx;
// collect the arguments
var args = [] ;
var alen = arguments.length ;
if (alen > 2) {
for(idx=2;idx<alen;idx++) args.push(arguments[idx]) ;
}
// call invoke
var ret = targetValue ;
for(idx=0;(ret === targetValue) && (idx<len);idx++) {
var next = this[idx] ;
var method = next ? next[methodName] : null ;
if (method) ret = method.apply(next, args) ;
}
return ret ;
},
toArray: function() {
var len = this.length ;
if (len <= 0) return [] ; // nothing to invoke....
// call invoke
var ret = [] ;
for(var idx=0;idx<len;idx++) {
var next = this[idx] ;
ret.push(next) ;
}
return ret ;
},
getEach: function(key) {
var ret = [];
var len = this.length ;
for(var idx=0;idx<len;idx++) {
var obj = this[idx];
ret[idx] = obj ? (obj.get ? obj.get(key) : obj[key]) : null;
}
return ret ;
},
setEach: function(key, value) {
var len = this.length;
for(var idx=0;idx<len;idx++) {
var obj = this[idx];
if (obj) {
if (obj.set) {
obj.set(key, value);
} else obj[key] = value ;
}
}
return this ;
}
};
// These methods will only be applied if they are not already defined b/c
// the browser is probably getting it.
var mixinIfMissing = {
// QUESTION: The lack of DRY is burning my eyes [YK]
forEach: function(callback, target) {
if (typeof callback !== "function") throw new TypeError() ;
// QUESTION: Is this necessary?
if (target === undefined) target = null;
for(var i=0, l=this.length; i<l; i++) {
var next = this[i] ;
callback.call(target, next, i, this);
}
return this ;
},
map: function(callback, target) {
if (typeof callback !== "function") throw new TypeError() ;
if (target === undefined) target = null;
var ret = [];
for(var i=0, l=this.length; i<l; i++) {
var next = this[i] ;
ret[i] = callback.call(target, next, i, this) ;
}
return ret ;
},
filter: function(callback, target) {
if (typeof callback !== "function") throw new TypeError() ;
if (target === undefined) target = null;
var ret = [];
for(var i=0, l=this.length; i<l; i++) {
var next = this[i] ;
if(callback.call(target, next, i, this)) ret.push(next) ;
}
return ret ;
},
every: function(callback, target) {
if (typeof callback !== "function") throw new TypeError() ;
var len = this.length ;
if (target === undefined) target = null;
var ret = YES;
for(var idx=0;ret && (idx<len);idx++) {
var next = this[idx] ;
if(!callback.call(target, next, idx, this)) ret = NO ;
}
return ret ;
},
some: function(callback, target) {
if (typeof callback !== "function") throw new TypeError() ;
var len = this.length ;
if (target === undefined) target = null;
var ret = NO;
for(var idx=0;(!ret) && (idx<len);idx++) {
var next = this[idx] ;
if(callback.call(target, next, idx, this)) ret = YES ;
}
return ret ;
},
reduce: function(callback, initialValue, reducerProperty) {
if (typeof callback !== "function") throw new TypeError() ;
var len = this.length ;
// no value to return if no initial value & empty
if (len===0 && initialValue === undefined) throw new TypeError();
var ret = initialValue;
for(var idx=0;idx<len;idx++) {
var next = this[idx] ;
// while ret is still undefined, just set the first value we get as
// ret. this is not the ideal behavior actually but it matches the
// FireFox implementation... :(
if (next !== null) {
if (ret === undefined) {
ret = next ;
} else {
ret = callback.call(null, ret, next, idx, this, reducerProperty);
}
}
}
// uh oh...we never found a value!
if (ret === undefined) throw new TypeError() ;
return ret ;
}
};
// Apply methods if missing...
for(var key in mixinIfMissing) {
if (!mixinIfMissing.hasOwnProperty(key)) continue ;
// The mixinIfMissing methods should be applied if they are not defined.
// If Prototype 1.6 is included, some of these methods will be defined
// already, but we want to override them anyway in this special case
// because our version is faster and functionally identitical.
if (!Array.prototype[key] || ((typeof Prototype === 'object') && Prototype.Version.match(/^1\.6/))) {
Array.prototype[key] = mixinIfMissing[key] ;
}
}
// Apply other methods...
SC.mixin(Array.prototype, alwaysMixin) ;
})() ;
/* end of original file: frameworks/sproutcore/frameworks/runtime/mixins/enumerable.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/system/range_observer.js*/
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
/** @class
A RangeObserver is used by Arrays to automatically observe all of the
objects in a particular range on the array. Whenever any property on one
of those objects changes, it will notify its delegate. Likewise, whenever
the contents of the array itself changes, it will notify its delegate and
possibly update its own registration.
This implementation uses only SC.Array methods. It can be used on any
object that complies with SC.Array. You may, however, choose to subclass
this object in a way that is more optimized for your particular design.
@since SproutCore 1.0
*/
SC.RangeObserver = {
/**
Walk like a duck.
@property {Boolean}
*/
isRangeObserver: YES,
/** @private */
toString: function() {
var base = this.indexes ? this.indexes.toString() : "SC.IndexSet<..>";
return base.replace('IndexSet', 'RangeObserver(%@)'.fmt(SC.guidFor(this)));
},
/**
Creates a new range observer owned by the source. The indexSet you pass
must identify the indexes you are interested in observing. The passed
target/method will be invoked whenever the observed range changes.
Note that changes to a range are buffered until the end of a run loop
unless a property on the record itself changes.
@param {SC.Array} source the source array
@param {SC.IndexSet} indexSet set of indexes to observer
@param {Object} target the target
@param {Function|String} method the method to invoke
@param {Object} context optional context to include in callback
@param {Boolean} isDeep if YES, observe property changes as well
@returns {SC.RangeObserver} instance
*/
create: function(source, indexSet, target, method, context, isDeep) {
var ret = SC.beget(this);
ret.source = source;
ret.indexes = indexSet ? indexSet.frozenCopy() : null;
ret.target = target;
ret.method = method;
ret.context = context ;
ret.isDeep = isDeep || false ;
ret.beginObserving();
return ret ;
},
/**
Create subclasses for the RangeObserver. Pass one or more attribute
hashes. Use this to create customized RangeObservers if needed for your
classes.
@param {Hash} attrs one or more attribute hashes
@returns {SC.RangeObserver} extended range observer class
*/
extend: function(attrs) {
var ret = SC.beget(this), args = arguments;
for(var i=0, l=args.length; i<l; i++) { SC.mixin(ret, args[i]); }
return ret ;
},
/**
Destroys an active ranger observer, cleaning up first.
@param {SC.Array} source the source array
@returns {SC.RangeObserver} receiver
*/
destroy: function(source) {
this.endObserving();
return this;
},
/**
Updates the set of indexes the range observer applies to. This will
stop observing the old objects for changes and start observing the
new objects instead.
@param {SC.Array} source the source array
@returns {SC.RangeObserver} receiver
*/
update: function(source, indexSet) {
if (this.indexes && this.indexes.isEqual(indexSet)) { return this ; }
this.indexes = indexSet ? indexSet.frozenCopy() : null ;
this.endObserving().beginObserving();
return this;
},
/**
Configures observing for each item in the current range. Should update
the observing array with the list of observed objects so they can be
torn down later
@returns {SC.RangeObserver} receiver
*/
beginObserving: function() {
if ( !this.isDeep ) { return this; } // nothing to do
var observing = this.observing = this.observing || SC.CoreSet.create();
// cache iterator function to keep things fast
var func = this._beginObservingForEach, source = this.source;
if( !func ) {
func = this._beginObservingForEach = function(idx) {
var obj = source.objectAt(idx);
if (obj && obj.addObserver) {
observing.push(obj);
obj._kvo_needsRangeObserver = true ;
}
};
}
this.indexes.forEach(func);
// add to pending range observers queue so that if any of these objects
// change we will have a chance to setup observing on them.
this.isObserving = false ;
SC.Observers.addPendingRangeObserver(this);
return this;
},
/** @private
Called when an object that appears to need range observers has changed.
Check to see if the range observer contains this object in its list. If
it does, go ahead and setup observers on all objects and remove ourself
from the queue.
*/
setupPending: function(object) {
var observing = this.observing ;
if ( this.isObserving || !observing || (observing.get('length')===0) ) {
return true ;
}
if (observing.contains(object)) {
this.isObserving = true ;
// cache iterator function to keep things fast
var func = this._setupPendingForEach;
if (!func) {
var source = this.source,
method = this.objectPropertyDidChange,
self = this;
func = this._setupPendingForEach = function(idx) {
var obj = source.objectAt(idx),
guid = SC.guidFor(obj),
key ;
if (obj && obj.addObserver) {
observing.push(obj);
obj.addObserver('*', self, method);
// also save idx of object on range observer itself. If there is
// more than one idx, convert to IndexSet.
key = self[guid];
if ( key == null ) {
self[guid] = idx ;
} else if (key.isIndexSet) {
key.add(idx);
} else {
self[guid] = SC.IndexSet.create(key).add(idx);
}
}
};
}
this.indexes.forEach(func);
return true ;
} else {
return false ;
}
},
/**
Remove observers for any objects currently begin observed. This is
called whenever the observed range changes due to an array change or
due to destroying the observer.
@returns {SC.RangeObserver} receiver
*/
endObserving: function() {
if ( !this.isDeep ) return this; // nothing to do
var observing = this.observing;
if (this.isObserving) {
var meth = this.objectPropertyDidChange,
source = this.source,
idx, lim, obj;
if (observing) {
lim = observing.length;
for(idx=0;idx<lim;idx++) {
obj = observing[idx];
obj.removeObserver('*', this, meth);
this[SC.guidFor(obj)] = null;
}
observing.length = 0 ; // reset
}
this.isObserving = false ;
}
if (observing) { observing.clear(); } // empty set.
return this ;
},
/**
Whenever the actual objects in the range changes, notify the delegate
then begin observing again. Usually this method will be passed an
IndexSet with the changed indexes. The range observer will only notify
its delegate if the changed indexes include some of all of the indexes
this range observer is monitoring.
@param {SC.IndexSet} changes optional set of changed indexes
@returns {SC.RangeObserver} receiver
*/
rangeDidChange: function(changes) {
var indexes = this.indexes;
if ( !changes || !indexes || indexes.intersects(changes) ) {
this.endObserving(); // remove old observers
this.method.call(this.target, this.source, null, '[]', changes, this.context);
this.beginObserving(); // setup new ones
}
return this ;
},
/**
Whenever an object changes, notify the delegate
@param {Object} the object that changed
@param {String} key the property that changed
@returns {SC.RangeObserver} receiver
*/
objectPropertyDidChange: function(object, key, value, rev) {
var context = this.context,
method = this.method,
guid = SC.guidFor(object),
index = this[guid];
// lazily convert index to IndexSet.
if ( index && !index.isIndexSet ) {
index = this[guid] = SC.IndexSet.create(index).freeze();
}
method.call(this.target, this.source, object, key, index, context || rev, rev);
}
};
/* end of original file: frameworks/sproutcore/frameworks/runtime/system/range_observer.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/mixins/array.js*/
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
// note: SC.Observable also enhances array. make sure we are called after
// SC.Observable so our version of unknownProperty wins.
sc_require('mixins/observable');
sc_require('mixins/enumerable');
sc_require('system/range_observer');
SC.OUT_OF_RANGE_EXCEPTION = "Index out of range" ;
/**
@namespace
This module implements Observer-friendly Array-like behavior. This mixin is
picked up by the Array class as well as other controllers, etc. that want to
appear to be arrays.
Unlike SC.Enumerable, this mixin defines methods specifically for
collections that provide index-ordered access to their contents. When you
are designing code that needs to accept any kind of Array-like object, you
should use these methods instead of Array primitives because these will
properly notify observers of changes to the array.
Although these methods are efficient, they do add a layer of indirection to
your application so it is a good idea to use them only when you need the
flexibility of using both true JavaScript arrays and "virtual" arrays such
as controllers and collections.
You can use the methods defined in this module to access and modify array
contents in a KVO-friendly way. You can also be notified whenever the
membership if an array changes by changing the syntax of the property to
.observes('*myProperty.[]') .
To support SC.Array in your own class, you must override two
primitives to use it: replace() and objectAt().
Note that the SC.Array mixin also incorporates the SC.Enumerable mixin. All
SC.Array-like objects are also enumerable.
@extends SC.Enumerable
@since SproutCore 0.9.0
*/
SC.Array = {
/**
Walk like a duck - use isSCArray to avoid conflicts
*/
isSCArray: YES,
/**
@field {Number} length
Your array must support the length property. Your replace methods should
set this property whenever it changes.
*/
// length: 0,
/**
This is one of the primitves you must implement to support SC.Array. You
should replace amt objects started at idx with the objects in the passed
array. You should also call this.enumerableContentDidChange() ;
@param {Number} idx
Starting index in the array to replace. If idx >= length, then append to
the end of the array.
@param {Number} amt
Number of elements that should be removed from the array, starting at
*idx*.
@param {Array} objects
An array of zero or more objects that should be inserted into the array at
*idx*
*/
replace: function(idx, amt, objects) {
throw "replace() must be implemented to support SC.Array" ;
},
/**
This is one of the primitives you must implement to support SC.Array.
Returns the object at the named index. If your object supports retrieving
the value of an array item using get() (i.e. myArray.get(0)), then you do
not need to implement this method yourself.
@param {Number} idx
The index of the item to return. If idx exceeds the current length,
return null.
*/
objectAt: function(idx) {
if (idx < 0) return undefined ;
if (idx >= this.get('length')) return undefined;
return this.get(idx);
},
/**
@field []
This is the handler for the special array content property. If you get
this property, it will return this. If you set this property it a new
array, it will replace the current content.
This property overrides the default property defined in SC.Enumerable.
*/
'[]': function(key, value) {
if (value !== undefined) {
this.replace(0, this.get('length'), value) ;
}
return this ;
}.property(),
/**
This will use the primitive replace() method to insert an object at the
specified index.
@param {Number} idx index of insert the object at.
@param {Object} object object to insert
*/
insertAt: function(idx, object) {
if (idx > this.get('length')) throw SC.OUT_OF_RANGE_EXCEPTION ;
this.replace(idx,0,[object]) ;
return this ;
},
/**
Remove an object at the specified index using the replace() primitive
method. You can pass either a single index, a start and a length or an
index set.
If you pass a single index or a start and length that is beyond the
length this method will throw an SC.OUT_OF_RANGE_EXCEPTION
@param {Number|SC.IndexSet} start index, start of range, or index set
@param {Number} length length of passing range
@returns {Object} receiver
*/
removeAt: function(start, length) {
var delta = 0, // used to shift range
empty = [];
if (typeof start === SC.T_NUMBER) {
if ((start < 0) || (start >= this.get('length'))) {
throw SC.OUT_OF_RANGE_EXCEPTION;
}
// fast case
if (length === undefined) {
this.replace(start,1,empty);
return this ;
} else {
start = SC.IndexSet.create(start, length);
}
}
this.beginPropertyChanges();
start.forEachRange(function(start, length) {
start -= delta ;
delta += length ;
this.replace(start, length, empty); // remove!
}, this);
this.endPropertyChanges();
return this ;
},
/**
Search the array of this object, removing any occurrences of it.
@param {object} obj object to remove
*/
removeObject: function(obj) {
var loc = this.get('length') || 0;
while(--loc >= 0) {
var curObject = this.objectAt(loc) ;
if (curObject == obj) this.removeAt(loc) ;
}
return this ;
},
/**
Search the array for the passed set of objects and remove any occurrences
of the.
@param {SC.Enumerable} objects the objects to remove
@returns {SC.Array} receiver
*/
removeObjects: function(objects) {
this.beginPropertyChanges();
objects.forEach(function(obj) { this.removeObject(obj); }, this);
this.endPropertyChanges();
return this;
},
/**
Push the object onto the end of the array. Works just like push() but it
is KVO-compliant.
*/
pushObject: function(obj) {
this.insertAt(this.get('length'), obj) ;
return obj ;
},
/**
Add the objects in the passed numerable to the end of the array. Defers
notifying observers of the change until all objects are added.
@param {SC.Enumerable} objects the objects to add
@returns {SC.Array} receiver
*/
pushObjects: function(objects) {
this.beginPropertyChanges();
objects.forEach(function(obj) { this.pushObject(obj); }, this);
this.endPropertyChanges();
return this;
},
/**
Pop object from array or nil if none are left. Works just like pop() but
it is KVO-compliant.
*/
popObject: function() {
var len = this.get('length') ;
if (len === 0) return null ;
var ret = this.objectAt(len-1) ;
this.removeAt(len-1) ;
return ret ;
},
/**
Shift an object from start of array or nil if none are left. Works just
like shift() but it is KVO-compliant.
*/
shiftObject: function() {
if (this.get('length') === 0) return null ;
var ret = this.objectAt(0) ;
this.removeAt(0) ;
return ret ;
},
/**
Unshift an object to start of array. Works just like unshift() but it is
KVO-compliant.
*/
unshiftObject: function(obj) {
this.insertAt(0, obj) ;
return obj ;
},
/**
Adds the named objects to the beginning of the array. Defers notifying
observers until all objects have been added.
@param {SC.Enumerable} objects the objects to add
@returns {SC.Array} receiver
*/
unshiftObjects: function(objects) {
this.beginPropertyChanges();
objects.forEach(function(obj) { this.unshiftObject(obj); }, this);
this.endPropertyChanges();
return this;
},
/**
Compares each item in the array. Returns true if they are equal.
*/
isEqual: function(ary) {
if (!ary) return false ;
if (ary == this) return true;
var loc = ary.get('length') ;
if (loc != this.get('length')) return false ;
while(--loc >= 0) {
if (!SC.isEqual(ary.objectAt(loc), this.objectAt(loc))) return false ;
}
return true ;
},
/**
Generates a new array with the contents of the old array, sans any null
values.
@returns {Array}
*/
compact: function() { return this.without(null); },
/**
Generates a new array with the contents of the old array, sans the passed
value.
@param {Object} value
@returns {Array}
*/
without: function(value) {
if (this.indexOf(value)<0) return this; // value not present.
var ret = [] ;
this.forEach(function(k) {
if (k !== value) ret[ret.length] = k;
}) ;
return ret ;
},
/**
Generates a new array with only unique values from the contents of the
old array.
@returns {Array}
*/
uniq: function() {
var ret = [] ;
this.forEach(function(k){
if (ret.indexOf(k)<0) ret[ret.length] = k;
});
return ret ;
},
/**
Returns a new array that is a one-dimensional flattening of this array,
i.e. for every element of this array extract that and it's elements into
a new array.
@returns {Array}
*/
flatten: function() {
var ret = [];
this.forEach(function(k) {
if (k && k.isEnumerable) {
ret = ret.pushObjects(k.flatten());
} else {
ret.pushObject(k);
}
});
return ret;
},
/**
Returns the largest Number in an array of Numbers. Make sure the array
only contains values of type Number to get expected result.
Note: This only works for dense arrays.
@returns {Number}
*/
max: function() {
return Math.max.apply(Math, this);
},
/**
Returns the smallest Number in an array of Numbers. Make sure the array
only contains values of type Number to get expected result.
Note: This only works for dense arrays.
@returns {Number}
*/
min: function() {
return Math.min.apply(Math, this);
},
rangeObserverClass: SC.RangeObserver,
/**
Returns YES if object is in the array
@param {Object} object to look for
@returns {Boolean}
*/
contains: function(obj){
return this.indexOf(obj) >= 0;
},
/**
Creates a new range observer on the receiver. The target/method callback
you provide will be invoked anytime any property on the objects in the
specified range changes. It will also be invoked if the objects in the
range itself changes also.
The callback for a range observer should have the signature:
{{{
function rangePropertyDidChange(array, objects, key, indexes, conext)
}}}
If the passed key is '[]' it means that the object itself changed.
The return value from this method is an opaque reference to the
range observer object. You can use this reference to destroy the
range observer when you are done with it or to update its range.
@param {SC.IndexSet} indexes indexes to observe
@param {Object} target object to invoke on change
@param {String|Function} method the method to invoke
@param {Object} context optional context
@returns {SC.RangeObserver} range observer
*/
addRangeObserver: function(indexes, target, method, context) {
var rangeob = this._array_rangeObservers;
if (!rangeob) rangeob = this._array_rangeObservers = SC.CoreSet.create() ;
// The first time a range observer is added, cache the current length so
// we can properly notify observers the first time through
if (this._array_oldLength===undefined) {
this._array_oldLength = this.get('length') ;
}
var C = this.rangeObserverClass ;
var isDeep = NO; //disable this feature for now
var ret = C.create(this, indexes, target, method, context, isDeep) ;
rangeob.add(ret);
// first time a range observer is added, begin observing the [] property
if (!this._array_isNotifyingRangeObservers) {
this._array_isNotifyingRangeObservers = YES ;
this.addObserver('[]', this, this._array_notifyRangeObservers);
}
return ret ;
},
/**
Moves a range observer so that it observes a new range of objects on the
array. You must have an existing range observer object from a call to
addRangeObserver().
The return value should replace the old range observer object that you
pass in.
@param {SC.RangeObserver} rangeObserver the range observer
@param {SC.IndexSet} indexes new indexes to observe
@returns {SC.RangeObserver} the range observer (or a new one)
*/
updateRangeObserver: function(rangeObserver, indexes) {
return rangeObserver.update(this, indexes);
},
/**
Removes a range observer from the receiver. The range observer must
already be active on the array.
The return value should replace the old range observer object. It will
usually be null.
@param {SC.RangeObserver} rangeObserver the range observer
@returns {SC.RangeObserver} updated range observer or null
*/
removeRangeObserver: function(rangeObserver) {
var ret = rangeObserver.destroy(this);
var rangeob = this._array_rangeObservers;
if (rangeob) rangeob.remove(rangeObserver) ; // clear
return ret ;
},
/**
Updates observers with content change. To support range observers,
you must pass three change parameters to this method. Otherwise this
method will assume the entire range has changed.
This also assumes you have already updated the length property.
@param {Number} start the starting index of the change
@param {Number} amt the final range of objects changed
@param {Number} delta if you added or removed objects, the delta change
@returns {SC.Array} receiver
*/
enumerableContentDidChange: function(start, amt, delta) {
var rangeob = this._array_rangeObservers,
oldlen = this._array_oldLength,
newlen, length, changes ;
this.beginPropertyChanges();
this.notifyPropertyChange('length'); // flush caches
// schedule info for range observers
if (rangeob && rangeob.length>0) {
// if no oldLength has been cached, just assume 0
if (oldlen === undefined) oldlen = 0;
this._array_oldLength = newlen = this.get('length');
// normalize input parameters
// if delta was not passed, assume it is the different between the
// new and old length.
if (start === undefined) start = 0;
if (delta === undefined) delta = newlen - oldlen ;
if (delta !== 0 || amt === undefined) {
length = newlen - start ;
if (delta<0) length -= delta; // cover removed range as well
} else {
length = amt ;
}
changes = this._array_rangeChanges;
if (!changes) changes = this._array_rangeChanges = SC.IndexSet.create();
changes.add(start, length);
}
this.notifyPropertyChange('[]') ;
this.endPropertyChanges();
return this ;
},
/** @private
Observer fires whenever the '[]' property changes. If there are
range observers, will notify observers of change.
*/
_array_notifyRangeObservers: function() {
var rangeob = this._array_rangeObservers,
changes = this._array_rangeChanges,
len = rangeob ? rangeob.length : 0,
idx, cur;
if (len > 0 && changes && changes.length > 0) {
for(idx=0;idx<len;idx++) rangeob[idx].rangeDidChange(changes);
changes.clear(); // reset for later notifications
}
}
} ;
// Add SC.Array to the built-in array before we add SC.Enumerable to SC.Array
// since built-in Array's are already enumerable.
SC.mixin(Array.prototype, SC.Array) ;
SC.Array = SC.mixin({}, SC.Enumerable, SC.Array) ;
// Add any extra methods to SC.Array that are native to the built-in Array.
/**
Returns a new array that is a slice of the receiver. This implementation
uses the observable array methods to retrieve the objects for the new
slice.
@param beginIndex {Integer} (Optional) index to begin slicing from.
@param endIndex {Integer} (Optional) index to end the slice at.
@returns {Array} New array with specified slice
*/
SC.Array.slice = function(beginIndex, endIndex) {
var ret = [];
var length = this.get('length') ;
if (SC.none(beginIndex)) beginIndex = 0 ;
if (SC.none(endIndex) || (endIndex > length)) endIndex = length ;
while(beginIndex < endIndex) ret[ret.length] = this.objectAt(beginIndex++) ;
return ret ;
} ;
/**
Returns the index for a particular object in the index.
@param {Object} object the item to search for
@param {NUmber} startAt optional starting location to search, default 0
@returns {Number} index of -1 if not found
*/
SC.Array.indexOf = function(object, startAt) {
var idx, len = this.get('length');
if (startAt === undefined) startAt = 0;
else startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt);
if (startAt < 0) startAt += len;
for(idx=startAt;idx<len;idx++) {
if (this.objectAt(idx, YES) === object) return idx ;
}
return -1;
};
// Some browsers do not support indexOf natively. Patch if needed
if (!Array.prototype.indexOf) Array.prototype.indexOf = SC.Array.indexOf;
/**
Returns the last index for a particular object in the index.
@param {Object} object the item to search for
@param {NUmber} startAt optional starting location to search, default 0
@returns {Number} index of -1 if not found
*/
SC.Array.lastIndexOf = function(object, startAt) {
var idx, len = this.get('length');
if (startAt === undefined) startAt = len-1;
else startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt);
if (startAt < 0) startAt += len;
for(idx=startAt;idx>=0;idx--) {
if (this.objectAt(idx) === object) return idx ;
}
return -1;
};
// Some browsers do not support lastIndexOf natively. Patch if needed
if (!Array.prototype.lastIndexOf) {
Array.prototype.lastIndexOf = SC.Array.lastIndexOf;
}
// ......................................................
// ARRAY SUPPORT
//
// Implement the same enhancements on Array. We use specialized methods
// because working with arrays are so common.
(function() {
SC.mixin(Array.prototype, {
// primitive for array support.
replace: function(idx, amt, objects) {
if (this.isFrozen) throw SC.FROZEN_ERROR ;
if (!objects || objects.length === 0) {
this.splice(idx, amt) ;
} else {
var args = [idx, amt].concat(objects) ;
this.splice.apply(this,args) ;
}
// if we replaced exactly the same number of items, then pass only the
// replaced range. Otherwise, pass the full remaining array length
// since everything has shifted
var len = objects ? (objects.get ? objects.get('length') : objects.length) : 0;
this.enumerableContentDidChange(idx, amt, len - amt) ;
return this ;
},
// If you ask for an unknown property, then try to collect the value
// from member items.
unknownProperty: function(key, value) {
var ret = this.reducedProperty(key, value) ;
if ((value !== undefined) && ret === undefined) {
ret = this[key] = value;
}
return ret ;
}
});
// If browser did not implement indexOf natively, then override with
// specialized version
var indexOf = Array.prototype.indexOf;
if (!indexOf || (indexOf === SC.Array.indexOf)) {
Array.prototype.indexOf = function(object, startAt) {
var idx, len = this.length;
if (startAt === undefined) startAt = 0;
else startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt);
if (startAt < 0) startAt += len;
for(idx=startAt;idx<len;idx++) {
if (this[idx] === object) return idx ;
}
return -1;
} ;
}
var lastIndexOf = Array.prototype.lastIndexOf ;
if (!lastIndexOf || (lastIndexOf === SC.Array.lastIndexOf)) {
Array.prototype.lastIndexOf = function(object, startAt) {
var idx, len = this.length;
if (startAt === undefined) startAt = len-1;
else startAt = (startAt < 0) ? Math.ceil(startAt) : Math.floor(startAt);
if (startAt < 0) startAt += len;
for(idx=startAt;idx>=0;idx--) {
if (this[idx] === object) return idx ;
}
return -1;
};
}
})();
/* end of original file: frameworks/sproutcore/frameworks/runtime/mixins/array.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/mixins/comparable.js*/
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
/**
@namespace
Implements some standard methods for comparing objects. Add this mixin to
any class you create that can compare its instances.
You should implement the compare() method.
@since SproutCore 1.0
*/
SC.Comparable = {
/**
walk like a duck. Indicates that the object can be compared.
@type Boolean
*/
isComparable: YES,
/**
Override to return the result of the comparison of the two parameters. The
compare method should return
-1 if a < b
0 if a == b
1 if a > b
Default implementation raises
an exception.
@param a {Object} the first object to compare
@param b {Object} the second object to compare
@returns {Integer} the result of the comparison
*/
compare: function(a, b) {
throw "%@.compare() is not implemented".fmt(this.toString());
}
};
/* end of original file: frameworks/sproutcore/frameworks/runtime/mixins/comparable.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/mixins/copyable.js*/
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
/**
@namespace
Impelements some standard methods for copying an object. Add this mixin to
any object you create that can create a copy of itself. This mixin is
added automatically to the built-in array.
You should generally implement the copy() method to return a copy of the
receiver.
Note that frozenCopy() will only work if you also implement SC.Freezable.
@since SproutCore 1.0
*/
SC.Copyable = {
/**
Walk like a duck. Indicates that the object can be copied.
@type Boolean
*/
isCopyable: YES,
/**
Override to return a copy of the receiver. Default implementation raises
an exception.
@param deep {Boolean} if true, a deep copy of the object should be made
@returns {Object} copy of receiver
*/
copy: function(deep) {
throw "%@.copy() is not implemented";
},
/**
If the object implements SC.Freezable, then this will return a new copy
if the object is not frozen and the receiver if the object is frozen.
Raises an exception if you try to call this method on a object that does
not support freezing.
You should use this method whenever you want a copy of a freezable object
since a freezable object can simply return itself without actually
consuming more memory.
@returns {Object} copy of receiver or receiver
*/
frozenCopy: function() {
var isFrozen = this.get ? this.get('isFrozen') : this.isFrozen;
if (isFrozen === YES) return this;
else if (isFrozen === undefined) throw "%@ does not support freezing".fmt(this);
else return this.copy().freeze();
}
};
// Make Array copyable
SC.mixin(Array.prototype, SC.Copyable);
Array.prototype.copy = function(deep) {
var ret = this.slice(), idx;
if (deep) {
idx = ret.length;
while (idx--) ret[idx] = SC.copy(ret[idx], true);
}
return ret;
}
/* end of original file: frameworks/sproutcore/frameworks/runtime/mixins/copyable.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/mixins/delegate_support.js*/
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
/**
@namespace
Support methods for the Delegate design pattern.
The Delegate design pattern makes it easy to delegate a portion of your
application logic to another object. This is most often used in views to
delegate various application-logic decisions to controllers in order to
avoid having to bake application-logic directly into the view itself.
The methods provided by this mixin make it easier to implement this pattern
but they are not required to support delegates.
h2. The Pattern
The delegate design pattern typically means that you provide a property,
usually ending in "delegate", that can be set to another object in the
system.
When events occur or logic decisions need to be made that you would prefer
to delegate, you can call methods on the delegate if it is set. If the
delegate is not set, you should provide some default functionality instead.
Note that typically delegates are not observable, hence it is not necessary
to use get() to retrieve the value of the delegate.
@since SproutCore 1.0
*/
SC.DelegateSupport = {
/**
Selects the delegate that implements the specified method name. Pass one
or more delegates. The receiver is automatically included as a default.
This can be more efficient than using invokeDelegateMethod() which has
to marshall arguments into a delegate call.
@param {String} methodName
@param {Object...} delegate one or more delegate arguments
@returns {Object} delegate or null
*/
delegateFor: function(methodName) {
var idx = 1,
len = arguments.length,
ret ;
while(idx<len) {
ret = arguments[idx];
if (ret && ret[methodName] !== undefined) return ret ;
idx++;
}
return (this[methodName] !== undefined) ? this : null;
},
/**
Invokes the named method on the delegate that you pass. If no delegate
is defined or if the delegate does not implement the method, then a
method of the same name on the receiver will be called instead.
You can pass any arguments you want to pass onto the delegate after the
delegate and methodName.
@param {Object} delegate a delegate object. May be null.
@param {String} methodName a method name
@param {Object...} args (OPTIONAL) any additional arguments
@returns {Object} value returned by delegate
*/
invokeDelegateMethod: function(delegate, methodName, args) {
args = SC.A(arguments); args = args.slice(2, args.length) ;
if (!delegate || !delegate[methodName]) delegate = this ;
var method = delegate[methodName];
return method ? method.apply(delegate, args) : null;
},
/**
Search the named delegates for the passed property. If one is found,
gets the property value and returns it. If none of the passed delegates
implement the property, search the receiver for the property as well.
@param {String} key the property to get.
@param {Object} delegate one or more delegate
@returns {Object} property value or undefined
*/
getDelegateProperty: function(key, delegate) {
var idx = 1,
len = arguments.length,
ret ;
while(idx<len) {
ret = arguments[idx++];
if (ret && ret[key] !== undefined) {
return ret.get ? ret.get(key) : ret[key] ;
}
}
return (this[key] !== undefined) ? this.get(key) : undefined ;
}
};
/* end of original file: frameworks/sproutcore/frameworks/runtime/mixins/delegate_support.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/mixins/freezable.js*/
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
/**
Standard Error that should be raised when you try to modify a frozen object.
@property {Error}
*/
SC.FROZEN_ERROR = new Error("Cannot modify a frozen object");
/**
@namespace
The SC.Freezable mixin implements some basic methods for marking an object
as frozen. Once an object is frozen it should be read only. No changes
may be made the internal state of the object.
h2. Enforcement
To fully support freezing in your subclass, you must include this mixin and
override any method that might alter any property on the object to instead
raise an exception. You can check the state of an object by checking the
isFrozen property.
Although future versions of JavaScript may support language-level freezing
object objects, that is not the case today. Even if an object is freezable,
it is still technically possible to modify the object, even though it could
break other parts of your application that do not expect a frozen object to
change. It is, therefore, very important that you always respect the
isFrozen property on all freezable objects.
h2. Example
The example below shows a simple object that implement the SC.Freezable
protocol.
{{{
Contact = SC.Object.extend(SC.Freezable, {
firstName: null,
lastName: null,
// swaps the names
swapNames: function() {
if (this.get('isFrozen')) throw SC.FROZEN_ERROR;
var tmp = this.get('firstName');
this.set('firstName', this.get('lastName'));
this.set('lastName', tmp);
return this;
}
});
c = Context.create({ firstName: "John", lastName: "Doe" });
c.swapNames(); => returns c
c.freeze();
c.swapNames(); => EXCEPTION
}}}
h2. Copying
Usually the SC.Freezable protocol is implemented in cooperation with the
SC.Copyable protocol, which defines a frozenCopy() method that will return
a frozen object, if the object implements this method as well.
*/
SC.Freezable = {
/**
Walk like a duck.
@property {Boolean}
*/
isFreezable: YES,
/**
Set to YES when the object is frozen. Use this property to detect whether
your object is frozen or not.
@property {Boolean}
*/
isFrozen: NO,
/**
Freezes the object. Once this method has been called the object should
no longer allow any properties to be edited.
@returns {Object} reciever
*/
freeze: function() {
// NOTE: Once someone actually implements Object.freeze() in the browser,
// add a call to that here also.
if (this.set) this.set('isFrozen', YES);
else this.isFrozen = YES;
return this;
}
};
// Add to Array
SC.mixin(Array.prototype, SC.Freezable);
/* end of original file: frameworks/sproutcore/frameworks/runtime/mixins/freezable.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/system/set.js*/
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
sc_require('mixins/enumerable') ;
sc_require('mixins/observable') ;
sc_require('mixins/freezable');
sc_require('mixins/copyable');
// IMPORTANT NOTE: This file actually defines two classes:
// SC.Set is a fully observable set class documented below.
// SC._CoreSet is just like SC.Set but is not observable. This is required
// because SC.Observable is built on using sets and requires sets without
// observability.
//
// We use pointer swizzling below to swap around the actual definitions so
// that the documentation will turn out right. (The docs should only
// define SC.Set - not SC._CoreSet)
/**
@class
An unordered collection of objects.
A Set works a bit like an array except that its items are not ordered.
You can create a set to efficiently test for membership for an object. You
can also iterate through a set just like an array, even accessing objects
by index, however there is no gaurantee as to their order.
Whether or not property observing is enabled, sets offer very powerful
notifications of items being added and removed, through the
`#js:addSetObserver` and `#js:removeSetObserver` methods; this can be
very useful, for instance, for filtering or mapping sets.
Note that SC.Set is a primitive object, like an array. It does implement
limited key-value observing support, but it does not extend from SC.Object
so you should not subclass it.
Creating a Set
--------------
You can create a set like you would most objects using SC.Set.create().
Most new sets you create will be empty, but you can also initialize the set
with some content by passing an array or other enumerable of objects to the
constructor.
Finally, you can pass in an existing set and the set will be copied. You
can also create a copy of a set by calling SC.Set#clone().
#js
// creates a new empty set
var foundNames = SC.Set.create();
// creates a set with four names in it.
var names = SC.Set.create(["Charles", "Tom", "Juan", "Alex"]) ; // :P
// creates a copy of the names set.
var namesCopy = SC.Set.create(names);
// same as above.
var anotherNamesCopy = names.clone();
Adding/Removing Objects
-----------------------
You generally add or remove objects from a set using add() or remove().
You can add any type of object including primitives such as numbers,
strings, and booleans.
Note that objects can only exist one time in a set. If you call add() on
a set with the same object multiple times, the object will only be added
once. Likewise, calling remove() with the same object multiple times will
remove the object the first time and have no effect on future calls until
you add the object to the set again.
Note that you cannot add/remove null or undefined to a set. Any attempt to
do so will be ignored.
In addition to add/remove you can also call push()/pop(). Push behaves just
like add() but pop(), unlike remove() will pick an arbitrary object, remove
it and return it. This is a good way to use a set as a job queue when you
don't care which order the jobs are executed in.
Testing for an Object
---------------------
To test for an object's presence in a set you simply call SC.Set#contains().
This method tests for the object's hash, which is generally the same as the
object's guid; however, if you implement the hash() method on the object, it will
use the return value from that method instead.
Observing changes
-----------------
When using `#js:SC.Set` (rather than `#js:SC.CoreSet`), you can observe the
`#js:"[]"` property to be alerted whenever the content changes.
This is often unhelpful. If you are filtering sets of objects, for instance,
it is very inefficient to re-filter all of the items each time the set changes.
It would be better if you could just adjust the filtered set based on what
was changed on the original set. The same issue applies to merging sets,
as well.
`#js:SC.Set` and `#js:SC.CoreSet` both offer another method of being observed:
`#js:addSetObserver` and `#js:removeSetObserver`. These take a single parameter:
an object which implements `#js:didAddItem(set, item)` and
`#js:didRemoveItem(set, item)`.
Whenever an item is added or removed from the set, all objects in the set
(a SC.CoreSet, actually) of observing objects will be alerted appropriately.
BIG WARNING
===========
SetObservers are not intended to be used "_creatively_"; for instance, do
not expect to be alerted immediately to any changes. **While the notifications
are currently sent out immediately, if we find a fast way to send them at end
of run loop, we most likely will do so.**
@extends SC.Enumerable
@extends SC.Observable
@extends SC.Copyable
@extends SC.Freezable
@since SproutCore 1.0
*/
SC.Set = SC.mixin({},
SC.Enumerable,
SC.Observable,
SC.Freezable,
/** @scope SC.Set.prototype */ {
/**
Creates a new set, with the optional array of items included in the
return set.
@param {SC.Enumerable} items items to add
@return {SC.Set}
*/
create: function(items) {
var ret, idx, pool = SC.Set._pool, isObservable = this.isObservable, len;
if (!isObservable && items===undefined && pool.length>0) {
return pool.pop();
} else {
ret = SC.beget(this);
if (isObservable) ret.initObservable();
if (items && items.isEnumerable && items.get('length') > 0) {
ret.isObservable = NO; // suspend change notifications
// arrays and sets get special treatment to make them a bit faster
if (items.isSCArray) {
len = items.get('length');
for(idx = 0; idx < len; idx++) ret.add(items.objectAt(idx));
} else if (items.isSet) {
len = items.length;
for(idx = 0; idx < len; idx++) ret.add(items[idx]);
// otherwise use standard SC.Enumerable API
} else {
items.forEach(function(i) { ret.add(i); }, this);
}
ret.isObservable = isObservable;
}
}
return ret ;
},
/**
Walk like a duck
@property {Boolean}
*/
isSet: YES,
/**
This property will change as the number of objects in the set changes.
@property {Number}
*/
length: 0,
/**
Returns the first object in the set or null if the set is empty
@property {Object}
*/
firstObject: function() {
return (this.length > 0) ? this[0] : undefined ;
}.property(),
/**
Clears the set
@returns {SC.Set}
*/
clear: function() {
if (this.isFrozen) throw SC.FROZEN_ERROR;
this.length = 0;
return this ;
},
/**
Call this method to test for membership.
@returns {Boolean}
*/
contains: function(obj) {
// because of the way a set is "reset", the guid for an object may
// still be stored as a key, but points to an index that is beyond the
// length. Therefore the found idx must both be defined and less than
// the current length.
if (obj === null) return NO ;
var idx = this[SC.hashFor(obj)] ;
return (!SC.none(idx) && (idx < this.length) && (this[idx]===obj)) ;
},
/**
Returns YES if the passed object is also a set that contains the same
objects as the receiver.
@param {SC.Set} obj the other object
@returns {Boolean}
*/
isEqual: function(obj) {
// fail fast
if (!obj || !obj.isSet || (obj.get('length') !== this.get('length'))) {
return NO ;
}
var loc = this.get('length');
while(--loc>=0) {
if (!obj.contains(this[loc])) return NO ;
}
return YES;
},
/**
Adds a set observers. Set observers must implement two methods:
- didAddItem(set, item)
- didRemoveItem(set, item)
Set observers are, in fact, stored in another set (a CoreSet).
*/
addSetObserver: function(setObserver) {
// create set observer set if needed
if (!this.setObservers) {
this.setObservers = SC.CoreSet.create();
}
// add
this.setObservers.add(setObserver);
},
/**
Removes a set observer.
*/
removeSetObserver: function(setObserver) {
// if there is no set, there can be no currently observing set observers
if (!this.setObservers) return;
// remove the set observer. Pretty simple, if you think about it. I mean,
// honestly.
this.setObservers.remove(setObserver);
},
/**
Call this method to add an object. performs a basic add.
If the object is already in the set it will not be added again.
@param obj {Object} the object to add
@returns {SC.Set} receiver
*/
add: function(obj) {
if (this.isFrozen) throw SC.FROZEN_ERROR;
// cannot add null to a set.
if (SC.none(obj)) return this;
// Implementation note: SC.hashFor() is inlined because sets are
// fundamental in SproutCore, and the inlined code is ~ 25% faster than
// calling SC.hashFor() in IE8.
var hashFunc,
guid = ((hashFunc = obj.hash) && (typeof hashFunc === "function")) ? hashFunc.call(obj) : SC.guidFor(obj),
idx = this[guid],
len = this.length;
if ((idx >= len) || (this[idx] !== obj)) {
this[len] = obj;
this[guid] = len;
this.length = len + 1;
if (this.setObservers) this.didAddItem(obj);
}
if (this.isObservable) this.enumerableContentDidChange();
return this ;
},
/**
Add all the items in the passed array or enumerable
@returns {SC.Set} receiver
*/
addEach: function(objects) {
if (this.isFrozen) throw SC.FROZEN_ERROR;
if (!objects || !objects.isEnumerable) {
throw "%@.addEach must pass enumerable".fmt(this);
}
var idx, isObservable = this.isObservable ;
if (isObservable) this.beginPropertyChanges();
if (objects.isSCArray) {
idx = objects.get('length');
while(--idx >= 0) this.add(objects.objectAt(idx)) ;
} else if (objects.isSet) {
idx = objects.length;
while(--idx>=0) this.add(objects[idx]);
} else objects.forEach(function(i) { this.add(i); }, this);
if (isObservable) this.endPropertyChanges();
return this ;
},
/**
Removes the object from the set if it is found.
If the object is not in the set, nothing will be changed.
@param obj {Object} the object to remove
@returns {SC.Set} receiver
*/
remove: function(obj) {
if (this.isFrozen) throw SC.FROZEN_ERROR;
// Implementation note: SC.none() and SC.hashFor() are inlined because
// sets are fundamental in SproutCore, and the inlined code is ~ 25%
// faster than calling them "normally" in IE8.
if (obj === null || obj === undefined) return this ;
var hashFunc,
guid = (obj && (hashFunc = obj.hash) && (typeof hashFunc === SC.T_FUNCTION)) ? hashFunc.call(obj) : SC.guidFor(obj),
idx = this[guid],
len = this.length,
tmp;
// not in set.
// (SC.none is inlined for the reasons given above)
if ((idx === null || idx === undefined) || (idx >= len) || (this[idx] !== obj)) return this;
// clear the guid key
delete this[guid];
// to clear the index, we will swap the object stored in the last index.
// if this is the last object, just reduce the length.
if (idx < (len-1)) {
// we need to keep a reference to "obj" so we can alert others below;
// so, no changing it. Instead, create a temporary variable.
tmp = this[idx] = this[len-1];
guid = (tmp && (hashFunc = tmp.hash) && (typeof hashFunc === SC.T_FUNCTION)) ? hashFunc.call(tmp) : SC.guidFor(tmp);
this[guid] = idx;
}
// reduce the length
this.length = len-1;
if (this.isObservable) this.enumerableContentDidChange();
if (this.setObservers) this.didRemoveItem(obj);
return this ;
},
/**
Removes an arbitrary object from the set and returns it.
@returns {Object} an object from the set or null
*/
pop: function() {
if (this.isFrozen) throw SC.FROZEN_ERROR;
var obj = (this.length > 0) ? this[this.length-1] : null ;
this.remove(obj) ;
return obj ;
},
/**
Removes all the items in the passed array.
@returns {SC.Set} receiver
*/
removeEach: function(objects) {
if (this.isFrozen) throw SC.FROZEN_ERROR;
if (!objects || !objects.isEnumerable) {
throw "%@.addEach must pass enumerable".fmt(this);
}
var idx, isObservable = this.isObservable ;
if (isObservable) this.beginPropertyChanges();
if (objects.isSCArray) {
idx = objects.get('length');
while(--idx >= 0) this.remove(objects.objectAt(idx)) ;
} else if (objects.isSet) {
idx = objects.length;
while(--idx>=0) this.remove(objects[idx]);
} else objects.forEach(function(i) { this.remove(i); }, this);
if (isObservable) this.endPropertyChanges();
return this ;
},
/**
Clones the set into a new set.
@returns {SC.Set} new copy
*/
copy: function() {
return this.constructor.create(this);
},
/**
Return a set to the pool for reallocation.
@returns {SC.Set} receiver
*/
destroy: function() {
this.isFrozen = NO ; // unfreeze to return to pool
if (!this.isObservable) SC.Set._pool.push(this.clear());
return this;
},
// .......................................
// PRIVATE
//
/** @private - optimized */
forEach: function(iterator, target) {
var len = this.length;
if (!target) target = this ;
for(var idx=0;idx<len;idx++) iterator.call(target, this[idx], idx, this);
return this ;
},
/** @private */
toString: function() {
var len = this.length, idx, ary = [];
for(idx=0;idx<len;idx++) ary[idx] = this[idx];
return "SC.Set<%@>".fmt(ary.join(',')) ;
},
/**
@private
Alerts set observers that an item has been added.
*/
didAddItem: function(item) {
// get the set observers
var o = this.setObservers;
// return if there aren't any
if (!o) return;
// loop through and call didAddItem.
var len = o.length, idx;
for (idx = 0; idx < len; idx++) o[idx].didAddItem(this, item);
},
/**
@private
Alerts set observers that an item has been removed.
*/
didRemoveItem: function(item) {
// get the set observers
var o = this.setObservers;
// return if there aren't any
if (!o) return;
// loop through and call didAddItem.
var len = o.length, idx;
for (idx = 0; idx < len; idx++) o[idx].didRemoveItem(this, item);
},
// the pool used for non-observable sets
_pool: [],
/** @private */
isObservable: YES
}) ;
SC.Set.constructor = SC.Set;
// Make SC.Set look a bit more like other enumerables
/** @private */
SC.Set.clone = SC.Set.copy ;
/** @private */
SC.Set.push = SC.Set.unshift = SC.Set.add ;
/** @private */
SC.Set.shift = SC.Set.pop ;
// add generic add/remove enumerable support
/** @private */
SC.Set.addObject = SC.Set.add ;
/** @private */
SC.Set.removeObject = SC.Set.remove;
SC.Set._pool = [];
// ..........................................................
// CORE SET
//
/** @class
CoreSet is just like set but not observable. If you want to use the set
as a simple data structure with no observing, CoreSet is slightly faster
and more memory efficient.
@extends SC.Set
@since SproutCore 1.0
*/
SC.CoreSet = SC.beget(SC.Set);
/** @private */
SC.CoreSet.isObservable = NO ;
/** @private */
SC.CoreSet.constructor = SC.CoreSet;
/* end of original file: frameworks/sproutcore/frameworks/runtime/system/set.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/private/observer_queue.js*/
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
sc_require('mixins/observable');
sc_require('system/set');
// ........................................................................
// OBSERVER QUEUE
//
// This queue is used to hold observers when the object you tried to observe
// does not exist yet. This queue is flushed just before any property
// notification is sent.
/**
@namespace
The private ObserverQueue is used to maintain a set of pending observers.
This allows you to setup an observer on an object before the object exists.
Whenever the observer fires, the queue will be flushed to connect any
pending observers.
@since SproutCore 1.0
*/
SC.Observers = {
queue: [],
/**
@private
Attempt to add the named observer. If the observer cannot be found, put
it into a queue for later.
*/
addObserver: function(propertyPath, target, method, pathRoot) {
var tuple ;
// try to get the tuple for this.
if (typeof propertyPath === "string") {
tuple = SC.tupleForPropertyPath(propertyPath, pathRoot) ;
} else {
tuple = propertyPath;
}
// if a tuple was found, add the observer immediately...
if (tuple) {
tuple[0].addObserver(tuple[1],target, method) ;
// otherwise, save this in the queue.
} else {
this.queue.push([propertyPath, target, method, pathRoot]) ;
}
},
/**
@private
Remove the observer. If it is already in the queue, remove it. Also
if already found on the object, remove that.
*/
removeObserver: function(propertyPath, target, method, pathRoot) {
var idx, queue, tuple, item;
tuple = SC.tupleForPropertyPath(propertyPath, pathRoot) ;
if (tuple) {
tuple[0].removeObserver(tuple[1], target, method) ;
}
idx = this.queue.length; queue = this.queue ;
while(--idx >= 0) {
item = queue[idx] ;
if ((item[0] === propertyPath) && (item[1] === target) && (item[2] == method) && (item[3] === pathRoot)) queue[idx] = null ;
}
},
/**
@private
Range Observers register here to indicate that they may potentially
need to start observing.
*/
addPendingRangeObserver: function(observer) {
var ro = this.rangeObservers;
if (!ro) ro = this.rangeObservers = SC.CoreSet.create();
ro.add(observer);
return this ;
},
_TMP_OUT: [],
/**
Flush the queue. Attempt to add any saved observers.
*/
flush: function(object) {
// flush any observers that we tried to setup but didn't have a path yet
var oldQueue = this.queue ;
if (oldQueue && oldQueue.length > 0) {
var newQueue = (this.queue = []) ;
for ( var i=0,l=oldQueue.length; i<l; i++ ) {
var item = oldQueue[i];
if ( !item ) continue;
var tuple = SC.tupleForPropertyPath( item[0], item[3] );
if( tuple ) {
tuple[0].addObserver( tuple[1], item[1], item[2] );
} else {
newQueue.push( item );
}
}
}
// if this object needsRangeObserver then see if any pending range
// observers need it.
if ( object._kvo_needsRangeObserver ) {
var set = this.rangeObservers,
len = set ? set.get('length') : 0,
out = this._TMP_OUT,
ro;
for ( var i=0; i<len; i++ ) {
ro = set[i]; // get the range observer
if ( ro.setupPending(object) ) {
out.push(ro); // save to remove later
}
}
// remove any that have setup
if ( out.length > 0 ) set.removeEach(out);
out.length = 0; // reset
object._kvo_needsRangeObserver = false ;
}
},
/** @private */
isObservingSuspended: 0,
_pending: SC.CoreSet.create(),
/** @private */
objectHasPendingChanges: function(obj) {
this._pending.add(obj) ; // save for later
},
/** @private */
// temporarily suspends all property change notifications.
suspendPropertyObserving: function() {
this.isObservingSuspended++ ;
},
// resume change notifications. This will call notifications to be
// delivered for all pending objects.
/** @private */
resumePropertyObserving: function() {
var pending ;
if(--this.isObservingSuspended <= 0) {
pending = this._pending ;
this._pending = SC.CoreSet.create() ;
var idx, len = pending.length;
for(idx=0;idx<len;idx++) {
pending[idx]._notifyPropertyObservers() ;
}
pending.clear();
pending = null ;
}
}
} ;
/* end of original file: frameworks/sproutcore/frameworks/runtime/private/observer_queue.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/system/object.js*/
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
sc_require('core') ;
sc_require('mixins/observable') ;
sc_require('private/observer_queue');
sc_require('mixins/array') ;
sc_require('system/set');
/*globals $$sel */
SC.BENCHMARK_OBJECTS = NO;
// ..........................................................
// PRIVATE HELPER METHODS
//
// Private helper methods. These are not kept as part of the class
// definition because SC.Object is copied frequently and we want to keep the
// number of class methods to a minimum.
/** @private
Augments a base object by copying the properties from the extended hash.
In addition to simply copying properties, this method also performs a
number of optimizations that can make init'ing a new object much faster
including:
- concatenating concatenatedProperties
- prepping a list of bindings, observers, and dependent keys
- caching local observers so they don't need to be manually constructed.
@param {Hash} base hash
@param {Hash} extension
@returns {Hash} base hash
*/
SC._object_extend = function _object_extend(base, ext) {
if (!ext) throw "SC.Object.extend expects a non-null value. Did you forget to 'sc_require' something? Or were you passing a Protocol to extend() as if it were a mixin?";
// set _kvo_cloned for later use
base._kvo_cloned = null;
// get some common vars
var key, idx, len, cur, cprops = base.concatenatedProperties, K = SC.K,
p1, p2, rkey ;
// first, save any concat props. use old or new array or concat
idx = (cprops) ? cprops.length : 0 ;
var concats = (idx>0) ? {} : null;
while(--idx>=0) {
key = cprops[idx]; p1 = base[key]; p2 = ext[key]; rkey = key + 'Reset';
// `key`Reset: YES means: don't concatenate `key` with base[key]
if (ext[rkey] || !p1) {
if (!(p2 instanceof Array)) p2 = SC.$A(p2);
concats[key] = p2 ;
delete ext[rkey] ; // don't propogate the reset to subclasses
} else if (p1) {
if (!(p1 instanceof Array)) p1 = SC.$A(p1);
concats[key] = (p2) ? p1.concat(p2) : p2 ;
}
}
// setup arrays for bindings, observers, and properties. Normally, just
// save the arrays from the base. If these need to be changed during
// processing, then they will be cloned first.
var bindings = base._bindings, clonedBindings = NO;
var observers = base._observers, clonedObservers = NO;
var properties = base._properties, clonedProperties = NO;
var paths, pathLoc, local ;
// outlets are treated a little differently because you can manually
// name outlets in the passed in hash. If this is the case, then clone
// the array first.
var outlets = base.outlets, clonedOutlets = NO ;
if (ext.outlets) {
outlets = (outlets || SC.EMPTY_ARRAY).concat(ext.outlets);
clonedOutlets = YES ;
}
// now copy properties, add superclass to func.
for(key in ext) {
if (key === '_kvo_cloned') continue; // do not copy
// avoid copying builtin methods
if (!ext.hasOwnProperty(key)) continue ;
// get the value. use concats if defined
var value = (concats.hasOwnProperty(key) ? concats[key] : null) || ext[key];
// Possibly add to a bindings.
if (key.length > 7 && key.slice(-7) === "Binding") {
if (!clonedBindings) {
bindings = (bindings || SC.EMPTY_ARRAY).slice() ;
clonedBindings = YES ;
}
if (bindings === null) bindings = (base._bindings || SC.EMPTY_ARRAY).slice();
bindings[bindings.length] = key ;
// Also add observers, outlets, and properties for functions...
} else if (value && (value instanceof Function)) {
// add super to funcs. Be sure not to set the base of a func to
// itself to avoid infinite loops.
if (!value.superclass && (value !== (cur=base[key]))) {
value.superclass = value.base = cur || K;
}
// handle regular observers
if (value.propertyPaths) {
if (!clonedObservers) {
observers = (observers || SC.EMPTY_ARRAY).slice() ;
clonedObservers = YES ;
}
observers[observers.length] = key ;
// handle local properties
}
if (paths = value.localPropertyPaths) {
pathLoc = paths.length;
while(--pathLoc >= 0) {
local = base._kvo_for(SC.keyFor('_kvo_local', paths[pathLoc]), SC.CoreSet);
local.add(key);
base._kvo_for('_kvo_observed_keys', SC.CoreSet).add(paths[pathLoc]);
}
// handle computed properties
}
if (value.dependentKeys) {
if (!clonedProperties) {
properties = (properties || SC.EMPTY_ARRAY).slice() ;
clonedProperties = YES ;
}
properties[properties.length] = key ;
// handle outlets
}
if (value.autoconfiguredOutlet) {
if (!clonedOutlets) {
outlets = (outlets || SC.EMPTY_ARRAY).slice();
clonedOutlets = YES ;
}
outlets[outlets.length] = key ;
}
}
// copy property
base[key] = value ;
}
// Manually set base on toString() because some JS engines (such as IE8) do
// not enumerate it
if (ext.hasOwnProperty('toString')) {
key = 'toString';
// get the value. use concats if defined
value = (concats.hasOwnProperty(key) ? concats[key] : null) || ext[key] ;
if (!value.superclass && (value !== (cur=base[key]))) {
value.superclass = value.base = cur || K ;
}
// copy property
base[key] = value ;
}
// copy bindings, observers, and properties
base._bindings = bindings || [];
base._observers = observers || [] ;
base._properties = properties || [] ;
base.outlets = outlets || [];
return base ;
} ;
/** @class
Root object for the SproutCore framework. SC.Object is the root class for
most classes defined by SproutCore. It builds on top of the native object
support provided by JavaScript to provide support for class-like
inheritance, automatic bindings, properties observers, and more.
Most of the classes you define in your application should inherit from
SC.Object or one of its subclasses. If you are writing objects of your
own, you should read this documentation to learn some of the details of
how SC.Object's behave and how they differ from other frameworks.
h2. About SproutCore Classes
JavaScript is not a class-based language. Instead it uses a type of
inheritence inspired by self called "prototypical" inheritance.
...
h2. Using SproutCore objects with other JavaScript object.
You can create a SproutCore object just like any other object...
obj = new SC.Object() ;
@extends SC.Observable
@since SproutCore 1.0
*/
SC.Object = function(props) { return this._object_init(props); };
SC.mixin(SC.Object, /** @scope SC.Object */ {
/**
Adds the passed properties to the object's class definition. You can
pass as many hashes as you want, including Mixins, and they will be
added in the order they are passed.
This is a shorthand for calling SC.mixin(MyClass, props...);
@params {Hash} props the properties you want to add.
@returns {Object} receiver
*/
mixin: function(props) {
var len = arguments.length, loc ;
for(loc =0;loc<len;loc++) SC.mixin(this, arguments[loc]);
return this ;
},
// ..........................................
// CREATING CLASSES AND INSTANCES
//
/**
Points to the superclass for this class. You can use this to trace a
class hierarchy.
@property {SC.Object}
*/
superclass: null,
/**
Creates a new subclass of the receiver, adding any passed properties to
the instance definition of the new class. You should use this method
when you plan to create several objects based on a class with similar
properties.
h2. Init
If you define an init() method, it will be called when you create
instances of your new class. Since SproutCore uses the init() method to
do important setup, you must be sure to always call arguments.callee.base.apply(this,arguments) somewhere
in your init() to allow the normal setup to proceed.
@params {Hash} props the methods of properties you want to add
@returns {Class} A new object class
*/
extend: function(props) {
var bench = SC.BENCHMARK_OBJECTS ;
if (bench) SC.Benchmark.start('SC.Object.extend') ;
// build a new constructor and copy class methods. Do this before
// adding any other properties so they are not overwritten by the copy.
var prop, ret = function(props) { return this._object_init(props); } ;
for(prop in this) {
if (!this.hasOwnProperty(prop)) continue ;
ret[prop] = this[prop];
}
// manually copy toString() because some JS engines do not enumerate it
if (this.hasOwnProperty('toString')) ret.toString = this.toString;
// now setup superclass, guid
ret.superclass = this ;
SC.generateGuid(ret, "sc"); // setup guid
ret.subclasses = SC.Set.create();
this.subclasses.add(ret); // now we can walk a class hierarchy
// setup new prototype and add properties to it
var base = (ret.prototype = SC.beget(this.prototype));
var idx, len = arguments.length;
for(idx=0;idx<len;idx++) SC._object_extend(base, arguments[idx]) ;
base.constructor = ret; // save constructor
if (bench) SC.Benchmark.end('SC.Object.extend') ;
return ret ;
},
/**
Creates a new instance of the class.
Unlike most frameworks, you do not pass parameters to the init function
for an object. Instead, you pass a hash of additional properties you
want to have assigned to the object when it is first created. This is
functionally like creating an anonymous subclass of the receiver and then
instantiating it, but more efficient.
You can use create() like you would a normal constructor in a
class-based system, or you can use it to create highly customized
singleton objects such as controllers or app-level objects. This is
often more efficient than creating subclasses and then instantiating
them.
You can pass any hash of properties to this method, including mixins.
@param {Hash} props
optional hash of method or properties to add to the instance.
@returns {SC.Object} new instance of the receiver class.
*/
create: function() {
var C=this, ret = new C(arguments);
if (SC.ObjectDesigner) {
SC.ObjectDesigner.didCreateObject(ret, SC.$A(arguments));
}
return ret ;
},
/**
Walk like a duck. You can use this to quickly test classes.
@property {Boolean}
*/
isClass: YES,
/**
Set of subclasses that extend from this class. You can observe this
array if you want to be notified when the object is extended.
@property {SC.Set}
*/
subclasses: SC.Set.create(),
/** @private */
toString: function() { return SC._object_className(this); },
// ..........................................
// PROPERTY SUPPORT METHODS
//
/**
Returns YES if the receiver is a subclass of the named class. If the
receiver is the class passed, this will return NO since the class is not
a subclass of itself. See also kindOf().
h2. Example
{{{
ClassA = SC.Object.extend();
ClassB = ClassA.extend();
ClassB.subclassOf(ClassA) => YES
ClassA.subclassOf(ClassA) => NO
}}}
@param {Class} scClass class to compare
@returns {Boolean}
*/
subclassOf: function(scClass) {
if (this === scClass) return NO ;
var t = this ;
while(t = t.superclass) if (t === scClass) return YES ;
return NO ;
},
/**
Returns YES if the passed object is a subclass of the receiver. This is
the inverse of subclassOf() which you call on the class you want to test.
@param {Class} scClass class to compare
@returns {Boolean}
*/
hasSubclass: function(scClass) {
return (scClass && scClass.subclassOf) ? scClass.subclassOf(this) : NO;
},
/**
Returns YES if the receiver is the passed class or is a subclass of the
passed class. Unlike subclassOf(), this method will return YES if you
pass the receiver itself, since class is a kind of itself. See also
subclassOf().
h2. Example
{{{
ClassA = SC.Object.extend();
ClassB = ClassA.extend();
ClassB.kindOf(ClassA) => YES
ClassA.kindOf(ClassA) => YES
}}}
@param {Class} scClass class to compare
@returns {Boolean}
*/
kindOf: function(scClass) {
return (this === scClass) || this.subclassOf(scClass) ;
},
// ..........................................................
// Designers
//
/**
This method works just like extend() except that it will also preserve
the passed attributes.
@param {Hash} attrs Attributes to add to view
@returns {Class} SC.Object subclass to create
@function
*/
design: function() {
if (this.isDesign) return this; // only run design one time
var ret = this.extend.apply(this, arguments);
ret.isDesign = YES ;
if (SC.ObjectDesigner) {
SC.ObjectDesigner.didLoadDesign(ret, this, SC.A(arguments));
}
return ret ;
}
}) ;
// ..........................................
// DEFAULT OBJECT INSTANCE
//
SC.Object.prototype = {
_kvo_enabled: YES,
/** @private
This is the first method invoked on a new instance. It will first apply
any added properties to the new instance and then calls the real init()
method.
@param {Array} extensions an array-like object with hashes to apply.
@returns {Object} receiver
*/
_object_init: function(extensions) {
// apply any new properties
var idx, len = (extensions) ? extensions.length : 0;
for(idx=0;idx<len;idx++) SC._object_extend(this, extensions[idx]) ;
SC.generateGuid(this, "sc") ; // add guid
this.init() ; // call real init
// Call 'initMixin' methods to automatically setup modules.
var inits = this.initMixin; len = (inits) ? inits.length : 0 ;
for(idx=0;idx < len; idx++) inits[idx].call(this);
return this ; // done!
},
/**
You can call this method on an object to mixin one or more hashes of
properties on the receiver object. In addition to simply copying
properties, this method will also prepare the properties for use in
bindings, computed properties, etc.
If you plan to use this method, you should call it before you call
the inherited init method from SC.Object or else your instance may not
function properly.
h2. Example
{{{
// dynamically apply a mixin specified in an object property
var MyClass = SC.Object.extend({
extraMixin: null,
init: function() {
this.mixin(this.extraMixin);
arguments.callee.base.apply(this,arguments);
}
});
var ExampleMixin = { foo: "bar" };
var instance = MyClass.create({ extraMixin: ExampleMixin }) ;
instance.get('foo') => "bar"
}}}
@param {Hash} ext a hash to copy. Only one.
@returns {Object} receiver
*/
mixin: function() {
var idx, len = arguments.length;
for(idx=0;idx<len;idx++) SC.mixin(this, arguments[idx]) ;
// call initMixin
for(idx=0;idx<len;idx++) {
var init = arguments[idx].initMixin ;
if (init) init.call(this) ;
}
return this ;
},
/**
This method is invoked automatically whenever a new object is
instantiated. You can override this method as you like to setup your
new object.
Within your object, be sure to call arguments.callee.base.apply(this,arguments) to ensure that the
built-in init method is also called or your observers and computed
properties may not be configured.
Although the default init() method returns the receiver, the return
value is ignored.
@returns {void}
*/
init: function() {
this.initObservable();
return this ;
},
/**
Set to NO once this object has been destroyed.
@property {Boolean}
*/
isDestroyed: NO,
/**
Call this method when you are finished with an object to teardown its
contents. Because JavaScript is garbage collected, you do not usually
need to call this method. However, you may choose to do so for certain
objects, especially views, in order to let them reclaim memory they
consume immediately.
If you would like to perform additional cleanup when an object is
finished, you may override this method. Be sure to call arguments.callee.base.apply(this,arguments).
@returns {SC.Object} receiver
*/
destroy: function() {
if (this.get('isDestroyed')) return this; // nothing to do
this.set('isDestroyed', YES);
// destroy any mixins
var idx, inits = this.destroyMixin, len = (inits) ? inits.length : 0 ;
for(idx=0;idx < len; idx++) inits[idx].call(this);
return this ;
},
/**
Walk like a duck. Always YES since this is an object and not a class.
@property {Boolean}
*/
isObject: true,
/**
Returns YES if the named value is an executable function.
@param methodName {String} the property name to check
@returns {Boolean}
*/
respondsTo: function( methodName ) {
return !!(this[methodName] instanceof Function);
},
/**
Attemps to invoke the named method, passing the included two arguments.
Returns NO if the method is either not implemented or if the handler
returns NO (indicating that it did not handle the event). This method
is invoked to deliver actions from menu items and to deliver events.
You can override this method to provide additional handling if you
prefer.
@param {String} methodName
@param {Object} arg1
@param {Object} arg2
@returns {Boolean} YES if handled, NO if not handled
*/
tryToPerform: function(methodName, arg1, arg2) {
return this.respondsTo(methodName) && (this[methodName](arg1, arg2) !== NO);
},
/**
EXPERIMENTAL: You can use this to invoke a superclass implementation in
any method. This does not work in Safari 2 or earlier. If you need to
target these methods, you should use one of the alternatives below:
- *With Build Tools:* arguments.callee.base.apply(this,arguments);
- *Without Build Tools:* arguments.callee.base.apply(this, arguments);
h2. Example
All of the following methods will call the superclass implementation of
your method:
{{{
SC.Object.create({
// DOES NOT WORK IN SAFARI 2 OR EARLIER
method1: function() {
this.superclass();
},
// REQUIRES SC-BUILD TOOLS
method2: function() {
arguments.callee.base.apply(this,arguments);
},
// WORKS ANYTIME
method3: function() {
arguments.callee.base.apply(this, arguments);
}
});
}}}
@params args {*args} any arguments you want to pass along.
@returns {Object} return value from super
*/
superclass: function(args) {
var caller = arguments.callee.caller;
if (!caller) throw "superclass cannot determine the caller method" ;
return caller.superclass ? caller.superclass.apply(this, arguments) : null;
},
/**
returns YES if the receiver is an instance of the named class. See also
kindOf().
h2. Example
{{{
var ClassA = SC.Object.extend();
var ClassB = SC.Object.extend();
var instA = ClassA.create();
var instB = ClassB.create();
instA.instanceOf(ClassA) => YES
instB.instanceOf(ClassA) => NO
}}}
@param {Class} scClass the class
@returns {Boolean}
*/
instanceOf: function(scClass) {
return this.constructor === scClass ;
},
/**
Returns true if the receiver is an instance of the named class or any
subclass of the named class. See also instanceOf().
h2. Example
{{{
var ClassA = SC.Object.extend();
var ClassB = SC.Object.extend();
var instA = ClassA.create();
var instB = ClassB.create();
instA.kindOf(ClassA) => YES
instB.kindOf(ClassA) => YES
}}}
@param scClass {Class} the class
@returns {Boolean}
*/
kindOf: function(scClass) { return this.constructor.kindOf(scClass); },
/** @private */
toString: function() {
if (!this._object_toString) {
// only cache the string if the klass name is available
var klassName = SC._object_className(this.constructor) ;
var string = "%@:%@".fmt(klassName, SC.guidFor(this));
if (klassName) this._object_toString = string ;
else return string ;
}
return this._object_toString ;
},
/**
Activates any outlet connections in object and syncs any bindings. This
method is called automatically for view classes but may be used for any
object.
@returns {void}
*/
awake: function(key) {
var outlets = this.outlets,
i, len, outlet;
for (i = 0, len = outlets.length; i < len; ++i) {
outlet = outlets[i];
this.get(outlet);
}
this.bindings.invoke('sync');
},
/**
Invokes the passed method or method name one time during the runloop. You
can use this method to schedule methods that need to execute but may be
too expensive to execute more than once, such as methods that update the
DOM.
Note that in development mode only, the object and method that call this
method will be recorded, for help in debugging scheduled code.
@param {Function|String} method method or method name
@returns {SC.Object} receiver
*/
invokeOnce: function(method) {
SC.RunLoop.currentRunLoop.invokeOnce(this, method) ;
return this ;
},
/**
Invokes the passed method once at the beginning of the next runloop,
before any other methods (including events) are processed. This is useful
for situations where you know you need to update something, but due to
the way the run loop works, you can't actually do the update until the
run loop has completed.
A simple example is setting the selection on a collection controller to a
newly created object. Because the collection controller won't have its
content collection updated until later in the run loop, setting the
selection immediately will have no effect. In this situation, you could do
this instead:
{{{
// Creates a new MyRecord object and sets the selection of the
// myRecord collection controller to the new object.
createObjectAction: function(sender, evt) {
// create a new record and add it to the store
var obj = MyRecord.newRecord() ;
// update the collection controller's selection
MyApp.myRecordCollectionController.invokeLast( function() {
this.set('selection', [obj]) ;
});
}
}}}
You can call invokeLast as many times as you like and the method will
only be invoked once.
Note that in development mode only, the object and method that call this
method will be recorded, for help in debugging scheduled code.
@param {Function|String} method method or method name
@returns {SC.Object} receiver
*/
invokeLast: function(method) {
SC.RunLoop.currentRunLoop.invokeLast(this, method) ;
return this ;
},
/**
The properties named in this array will be concatenated in subclasses
instead of replaced. This allows you to name special properties that
should contain any values you specify plus values specified by parents.
It is used by SproutCore and is available for your use, though you
should limit the number of properties you include in this list as it
adds a slight overhead to new class and instance creation.
@property {Array}
*/
concatenatedProperties: ['concatenatedProperties', 'initMixin', 'destroyMixin']
} ;
// bootstrap the constructor for SC.Object.
SC.Object.prototype.constructor = SC.Object;
// Add observable to mixin
SC.mixin(SC.Object.prototype, SC.Observable) ;
// ..........................................................
// CLASS NAME SUPPORT
//
/** @private
This is a way of performing brute-force introspection. This searches
through all the top-level properties looking for classes. When it finds
one, it saves the class path name.
*/
function findClassNames() {
if (SC._object_foundObjectClassNames) return ;
SC._object_foundObjectClassNames = true ;
var seen = [] ;
var detectedSC = false;
var searchObject = function(root, object, levels) {
levels-- ;
// not the fastest, but safe
if (seen.indexOf(object) >= 0) return ;
seen.push(object) ;
for(var key in object) {
if (key == '__scope__') continue ;
if (key == 'superclass') continue ;
if (key == '__SC__') key = 'SC' ;
if (!key.match(/^[A-Z0-9]/)) continue ;
if (key == 'SC') {
if (detectedSC) continue;
detectedSC = true;
}
var path = (root) ? [root,key].join('.') : key ;
var value = object[key] ;
try {
var type = SC.typeOf(value);
} catch (e) {
// Firefox gives security errors when trying to run typeOf on certain objects
break;
}
switch(type) {
case SC.T_CLASS:
if (!value._object_className) value._object_className = path;
if (levels>=0) searchObject(path, value, levels) ;
break ;
case SC.T_OBJECT:
if (levels>=0) searchObject(path, value, levels) ;
break ;
case SC.T_HASH:
if (((root) || (path==='SC')) && (levels>=0)) searchObject(path, value, levels) ;
break ;
default:
break;
}
}
} ;
// Fix for IE 7 and 8 in order to detect the SC global variable. When you create
// a global variable in IE, it is not added to the window object like in other
// browsers. Therefore the searchObject method will not pick it up. So we have to
// update the window object to have a reference to the global variable. And
// doing window['SC'] does not work since the global variable already exists. For
// any object that you create that is used act as a namespace, be sure to create it
// like so:
//
// window.MyApp = window.MyApp || SC.Object.create({ ... })
//
window['__SC__'] = SC;
searchObject(null, window, 2) ;
}
/**
Same as the instance method, but lets you check instanceOf without
having to first check if instanceOf exists as a method.
@param {Object} scObject the object to check instance of
@param {Class} scClass the class
@returns {Boolean} if object1 is instance of class
*/
SC.instanceOf = function(scObject, scClass) {
return !!(scObject && scObject.constructor === scClass) ;
} ;
/**
Same as the instance method, but lets you check kindOf without having to
first check if kindOf exists as a method.
@param {Object} scObject object to check kind of
@param {Class} scClass the class to check
@returns {Boolean} if object is an instance of class or subclass
*/
SC.kindOf = function(scObject, scClass) {
if (scObject && !scObject.isClass) scObject = scObject.constructor;
return !!(scObject && scObject.kindOf && scObject.kindOf(scClass));
};
/** @private
Returns the name of this class. If the name is not known, triggers
a search. This can be expensive the first time it is called.
This method is used to allow classes to determine their own name.
*/
SC._object_className = function(obj) {
if (SC.isReady === NO) return ''; // class names are not available until ready
if (!obj._object_className) findClassNames() ;
if (obj._object_className) return obj._object_className ;
// if no direct classname was found, walk up class chain looking for a
// match.
var ret = obj ;
while(ret && !ret._object_className) ret = ret.superclass;
return (ret && ret._object_className) ? ret._object_className : 'Anonymous';
} ;
/* end of original file: frameworks/sproutcore/frameworks/runtime/system/object.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/private/chain_observer.js*/
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
require('system/object');
// ........................................................................
// CHAIN OBSERVER
//
// This is a private class used by the observable mixin to support chained
// properties.
// ChainObservers are used to automatically monitor a property several
// layers deep.
// org.plan.name = SC._ChainObserver.create({
// target: this, property: 'org',
// next: SC._ChainObserver.create({
// property: 'plan',
// next: SC._ChainObserver.create({
// property: 'name', func: myFunc
// })
// })
// })
//
SC._ChainObserver = function(property) {
this.property = property ;
} ;
// This is the primary entry point. Configures the chain.
SC._ChainObserver.createChain = function(rootObject, path, target, method, context) {
// First we create the chain.
var parts = path.split('.'),
root = new SC._ChainObserver(parts[0]),
tail = root;
for(var i=1, l=parts.length; i<l; i++) {
tail = tail.next = new SC._ChainObserver(parts[i]) ;
}
// Now root has the first observer and tail has the last one.
// Feed the rootObject into the front to setup the chain...
// do this BEFORE we set the target/method so they will not be triggered.
root.objectDidChange(rootObject);
// Finally, set the target/method on the tail so that future changes will
// trigger.
tail.target = target; tail.method = method ; tail.context = context ;
// and return the root to save
return root ;
};
SC._ChainObserver.prototype = {
isChainObserver: true,
// the object this instance is observing
object: null,
// the property on the object this link is observing.
property: null,
// if not null, this is the next link in the chain. Whenever the
// current property changes, the next observer will be notified.
next: null,
// if not null, this is the final target observer.
target: null,
// if not null, this is the final target method
method: null,
// invoked when the source object changes. removes observer on old
// object, sets up new observer, if needed.
objectDidChange: function(newObject) {
if (newObject === this.object) return; // nothing to do.
// if an old object, remove observer on it.
if (this.object && this.object.removeObserver) {
this.object.removeObserver(this.property, this, this.propertyDidChange);
}
// if a new object, add observer on it...
this.object = newObject ;
if (this.object && this.object.addObserver) {
this.object.addObserver(this.property, this, this.propertyDidChange);
}
// now, notify myself that my property value has probably changed.
this.propertyDidChange() ;
},
// the observer method invoked when the observed property changes.
propertyDidChange: function() {
// get the new value
var object = this.object ;
var property = this.property ;
var value = (object && object.get) ? object.get(property) : null ;
// if we have a next object in the chain, notify it that its object
// did change...
if (this.next) this.next.objectDidChange(value) ;
// if we have a target/method, call it.
var target = this.target,
method = this.method,
context = this.context ;
if (target && method) {
var rev = object ? object.propertyRevision : null ;
if (context) {
method.call(target, object, property, value, context, rev);
} else {
method.call(target, object, property, value, rev) ;
}
}
},
// teardown the chain...
destroyChain: function() {
// remove observer
var obj = this.object ;
if (obj && obj.removeObserver) {
obj.removeObserver(this.property, this, this.propertyDidChange) ;
}
// destroy next item in chain
if (this.next) this.next.destroyChain() ;
// and clear left overs...
this.next = this.target = this.method = this.object = this.context = null;
return null ;
}
} ;
/* end of original file: frameworks/sproutcore/frameworks/runtime/private/chain_observer.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/protocols/observable_protocol.js*/
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
/**
The SC.ObservableProtocol defines optional methods you can implement on your
objects. They will be used if defined but are not required for observing to
work.
*/
SC.ObservableProtocol = {
/**
Generic property observer called whenever a property on the receiver
changes.
If you need to observe a large number of properties on your object, it
is sometimes more efficient to implement this observer only and then to
handle requests yourself. Although this observer will be triggered
more often than an observer registered on a specific property, it also
does not need to be registered which can make it faster to setup your
object instance.
You will often implement this observer using a switch statement on the
key parameter, taking appropriate action.
@param observer {null} no longer used; usually null
@param target {Object} the target of the change. usually this
@param key {String} the name of the property that changed
@param value {Object} the new value of the property.
@param revision {Number} a revision you can use to quickly detect changes.
@returns {void}
*/
propertyObserver: function(observer,target,key,value, revision) {
}
};
/* end of original file: frameworks/sproutcore/frameworks/runtime/protocols/observable_protocol.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/protocols/sparse_array_delegate.js*/
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
/** @namespace
Delegate that provides data for a sparse array. If you set the delegate for
a sparse array to an object that implements one or more of these methods,
they will be invoked by the sparse array to fetch data or to update the
array content as needed.
Your object does not need to implement all of these methods, but it should
at least implment the sparseArrayDidRequestIndex() method.
@since SproutCore 1.0
*/
SC.SparseArrayDelegate = {
/**
Invoked when an object requests the length of the sparse array and the
length has not yet been set. You can implement this method to update
the length property of the sparse array immediately or at a later time
by calling the provideLength() method on the sparse array.
This method will only be called once on your delegate unless you
subsequently call provideLength(null) on the array, which will effectively
"empty" the array and cause the array to invoke the delegate again the
next time its length is request.
If you do not set a length on the sparse array immediately, it will return
a length of 0 until you provide the length.
@param {SC.SparseArray} sparseArray the array that needs a length.
@returns {void}
*/
sparseArrayDidRequestLength: function(sparseArray) {
// Default does nothing.
},
/**
Invoked when an object requests an index on the sparse array that has not
yet been set. You should implement this method to set the object at the
index using provideObjectsAtIndex() or provideObjectsInRange() on the
sparse array. You can call these methods immediately during this handler
or you can wait and call them at a later time once you have loaded any
data.
This method will only be called when an index is requested on the sparse
array that has not yet been filled. If you have filled an index or range
and you would like to reset it, call the objectsDidChangeInRange() method
on the sparse array.
Note that if you implement the sparseArrayDidRequestRange() method, that
method will be invoked instead of this one whenever possible to allow you
to fill in the array with the most efficiency possible.
@param {SC.SparseArray} sparseArray the sparse array
@param {Number} index the requested index
@returns {void}
*/
sparseArrayDidRequestIndex: function(sparseArray, index) {
},
/**
Alternative method invoked when an object requests an index on the
sparse array that has not yet been set. If you set the
rangeWindowSize property on the Sparse Array, then all object index
requests will be expanded to to nearest range window and then this
method will be called with that range.
You should fill in the passed range by calling the provideObjectsInRange()
method on the sparse array.
If you do not implement this method but set the rangeWindowSize anyway,
then the sparseArrayDidRequestIndex() method will be invoked instead.
Note that the passed range is a temporary object. Be sure to clone it if
you want to keep the range for later use.
@param {SC.SparseArray} sparseArray the sparse array
@param {Range} range read only range.
@returns {void}
*/
sparseArrayDidRequestRange: function(sparseArray, range) {
},
/**
Optional delegate method you can use to determine the index of a
particular object. If you do not implement this method, then the
sparse array will just search the objects it has loaded already.
@param {SC.SparseArray} sparseArray the sparse array
@param {Object} object the object to find the index of
@return {Number} the index or -1
@returns {void}
*/
sparseArrayDidRequestIndexOf: function(sparseArray, object) {
},
/**
Optional delegate method invoked whenever the sparse array attempts to
changes its contents. If you do not implement this method or if you
return NO from this method, then the edit will not be allowed.
@param {SC.SparseArray} sparseArray the sparse array
@param {Number} idx the starting index to replace
@param {Number} amt the number if items to replace
@param {Array} objects the array of objects to insert
@returns {Boolean} YES to allow replace, NO to deny
*/
sparseArrayShouldReplace: function(sparseArray, idx, amt, objects) {
return NO ;
},
/**
Invoked whenever the sparse array is reset. Resetting a sparse array
will cause it to flush its content and go back to the delegate for all
property requests again.
@param {SC.SparseArray} sparseArray the sparse array
@returns {void}
*/
sparseArrayDidReset: function(sparseArray) {
}
};
/* end of original file: frameworks/sproutcore/frameworks/runtime/protocols/sparse_array_delegate.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/system/binding.js*/
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
sc_require('system/object') ;
/**
Debug parameter you can turn on. This will log all bindings that fire to
the console. This should be disabled in production code. Note that you
can also enable this from the console or temporarily.
@property {Boolean}
*/
SC.LOG_BINDINGS = NO ;
/**
Performance paramter. This will benchmark the time spent firing each
binding.
@property {Boolean}
*/
SC.BENCHMARK_BINDING_NOTIFICATIONS = NO ;
/**
Performance parameter. This will benchmark the time spend configuring each
binding.
@property {Boolean}
*/
SC.BENCHMARK_BINDING_SETUP = NO;
/**
Default placeholder for multiple values in bindings.
@property {String}
*/
SC.MULTIPLE_PLACEHOLDER = '@@MULT@@' ;
/**
Default placeholder for null values in bindings.
@property {String}
*/
SC.NULL_PLACEHOLDER = '@@NULL@@' ;
/**
Default placeholder for empty values in bindings.
@property {String}
*/
SC.EMPTY_PLACEHOLDER = '@@EMPTY@@' ;
/**
@namespace
A binding simply connects the properties of two objects so that whenever the
value of one property changes, the other property will be changed also. You
do not usually work with Binding objects directly but instead describe
bindings in your class definition using something like:
{{{
valueBinding: "MyApp.someController.title"
}}}
This will create a binding from "MyApp.someController.title" to the "value"
property of your object instance automatically. Now the two values will be
kept in sync.
h2. Customizing Your Bindings
In addition to synchronizing values, bindings can also perform some basic
transforms on values. These transforms can help to make sure the data fed
into one object always meets the expectations of that object regardless of
what the other object outputs.
To customize a binding, you can use one of the many helper methods defined
on SC.Binding like so:
{{{
valueBinding: SC.Binding.single("MyApp.someController.title")
}}}
This will create a binding just like the example above, except that now the
binding will convert the value of MyApp.someController.title to a single
object (removing any arrays) before applying it to the "value" property of
your object.
You can also chain helper methods to build custom bindings like so:
{{{
valueBinding: SC.Binding.single("MyApp.someController.title").notEmpty("(EMPTY)")
}}}
This will force the value of MyApp.someController.title to be a single value
and then check to see if the value is "empty" (null, undefined, empty array,
or an empty string). If it is empty, the value will be set to the string
"(EMPTY)".
h2. One Way Bindings
One especially useful binding customization you can use is the oneWay()
helper. This helper tells SproutCore that you are only interested in
receiving changes on the object you are binding from. For example, if you
are binding to a preference and you want to be notified if the preference
has changed, but your object will not be changing the preference itself, you
could do:
{{{
bigTitlesBinding: SC.Binding.oneWay("MyApp.preferencesController.bigTitles")
}}}
This way if the value of MyApp.preferencesController.bigTitles changes the
"bigTitles" property of your object will change also. However, if you
change the value of your "bigTitles" property, it will not update the
preferencesController.
One way bindings are almost twice as fast to setup and twice as fast to
execute because the binding only has to worry about changes to one side.
You should consider using one way bindings anytime you have an object that
may be created frequently and you do not intend to change a property; only
to monitor it for changes. (such as in the example above).
h2. Adding Custom Transforms
In addition to using the standard helpers provided by SproutCore, you can
also defined your own custom transform functions which will be used to
convert the value. To do this, just define your transform function and add
it to the binding with the transform() helper. The following example will
not allow Integers less than ten. Note that it checks the value of the
bindings and allows all other values to pass:
{{{
valueBinding: SC.Binding.transform(function(value, binding) {
return ((SC.typeOf(value) === SC.T_NUMBER) && (value < 10)) ? 10 : value;
}).from("MyApp.someController.value")
}}}
If you would like to instead use this transform on a number of bindings,
you can also optionally add your own helper method to SC.Binding. This
method should simply return the value of this.transform(). The example
below adds a new helper called notLessThan() which will limit the value to
be not less than the passed minimum:
{{{
SC.Binding.notLessThan = function(minValue) {
return this.transform(function(value, binding) {
return ((SC.typeOf(value) === SC.T_NUMBER) && (value < minValue)) ? minValue : value ;
}) ;
} ;
}}}
You could specify this in your core.js file, for example. Then anywhere in
your application you can use it to define bindings like so:
{{{
valueBinding: SC.Binding.from("MyApp.someController.value").notLessThan(10)
}}}
Also, remember that helpers are chained so you can use your helper along with
any other helpers. The example below will create a one way binding that
does not allow empty values or values less than 10:
{{{
valueBinding: SC.Binding.oneWay("MyApp.someController.value").notEmpty().notLessThan(10)
}}}
Note that the built in helper methods all allow you to pass a "from"
property path so you don't have to use the from() helper to set the path.
You can do the same thing with your own helper methods if you like, but it
is not required.
h2. Creating Custom Binding Templates
Another way you can customize bindings is to create a binding template. A
template is simply a binding that is already partially or completely
configured. You can specify this template anywhere in your app and then use
it instead of designating your own custom bindings. This is a bit faster on
app startup but it is mostly useful in making your code less verbose.
For example, let's say you will be frequently creating one way, not empty
bindings that allow values greater than 10 throughout your app. You could
create a binding template in your core.js like this:
{{{
MyApp.LimitBinding = SC.Binding.oneWay().notEmpty().notLessThan(10);
}}}
Then anywhere you want to use this binding, just refer to the template like
so:
{{{
valueBinding: MyApp.LimitBinding.beget("MyApp.someController.value")
}}}
Note that when you use binding templates, it is very important that you
always start by using beget() to extend the template. If you do not do
this, you will end up using the same binding instance throughout your app
which will lead to erratic behavior.
h2. How to Manually Activate a Binding
All of the examples above show you how to configure a custom binding, but
the result of these customizations will be a binding template, not a fully
active binding. The binding will actually become active only when you
instantiate the object the binding belongs to. It is useful however, to
understand what actually happens when the binding is activated.
For a binding to function it must have at least a "from" property and a "to"
property. The from property path points to the object/key that you want to
bind from while the to path points to the object/key you want to bind to.
When you define a custom binding, you are usually describing the property
you want to bind from (such as "MyApp.someController.value" in the examples
above). When your object is created, it will automatically assign the value
you want to bind "to" based on the name of your binding key. In the
examples above, during init, SproutCore objects will effectively call
something like this on your binding:
{{{
binding = this.valueBinding.beget().to("value", this) ;
}}}
This creates a new binding instance based on the template you provide, and
sets the to path to the "value" property of the new object. Now that the
binding is fully configured with a "from" and a "to", it simply needs to be
connected to become active. This is done through the connect() method:
{{{
binding.connect() ;
}}}
Now that the binding is connected, it will observe both the from and to side
and relay changes.
If you ever needed to do so (you almost never will, but it is useful to
understand this anyway), you could manually create an active binding by
doing the following:
{{{
SC.Binding.from("MyApp.someController.value")
.to("MyApp.anotherObject.value")
.connect();
}}}
You could also use the bind() helper method provided by SC.Object. (This is
the same method used by SC.Object.init() to setup your bindings):
{{{
MyApp.anotherObject.bind("value", "MyApp.someController.value") ;
}}}
Both of these code fragments have the same effect as doing the most friendly
form of binding creation like so:
{{{
MyApp.anotherObject = SC.Object.create({
valueBinding: "MyApp.someController.value",
// OTHER CODE FOR THIS OBJECT...
}) ;
}}}
SproutCore's built in binding creation method make it easy to automatically
create bindings for you. You should always use the highest-level APIs
available, even if you understand how to it works underneath.
@since SproutCore 1.0
*/
SC.Binding = {
/**
This is the core method you use to create a new binding instance. The
binding instance will have the receiver instance as its parent which means
any configuration you have there will be inherited.
The returned instance will also have its parentBinding property set to the
receiver.
@param fromPath {String} optional from path.
@returns {SC.Binding} new binding instance
*/
beget: function(fromPath) {
var ret = SC.beget(this) ;
ret.parentBinding = this;
if (fromPath !== undefined) ret = ret.from(fromPath) ;
return ret ;
},
/**
Returns a builder function for compatibility.
*/
builder: function() {
var binding = this,
ret = function(fromProperty) { return binding.beget().from(fromProperty); };
ret.beget = function() { return binding.beget(); } ;
return ret ;
},
/**
This will set "from" property path to the specified value. It will not
attempt to resolve this property path to an actual object/property tuple
until you connect the binding.
The binding will search for the property path starting at the root level
unless you specify an alternate root object as the second paramter to this
method. Alternatively, you can begin your property path with either "." or
"*", which will use the root object of the to side be default. This special
behavior is used to support the high-level API provided by SC.Object.
@param propertyPath {String|Tuple} A property path or tuple
@param root {Object} optional root object to use when resolving the path.
@returns {SC.Binding} this
*/
from: function(propertyPath, root) {
// if the propertyPath is null/undefined, return this. This allows the
// method to be called from other methods when the fromPath might be
// optional. (cf single(), multiple())
if (!propertyPath) return this ;
// beget if needed.
var binding = (this === SC.Binding) ? this.beget() : this ;
binding._fromPropertyPath = propertyPath ;
binding._fromRoot = root ;
binding._fromTuple = null ;
return binding ;
},
/**
This will set the "to" property path to the specified value. It will not
attempt to reoslve this property path to an actual object/property tuple
until you connect the binding.
@param propertyPath {String|Tuple} A property path or tuple
@param root {Object} optional root object to use when resolving the path.
@returns {SC.Binding} this
*/
to: function(propertyPath, root) {
// beget if needed.
var binding = (this === SC.Binding) ? this.beget() : this ;
binding._toPropertyPath = propertyPath ;
binding._toRoot = root ;
binding._toTuple = null ; // clear out any existing one.
return binding ;
},
/**
Attempts to connect this binding instance so that it can receive and relay
changes. This method will raise an exception if you have not set the
from/to properties yet.
@returns {SC.Binding} this
*/
connect: function() {
// If the binding is already connected, do nothing.
if (this.isConnected) return this ;
this.isConnected = YES ;
this._connectionPending = YES ; // its connected but not really...
this._syncOnConnect = YES ;
SC.Binding._connectQueue.add(this) ;
return this;
},
/** @private
Actually connects the binding. This is done at the end of the runloop
to give you time to setup your entire object graph before the bindings
try to activate.
*/
_connect: function() {
if (!this._connectionPending) return; //nothing to do
this._connectionPending = NO ;
var path, root,
bench = SC.BENCHMARK_BINDING_SETUP;
if (bench) SC.Benchmark.start("SC.Binding.connect()");
// try to connect the from side.
// as a special behavior, if the from property path begins with either a
// . or * and the fromRoot is null, use the toRoot instead. This allows
// for support for the SC.Object shorthand:
//
// contentBinding: "*owner.value"
//
path = this._fromPropertyPath; root = this._fromRoot ;
if (typeof path === "string") {
// if the first character is a '.', this is a static path. make the
// toRoot the default root.
if (path.indexOf('.') === 0) {
path = path.slice(1);
if (!root) root = this._toRoot ;
// if the first character is a '*', then setup a tuple since this is a
// chained path.
} else if (path.indexOf('*') === 0) {
path = [this._fromRoot || this._toRoot, path.slice(1)] ;
root = null ;
}
}
this._fromObserverData = [path, this, this.fromPropertyDidChange, root];
SC.Observers.addObserver.apply(SC.Observers, this._fromObserverData);
// try to connect the to side
if (!this._oneWay) {
path = this._toPropertyPath; root = this._toRoot ;
this._toObserverData = [path, this, this.toPropertyDidChange, root];
SC.Observers.addObserver.apply(SC.Observers, this._toObserverData);
}
if (bench) SC.Benchmark.end("SC.Binding.connect()");
// now try to sync if needed
if (this._syncOnConnect) {
this._syncOnConnect = NO ;
if (bench) SC.Benchmark.start("SC.Binding.connect().sync");
this.sync();
if (bench) SC.Benchmark.end("SC.Binding.connect().sync");
}
},
/**
Disconnects the binding instance. Changes will no longer be relayed. You
will not usually need to call this method.
@returns {SC.Binding} this
*/
disconnect: function() {
if (!this.isConnected) return this; // nothing to do.
// if connection is still pending, just cancel
if (this._connectionPending) {
this._connectionPending = NO ;
// connection is completed, disconnect.
} else {
SC.Observers.removeObserver.apply(SC.Observers, this._fromObserverData);
if (!this._oneWay) {
SC.Observers.removeObserver.apply(SC.Observers, this._toObserverData);
}
}
this.isConnected = NO ;
return this ;
},
/**
Invoked whenever the value of the "from" property changes. This will mark
the binding as dirty if the value has changed.
*/
fromPropertyDidChange: function(target, key) {
var v = target ? target.get(key) : null;
//console.log("fromPropertyDidChange: %@ v = %@".fmt(this, v)) ;
// if the new value is different from the current binding value, then
// schedule to register an update.
if (v !== this._bindingValue || key === '[]') {
this._setBindingValue(target, key) ;
this._changePending = YES ;
SC.Binding._changeQueue.add(this) ; // save for later.
}
},
/**
Invoked whenever the value of the "to" property changes. This will mark the
binding as dirty only if:
- the binding is not one way
- the value does not match the stored transformedBindingValue
if the value does not match the transformedBindingValue, then it will
become the new bindingValue.
*/
toPropertyDidChange: function(target, key) {
if (this._oneWay) return; // nothing to do
var v = target.get(key) ;
// if the new value is different from the current binding value, then
// schedule to register an update.
if (v !== this._transformedBindingValue) {
this._setBindingValue(target, key) ;
this._changePending = YES ;
SC.Binding._changeQueue.add(this) ; // save for later.
}
},
/** @private
Saves the source location for the binding value. This will be used later
to actually update the binding value.
*/
_setBindingValue: function(source, key) {
this._bindingSource = source;
this._bindingKey = key;
},
/** @private
Updates the binding value from the current binding source if needed. This
should be called just before using this._bindingValue.
*/
_computeBindingValue: function() {
var source = this._bindingSource,
key = this._bindingKey,
v, idx;
this._bindingValue = v = (source ? source.getPath(key) : null);
// apply any transforms to get the to property value also
var transforms = this._transforms;
if (transforms) {
var len = transforms.length,
transform;
for(idx=0;idx<len;idx++) {
transform = transforms[idx] ;
v = transform(v, this) ;
}
}
// if error objects are not allowed, and the value is an error, then
// change it to null.
if (this._noError && SC.typeOf(v) === SC.T_ERROR) v = null ;
this._transformedBindingValue = v;
},
_connectQueue: SC.CoreSet.create(),
_alternateConnectQueue: SC.CoreSet.create(),
_changeQueue: SC.CoreSet.create(),
_alternateChangeQueue: SC.CoreSet.create(),
_changePending: NO,
/**
Call this method on SC.Binding to flush all bindings with changed pending.
@returns {Boolean} YES if changes were flushed.
*/
flushPendingChanges: function() {
// don't allow flushing more than one at a time
if (this._isFlushing) return NO;
this._isFlushing = YES ;
SC.Observers.suspendPropertyObserving();
var didFlush = NO,
log = SC.LOG_BINDINGS,
// connect any bindings
queue, binding ;
while((queue = this._connectQueue).length >0) {
this._connectQueue = this._alternateConnectQueue ;
this._alternateConnectQueue = queue ;
while(binding = queue.pop()) binding._connect() ;
}
// loop through the changed queue...
while ((queue = this._changeQueue).length > 0) {
if (log) console.log("Begin: Trigger changed bindings") ;
didFlush = YES ;
// first, swap the change queues. This way any binding changes that
// happen while we flush the current queue can be queued up.
this._changeQueue = this._alternateChangeQueue ;
this._alternateChangeQueue = queue ;
// next, apply any bindings in the current queue. This may cause
// additional bindings to trigger, which will end up in the new active
// queue.
while(binding = queue.pop()) binding.applyBindingValue() ;
// now loop back and see if there are additional changes pending in the
// active queue. Repeat this until all bindings that need to trigger
// have triggered.
if (log) console.log("End: Trigger changed bindings") ;
}
// clean up
this._isFlushing = NO ;
SC.Observers.resumePropertyObserving();
return didFlush ;
},
/**
This method is called at the end of the Run Loop to relay the changed
binding value from one side to the other.
*/
applyBindingValue: function() {
this._changePending = NO ;
// compute the binding targets if needed.
this._computeBindingTargets() ;
this._computeBindingValue();
var v = this._bindingValue,
tv = this._transformedBindingValue,
bench = SC.BENCHMARK_BINDING_NOTIFICATIONS,
log = SC.LOG_BINDINGS ;
// the from property value will always be the binding value, update if
// needed.
if (!this._oneWay && this._fromTarget) {
if (log) console.log("%@: %@ -> %@".fmt(this, v, tv)) ;
if (bench) SC.Benchmark.start(this.toString() + "->") ;
this._fromTarget.setPathIfChanged(this._fromPropertyKey, v) ;
if (bench) SC.Benchmark.end(this.toString() + "->") ;
}
// update the to value with the transformed value if needed.
if (this._toTarget) {
if (log) console.log("%@: %@ <- %@".fmt(this, v, tv)) ;
if (bench) SC.Benchmark.start(this.toString() + "<-") ;
this._toTarget.setPathIfChanged(this._toPropertyKey, tv) ;
if (bench) SC.Benchmark.start(this.toString() + "<-") ;
}
},
/**
Calling this method on a binding will cause it to check the value of the
from side of the binding matches the current expected value of the
binding. If not, it will relay the change as if the from side's value has
just changed.
This method is useful when you are dynamically connecting bindings to a
network of objects that may have already been initialized.
*/
sync: function() {
// do nothing if not connected
if (!this.isConnected) return this;
// connection is pending, just note that we should sync also
if (this._connectionPending) {
this._syncOnConnect = YES ;
// we are connected, go ahead and sync
} else {
this._computeBindingTargets() ;
var target = this._fromTarget,
key = this._fromPropertyKey ;
if (!target || !key) return this ; // nothing to do
// get the new value
var v = target.getPath(key) ;
// if the new value is different from the current binding value, then
// schedule to register an update.
if (v !== this._bindingValue || key === '[]') {
this._setBindingValue(target, key) ;
this._changePending = YES ;
SC.Binding._changeQueue.add(this) ; // save for later.
}
}
return this ;
},
// set if you call sync() when the binding connection is still pending.
_syncOnConnect: NO,
_computeBindingTargets: function() {
if (!this._fromTarget) {
var path, root, tuple ;
// if the fromPropertyPath begins with a . or * then we may use the
// toRoot as the root object. Similar code exists in connect() so if
// you make a change to one be sure to update the other.
path = this._fromPropertyPath; root = this._fromRoot ;
if (typeof path === "string") {
// static path beginning with the toRoot
if (path.indexOf('.') === 0) {
path = path.slice(1) ; // remove the .
if (!root) root = this._toRoot; // use the toRoot optionally
// chained path beginning with toRoot. Setup a tuple
} else if (path.indexOf('*') === 0) {
path = [root || this._toRoot, path.slice(1)];
root = null ;
}
}
tuple = SC.tupleForPropertyPath(path, root) ;
if (tuple) {
this._fromTarget = tuple[0]; this._fromPropertyKey = tuple[1] ;
}
}
if (!this._toTarget) {
path = this._toPropertyPath; root = this._toRoot ;
tuple = SC.tupleForPropertyPath(path, root) ;
if (tuple) {
this._toTarget = tuple[0]; this._toPropertyKey = tuple[1] ;
}
}
},
/**
Configures the binding as one way. A one-way binding will relay changes
on the "from" side to the "to" side, but not the other way around. This
means that if you change the "to" side directly, the "from" side may have
a different value.
@param fromPath {String} optional from path to connect.
@param aFlag {Boolean} Optionally pass NO to set the binding back to two-way
@returns {SC.Binding} this
*/
oneWay: function(fromPath, aFlag) {
// If fromPath is a bool but aFlag is undefined, swap.
if ((aFlag === undefined) && (SC.typeOf(fromPath) === SC.T_BOOL)) {
aFlag = fromPath; fromPath = null ;
}
// beget if needed.
var binding = this.from(fromPath) ;
if (binding === SC.Binding) binding = binding.beget() ;
binding._oneWay = (aFlag === undefined) ? YES : aFlag ;
return binding ;
},
/**
Adds the specified transform function to the array of transform functions.
The function you pass must have the following signature:
{{{
function(value) {} ;
}}}
It must return either the transformed value or an error object.
Transform functions are chained, so they are called in order. If you are
extending a binding and want to reset the transforms, you can call
resetTransform() first.
@param transformFunc {Function} the transform function.
@returns {SC.Binding} this
*/
transform: function(transformFunc) {
var binding = (this === SC.Binding) ? this.beget() : this ;
var t = binding._transforms ;
// clone the transform array if this comes from the parent
if (t && (t === binding.parentBinding._transform)) {
t = binding._transforms = t.slice() ;
}
// create the transform array if needed.
if (!t) t = binding._transforms = [] ;
// add the transform function
t.push(transformFunc) ;
return binding;
},
/**
Resets the transforms for the binding. After calling this method the
binding will no longer transform values. You can then add new transforms
as needed.
@returns {SC.Binding} this
*/
resetTransforms: function() {
var binding = (this === SC.Binding) ? this.beget() : this ;
binding._transforms = null ; return binding ;
},
/**
Specifies that the binding should not return error objects. If the value
of a binding is an Error object, it will be transformed to a null value
instead.
Note that this is not a transform function since it will be called at the
end of the transform chain.
@param fromPath {String} optional from path to connect.
@param aFlag {Boolean} optionally pass NO to allow error objects again.
@returns {SC.Binding} this
*/
noError: function(fromPath, aFlag) {
// If fromPath is a bool but aFlag is undefined, swap.
if ((aFlag === undefined) && (SC.typeOf(fromPath) === SC.T_BOOL)) {
aFlag = fromPath; fromPath = null ;
}
// beget if needed.
var binding = this.from(fromPath) ;
if (binding === SC.Binding) binding = binding.beget() ;
binding._noError = (aFlag === undefined) ? YES : aFlag ;
return binding ;
},
/**
Adds a transform to the chain that will allow only single values to pass.
This will allow single values, nulls, and error values to pass through. If
you pass an array, it will be mapped as so:
{{{
[] => null
[a] => a
[a,b,c] => Multiple Placeholder
}}}
You can pass in an optional multiple placeholder or it will use the
default.
Note that this transform will only happen on forwarded valued. Reverse
values are send unchanged.
@param fromPath {String} from path or null
@param placeholder {Object} optional placeholder value.
@returns {SC.Binding} this
*/
single: function(fromPath, placeholder) {
if (placeholder === undefined) {
placeholder = SC.MULTIPLE_PLACEHOLDER ;
}
return this.from(fromPath).transform(function(value, isForward) {
if (value && value.isEnumerable) {
var len = value.get('length');
value = (len>1) ? placeholder : (len<=0) ? null : value.firstObject();
}
return value ;
}) ;
},
/**
Adds a transform that will return the placeholder value if the value is
null, undefined, an empty array or an empty string. See also notNull().
@param fromPath {String} from path or null
@param placeholder {Object} optional placeholder.
@returns {SC.Binding} this
*/
notEmpty: function(fromPath, placeholder) {
if (placeholder === undefined) placeholder = SC.EMPTY_PLACEHOLDER ;
return this.from(fromPath).transform(function(value, isForward) {
if (SC.none(value) || (value === '') || (SC.isArray(value) && value.length === 0)) {
value = placeholder ;
}
return value ;
}) ;
},
/**
Adds a transform that will return the placeholder value if the value is
null. Otherwise it will passthrough untouched. See also notEmpty().
@param fromPath {String} from path or null
@param placeholder {Object} optional placeholder;
@returns {SC.Binding} this
*/
notNull: function(fromPath, placeholder) {
if (placeholder === undefined) placeholder = SC.EMPTY_PLACEHOLDER ;
return this.from(fromPath).transform(function(value, isForward) {
if (SC.none(value)) value = placeholder ;
return value ;
}) ;
},
/**
Adds a transform that will convert the passed value to an array. If
the value is null or undefined, it will be converted to an empty array.
@param fromPath {String} optional from path
@returns {SC.Binding} this
*/
multiple: function(fromPath) {
return this.from(fromPath).transform(function(value) {
if (!SC.isArray(value)) value = (value == null) ? [] : [value] ;
return value ;
}) ;
},
/**
Adds a transform to convert the value to a bool value. If the value is
an array it will return YES if array is not empty. If the value is a string
it will return YES if the string is not empty.
@param fromPath {String} optional from path
@returns {SC.Binding} this
*/
bool: function(fromPath) {
return this.from(fromPath).transform(function(v) {
var t = SC.typeOf(v) ;
if (t === SC.T_ERROR) return v ;
return (t == SC.T_ARRAY) ? (v.length > 0) : (v === '') ? NO : !!v ;
}) ;
},
/**
Adds a transform that forwards the logical 'AND' of values at 'pathA' and
'pathB' whenever either source changes. Note that the transform acts strictly
as a one-way binding, working only in the direction
'pathA' AND 'pathB' --> value (value returned is the result of ('pathA' && 'pathB'))
Usage example where a delete button's 'isEnabled' value is determined by whether
something is selected in a list and whether the current user is allowed to delete:
deleteButton: SC.ButtonView.design({
isEnabledBinding: SC.Binding.and('MyApp.itemsController.hasSelection', 'MyApp.userController.canDelete')
})
*/
and: function(pathA, pathB) {
// create an object to do the logical computation
var gate = SC.Object.create({
valueABinding: pathA,
valueBBinding: pathB,
and: function() {
return (this.get('valueA') && this.get('valueB'));
}.property('valueA', 'valueB').cacheable()
});
// add a transform that depends on the result of that computation.
return this.from('and', gate).oneWay();
},
/**
Adds a transform that forwards the 'OR' of values at 'pathA' and
'pathB' whenever either source changes. Note that the transform acts strictly
as a one-way binding, working only in the direction
'pathA' AND 'pathB' --> value (value returned is the result of ('pathA' || 'pathB'))
*/
or: function(pathA, pathB) {
// create an object to the logical computation
var gate = SC.Object.create({
valueABinding: pathA,
valueBBinding: pathB,
or: function() {
return (this.get('valueA') || this.get('valueB'));
}.property('valueA', 'valueB').cacheable()
});
return this.from('or', gate).oneWay();
},
/**
Adds a transform to convert the value to the inverse of a bool value. This
uses the same transform as bool() but inverts it.
@param fromPath {String} optional from path
@returns {SC.Binding} this
*/
not: function(fromPath) {
return this.from(fromPath).transform(function(v) {
var t = SC.typeOf(v) ;
if (t === SC.T_ERROR) return v ;
return !((t == SC.T_ARRAY) ? (v.length > 0) : (v === '') ? NO : !!v) ;
}) ;
},
/**
Adds a transform that will return YES if the value is null, NO otherwise.
@returns {SC.Binding} this
*/
isNull: function(fromPath) {
return this.from(fromPath).transform(function(v) {
var t = SC.typeOf(v) ;
return (t === SC.T_ERROR) ? v : SC.none(v) ;
});
},
toString: function() {
var from = this._fromRoot ? "<%@>:%@".fmt(this._fromRoot,this._fromPropertyPath) : this._fromPropertyPath;
var to = this._toRoot ? "<%@>:%@".fmt(this._toRoot,this._toPropertyPath) : this._toPropertyPath;
var oneWay = this._oneWay ? '[oneWay]' : '';
return "SC.Binding%@(%@ -> %@)%@".fmt(SC.guidFor(this), from, to, oneWay);
}
} ;
/**
Shorthand method to define a binding. This is the same as calling:
{{{
SC.binding(path) = SC.Binding.from(path)
}}}
*/
SC.binding = function(path, root) { return SC.Binding.from(path,root); } ;
/* end of original file: frameworks/sproutcore/frameworks/runtime/system/binding.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/system/cookie.js*/
// ==========================================================================
// SC.Cookie
// ==========================================================================
/** @class
Allows for easier handling of the document.cookie object. To create a cookie,
simply call SC.Cookie.create. To retrieve a cookie, use SC.Cookie.find.
Cookies are not added to document.cookie, which SC.Cookie.find uses, until you
have called SC.Cookie#write.
Heavy inspiration from the
{@link <a href="http://plugins.jquery.com/project/cookie">jQuery cookie plugin</a>}.
@extends SC.Object
@since Sproutcore 1.0
@author Colin Campbell
*/
SC.Cookie = SC.Object.extend({
// ..........................................................
// PROPERTIES
//
/**
The name of the cookie
@property {String}
*/
name: null,
/**
The value of the cookie
@property {String}
*/
value: '',
/**
Amount of time until the cookie expires. Set to -1 in order to delete the cookie.
@property {Integer|SC.DateTime|Date}
*/
expires: null,
/**
The value of the path atribute of the cookie (default: path of page that created the cookie).
@property {String}
*/
path: null,
/**
The value of the domain attribute of the cookie (default: domain of page that created the cookie).
@property {String}
*/
domain: null,
/**
If true, the secure attribute of the cookie will be set and the cookie transmission will
require a secure protocol (like HTTPS).
@property {Boolean}
*/
secure: NO,
/**
Walk like a duck
@property {Boolean}
@isReadOnly
*/
isCookie: YES,
// ..........................................................
// METHODS
//
/**
Sets SC.Cookie#expires to -1, which destroys the cookie.
*/
destroy: function() {
this.set('expires', -1);
this.write();
arguments.callee.base.apply(this,arguments);
},
/**
Writes this SC.Cookie to document.cookie and adds it to SC.Cookie collection. To find this
cookie later, or on reload, use SC.Cookie.find.
@see SC.Cookie.find
*/
write: function() {
var name = this.get('name'),
value = this.get('value'),
expires = this.get('expires'),
path = this.get('path'),
domain = this.get('domain'),
secure = this.get('secure');
var expiresOutput = '';
if (expires && (SC.typeOf(expires) === SC.T_NUMBER || (SC.DateTime && expires.get && expires.get('milliseconds')) || SC.typeOf(expires.toUTCString) === SC.T_FUNCTION)) {
var date;
if (SC.typeOf(expires) === SC.T_NUMBER) {
date = new Date();
date.setTime(date.getTime()+(expires*24*60*60*1000));
}
else if (SC.DateTime && expires.get && expires.get('milliseconds')) {
date = new Date(expires.get('milliseconds'));
}
else if (SC.typeOf(expires.toUTCString) === SC.T_FUNCTION) {
date = expires;
}
if (date) {
expiresOutput = '; expires=' + date.toUTCString();
}
}
var pathOutput = path ? '; path=' + path : '';
var domainOutput = domain ? '; domain=' + domain : '';
var secureOutput = secure ? '; secure' : '';
document.cookie = [name, '=', encodeURIComponent(value), expiresOutput, pathOutput, domainOutput, secureOutput].join('');
return this;
}
});
SC.Cookie.mixin(
/** @scope SC.Cookie */ {
/**
Finds a cookie that has been stored
@param {String} name The name of the cookie
@returns SC.Cookie object containing name and value of cookie
*/
find: function(name) {
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = String(cookies[i]).trim();
if (cookie.substring(0, name.length + 1) === (name + "=")) {
return SC.Cookie.create({
name: name,
value: decodeURIComponent(cookie.substring(name.length + 1))
});
}
}
}
return null;
}
});
/* end of original file: frameworks/sproutcore/frameworks/runtime/system/cookie.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/system/error.js*/
// ==========================================================================
// Project: SproutCore - JavaScript Application Framework
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
/**
@class
An error, used to represent an error state.
Many API's within SproutCore will return an instance of this object whenever
they have an error occur. An error includes an error code, description,
and optional human readable label that indicates the item that failed.
Depending on the error, other properties may also be added to the object
to help you recover from the failure.
You can pass error objects to various UI elements to display the error in
the interface. You can easily determine if the value returned by some API is
an error or not using the helper SC.ok(value).
h2. Faking Error Objects
You can actually make any object you want to be treated like an Error object
by simply implementing two properties: isError and errorValue. If you
set isError to YES, then calling SC.ok(obj) on your object will return NO.
If isError is YES, then SC.val(obj) will return your errorValue property
instead of the receiver.
@extends SC.Object
@since SproutCore 1.0
*/
SC.Error = SC.Object.extend(
/** @scope SC.Error.prototype */ {
/**
error code. Used to designate the error type.
@property {Number}
*/
code: -1,
/**
Human readable description of the error. This can also be a non-localized
key.
@property {String}
*/
message: '',
/**
The value the error represents. This is used when wrapping a value inside
of an error to represent the validation failure.
@property {Object}
*/
errorValue: null,
/**
The original error object. Normally this will return the receiver.
However, sometimes another object will masquarade as an error; this gives
you a way to get at the underyling error.
@property {SC.Error}
*/
errorObject: function() {
return this;
}.property().cacheable(),
/**
Human readable name of the item with the error.
@property {String}
*/
label: null,
/** @private */
toString: function() {
return "SC.Error:%@:%@ (%@)".fmt(SC.guidFor(this), this.get('message'), this.get('code'));
},
/**
Walk like a duck.
@property {Boolean}
*/
isError: YES
}) ;
/**
Creates a new SC.Error instance with the passed description, label, and
code. All parameters are optional.
@param description {String} human readable description of the error
@param label {String} human readable name of the item with the error
@param code {Number} an error code to use for testing.
@returns {SC.Error} new error instance.
*/
SC.Error.desc = function(description, label, value, code) {
var opts = { message: description } ;
if (label !== undefined) opts.label = label ;
if (code !== undefined) opts.code = code ;
if (value !== undefined) opts.errorValue = value ;
return this.create(opts) ;
} ;
/**
Shorthand form of the SC.Error.desc method.
@param description {String} human readable description of the error
@param label {String} human readable name of the item with the error
@param code {Number} an error code to use for testing.
@returns {SC.Error} new error instance.
*/
SC.$error = function(description, label, value, c) {
return SC.Error.desc(description,label, value, c);
} ;
/**
Returns YES if the passed value is an error object or false.
@param {Object} ret object value
@returns {Boolean}
*/
SC.ok = function(ret) {
return (ret !== false) && !(ret && ret.isError);
};
/** @private */
SC.$ok = SC.ok;
/**
Returns the value of an object. If the passed object is an error, returns
the value associated with the error; otherwise returns the receiver itself.
@param {Object} obj the object
@returns {Object} value
*/
SC.val = function(obj) {
if (obj && obj.isError) {
return obj.get ? obj.get('errorValue') : null ; // Error has no value
} else return obj ;
};
/** @private */
SC.$val = SC.val;
// STANDARD ERROR OBJECTS
/**
Standard error code for errors that do not support multiple values.
@property {Number}
*/
SC.Error.HAS_MULTIPLE_VALUES = -100 ;
/* end of original file: frameworks/sproutcore/frameworks/runtime/system/error.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/system/index_set.js*/
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
sc_require('mixins/enumerable') ;
sc_require('mixins/observable') ;
sc_require('mixins/freezable');
sc_require('mixins/copyable');
/**
@class
A collection of ranges. You can use an IndexSet to keep track of non-
continuous ranges of items in a parent array. IndexSet's are used for
selection, for managing invalidation ranges and other data-propogation.
h2. Examples
{{{
var set = SC.IndexSet.create(ranges) ;
set.contains(index);
set.add(index, length);
set.remove(index, length);
// uses a backing SC.Array object to return each index
set.forEach(function(object) { .. })
// returns the index
set.forEachIndex(function(index) { ... });
// returns ranges
set.forEachRange(function(start, length) { .. });
}}}
h2. Implementation Notes
An IndexSet stores indices on the object. A positive value great than the
index tells you the end of an occupied range. A negative values tells you
the end of an empty range. A value less than the index is a search
accelerator. It tells you the start of the nearest range.
@extends SC.Enumerable
@extends SC.Observable
@extends SC.Copyable
@extends SC.Freezable
@since SproutCore 1.0
*/
SC.IndexSet = SC.mixin({},
SC.Enumerable, SC.Observable, SC.Freezable, SC.Copyable,
/** @scope SC.IndexSet.prototype */ {
/** @private
Walks a content array and copies its contents to a new array. For large
content arrays this is faster than using slice()
*/
_sc_sliceContent: function(c) {
if (c.length < 1000) return c.slice(); // use native when faster
var cur = 0, ret = [], next = c[0];
while(next !== 0) {
ret[cur] = next ;
cur = (next<0) ? (0-next) : next ;
next = c[cur];
}
ret[cur] = 0;
this._hint(0, cur, ret); // hints are not copied manually - add them
return ret ;
},
/**
To create a set, pass either a start and index or another IndexSet.
@returns {SC.IndexSet}
*/
create: function(start, length) {
var ret = SC.beget(this);
ret.initObservable();
ret.registerDependentKey('min', '[]');
// optimized method to clone an index set.
if (start && start.isIndexSet) {
ret._content = this._sc_sliceContent(start._content);
ret.max = start.max;
ret.length = start.length;
ret.source = start.source ;
// otherwise just do a regular add
} else {
ret._content = [0];
if (start !== undefined) ret.add(start, length);
}
return ret ;
},
/**
Walk like a duck.
@property {Boolean}
*/
isIndexSet: YES,
/** @private
Internal setting determines the preferred skip size for hinting sets.
@property {Number}
*/
HINT_SIZE: 256,
/**
Total number of indexes contained in the set
@property {Number}
*/
length: 0,
/**
One greater than the largest index currently stored in the set. This
is sometimes useful when determining the total range of items covering
the index set.
@property {Number}
*/
max: 0,
/**
The first index included in the set or -1.
@property {Number}
*/
min: function() {
var content = this._content,
cur = content[0];
return (cur === 0) ? -1 : (cur>0) ? 0 : Math.abs(cur);
}.property('[]').cacheable(),
/**
Returns the first index in the set .
@property {Number}
*/
firstObject: function() {
return (this.get('length')>0) ? this.get('min') : undefined;
}.property(),
/**
Returns the starting index of the nearest range for the specified
index.
@param {Number} index
@returns {Number} starting index
*/
rangeStartForIndex: function(index) {
var content = this._content,
max = this.get('max'),
ret, next, accel;
// fast cases
if (index >= max) return max ;
if (Math.abs(content[index]) > index) return index ; // we hit a border
// use accelerator to find nearest content range
accel = index - (index % SC.IndexSet.HINT_SIZE);
ret = content[accel];
if (ret<0 || ret>index) ret = accel;
next = Math.abs(content[ret]);
// now step forward through ranges until we find one that includes the
// index.
while (next < index) {
ret = next ;
next = Math.abs(content[ret]);
}
return ret ;
},
/**
Returns YES if the passed index set contains the exact same indexes as
the receiver. If you pass any object other than an index set, returns NO.
@param {Object} obj another object.
@returns {Boolean}
*/
isEqual: function(obj) {
// optimize for some special cases
if (obj === this) return YES ;
if (!obj || !obj.isIndexSet || (obj.max !== this.max) || (obj.length !== this.length)) return NO;
// ok, now we need to actually compare the ranges of the two.
var lcontent = this._content,
rcontent = obj._content,
cur = 0,
next = lcontent[cur];
do {
if (rcontent[cur] !== next) return NO ;
cur = Math.abs(next) ;
next = lcontent[cur];
} while (cur !== 0);
return YES ;
},
/**
Returns the first index in the set before the passed index or null if
there are no previous indexes in the set.
@param {Number} index index to check
@returns {Number} index or -1
*/
indexBefore: function(index) {
if (index===0) return -1; // fast path
index--; // start with previous index
var content = this._content,
max = this.get('max'),
start = this.rangeStartForIndex(index);
if (!content) return null;
// loop backwards until we find a range that is in the set.
while((start===max) || (content[start]<0)) {
if (start === 0) return -1 ; // nothing before; just quit
index = start -1 ;
start = this.rangeStartForIndex(index);
}
return index;
},
/**
Returns the first index in the set after the passed index or null if
there are no additional indexes in the set.
@param {Number} index index to check
@returns {Number} index or -1
*/
indexAfter: function(index) {
var content = this._content,
max = this.get('max'),
start, next ;
if (!content || (index>=max)) return -1; // fast path
index++; // start with next index
// loop forwards until we find a range that is in the set.
start = this.rangeStartForIndex(index);
next = content[start];
while(next<0) {
if (next === 0) return -1 ; //nothing after; just quit
index = start = Math.abs(next);
next = content[start];
}
return index;
},
/**
Returns YES if the index set contains the named index
@param {Number} start index or range
@param {Number} length optional range length
@returns {Boolean}
*/
contains: function(start, length) {
var content, cur, next, rstart, rnext;
// normalize input
if (length === undefined) {
if (start === null || start === undefined) return NO ;
if (typeof start === SC.T_NUMBER) {
length = 1 ;
// if passed an index set, check each receiver range
} else if (start && start.isIndexSet) {
if (start === this) return YES ; // optimization
content = start._content ;
cur = 0 ;
next = content[cur];
while (next !== 0) {
if ((next>0) && !this.contains(cur, next-cur)) return NO ;
cur = Math.abs(next);
next = content[cur];
}
return YES ;
} else {
length = start.length;
start = start.start;
}
}
rstart = this.rangeStartForIndex(start);
rnext = this._content[rstart];
return (rnext>0) && (rstart <= start) && (rnext >= (start+length));
},
/**
Returns YES if the index set contains any of the passed indexes. You
can pass a single index, a range or an index set.
@param {Number} start index, range, or IndexSet
@param {Number} length optional range length
@returns {Boolean}
*/
intersects: function(start, length) {
var content, cur, next, lim;
// normalize input
if (length === undefined) {
if (typeof start === SC.T_NUMBER) {
length = 1 ;
// if passed an index set, check each receiver range
} else if (start && start.isIndexSet) {
if (start === this) return YES ; // optimization
content = start._content ;
cur = 0 ;
next = content[cur];
while (next !== 0) {
if ((next>0) && this.intersects(cur, next-cur)) return YES ;
cur = Math.abs(next);
next = content[cur];
}
return NO ;
} else {
length = start.length;
start = start.start;
}
}
cur = this.rangeStartForIndex(start);
content = this._content;
next = content[cur];
lim = start + length;
while (cur < lim) {
if (next === 0) return NO; // no match and at end!
if ((next > 0) && (next > start)) return YES ; // found a match
cur = Math.abs(next);
next = content[cur];
}
return NO ; // no match
},
/**
Returns a new IndexSet without the passed range or indexes. This is a
convenience over simply cloning and removing. Does some optimizations.
@param {Number} start index, range, or IndexSet
@param {Number} length optional range length
@returns {SC.IndexSet} new index set
*/
without: function(start, length) {
if (start === this) return SC.IndexSet.create(); // just need empty set
return this.clone().remove(start, length);
},
/**
Replace the index set's current content with the passed index set. This
is faster than clearing the index set adding the values again.
@param {Number} start index, Range, or another IndexSet
@param {Number} length optional length of range.
@returns {SC.IndexSet} receiver
*/
replace: function(start, length) {
if (length === undefined) {
if (typeof start === SC.T_NUMBER) {
length = 1 ;
} else if (start && start.isIndexSet) {
this._content = this._sc_sliceContent(start._content);
this.beginPropertyChanges()
.set('max', start.max)
.set('length', start.length)
.set('source', start.source)
.enumerableContentDidChange()
.endPropertyChanges();
return this ;
} else {
length = start.length;
start = start.start;
}
}
var oldlen = this.length;
this._content.length=1;
this._content[0] = 0;
this.length = this.max = 0 ; // reset without notifying since add()
return this.add(start, length);
},
/**
Adds the specified range of indexes to the set. You can also pass another
IndexSet to union the contents of the index set with the receiver.
@param {Number} start index, Range, or another IndexSet
@param {Number} length optional length of range.
@returns {SC.IndexSet} receiver
*/
add: function(start, length) {
if (this.isFrozen) throw SC.FROZEN_ERROR;
var content, cur, next;
// normalize IndexSet input
if (start && start.isIndexSet) {
content = start._content;
if (!content) return this; // nothing to do
cur = 0 ;
next = content[0];
while(next !== 0) {
if (next>0) this.add(cur, next-cur);
cur = next<0 ? 0-next : next;
next = content[cur];
}
return this ;
} else if (length === undefined) {
if (start === null || start === undefined) {
return this; // nothing to do
} else if (typeof start === SC.T_NUMBER) {
length = 1 ;
} else {
length = start.length;
start = start.start;
}
} else if (length === null) length = 1 ;
// if no length - do nothing.
if (length <= 0) return this;
// special case - appending to end of set
var max = this.get('max'),
oldmax = max,
delta, value ;
content = this._content ;
if (start === max) {
// if adding to the end and the end is in set, merge.
if (start > 0) {
cur = this.rangeStartForIndex(start-1);
next = content[cur];
// just extend range at end
if (next > 0) {
delete content[max]; // no 0
content[cur] = max = start + length ;
start = cur ;
// previous range was not in set, just tack onto the end
} else {
content[max] = max = start + length;
}
} else {
content[start] = max = length;
}
content[max] = 0 ;
this.set('max', max);
this.set('length', this.length + length) ;
length = max - start ;
} else if (start > max) {
content[max] = 0-start; // empty!
content[start] = start+length ;
content[start+length] = 0; // set end
this.set('max', start + length) ;
this.set('length', this.length + length) ;
// affected range goes from starting range to end of content.
length = start + length - max ;
start = max ;
// otherwise, merge into existing range
} else {
// find nearest starting range. split or join that range
cur = this.rangeStartForIndex(start);
next = content[cur];
max = start + length ;
delta = 0 ;
// we are right on a boundary and we had a range or were the end, then
// go back one more.
if ((start>0) && (cur === start) && (next <= 0)) {
cur = this.rangeStartForIndex(start-1);
next = content[cur] ;
}
// previous range is not in set. splice it here
if (next < 0) {
content[cur] = 0-start ;
// if previous range extends beyond this range, splice afterwards also
if (Math.abs(next) > max) {
content[start] = 0-max;
content[max] = next ;
} else content[start] = next;
// previous range is in set. merge the ranges
} else {
start = cur ;
if (next > max) {
// delta -= next - max ;
max = next ;
}
}
// at this point there should be clean starting point for the range.
// just walk the ranges, adding up the length delta and then removing
// the range until we find a range that passes last
cur = start;
while (cur < max) {
// get next boundary. splice if needed - if value is 0, we are at end
// just skip to last
value = content[cur];
if (value === 0) {
content[max] = 0;
next = max ;
delta += max - cur ;
} else {
next = Math.abs(value);
if (next > max) {
content[max] = value ;
next = max ;
}
// ok, cur range is entirely inside top range.
// add to delta if needed
if (value < 0) delta += next - cur ;
}
delete content[cur] ; // and remove range
cur = next;
}
// cur should always === last now. if the following range is in set,
// merge in also - don't adjust delta because these aren't new indexes
if ((cur = content[max]) > 0) {
delete content[max];
max = cur ;
}
// finally set my own range.
content[start] = max ;
if (max > oldmax) this.set('max', max) ;
// adjust length
this.set('length', this.get('length') + delta);
// compute hint range
length = max - start ;
}
this._hint(start, length);
if (delta !== 0) this.enumerableContentDidChange();
return this;
},
/**
Removes the specified range of indexes from the set
@param {Number} start index, Range, or IndexSet
@param {Number} length optional length of range.
@returns {SC.IndexSet} receiver
*/
remove: function(start, length) {
if (this.isFrozen) throw SC.FROZEN_ERROR;
// normalize input
if (length === undefined) {
if (start === null || start === undefined) {
return this; // nothing to do
} else if (typeof start === SC.T_NUMBER) {
length = 1 ;
// if passed an index set, just add each range in the index set.
} else if (start.isIndexSet) {
start.forEachRange(this.remove, this);
return this;
} else {
length = start.length;
start = start.start;
}
}
if (length <= 0) return this; // nothing to do
// special case - appending to end of set
var max = this.get('max'),
oldmax = max,
content = this._content,
cur, next, delta, value, last ;
// if we're past the end, do nothing.
if (start >= max) return this;
// find nearest starting range. split or join that range
cur = this.rangeStartForIndex(start);
next = content[cur];
last = start + length ;
delta = 0 ;
// we are right on a boundary and we had a range or were the end, then
// go back one more.
if ((start>0) && (cur === start) && (next > 0)) {
cur = this.rangeStartForIndex(start-1);
next = content[cur] ;
}
// previous range is in set. splice it here
if (next > 0) {
content[cur] = start ;
// if previous range extends beyond this range, splice afterwards also
if (next > last) {
content[start] = last;
content[last] = next ;
} else content[start] = next;
// previous range is not in set. merge the ranges
} else {
start = cur ;
next = Math.abs(next);
if (next > last) {
last = next ;
}
}
// at this point there should be clean starting point for the range.
// just walk the ranges, adding up the length delta and then removing
// the range until we find a range that passes last
cur = start;
while (cur < last) {
// get next boundary. splice if needed - if value is 0, we are at end
// just skip to last
value = content[cur];
if (value === 0) {
content[last] = 0;
next = last ;
} else {
next = Math.abs(value);
if (next > last) {
content[last] = value ;
next = last ;
}
// ok, cur range is entirely inside top range.
// add to delta if needed
if (value > 0) delta += next - cur ;
}
delete content[cur] ; // and remove range
cur = next;
}
// cur should always === last now. if the following range is not in set,
// merge in also - don't adjust delta because these aren't new indexes
if ((cur = content[last]) < 0) {
delete content[last];
last = Math.abs(cur) ;
}
// set my own range - if the next item is 0, then clear it.
if (content[last] === 0) {
delete content[last];
content[start] = 0 ;
this.set('max', start); //max has changed
} else {
content[start] = 0-last ;
}
// adjust length
this.set('length', this.get('length') - delta);
// compute hint range
length = last - start ;
this._hint(start, length);
if (delta !== 0) this.enumerableContentDidChange();
return this;
},
/** @private
iterates through a named range, setting hints every HINT_SIZE indexes
pointing to the nearest range start. The passed range must start on a
range boundary. It can end anywhere.
*/
_hint: function(start, length, content) {
if (content === undefined) content = this._content;
var skip = SC.IndexSet.HINT_SIZE,
next = Math.abs(content[start]), // start of next range
loc = start - (start % skip) + skip, // next hint loc
lim = start + length ; // stop
while (loc < lim) {
// make sure we are in current rnage
while ((next !== 0) && (next <= loc)) {
start = next ;
next = Math.abs(content[start]) ;
}
// past end
if (next === 0) {
delete content[loc];
// do not change if on actual boundary
} else if (loc !== start) {
content[loc] = start ; // set hint
}
loc += skip;
}
},
/**
Clears the set
*/
clear: function() {
if (this.isFrozen) throw SC.FROZEN_ERROR;
var oldlen = this.length;
this._content.length=1;
this._content[0] = 0;
this.set('length', 0).set('max', 0);
if (oldlen > 0) this.enumerableContentDidChange();
},
/**
Add all the ranges in the passed array.
*/
addEach: function(objects) {
if (this.isFrozen) throw SC.FROZEN_ERROR;
this.beginPropertyChanges();
var idx = objects.get('length') ;
if (objects.isSCArray) {
while(--idx >= 0) this.add(objects.objectAt(idx)) ;
} else if (objects.isEnumerable) {
objects.forEach(function(idx) { this.add(idx); }, this);
}
this.endPropertyChanges();
return this ;
},
/**
Removes all the ranges in the passed array.
*/
removeEach: function(objects) {
if (this.isFrozen) throw SC.FROZEN_ERROR;
this.beginPropertyChanges();
var idx = objects.get('length') ;
if (objects.isSCArray) {
while(--idx >= 0) this.remove(objects.objectAt(idx)) ;
} else if (objects.isEnumerable) {
objects.forEach(function(idx) { this.remove(idx); }, this);
}
this.endPropertyChanges();
return this ;
},
/**
Clones the set into a new set.
*/
clone: function() {
return SC.IndexSet.create(this);
},
/**
Returns a string describing the internal range structure. Useful for
debugging.
@returns {String}
*/
inspect: function() {
var content = this._content,
len = content.length,
idx = 0,
ret = [],
item;
for(idx=0;idx<len;idx++) {
item = content[idx];
if (item !== undefined) ret.push("%@:%@".fmt(idx,item));
}
return "SC.IndexSet<%@>".fmt(ret.join(' , '));
},
/**
Invoke the callback, passing each occuppied range instead of each
index. This can be a more efficient way to iterate in some cases. The
callback should have the signature:
{{{
callback(start, length, indexSet, source) { ... }
}}}
If you pass a target as a second option, the callback will be called in
the target context.
@param {Function} callback the iterator callback
@param {Object} target the target
@returns {SC.IndexSet} receiver
*/
forEachRange: function(callback, target) {
var content = this._content,
cur = 0,
next = content[cur],
source = this.source;
if (target === undefined) target = null ;
while (next !== 0) {
if (next > 0) callback.call(target, cur, next - cur, this, source);
cur = Math.abs(next);
next = content[cur];
}
return this ;
},
/**
Invokes the callback for each index within the passed start/length range.
Otherwise works just like regular forEach().
@param {Number} start starting index
@param {Number} length length of range
@param {Function} callback
@param {Object} target
@returns {SC.IndexSet} receiver
*/
forEachIn: function(start, length, callback, target) {
var content = this._content,
cur = 0,
idx = 0,
lim = start + length,
source = this.source,
next = content[cur];
if (target === undefined) target = null ;
while (next !== 0) {
if (cur < start) cur = start ; // skip forward
while((cur < next) && (cur < lim)) {
callback.call(target, cur++, idx++, this, source);
}
if (cur >= lim) {
cur = next = 0 ;
} else {
cur = Math.abs(next);
next = content[cur];
}
}
return this ;
},
/**
Total number of indexes within the specified range.
@param {Number|SC.IndexSet} start index, range object or IndexSet
@param {Number} length optional range length
@returns {Number} count of indexes
*/
lengthIn: function(start, length) {
var ret = 0 ;
// normalize input
if (length === undefined) {
if (start === null || start === undefined) {
return 0; // nothing to do
} else if (typeof start === SC.T_NUMBER) {
length = 1 ;
// if passed an index set, just add each range in the index set.
} else if (start.isIndexSet) {
start.forEachRange(function(start, length) {
ret += this.lengthIn(start, length);
}, this);
return ret;
} else {
length = start.length;
start = start.start;
}
}
// fast path
if (this.get('length') === 0) return 0;
var content = this._content,
cur = 0,
next = content[cur],
lim = start + length ;
while (cur<lim && next !== 0) {
if (next>0) {
ret += (next>lim) ? lim-cur : next-cur;
}
cur = Math.abs(next);
next = content[cur];
}
return ret ;
},
// ..........................................................
// OBJECT API
//
/**
Optionally set the source property on an index set and then you can
iterate over the actual object values referenced by the index set. See
indexOf(), lastIndexOf(), forEachObject(), addObject() and removeObject().
*/
source: null,
/**
Returns the first index in the set that matches the passed object. You
must have a source property on the set for this to work.
@param {Object} object the object to check
@param {Number} startAt optional starting point
@returns {Number} found index or -1 if not in set
*/
indexOf: function(object, startAt) {
var source = this.source;
if (!source) throw "%@.indexOf() requires source".fmt(this);
var len = source.get('length'),
// start with the first index in the set
content = this._content,
cur = content[0]<0 ? Math.abs(content[0]) : 0,
idx ;
while(cur>=0 && cur<len) {
idx = source.indexOf(object, cur);
if (idx<0) return -1 ; // not found in source
if (this.contains(idx)) return idx; // found in source and in set.
cur = idx+1;
}
return -1; // not found
},
/**
Returns the last index in the set that matches the passed object. You
must have a source property on the set for this to work.
@param {Object} object the object to check
@param {Number} startAt optional starting point
@returns {Number} found index or -1 if not in set
*/
lastIndexOf: function(object, startAt) {
var source = this.source;
if (!source) throw "%@.lastIndexOf() requires source".fmt(this);
// start with the last index in the set
var len = source.get('length'),
cur = this.max-1,
idx ;
if (cur >= len) cur = len-1;
while (cur>=0) {
idx = source.lastIndexOf(object, cur);
if (idx<0) return -1 ; // not found in source
if (this.contains(idx)) return idx; // found in source and in set.
cur = idx+1;
}
return -1; // not found
},
/**
Iterates through the objects at each index location in the set. You must
have a source property on the set for this to work. The callback you pass
will be invoked for each object in the set with the following signature:
{{{
function callback(object, index, source, indexSet) { ... }
}}}
If you pass a target, it will be used when the callback is called.
@param {Function} callback function to invoke.
@param {Object} target optional content. otherwise uses window
@returns {SC.IndexSet} receiver
*/
forEachObject: function(callback, target) {
var source = this.source;
if (!source) throw "%@.forEachObject() requires source".fmt(this);
var content = this._content,
cur = 0,
idx = 0,
next = content[cur];
if (target === undefined) target = null ;
while (next !== 0) {
while(cur < next) {
callback.call(target, source.objectAt(cur), cur, source, this);
cur++;
}
cur = Math.abs(next);
next = content[cur];
}
return this ;
},
/**
Adds all indexes where the object appears to the set. If firstOnly is
passed, then it will find only the first index and add it. If you know
the object only appears in the source array one time, firstOnly may make
this method faster.
Requires source to work.
@param {Object} object the object to add
@returns {SC.IndexSet} receiver
*/
addObject: function(object, firstOnly) {
var source = this.source;
if (!source) throw "%@.addObject() requires source".fmt(this);
var len = source.get('length'),
cur = 0, idx;
while(cur>=0 && cur<len) {
idx = source.indexOf(object, cur);
if (idx >= 0) {
this.add(idx);
if (firstOnly) return this ;
cur = idx++;
} else return this ;
}
return this ;
},
/**
Adds any indexes matching the passed objects. If firstOnly is passed,
then only finds the first index for each object.
@param {SC.Enumerable} objects the objects to add
@returns {SC.IndexSet} receiver
*/
addObjects: function(objects, firstOnly) {
objects.forEach(function(object) {
this.addObject(object, firstOnly);
}, this);
return this;
},
/**
Removes all indexes where the object appears to the set. If firstOnly is
passed, then it will find only the first index and add it. If you know
the object only appears in the source array one time, firstOnly may make
this method faster.
Requires source to work.
@param {Object} object the object to add
@returns {SC.IndexSet} receiver
*/
removeObject: function(object, firstOnly) {
var source = this.source;
if (!source) throw "%@.removeObject() requires source".fmt(this);
var len = source.get('length'),
cur = 0, idx;
while(cur>=0 && cur<len) {
idx = source.indexOf(object, cur);
if (idx >= 0) {
this.remove(idx);
if (firstOnly) return this ;
cur = idx+1;
} else return this ;
}
return this ;
},
/**
Removes any indexes matching the passed objects. If firstOnly is passed,
then only finds the first index for each object.
@param {SC.Enumerable} objects the objects to add
@returns {SC.IndexSet} receiver
*/
removeObjects: function(objects, firstOnly) {
objects.forEach(function(object) {
this.removeObject(object, firstOnly);
}, this);
return this;
},
// .......................................
// PRIVATE
//
/**
Usually observing notifications from IndexSet are not useful, so
supress them by default.
@property {Boolean}
*/
LOG_OBSERVING: NO,
/** @private - optimized call to forEach() */
forEach: function(callback, target) {
var content = this._content,
cur = 0,
idx = 0,
source = this.source,
next = content[cur];
if (target === undefined) target = null ;
while (next !== 0) {
while(cur < next) {
callback.call(target, cur++, idx++, this, source);
}
cur = Math.abs(next);
next = content[cur];
}
return this ;
},
/** @private - support iterators */
nextObject: function(ignore, idx, context) {
var content = this._content,
next = context.next,
max = this.get('max'); // next boundary
// seed.
if (idx === null) {
idx = next = 0 ;
} else if (idx >= max) {
delete context.next; // cleanup context
return null ; // nothing left to do
} else idx++; // look on next index
// look for next non-empty range if needed.
if (idx === next) {
do {
idx = Math.abs(next);
next = content[idx];
} while(next < 0);
context.next = next;
}
return idx;
},
toString: function() {
var str = [];
this.forEachRange(function(start, length) {
str.push(length === 1 ? start : "%@..%@".fmt(start, start + length - 1));
}, this);
return "SC.IndexSet<%@>".fmt(str.join(',')) ;
},
max: 0
}) ;
SC.IndexSet.slice = SC.IndexSet.copy = SC.IndexSet.clone ;
SC.IndexSet.EMPTY = SC.IndexSet.create().freeze();
/* end of original file: frameworks/sproutcore/frameworks/runtime/system/index_set.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/system/logger.js*/
// ==========================================================================
// SC.Logger
// ==========================================================================
/**
If {@link SC.Logger.format} is true, this delimiter will be put between arguments.
@property {String}
*/
SC.LOGGER_LOG_DELIMITER = ", ";
/**
If {@link SC.Logger.error} falls back onto {@link SC.Logger.log}, this will be
prepended to the output.
@property {String}
*/
SC.LOGGER_LOG_ERROR = "ERROR: ";
/**
If {@link SC.Logger.info} falls back onto {@link SC.Logger.log}, this will be
prepended to the output.
@property {String}
*/
SC.LOGGER_LOG_INFO = "INFO: ";
/**
If {@link SC.Logger.warn} falls back onto {@link SC.Logger.log}, this will be
prepended to the output.
@property {String}
*/
SC.LOGGER_LOG_WARN = "WARNING: ";
/**
If {@link SC.Logger.debug} falls back onto {@link SC.Logger.log}, this will be
prepended to the output.
@property {String}
*/
SC.LOGGER_LOG_DEBUG = "DEBUG: ";
/** @class
Object to allow for safe logging actions, such as using the browser console.
The FireFox plugin Firebug was used as a function reference. Please see
{@link <a href="http://getfirebug.com/logging.html">Firebug Logging Reference</a>}
for further information.
@author Colin Campbell
@author Benedikt Böhm
@extends SC.Object
@since Sproutcore 1.0
@see <a href="http://getfirebug.com/logging.html">Firebug Logging Reference</a>
*/
SC.Logger = SC.Object.create({
// ..........................................................
// PROPERTIES
//
/**
Whether or not to enable debug logging.
@property: {Boolean}
*/
debugEnabled: NO,
/**
Computed property that checks for the existence of the reporter object.
@property {Boolean}
*/
exists: function() {
return typeof(this.get('reporter')) !== 'undefined' && this.get('reporter') != null;
}.property('reporter').cacheable(),
/**
If console.log does not exist, SC.Logger will use window.alert instead.
This property is only used inside {@link SC.Logger.log}. If fallBackOnLog is
false and you call a different function, an alert will not be opened.
@property {Boolean}
*/
fallBackOnAlert: NO,
/**
If some function, such as console.dir, does not exist,
SC.Logger will try console.log if this is true.
@property {Boolean}
*/
fallBackOnLog: YES,
/**
Whether or not to format multiple arguments together
or let the browser deal with that.
@property {Boolean}
*/
format: YES,
/**
The reporter is the object which implements the actual logging functions.
@default The browser's console
@property {Object}
*/
reporter: console,
// ..........................................................
// METHODS
//
/**
Log output to the console, but only if it exists.
@param {String|Array|Function|Object}
@returns {Boolean} true if reporter.log exists, false otherwise
*/
log: function() {
var reporter = this.get('reporter');
// log through the reporter
if (this.get('exists') && typeof(reporter.log) === "function") {
if (this.get('format')) {
reporter.log(this._argumentsToString.apply(this, arguments));
}
else {
reporter.log.apply(reporter, arguments);
}
return true;
}
// log through alert
else if (this.fallBackOnAlert) {
var s = this.get('format') ? this._argumentsToString.apply(this, arguments) : arguments;
// include support for overriding the alert through the reporter
// if it has come this far, it's likely this will fail
if (this.get('exists') && typeof(reporter.alert) === "function") {
reporter.alert(s);
}
else {
alert(s);
}
return true;
}
return false;
},
/**
Log a debug message to the console.
Logs the response using {@link SC.Logger.log} if reporter.debug does not exist and
{@link SC.Logger.fallBackOnLog} is true.
@param {String|Array|Function|Object}
@returns {Boolean} true if logged to reporter, false if not
*/
debug: function() {
var reporter = this.get('reporter');
if (this.get('debugEnabled') !== YES) {
return false;
}
if (this.get('exists') && (typeof reporter.debug === "function")) {
reporter.debug.apply(reporter, arguments);
return true;
}
else if (this.fallBackOnLog) {
var a = this._argumentsToArray(arguments);
if (typeof(a.unshift) === "function") a.unshift(SC.LOGGER_LOG_DEBUG);
return this.log.apply(this, a);
}
return false;
},
/**
Prints the properties of an object.
Logs the object using {@link SC.Logger.log} if the reporter.dir function does not exist and
{@link SC.Logger.fallBackOnLog} is true.
@param {Object}
@returns {Boolean} true if logged to console, false if not
*/
dir: function() {
var reporter = this.get('reporter');
if (this.get('exists') && typeof(reporter.dir) === "function") {
// Firebug's console.dir doesn't support multiple objects here
// but maybe custom reporters will
reporter.dir.apply(reporter, arguments);
return true;
}
return (this.fallBackOnLog) ? this.log.apply(this, arguments) : false;
},
/**
Prints an XML outline for any HTML or XML object.
Logs the object using {@link SC.Logger.log} if reporter.dirxml function does not exist and
{@lnk SC.Logger.fallBackOnLog} is true.
@param {Object}
@returns {Boolean} true if logged to reporter, false if not
*/
dirxml: function() {
var reporter = this.get('reporter');
if (this.get('exists') && typeof(reporter.dirxml) === "function") {
// Firebug's console.dirxml doesn't support multiple objects here
// but maybe custom reporters will
reporter.dirxml.apply(reporter, arguments);
return true;
}
return (this.fallBackOnLog) ? this.log.apply(this, arguments) : false;
},
/**
Log an error to the console
Logs the error using {@link SC.Logger.log} if reporter.error does not exist and
{@link SC.Logger.fallBackOnLog} is true.
@param {String|Array|Function|Object}
@returns {Boolean} true if logged to reporter, false if not
*/
error: function() {
var reporter = this.get('reporter');
if (this.get('exists') && typeof(reporter.error) === "function") {
reporter.error.apply(reporter, arguments);
return true;
}
else if (this.fallBackOnLog) {
var a = this._argumentsToArray(arguments);
if (typeof(a.unshift) === "function") a.unshift(SC.LOGGER_LOG_ERROR);
return this.log.apply(this, a);
}
return false;
},
/**
Every log after this call until {@link SC.Logger.groupEnd} is called
will be indented for readability. You can create as many levels
as you want.
@param {String} [title] An optional title to display above the group
@returns {Boolean} true if reporter.group exists, false otherwise
*/
group: function(s) {
var reporter = this.get('reporter');
if (this.get('exists') && typeof(reporter.group) === "function") {
reporter.group(s);
return true;
}
return false;
},
/**
Ends a group declared with {@link SC.Logger.group}.
@returns {Boolean} true if the reporter.groupEnd exists, false otherwise
@see SC.Logger.group
*/
groupEnd: function() {
var reporter = this.get('reporter');
if (this.get('exists') && typeof(reporter.groupEnd) === "function") {
reporter.groupEnd();
return true;
}
return false;
},
/**
Log an information response to the reporter.
Logs the response using {@link SC.Logger.log} if reporter.info does not exist and
{@link SC.Logger.fallBackOnLog} is true.
@param {String|Array|Function|Object}
@returns {Boolean} true if logged to reporter, false if not
*/
info: function() {
var reporter = this.get('reporter');
if (this.get('exists') && typeof(reporter.info) === "function") {
reporter.info.apply(reporter, arguments);
return true;
}
else if (this.fallBackOnLog) {
var a = this._argumentsToArray(arguments);
if (typeof(a.unshift) === "function") a.unshift(SC.LOGGER_LOG_INFO);
return this.log.apply(this, a);
}
return false;
},
/**
Begins the JavaScript profiler, if it exists. Call {@link SC.Logger.profileEnd}
to end the profiling process and receive a report.
@returns {Boolean} true if reporter.profile exists, false otherwise
*/
profile: function() {
var reporter = this.get('reporter');
if (this.get('exists') && typeof(reporter.profile) === "function") {
reporter.profile();
return true;
}
return false;
},
/**
Ends the JavaScript profiler, if it exists.
@returns {Boolean} true if reporter.profileEnd exists, false otherwise
@see SC.Logger.profile
*/
profileEnd: function() {
var reporter = this.get('reporter');
if (this.get('exists') && typeof(reporter.profileEnd) === "function") {
reporter.profileEnd();
return true;
}
return false;
},
/**
Measure the time between when this function is called and
{@link SC.Logger.timeEnd} is called.
@param {String} name The name of the profile to begin
@returns {Boolean} true if reporter.time exists, false otherwise
@see SC.Logger.timeEnd
*/
time: function(name) {
var reporter = this.get('reporter');
if (this.get('exists') && typeof(reporter.time) === "function") {
reporter.time(name);
return true;
}
return false;
},
/**
Ends the profile specified.
@param {String} name The name of the profile to end
@returns {Boolean} true if reporter.timeEnd exists, false otherwise
@see SC.Logger.time
*/
timeEnd: function(name) {
var reporter = this.get('reporter');
if (this.get('exists') && typeof(reporter.timeEnd) === "function") {
reporter.timeEnd(name);
return true;
}
return false;
},
/**
Prints a stack-trace.
@returns {Boolean} true if reporter.trace exists, false otherwise
*/
trace: function() {
var reporter = this.get('reporter');
if (this.get('exists') && typeof(reporter.trace) === "function") {
reporter.trace();
return true;
}
return false;
},
/**
Log a warning to the console.
Logs the warning using {@link SC.Logger.log} if reporter.warning does not exist and
{@link SC.Logger.fallBackOnLog} is true.
@param {String|Array|Function|Object}
@returns {Boolean} true if logged to reporter, false if not
*/
warn: function() {
var reporter = this.get('reporter');
if (this.get('exists') && typeof(reporter.warn) === "function") {
reporter.warn.apply(reporter, arguments);
return true;
}
else if (this.fallBackOnLog) {
var a = this._argumentsToArray(arguments);
if (typeof(a.unshift) === "function") a.unshift(SC.LOGGER_LOG_WARN);
return this.log.apply(this, a);
}
return false;
},
// ..........................................................
// INTERNAL SUPPORT
//
/**
@private
The arguments function property doesn't support Array#unshift. This helper
copies the elements of arguments to a blank array.
@param {Array} arguments The arguments property of a function
@returns {Array} An array containing the elements of arguments parameter
*/
_argumentsToArray: function(args) {
if (!args) return [];
var a = [];
for (var i = 0; i < args.length; i++) {
a[i] = args[i];
}
return a;
},
/**
@private
Formats the arguments array of a function by creating a string
with SC.LOGGER_LOG_DELIMITER between the elements.
@returns {String} A string of formatted arguments
*/
_argumentsToString: function() {
var s = "";
for (var i = 0; i<arguments.length - 1; i++) {
s += arguments[i] + SC.LOGGER_LOG_DELIMITER;
}
s += arguments[arguments.length-1];
return s;
}
});
/* end of original file: frameworks/sproutcore/frameworks/runtime/system/logger.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/system/run_loop.js*/
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
sc_require('private/observer_set');
/**
@class
The run loop provides a universal system for coordinating events within
your application. The run loop processes timers as well as pending
observer notifications within your application.
To use a RunLoop within your application, you should make sure your event
handlers always begin and end with SC.RunLoop.begin() and SC.RunLoop.end()
The RunLoop is important because bindings do not fire until the end of
your run loop is reached. This improves the performance of your
application.
h2. Example
This is how you could write your mouseup handler in jQuery:
{{{
$('#okButton').on('click', function() {
SC.RunLoop.begin();
// handle click event...
SC.RunLoop.end(); // allows bindings to trigger...
});
}}}
@extends SC.Object
@since SproutCore 1.0
*/
SC.RunLoop = SC.Object.extend(/** @scope SC.RunLoop.prototype */ {
/**
Call this method whenver you begin executing code.
This is typically invoked automatically for you from event handlers and
the timeout handler. If you call setTimeout() or setInterval() yourself,
you may need to invoke this yourself.
@returns {SC.RunLoop} receiver
*/
beginRunLoop: function() {
this._start = new Date().getTime() ; // can't use Date.now() in runtime
if (SC.LOG_BINDINGS || SC.LOG_OBSERVERS) {
console.log("-- SC.RunLoop.beginRunLoop at %@".fmt(this._start));
}
this._runLoopInProgress = YES;
return this ;
},
/**
YES when a run loop is in progress
@property {Boolean}
*/
isRunLoopInProgress: function() {
return this._runLoopInProgress;
}.property(),
/**
Call this method whenever you are done executing code.
This is typically invoked automatically for you from event handlers and
the timeout handler. If you call setTimeout() or setInterval() yourself
you may need to invoke this yourself.
@returns {SC.RunLoop} receiver
*/
endRunLoop: function() {
// at the end of a runloop, flush all the delayed actions we may have
// stored up. Note that if any of these queues actually run, we will
// step through all of them again. This way any changes get flushed
// out completely.
if (SC.LOG_BINDINGS || SC.LOG_OBSERVERS) {
console.log("-- SC.RunLoop.endRunLoop ~ flushing application queues");
}
this.flushAllPending();
this._start = null ;
if (SC.LOG_BINDINGS || SC.LOG_OBSERVERS) {
console.log("-- SC.RunLoop.endRunLoop ~ End");
}
SC.RunLoop.lastRunLoopEnd = Date.now();
this._runLoopInProgress = NO;
return this ;
},
/**
Repeatedly flushes all bindings, observers, and other queued functions until all queues are empty.
*/
flushAllPending: function() {
var didChange ;
do {
didChange = this.flushApplicationQueues() ;
if (!didChange) didChange = this._flushinvokeLastQueue() ;
} while(didChange) ;
},
/**
Invokes the passed target/method pair once at the end of the runloop.
You can call this method as many times as you like and the method will
only be invoked once.
Usually you will not call this method directly but use invokeOnce()
defined on SC.Object.
Note that in development mode only, the object and method that call this
method will be recorded, for help in debugging scheduled code.
@param {Object} target
@param {Function} method
@returns {SC.RunLoop} receiver
*/
invokeOnce: function(target, method) {
// normalize
if (method === undefined) {
method = target; target = this ;
}
if (typeof method === "string") method = target[method];
if (!this._invokeQueue) this._invokeQueue = SC.ObserverSet.create();
if ( method ) this._invokeQueue.add(target, method);
return this ;
},
/**
Invokes the passed target/method pair at the very end of the run loop,
once all other delayed invoke queues have been flushed. Use this to
schedule cleanup methods at the end of the run loop once all other work
(including rendering) has finished.
If you call this with the same target/method pair multiple times it will
only invoke the pair only once at the end of the runloop.
Usually you will not call this method directly but use invokeLast()
defined on SC.Object.
Note that in development mode only, the object and method that call this
method will be recorded, for help in debugging scheduled code.
@param {Object} target
@param {Function} method
@returns {SC.RunLoop} receiver
*/
invokeLast: function(target, method) {
// normalize
if (method === undefined) {
method = target; target = this ;
}
if (typeof method === "string") method = target[method];
if (!this._invokeLastQueue) this._invokeLastQueue = SC.ObserverSet.create();
this._invokeLastQueue.add(target, method);
return this ;
},
/**
Executes any pending events at the end of the run loop. This method is
called automatically at the end of a run loop to flush any pending
queue changes.
The default method will invoke any one time methods and then sync any
bindings that might have changed. You can override this method in a
subclass if you like to handle additional cleanup.
This method must return YES if it found any items pending in its queues
to take action on. endRunLoop will invoke this method repeatedly until
the method returns NO. This way if any if your final executing code
causes additional queues to trigger, then can be flushed again.
@returns {Boolean} YES if items were found in any queue, NO otherwise
*/
flushApplicationQueues: function() {
var hadContent = NO,
// execute any methods in the invokeQueue.
queue = this._invokeQueue;
if ( queue && queue.getMembers().length ) {
this._invokeQueue = null; // reset so that a new queue will be created
hadContent = YES ; // needs to execute again
queue.invokeMethods();
}
// flush any pending changed bindings. This could actually trigger a
// lot of code to execute.
return SC.Binding.flushPendingChanges() || hadContent ;
},
_flushinvokeLastQueue: function() {
var queue = this._invokeLastQueue, hadContent = NO ;
if (queue && queue.getMembers().length ) {
this._invokeLastQueue = null; // reset queue.
hadContent = YES; // has targets!
if (hadContent) queue.invokeMethods();
}
return hadContent ;
}
});
/**
The current run loop. This is created automatically the first time you
call begin().
@property {SC.RunLoop}
*/
SC.RunLoop.currentRunLoop = null;
/**
The default RunLoop class. If you choose to extend the RunLoop, you can
set this property to make sure your class is used instead.
@property {Class}
*/
SC.RunLoop.runLoopClass = SC.RunLoop;
/**
Begins a new run loop on the currentRunLoop. If you are already in a
runloop, this method has no effect.
@returns {SC.RunLoop} receiver
*/
SC.RunLoop.begin = function() {
var runLoop = this.currentRunLoop;
if (!runLoop) runLoop = this.currentRunLoop = this.runLoopClass.create();
runLoop.beginRunLoop();
return this ;
};
/**
Ends the run loop on the currentRunLoop. This will deliver any final
pending notifications and schedule any additional necessary cleanup.
@returns {SC.RunLoop} receiver
*/
SC.RunLoop.end = function() {
var runLoop = this.currentRunLoop;
if (!runLoop) {
throw "SC.RunLoop.end() called outside of a runloop!";
}
runLoop.endRunLoop();
return this ;
} ;
/**
Returns YES when a run loop is in progress
@return {Boolean}
*/
SC.RunLoop.isRunLoopInProgress = function() {
if(this.currentRunLoop) return this.currentRunLoop.get('isRunLoopInProgress');
return NO;
};
/**
Executes a passed function in the context of a run loop. If called outside a
runloop, starts and ends one. If called inside an existing runloop, is
simply executes the function unless you force it to create a nested runloop.
If an exception is thrown during execution, we give an error catcher the
opportunity to handle it before allowing the exception to bubble again.
@param {Function} callback callback to execute
@param {Object} target context for callback
@param {Boolean} if YES, starts/ends a new runloop even if one is already running
*/
SC.run = function(callback, target, forceNested) {
var alreadyRunning = SC.RunLoop.isRunLoopInProgress();
// Only use a try/catch block if we have an ExceptionHandler
// since in some browsers try/catch causes a loss of the backtrace
if (SC.ExceptionHandler && SC.ExceptionHandler.enabled) {
try {
if(forceNested || !alreadyRunning) SC.RunLoop.begin();
if (callback) callback.call(target);
if(forceNested || !alreadyRunning) SC.RunLoop.end();
} catch (e) {
SC.ExceptionHandler.handleException(e);
// Now that we've handled the exception, throw it again so the browser
// can deal with it (and potentially use it for debugging).
// (We don't throw it in IE because the user will see two errors)
if (!SC.browser.msie) {
throw e;
}
}
} else {
if(forceNested || !alreadyRunning) SC.RunLoop.begin();
if (callback) callback.call(target);
if(forceNested || !alreadyRunning) SC.RunLoop.end();
}
};
/* end of original file: frameworks/sproutcore/frameworks/runtime/system/run_loop.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/system/selection_set.js*/
// ==========================================================================
// Project: SproutCore - JavaScript Application Framework
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
sc_require('system/object');
sc_require('mixins/enumerable');
sc_require('mixins/copyable');
sc_require('mixins/freezable');
/** @class
A SelectionSet contains a set of objects that represent the current
selection. You can select objects by either adding them to the set directly
or indirectly by selecting a range of indexes on a source object.
@extends SC.Object
@extends SC.Enumerable
@extends SC.Freezable
@extends SC.Copyable
@since SproutCore 1.0
*/
SC.SelectionSet = SC.Object.extend(SC.Enumerable, SC.Freezable, SC.Copyable,
/** @scope SC.SelectionSet.prototype */ {
/**
Walk like a duck.
@property {Boolean}
*/
isSelectionSet: YES,
/**
Total number of indexes in the selection set
@property {Number}
*/
length: function() {
var ret = 0,
sets = this._sets,
objects = this._objects;
if (objects) ret += objects.get('length');
if (sets) sets.forEach(function(s) { ret += s.get('length'); });
return ret ;
}.property().cacheable(),
// ..........................................................
// INDEX-BASED SELECTION
//
/**
A set of all the source objects used in the selection set. This
property changes automatically as you add or remove index sets.
@property {SC.Array}
*/
sources: function() {
var ret = [],
sets = this._sets,
len = sets ? sets.length : 0,
idx, set, source;
for(idx=0;idx<len;idx++) {
set = sets[idx];
if (set && set.get('length')>0 && set.source) ret.push(set.source);
}
return ret ;
}.property().cacheable(),
/**
Returns the index set for the passed source object or null if no items are
seleted in the source.
@param {SC.Array} source the source object
@returns {SC.IndexSet} index set or null
*/
indexSetForSource: function(source) {
if (!source || !source.isSCArray) return null; // nothing to do
var cache = this._indexSetCache,
objects = this._objects,
ret, idx;
// try to find in cache
if (!cache) cache = this._indexSetCache = {};
ret = cache[SC.guidFor(source)];
if (ret && ret._sourceRevision && (ret._sourceRevision !== source.propertyRevision)) {
ret = null;
}
// not in cache. generate from index sets and any saved objects
if (!ret) {
ret = this._indexSetForSource(source, NO);
if (ret && ret.get('length')===0) ret = null;
if (objects) {
if (ret) ret = ret.copy();
objects.forEach(function(o) {
if ((idx = source.indexOf(o)) >= 0) {
if (!ret) ret = SC.IndexSet.create();
ret.add(idx);
}
}, this);
}
if (ret) {
ret = cache[SC.guidFor(source)] = ret.frozenCopy();
ret._sourceRevision = source.propertyRevision;
}
}
return ret;
},
/**
@private
Internal method gets the index set for the source, ignoring objects
that have been added directly.
*/
_indexSetForSource: function(source, canCreate) {
if (canCreate === undefined) canCreate = YES;
var guid = SC.guidFor(source),
index = this[guid],
sets = this._sets,
len = sets ? sets.length : 0,
ret = null;
if (index >= len) index = null;
if (SC.none(index)) {
if (canCreate && !this.isFrozen) {
this.propertyWillChange('sources');
if (!sets) sets = this._sets = [];
ret = sets[len] = SC.IndexSet.create();
ret.source = source ;
this[guid] = len;
this.propertyDidChange('sources');
}
} else ret = sets ? sets[index] : null;
return ret ;
},
/**
Add the passed index, range of indexSet belonging to the passed source
object to the selection set.
The first parameter you pass must be the source array you are selecting
from. The following parameters may be one of a start/length pair, a
single index, a range object or an IndexSet. If some or all of the range
you are selecting is already in the set, it will not be selected again.
You can also pass an SC.SelectionSet to this method and all the selected
sets will be added from their instead.
@param {SC.Array} source source object or object to add.
@param {Number} start index, start of range, range or IndexSet
@param {Number} length length if passing start/length pair.
@returns {SC.SelectionSet} receiver
*/
add: function(source, start, length) {
if (this.isFrozen) throw SC.FROZEN_ERROR ;
var sets, len, idx, set, oldlen, newlen, setlen, objects;
// normalize
if (start === undefined && length === undefined) {
if (!source) throw "Must pass params to SC.SelectionSet.add()";
if (source.isIndexSet) return this.add(source.source, source);
if (source.isSelectionSet) {
sets = source._sets;
objects = source._objects;
len = sets ? sets.length : 0;
this.beginPropertyChanges();
for(idx=0;idx<len;idx++) {
set = sets[idx];
if (set && set.get('length')>0) this.add(set.source, set);
}
if (objects) this.addObjects(objects);
this.endPropertyChanges();
return this ;
}
}
set = this._indexSetForSource(source, YES);
oldlen = this.get('length');
setlen = set.get('length');
newlen = oldlen - setlen;
set.add(start, length);
this._indexSetCache = null;
newlen += set.get('length');
if (newlen !== oldlen) {
this.propertyDidChange('length');
this.enumerableContentDidChange();
if (setlen === 0) this.notifyPropertyChange('sources');
}
return this ;
},
/**
Removes the passed index, range of indexSet belonging to the passed source
object from the selection set.
The first parameter you pass must be the source array you are selecting
from. The following parameters may be one of a start/length pair, a
single index, a range object or an IndexSet. If some or all of the range
you are selecting is already in the set, it will not be selected again.
@param {SC.Array} source source object. must not be null
@param {Number} start index, start of range, range or IndexSet
@param {Number} length length if passing start/length pair.
@returns {SC.SelectionSet} receiver
*/
remove: function(source, start, length) {
if (this.isFrozen) throw SC.FROZEN_ERROR ;
var sets, len, idx, set, oldlen, newlen, setlen, objects;
// normalize
if (start === undefined && length === undefined) {
if (!source) throw "Must pass params to SC.SelectionSet.remove()";
if (source.isIndexSet) return this.remove(source.source, source);
if (source.isSelectionSet) {
sets = source._sets;
objects = source._objects;
len = sets ? sets.length : 0;
this.beginPropertyChanges();
for(idx=0;idx<len;idx++) {
set = sets[idx];
if (set && set.get('length')>0) this.remove(set.source, set);
}
if (objects) this.removeObjects(objects);
this.endPropertyChanges();
return this ;
}
}
// save starter info
set = this._indexSetForSource(source, YES);
oldlen = this.get('length');
newlen = oldlen - set.get('length');
// if we have objects selected, determine if they are in the index
// set and remove them as well.
if (set && (objects = this._objects)) {
// convert start/length to index set so the iterator below will work...
if (length !== undefined) {
start = SC.IndexSet.create(start, length);
length = undefined;
}
objects.forEach(function(object) {
idx = source.indexOf(object);
if (start.contains(idx)) {
objects.remove(object);
newlen--;
}
}, this);
}
// remove indexes from source index set
set.remove(start, length);
setlen = set.get('length');
newlen += setlen;
// update caches; change enumerable...
this._indexSetCache = null;
if (newlen !== oldlen) {
this.propertyDidChange('length');
this.enumerableContentDidChange();
if (setlen === 0) this.notifyPropertyChange('sources');
}
return this ;
},
/**
Returns YES if the selection contains the named index, range of indexes.
@param {Object} source source object for range
@param {Number} start index, start of range, range object, or indexSet
@param {Number} length optional range length
@returns {Boolean}
*/
contains: function(source, start, length) {
if (start === undefined && length === undefined) {
return this.containsObject(source);
}
var set = this.indexSetForSource(source);
if (!set) return NO ;
return set.contains(start, length);
},
/**
Returns YES if the index set contains any of the passed indexes. You
can pass a single index, a range or an index set.
@param {Object} source source object for range
@param {Number} start index, range, or IndexSet
@param {Number} length optional range length
@returns {Boolean}
*/
intersects: function(source, start, length) {
var set = this.indexSetForSource(source, NO);
if (!set) return NO ;
return set.intersects(start, length);
},
// ..........................................................
// OBJECT-BASED API
//
_TMP_ARY: [],
/**
Adds the object to the selection set. Unlike adding an index set, the
selection will actually track the object independent of its location in
the array.
@param {Object} object
@returns {SC.SelectionSet} receiver
*/
addObject: function(object) {
var ary = this._TMP_ARY, ret;
ary[0] = object;
ret = this.addObjects(ary);
ary.length = 0;
return ret;
},
/**
Adds objects in the passed enumerable to the selection set. Unlike adding
an index set, the seleciton will actually track the object independent of
its location the array.
@param {SC.Enumerable} objects
@returns {SC.SelectionSet} receiver
*/
addObjects: function(objects) {
var cur = this._objects,
oldlen, newlen;
if (!cur) cur = this._objects = SC.CoreSet.create();
oldlen = cur.get('length');
cur.addEach(objects);
newlen = cur.get('length');
this._indexSetCache = null;
if (newlen !== oldlen) {
this.propertyDidChange('length');
this.enumerableContentDidChange();
}
return this;
},
/**
Removes the object from the selection set. Note that if the selection
set also selects a range of indexes that includes this object, it may
still be in the selection set.
@param {Object} object
@returns {SC.SelectionSet} receiver
*/
removeObject: function(object) {
var ary = this._TMP_ARY, ret;
ary[0] = object;
ret = this.removeObjects(ary);
ary.length = 0;
return ret;
},
/**
Removes the objects from the selection set. Note that if the selection
set also selects a range of indexes that includes this object, it may
still be in the selection set.
@param {Object} object
@returns {SC.SelectionSet} receiver
*/
removeObjects: function(objects) {
var cur = this._objects,
oldlen, newlen, sets;
if (!cur) return this;
oldlen = cur.get('length');
cur.removeEach(objects);
newlen = cur.get('length');
// also remove from index sets, if present
if (sets = this._sets) {
sets.forEach(function(set) {
oldlen += set.get('length');
set.removeObjects(objects);
newlen += set.get('length');
}, this);
}
this._indexSetCache = null;
if (newlen !== oldlen) {
this.propertyDidChange('length');
this.enumerableContentDidChange();
}
return this;
},
/**
Returns YES if the selection contains the passed object. This will search
selected ranges in all source objects.
@param {Object} object the object to search for
@returns {Boolean}
*/
containsObject: function(object) {
// fast path
var objects = this._objects ;
if (objects && objects.contains(object)) return YES ;
var sets = this._sets,
len = sets ? sets.length : 0,
idx, set;
for(idx=0;idx<len;idx++) {
set = sets[idx];
if (set && set.indexOf(object)>=0) return YES;
}
return NO ;
},
// ..........................................................
// GENERIC HELPER METHODS
//
/**
Constrains the selection set to only objects found in the passed source
object. This will remove any indexes selected in other sources, any
indexes beyond the length of the content, and any objects not found in the
set.
@param {Object} source the source to limit
@returns {SC.SelectionSet} receiver
*/
constrain: function(source) {
var set, len, max, objects;
this.beginPropertyChanges();
// remove sources other than this one
this.get('sources').forEach(function(cur) {
if (cur === source) return; //skip
var set = this._indexSetForSource(source, NO);
if (set) this.remove(source, set);
},this);
// remove indexes beyond end of source length
set = this._indexSetForSource(source, NO);
if (set && ((max=set.get('max'))>(len=source.get('length')))) {
this.remove(source, len, max-len);
}
// remove objects not in source
if (objects = this._objects) {
objects.forEach(function(cur) {
if (source.indexOf(cur)<0) this.removeObject(cur);
},this);
}
this.endPropertyChanges();
return this ;
},
/**
Returns YES if the passed index set or selection set contains the exact
same source objects and indexes as the receiver. If you pass any object
other than an IndexSet or SelectionSet, returns NO.
@param {Object} obj another object.
@returns {Boolean}
*/
isEqual: function(obj) {
var left, right, idx, len, sources, source;
// fast paths
if (!obj || !obj.isSelectionSet) return NO ;
if (obj === this) return YES;
if ((this._sets === obj._sets) && (this._objects === obj._objects)) return YES;
if (this.get('length') !== obj.get('length')) return NO;
// check objects
left = this._objects;
right = obj._objects;
if (left || right) {
if ((left ? left.get('length'):0) !== (right ? right.get('length'):0)) {
return NO;
}
if (left && !left.isEqual(right)) return NO ;
}
// now go through the sets
sources = this.get('sources');
len = sources.get('length');
for(idx=0;idx<len;idx++) {
source = sources.objectAt(idx);
left = this._indexSetForSource(source, NO);
right = this._indexSetForSource(source, NO);
if (!!right !== !!left) return NO ;
if (left && !left.isEqual(right)) return NO ;
}
return YES ;
},
/**
Clears the set. Removes all IndexSets from the object
@returns {SC.SelectionSet}
*/
clear: function() {
if (this.isFrozen) throw SC.FROZEN_ERROR;
if (this._sets) this._sets.length = 0 ; // truncate
if (this._objects) this._objects = null;
this._indexSetCache = null;
this.propertyDidChange('length');
this.enumerableContentDidChange();
this.notifyPropertyChange('sources');
return this ;
},
/**
Clones the set into a new set.
@returns {SC.SelectionSet}
*/
copy: function() {
var ret = this.constructor.create(),
sets = this._sets,
len = sets ? sets.length : 0 ,
idx, set;
if (sets && len>0) {
sets = ret._sets = sets.slice();
for(idx=0;idx<len;idx++) {
if (!(set = sets[idx])) continue ;
set = sets[idx] = set.copy();
ret[SC.guidFor(set.source)] = idx;
}
}
if (this._objects) ret._objects = this._objects.copy();
return ret ;
},
/**
@private
Freezing a SelectionSet also freezes its internal sets.
*/
freeze: function() {
if (this.isFrozen) return this ;
var sets = this._sets,
loc = sets ? sets.length : 0,
set ;
while(--loc >= 0) {
if (set = sets[loc]) set.freeze();
}
if (this._objects) this._objects.freeze();
return arguments.callee.base.apply(this,arguments);
},
// ..........................................................
// ITERATORS
//
/** @private */
toString: function() {
var sets = this._sets || [];
sets = sets.map(function(set) {
return set.toString().replace("SC.IndexSet", SC.guidFor(set.source));
}, this);
if (this._objects) sets.push(this._objects.toString());
return "SC.SelectionSet:%@<%@>".fmt(SC.guidFor(this), sets.join(','));
},
/** @private */
firstObject: function() {
var sets = this._sets,
objects = this._objects;
// if we have sets, get the first one
if (sets && sets.get('length')>0) {
var set = sets ? sets[0] : null,
src = set ? set.source : null,
idx = set ? set.firstObject() : -1;
if (src && idx>=0) return src.objectAt(idx);
}
// otherwise if we have objects, get the first one
return objects ? objects.firstObject() : undefined;
}.property(),
/** @private
Implement primitive enumerable support. Returns each object in the
selection.
*/
nextObject: function(count, lastObject, context) {
var objects, ret;
// TODO: Make this more efficient. Right now it collects all objects
// first.
if (count === 0) {
objects = context.objects = [];
this.forEach(function(o) { objects.push(o); }, this);
context.max = objects.length;
}
objects = context.objects ;
ret = objects[count];
if (count+1 >= context.max) {
context.objects = context.max = null;
}
return ret ;
},
/**
Iterates over the selection, invoking your callback with each __object__.
This will actually find the object referenced by each index in the
selection, not just the index.
The callback must have the following signature:
{{{
function callback(object, index, source, indexSet) { ... }
}}}
If you pass a target, it will be used when the callback is called.
@param {Function} callback function to invoke.
@param {Object} target optional content. otherwise uses window
@returns {SC.SelectionSet} receiver
*/
forEach: function(callback, target) {
var sets = this._sets,
objects = this._objects,
len = sets ? sets.length : 0,
set, idx;
for(idx=0;idx<len;idx++) {
set = sets[idx];
if (set) set.forEachObject(callback, target);
}
if (objects) objects.forEach(callback, target);
return this ;
}
});
/** @private */
SC.SelectionSet.prototype.clone = SC.SelectionSet.prototype.copy;
/**
Default frozen empty selection set
@property {SC.SelectionSet}
*/
SC.SelectionSet.EMPTY = SC.SelectionSet.create().freeze();
/* end of original file: frameworks/sproutcore/frameworks/runtime/system/selection_set.js*/
/* start of original file: frameworks/sproutcore/frameworks/runtime/system/sparse_array.js*/
// ==========================================================================
// Project: SproutCore Costello - Property Observing Library
// Copyright: ©2006-2010 Sprout Systems, Inc. and contributors.
// Portions ©2008-2010 Apple Inc. All rights reserved.
// License: Licensed under MIT license (see license.js)
// ==========================================================================
sc_require('mixins/enumerable') ;
sc_require('mixins/array') ;
sc_require('mixins/observable') ;
sc_require('mixins/delegate_support') ;
/**
@class
A dynamically filled array. A SparseArray makes it easy for you to create
very large arrays of data but then to defer actually populating that array
until it is actually needed. This is often much faster than generating an
array up front and paying the cost to load your data then.
Although technically all arrays in JavaScript are "sparse" (in the sense
that you can read and write properties at arbitrary indexes), this array
keeps track of which elements in the array have been populated already
and which ones have not. If you try to get a value at an index that has
not yet been populated, the SparseArray will notify a delegate object first,
giving the delegate a chance to populate the component.
Most of the time, you will use a SparseArray to incrementally load data
from the server. For example, if you have a contact list with 3,000
contacts in it, you may create a SparseArray with a length of 3,000 and set
that as the content for a ListView. As the ListView tries to display the
visible contacts, it will request them from the SparseArray, which will in
turn notify your delegate, giving you a chance to load the contact data from
the server.
@extends SC.Enumerable
@extends SC.Array
@extends SC.Observable
@extends SC.DelegateSupport
@since SproutCore 1.0
*/
SC.SparseArray = SC.Object.extend(SC.Observable, SC.Enumerable, SC.Array,
SC.DelegateSupport, /** @scope SC.SparseArray.prototype */ {
// ..........................................................
// LENGTH SUPPORT
//
_requestingLength: 0,
_requestingIndex: 0,
/**
The length of the sparse array. The delegate for the array should set
this length.
@property {Number}
*/
length: function() {
var del = this.delegate ;
if (del && SC.none(this._length) && del.sparseArrayDidRequestLength) {
this._requestingLength++ ;
del.sparseArrayDidRequestLength(this);
this._requestingLength-- ;
}
return this._length || 0 ;
}.property().cacheable(),
/**
Call this method from a delegate to provide a length for the sparse array.
If you pass null for this property, it will essentially "reset" the array
causing your delegate to be called again the next time another object
requests the array length.
@param {Number} length the length or null
@returns {SC.SparseArray} receiver
*/
provideLength: function(length) {
if (SC.none(length)) this._sa_content = null ;
if (length !== this._length) {
this._length = length ;
if (this._requestingLength <= 0) this.enumerableContentDidChange() ;
}
return this ;
},
// ..........................................................
// READING CONTENT
//
/**
The minimum range of elements that should be requested from the delegate.
If this value is set to larger than 1, then the sparse array will always
fit a requested index into a range of this size and request it.
@property {Number}
*/
rangeWindowSize: 1,
/**
This array contains all the start_indexes of ranges requested. This is to
avoid calling sparseArrayDidRequestRange to often. Indexes are removed and
added as range requests are completed.
*/
requestedRangeIndex: null,
/**
Make sure to create the index array during init so that it is not shared
between all instances.
*/
init: function() {
arguments.callee.base.apply(this,arguments);
this.requestedRangeIndex = [];
this._TMP_PROVIDE_ARRAY = [];
this._TMP_PROVIDE_RANGE = { length: 1 };
this._TMP_RANGE = {};
},
/**
Returns the object at the specified index. If the value for the index
is currently undefined, invokes the didRequestIndex() method to notify
the delegate.
The omitMaterializing flag ensures that the object will not be materialized,
but it simply checks for the presence of an object at the specified index
and will return YES (or undefined if not found). This is useful in the case
of SparseArrays, where you might NOT want to request the index to be loaded,
but simply need a shallow check to see if the position has been filled.
@param {Number} idx the index to get
@param {Boolean} omitMaterializing
@return {Object} the object
*/
objectAt: function(idx, omitMaterializing) {
var content = this._sa_content, ret ;
if (!content) content = this._sa_content = [] ;
if ((ret = content[idx]) === undefined) {
if(!omitMaterializing) this.requestIndex(idx);
ret = content[idx]; // just in case the delegate provided immediately
}
return ret ;
},
/**
Returns the set of indexes that are currently defined on the sparse array.
If you pass an optional index set, the search will be limited to only
those indexes. Otherwise this method will return an index set containing
all of the defined indexes. Currently this can be quite expensive if
you have a lot of indexes defined.
@param {SC.IndexSet} indexes optional from indexes
@returns {SC.IndexSet} defined indexes
*/
definedIndexes: function(indexes) {
var ret = SC.IndexSet.create(),
content = this._sa_content,
idx, len;
if (!content) return ret.freeze(); // nothing to do
if (indexes) {
indexes.forEach(function(idx) {
if (content[idx] !== undefined) ret.add(idx);
});
} else {
len = content.length;
for(idx=0;idx<len;idx++) {
if (content[idx] !== undefined) ret.add(idx);
}
}
return ret.freeze();
},
_TMP_RANGE: {},
/**
Called by objectAt() whenever you request an index that has not yet been
loaded. This will possibly expand the index into a range and then invoke
an appropriate method on the delegate to request the data.
It will check if the range has been already requested.
@param {Number} idx the index to retrieve
@returns {SC.SparseArray} receiver
*/
requestIndex: function(idx) {
var del = this.delegate;
if (!del) return this; // nothing to do
// adjust window
var len = this.get('rangeWindowSize'), start = idx;
if (len > 1) start = start - Math.floor(start % len);
if (len < 1) len = 1 ;
// invoke appropriate callback
this._requestingIndex++;
if (del.sparseArrayDidRequestRange) {
var range = this._TMP_RANGE;
if(this.wasRangeRequested(start)===-1){
range.start = start;
range.length = len;
this.requestedRangeIndex.push(start);
del.sparseArrayDidRequestRange(this, range);
}
} else if (del.sparseArrayDidRequestIndex) {
while(--len >= 0) del.sparseArrayDidRequestIndex(this, start + len);
}
this._requestingIndex--;
return this ;
},
/*
This method is called by requestIndex to check if the range has already
been requested. We assume that rangeWindowSize is not changed often.
@param {Number} startIndex
@return {Number} index in requestRangeIndex
*/
wasRangeRequested: function(rangeStart) {
var i, ilen;
for(i=0, ilen=this.requestedRangeIndex.length; i<ilen; i++){
if(this.requestedRangeIndex[i]===rangeStart) return i;
}
return -1;
},
/*
This method has to be called after a request for a range has completed.
To remove the index from the sparseArray to allow future updates on the
range.
@param {Number} startIndex
@return {Number} index in requestRangeIndex
*/
rangeRequestCompleted: function(start) {
var i = this.wasRangeRequested(start);
if(i>=0) {
this.requestedRangeIndex.removeAt(i,1);
return YES;
}
return NO;
},
/**
This method sets the content for the specified to the objects in the
passed array. If you change the way SparseArray implements its internal
tracking of objects, you should override this method along with
objectAt().
@param {Range} range the range to apply to
@param {Array} array the array of objects to insert
@returns {SC.SparseArray} reciever
*/
provideObjectsInRange: function(range, array) {
var content = this._sa_content ;
if (!content) content = this._sa_content = [] ;
var start = range.start, len = range.length;
while(--len >= 0) content[start+len] = array.objectAt(len);
if (this._requestingIndex <= 0) this.enumerableContentDidChange(range.start, range.length);
return this ;
},
/**
Convenience method to provide a single object at a specified index. Under
the covers this calls provideObjectsInRange() so you can override only
that method and this one will still work.
@param {Number} index the index to insert
@param {Object} the object to insert
@return {SC.SparseArray} receiver
*/
provideObjectAtIndex: function(index, object) {
var array = this._TMP_PROVIDE_ARRAY, range = this._TMP_PROVIDE_RANGE;
array[0] = object;
range.start = index;
return this.provideObjectsInRange(range, array);
},
/**
Invalidates the array content in the specified range. This is not the
same as editing an array. Rather it will cause the array to reload the
content from the delegate again when it is requested.
@param {Range} the range
@returns {SC.SparseArray} receiver
*/
objectsDidChangeInRange: function(range) {
// delete cached content
var content = this._sa_content ;
if (content) {
// if range covers entire length of cached content, just reset array
if (range.start === 0 && SC.maxRange(range)>=content.length) {
this._sa_content = null ;
// otherwise, step through the changed parts and delete them.
} else {
var start = range.start, loc = Math.min(start + range.length, content.length);
while (--loc>=start) content[loc] = undefined;
}
}
this.enumerableContentDidChange(range.start, range.length) ; // notify
return this ;
},
/**
Optimized version of indexOf(). Asks the delegate to provide the index
of the specified object. If the delegate does not implement this method
then it will search the internal array directly.
@param {Object} obj the object to search for
@returns {Number} the discovered index or -1 if not found
*/
indexOf: function(obj) {
var del = this.delegate ;
if (del && del.sparseArrayDidRequestIndexOf) {
return del.sparseArrayDidRequestIndexOf(this, obj);
} else {
var content = this._sa_content ;
if (!content) content = this._sa_content = [] ;
return content.indexOf(obj) ;
}
},
// ..........................................................
// EDITING
//
/**
Array primitive edits the objects at the specified index unless the
delegate rejects the change.
@param {Number} idx the index to begin to replace
@param {Number} amt the number of items to replace
@param {Array} objects the new objects to set instead
@returns {SC.SparseArray} receiver
*/
replace: function(idx, amt, objects) {
objects = objects || [] ;
// if we have a delegate, get permission to make the replacement.
var del = this.delegate ;
if (del) {
if (!del.sparseArrayShouldReplace ||
!del.sparseArrayShouldReplace(this, idx, amt, objects)) {
return this;
}
}
// go ahead and apply to local content.
var content = this._sa_content ;
if (!content) content = this._sa_content = [] ;
content.replace(idx, amt, objects) ;
// update length
var len = objects ? (objects.get ? objects.get('length') : objects.length) : 0;
var delta = len - amt ;
if (!SC.none(this._length)) {
this.propertyWillChange('length');
this._length += delta;
this.propertyDidChange('length');
}
this.enumerableContentDidChange(idx, amt, delta) ;
return this ;
},
/**
Resets the SparseArray, causing it to reload its content from the
delegate again.
@returns {SC.SparseArray} receiver
*/
reset: function() {
this._sa_content = null ;
this._length = null ;
this.enumerableContentDidChange() ;
this.invokeDelegateMethod(this.delegate, 'sparseArrayDidReset', this);
return this ;
}
}) ;
/**
Convenience metohd returns a new sparse array with a default length already
provided.
@param {Number} len the length of the array
@returns {SC.SparseArray}
*/
SC.SparseArray.array = function(len) {
return this.create({ _length: len||0 });
};
/* end of original file: frameworks/sproutcore/frameworks/runtime/system/sparse_array.js*/
/* end of original file: frameworks/sproutcore/frameworks/runtime.js*/
{
"!name": "Sproutcore",
"!define": {
"Array.prototype.filter.!ret": "[?]",
"Array.prototype.map.!0": {
"!type": "fn(set: ?)",
"!span": "357944[11377:20]-358041[11379:5]"
},
"Array.prototype.map.!0.!0": {
"_sc_sliceContent": {
"!type": "fn(c: ?) -> [!0.<i>]",
"!span": "287865[8862:2]-287881[8862:18]",
"!doc": "@private Walks a content array and copies its contents to a new array."
},
"create": {
"!type": "fn(start: ?, length: ?) -> SC._pool.<i>",
"!span": "288349[8880:2]-288355[8880:8]",
"!doc": "To create a set, pass either a start and index or another IndexSet."
},
"isIndexSet": {
"!type": "bool",
"!span": "288944[8905:2]-288954[8905:12]",
"!doc": "Walk like a duck."
},
"HINT_SIZE": {
"!type": "number",
"!span": "289083[8912:2]-289092[8912:11]",
"!doc": "@private Internal setting determines the preferred skip size for hinting sets."
},
"length": {
"!type": "number",
"!span": "289186[8919:2]-289192[8919:8]",
"!doc": "Total number of indexes contained in the set @property {Number}"
},
"max": {
"!type": "number",
"!span": "289403[8928:2]-289406[8928:5]",
"!doc": "One greater than the largest index currently stored in the set."
},
"rangeStartForIndex": {
"!type": "fn(index: number) -> !0",
"!span": "290010[8958:2]-290028[8958:20]",
"!doc": "Returns the starting index of the nearest range for the specified index."
},
"isEqual": {
"!type": "fn(obj: ?) -> bool",
"!span": "290914[8989:2]-290921[8989:9]",
"!doc": "Returns YES if the passed index set contains the exact same indexes as the receiver."
},
"indexBefore": {
"!type": "fn(index: number) -> !0",
"!span": "291676[9016:2]-291687[9016:13]",
"!doc": "Returns the first index in the set before the passed index or null if there are no previous indexes in the set."
},
"indexAfter": {
"!type": "fn(index: number) -> !0",
"!span": "292433[9043:2]-292443[9043:12]",
"!doc": "Returns the first index in the set after the passed index or null if there are no additional indexes in the set."
},
"contains": {
"!type": "fn(start: number, length: number) -> bool",
"!span": "293151[9070:2]-293159[9070:10]",
"!doc": "Returns YES if the index set contains the named index @param {Number} start index or range @param {Number} length optional range length @returns {Boolean}"
},
"intersects": {
"!type": "fn(start: number, length: number) -> bool",
"!span": "294366[9114:2]-294376[9114:12]",
"!doc": "Returns YES if the index set contains any of the passed indexes."
},
"without": {
"!type": "fn(start: number, length: number) -> SC._pool.<i>",
"!span": "295773[9163:2]-295780[9163:9]",
"!doc": "Returns a new IndexSet without the passed range or indexes."
},
"replace": {
"!type": "fn(start: number, length: number) -> !this",
"!span": "296244[9176:2]-296251[9176:9]",
"!doc": "Replace the index set's current content with the passed index set."
},
"add": {
"!type": "fn(start: number, length: number) -> !this",
"!span": "297327[9212:2]-297330[9212:5]",
"!doc": "Adds the specified range of indexes to the set."
},
"remove": {
"!type": "fn(start: number, length: number) -> !this",
"!span": "302141[9386:2]-302147[9386:8]",
"!doc": "Removes the specified range of indexes from the set @param {Number} start index, Range, or IndexSet @param {Number} length optional length of range."
},
"_hint": {
"!type": "fn(start: number, length: ?, content: ?)",
"!span": "305676[9513:2]-305681[9513:7]",
"!doc": "@private iterates through a named range, setting hints every HINT_SIZE indexes pointing to the nearest range start."
},
"clear": {
"!type": "fn()",
"!span": "306448[9544:2]-306453[9544:7]",
"!doc": "Clears the set"
},
"addEach": {
"!type": "fn(objects: ?) -> !this",
"!span": "306758[9557:2]-306765[9557:9]",
"!doc": "Add all the ranges in the passed array."
},
"removeEach": {
"!type": "fn(objects: ?) -> !this",
"!span": "307217[9575:2]-307227[9575:12]",
"!doc": "Removes all the ranges in the passed array."
},
"clone": {
"!type": "fn()",
"!span": "307673[9595:2]-307678[9595:7]",
"!doc": "Clones the set into a new set."
},
"inspect": {
"!type": "fn() -> string",
"!span": "307861[9605:2]-307868[9605:9]",
"!doc": "Returns a string describing the internal range structure."
},
"forEachRange": {
"!type": "fn(callback: +Function, target: ?) -> SC._pool.<i>",
"!span": "308693[9635:2]-308705[9635:14]",
"!doc": "Invoke the callback, passing each occuppied range instead of each index."
},
"forEachIn": {
"!type": "fn(start: number, length: number, callback: +Function, target: ?) -> SC._pool.<i>",
"!span": "309405[9661:2]-309414[9661:11]",
"!doc": "Invokes the callback for each index within the passed start/length range."
},
"lengthIn": {
"!type": "fn(start: number|SC._pool.<i>, length: number) -> number",
"!span": "310258[9693:2]-310266[9693:10]",
"!doc": "Total number of indexes within the specified range."
},
"indexOf": {
"!type": "fn(object: ?, startAt: number) -> number",
"!span": "311891[9756:2]-311898[9756:9]",
"!doc": "Returns the first index in the set that matches the passed object."
},
"lastIndexOf": {
"!type": "fn(object: ?, startAt: number) -> number",
"!span": "312782[9785:2]-312793[9785:13]",
"!doc": "Returns the last index in the set that matches the passed object."
},
"forEachObject": {
"!type": "fn(callback: +Function, target: ?) -> SC._pool.<i>",
"!span": "313893[9820:2]-313906[9820:15]",
"!doc": "Iterates through the objects at each index location in the set."
},
"addObject": {
"!type": "fn(object: ?, firstOnly: ?) -> !this",
"!span": "314806[9854:2]-314815[9854:11]",
"!doc": "Adds all indexes where the object appears to the set."
},
"addObjects": {
"!type": "fn(objects: SC.Enumerable, firstOnly: ?) -> !this",
"!span": "315469[9879:2]-315479[9879:12]",
"!doc": "Adds any indexes matching the passed objects."
},
"removeObject": {
"!type": "fn(object: ?, firstOnly: ?) -> !this",
"!span": "316010[9897:2]-316022[9897:14]",
"!doc": "Removes all indexes where the object appears to the set."
},
"removeObjects": {
"!type": "fn(objects: SC.Enumerable, firstOnly: ?) -> !this",
"!span": "316685[9922:2]-316698[9922:15]",
"!doc": "Removes any indexes matching the passed objects."
},
"LOG_OBSERVING": {
"!type": "bool",
"!span": "317052[9940:2]-317065[9940:15]",
"!doc": "Usually observing notifications from IndexSet are not useful, so supress them by default."
},
"forEach": {
"!type": "fn(callback: ?, target: ?)",
"!span": "317122[9943:2]-317129[9943:9]",
"!doc": "@private - optimized call to forEach()"
},
"nextObject": {
"!type": "fn(ignore: ?, idx: number, context: ?) -> !1",
"!span": "317589[9962:2]-317599[9962:12]",
"!doc": "@private - support iterators"
},
"toString": {
"!type": "fn()",
"!span": "318197[9989:2]-318205[9989:10]"
},
"!span": "287710[8856:36]-318447[9999:1]"
},
"Array.prototype.map.!0.!0._sc_sliceContent.!ret": "[number]",
"Array.prototype.map.!0.!0._hint.!1": {
"!type": "number",
"!span": "305692[9513:18]-305697[9513:23]"
},
"Array.prototype.map.!0.!0.addObjects.!0": {
"isEnumerable": {
"!type": "bool",
"!span": "99241[2947:2]-99253[2947:14]",
"!doc": "Walk like a duck."
},
"copy": "SC.copy",
"indexOf": "SC.indexOf",
"lastIndexOf": "SC.lastIndexOf",
"slice": "SC.copy",
"clone": "SC.copy",
"shift": "SC.shift",
"_pool": "SC._pool",
"nextObject": "SC.Enumerable.nextObject",
"enumerator": "SC.Enumerable.enumerator",
"forEach": "SC.Enumerable.forEach",
"getEach": "SC.Enumerable.getEach",
"setEach": "SC.Enumerable.setEach",
"map": "SC.Enumerable.map",
"mapProperty": "SC.Enumerable.mapProperty",
"filter": "SC.Enumerable.filter",
"sortProperty": "SC.Enumerable.sortProperty",
"filterProperty": "SC.Enumerable.filterProperty",
"find": "SC.Enumerable.find",
"findProperty": "SC.Enumerable.findProperty",
"every": "SC.Enumerable.every",
"everyProperty": "SC.Enumerable.everyProperty",
"some": "SC.Enumerable.some",
"someProperty": "SC.Enumerable.someProperty",
"reduce": "SC.Enumerable.reduce",
"invoke": "SC.Enumerable.invoke",
"invokeWhile": "SC.Enumerable.invokeWhile",
"toArray": "SC.Enumerable.toArray",
"groupBy": {}
},
"Array.prototype.map.!0.!0.nextObject.!2": "+SC._ChainObserver",
"Array.prototype.map.!ret": "[?]",
"SC._detectBrowser.!ret": {
"safari": {
"!type": "number",
"!span": "1765[35:4]-1771[35:10]"
},
"opera": {
"!type": "number",
"!span": "1829[36:4]-1834[36:9]"
},
"msie": {
"!type": "number",
"!span": "1886[37:4]-1890[37:8]"
},
"mozilla": {
"!type": "number",
"!span": "1970[38:4]-1977[38:11]"
},
"mobileSafari": {
"!type": "number",
"!span": "2073[39:4]-2085[39:16]"
},
"chrome": {
"!type": "number",
"!span": "2146[40:4]-2152[40:10]"
},
"windows": {
"!type": "bool",
"!span": "2206[41:4]-2213[41:11]"
},
"mac": {
"!type": "bool",
"!span": "2253[42:4]-2256[42:7]"
},
"current": {
"!type": "string",
"!span": "2426[46:10]-2433[46:17]"
}
},
"SC.copy.!ret": {
"!span": "25997[769:12]-25999[769:14]"
},
"SC.Function.property.!1": "[?]",
"SC.Function.observes.!1": "[?]",
"SC.CoreString.fmt.!1": "[?]",
"SC.CoreString.loc.!1": "[?]",
"SC.CoreString.w.!ret": "[?]",
"SC.ObserverSet.clone.!ret": "+SC.ObserverSet.constructor",
"SC.Observable._kvo_addCachedDependents.!0": "[?]",
"SC.Observable._kvo_addCachedDependents.!1": "[?]",
"SC.Observable._kvo_computeCachedDependentsFor.!ret": "[?]",
"SC.Observable.observersForKey.!ret": "[?]",
"SC.Observable.getEach.!ret": "[?]",
"SC.Enumerator.create.!0": {
"isEnumerable": {
"!type": "bool",
"!span": "99241[2947:2]-99253[2947:14]",
"!doc": "Walk like a duck."
},
"copy": "SC.copy",
"indexOf": "SC.indexOf",
"lastIndexOf": "SC.lastIndexOf",
"slice": "SC.copy",
"clone": "SC.copy",
"shift": "SC.shift",
"_pool": "SC._pool",
"nextObject": "SC.Enumerable.nextObject",
"enumerator": "SC.Enumerable.enumerator",
"forEach": "SC.Enumerable.forEach",
"getEach": "SC.Enumerable.getEach",
"setEach": "SC.Enumerable.setEach",
"map": "SC.Enumerable.map",
"mapProperty": "SC.Enumerable.mapProperty",
"filter": "SC.Enumerable.filter",
"sortProperty": "SC.Enumerable.sortProperty",
"filterProperty": "SC.Enumerable.filterProperty",
"find": "SC.Enumerable.find",
"findProperty": "SC.Enumerable.findProperty",
"every": "SC.Enumerable.every",
"everyProperty": "SC.Enumerable.everyProperty",
"some": "SC.Enumerable.some",
"someProperty": "SC.Enumerable.someProperty",
"reduce": "SC.Enumerable.reduce",
"invoke": "SC.Enumerable.invoke",
"invokeWhile": "SC.Enumerable.invokeWhile",
"toArray": "SC.Enumerable.toArray",
"groupBy": {}
},
"SC.Enumerator.!0": {
"isEnumerable": {
"!type": "bool",
"!span": "99241[2947:2]-99253[2947:14]",
"!doc": "Walk like a duck."
},
"copy": "SC.copy",
"indexOf": "SC.indexOf",
"lastIndexOf": "SC.lastIndexOf",
"slice": "SC.copy",
"clone": "SC.copy",
"shift": "SC.shift",
"_pool": "SC._pool",
"nextObject": "SC.Enumerable.nextObject",
"enumerator": "SC.Enumerable.enumerator",
"forEach": "SC.Enumerable.forEach",
"getEach": "SC.Enumerable.getEach",
"setEach": "SC.Enumerable.setEach",
"map": "SC.Enumerable.map",
"mapProperty": "SC.Enumerable.mapProperty",
"filter": "SC.Enumerable.filter",
"sortProperty": "SC.Enumerable.sortProperty",
"filterProperty": "SC.Enumerable.filterProperty",
"find": "SC.Enumerable.find",
"findProperty": "SC.Enumerable.findProperty",
"every": "SC.Enumerable.every",
"everyProperty": "SC.Enumerable.everyProperty",
"some": "SC.Enumerable.some",
"someProperty": "SC.Enumerable.someProperty",
"reduce": "SC.Enumerable.reduce",
"invoke": "SC.Enumerable.invoke",
"invokeWhile": "SC.Enumerable.invokeWhile",
"toArray": "SC.Enumerable.toArray",
"groupBy": {}
},
"SC.Enumerator.!ret": "+SC.Enumerator",
"SC.Enumerable.forEach.!0": {
"!type": "fn(object: ?)",
"!span": "316751[9923:20]-316819[9925:5]"
},
"SC.Enumerable.getEach.!ret": "[?]",
"SC.Enumerable.map.!ret": "[?]",
"SC.Enumerable.mapProperty.!ret": "[?]",
"SC.Enumerable.filter.!ret": "[?]",
"SC.Enumerable.sortProperty.!ret": "[?]",
"SC.Enumerable.filterProperty.!ret": "[?]",
"SC.Enumerable.invoke.!ret": "[?]",
"SC.Enumerable.invokeWhile.!ret": "[?]",
"SC.Enumerable.toArray.!ret": "[?]",
"SC.Enumerable.groupBy.!ret": "[[?]]",
"SC.Enumerable.groupBy.!ret.<i>": "[?]",
"SC.Reducers.reduceStddev.!0": {
"devsum": {
"!type": "number",
"!span": "131463[3887:50]-131469[3887:56]"
},
"!span": "131449[3887:36]-131474[3887:61]"
},
"SC.Reducers.reduceStddevsample.!0": {
"devsum": {
"!type": "number",
"!span": "132000[3903:50]-132006[3903:56]"
},
"!span": "131986[3903:36]-132011[3903:61]"
},
"SC.RangeObserver.create.!0": {
"isSCArray": {
"!type": "bool",
"!span": "152411[4560:2]-152420[4560:11]",
"!doc": "Walk like a duck - use isSCArray to avoid conflicts"
},
"replace": {
"!type": "fn(idx: number, amt: number, objects: [?])",
"!span": "153210[4587:2]-153217[4587:9]",
"!doc": "This is one of the primitves you must implement to support SC.Array."
},
"objectAt": {
"!type": "fn(idx: number)",
"!span": "153730[4601:2]-153738[4601:10]",
"!doc": "This is one of the primitives you must implement to support SC.Array."
},
"insertAt": {
"!type": "fn(idx: number, object: ?) -> !this",
"!span": "154542[4630:2]-154550[4630:10]",
"!doc": "This will use the primitive replace() method to insert an object at the specified index."
},
"removeAt": {
"!type": "fn(start: number|SC._pool.<i>, length: number) -> !this",
"!span": "155174[4648:2]-155182[4648:10]",
"!doc": "Remove an object at the specified index using the replace() primitive method."
},
"removeObject": {
"!type": "fn(obj: ?) -> !this",
"!span": "155991[4683:2]-156003[4683:14]",
"!doc": "@private"
},
"removeObjects": {
"!type": "fn(objects: SC.Enumerable) -> !this",
"!span": "156400[4699:2]-156413[4699:15]",
"!doc": "Search the array for the passed set of objects and remove any occurrences of the."
},
"pushObject": {
"!type": "fn(obj: ?) -> !0",
"!span": "156705[4710:2]-156715[4710:12]",
"!doc": "Push the object onto the end of the array."
},
"pushObjects": {
"!type": "fn(objects: SC.Enumerable) -> !this",
"!span": "157047[4723:2]-157058[4723:13]",
"!doc": "Add the objects in the passed numerable to the end of the array."
},
"popObject": {
"!type": "fn()",
"!span": "157351[4734:2]-157360[4734:11]",
"!doc": "Pop object from array or nil if none are left."
},
"shiftObject": {
"!type": "fn()",
"!span": "157663[4747:2]-157674[4747:13]",
"!doc": "Shift an object from start of array or nil if none are left."
},
"unshiftObject": {
"!type": "fn(obj: ?) -> !0",
"!span": "157926[4758:2]-157939[4758:15]",
"!doc": "Unshift an object to start of array."
},
"unshiftObjects": {
"!type": "fn(objects: SC.Enumerable) -> !this",
"!span": "158235[4771:2]-158249[4771:16]",
"!doc": "Adds the named objects to the beginning of the array."
},
"isEqual": {
"!type": "fn(ary: ?) -> bool",
"!span": "158512[4781:2]-158519[4781:9]",
"!doc": "Compares each item in the array."
},
"compact": {
"!type": "fn() -> [?]",
"!span": "158940[4800:2]-158947[4800:9]",
"!doc": "Generates a new array with the contents of the old array, sans any null values."
},
"without": {
"!type": "fn(value: ?) -> [?]",
"!span": "159143[4809:2]-159150[4809:9]",
"!doc": "Generates a new array with the contents of the old array, sans the passed value."
},
"uniq": {
"!type": "fn() -> [?]",
"!span": "159487[4824:2]-159491[4824:6]",
"!doc": "Generates a new array with only unique values from the contents of the old array."
},
"flatten": {
"!type": "fn() -> [?]",
"!span": "159841[4839:2]-159848[4839:9]",
"!doc": "Returns a new array that is a one-dimensional flattening of this array, i.e."
},
"max": {
"!type": "fn() -> number",
"!span": "160289[4859:2]-160292[4859:5]",
"!doc": "Returns the largest Number in an array of Numbers."
},
"min": {
"!type": "fn() -> number",
"!span": "160573[4871:2]-160576[4871:5]",
"!doc": "Returns the smallest Number in an array of Numbers."
},
"contains": {
"!type": "fn(obj: ?) -> bool",
"!span": "160795[4883:2]-160803[4883:10]",
"!doc": "Returns YES if object is in the array @param {Object} object to look for @returns {Boolean}"
},
"addRangeObserver": {
"!type": "fn(indexes: SC._pool.<i>, target: ?, method: string|+Function, context: ?) -> SC.RangeObserver",
"!span": "161829[4911:2]-161845[4911:18]",
"!doc": "Creates a new range observer on the receiver."
},
"updateRangeObserver": {
"!type": "fn(rangeObserver: SC.RangeObserver, indexes: SC._pool.<i>) -> SC.RangeObserver",
"!span": "163190[4947:2]-163209[4947:21]",
"!doc": "Moves a range observer so that it observes a new range of objects on the array."
},
"removeRangeObserver": {
"!type": "fn(rangeObserver: SC.RangeObserver) -> SC.RangeObserver",
"!span": "163647[4961:2]-163666[4961:21]",
"!doc": "Removes a range observer from the receiver."
},
"enumerableContentDidChange": {
"!type": "fn(start: number, amt: number, delta: number) -> !this",
"!span": "164372[4979:2]-164398[4979:28]",
"!doc": "Updates observers with content change."
},
"_array_notifyRangeObservers": {
"!type": "fn()",
"!span": "165719[5021:2]-165746[5021:29]",
"!doc": "@private Observer fires whenever the '[]' property changes."
},
"!span": "152339[4555:11]-166107[5033:1]",
"rangeObserverClass": "SC.RangeObserver",
"copy": "SC.copy",
"indexOf": "SC.indexOf",
"lastIndexOf": "SC.lastIndexOf",
"slice": "SC.copy",
"clone": "SC.copy",
"shift": "SC.shift",
"_pool": "SC._pool"
},
"SC.RangeObserver.create.!0.replace.!2": "[?]",
"SC.RangeObserver.create.!0.compact.!ret": "[?]",
"SC.RangeObserver.create.!0.without.!ret": "[?]",
"SC.RangeObserver.create.!0.uniq.!ret": "[?]",
"SC.RangeObserver.create.!0.flatten.!ret": "[?]",
"SC._pool.<i>": {
"!span": "10343[226:26]-10345[226:28]",
"copy": "SC.copy",
"indexOf": "SC.indexOf",
"lastIndexOf": "SC.lastIndexOf",
"slice": "SC.copy",
"clone": "SC.copy",
"shift": "SC.shift",
"_pool": "SC._pool"
},
"SC.Observers.queue.<i>": "[?]",
"SC._object_extend.!0": "+SC.Object",
"SC.Object.!0": "[?]",
"SC.SparseArrayDelegate.sparseArrayShouldReplace.!3": "[?]",
"SC.Binding.builder.!ret": {
"beget": {
"!type": "fn()",
"!span": "254006[7775:8]-254011[7775:13]"
},
"!type": "fn(fromProperty: ?)",
"!span": "253927[7774:14]-253996[7774:83]"
},
"SC.ObserverSet.constructor.members.<i>": "[?]",
"Array.prototype.map.!0.!0.addObjects.!0.groupBy.!ret": {
"<i>": "SC.Enumerable.groupBy.!ret.<i>"
},
"SC.Enumerator.create.!0.groupBy.!ret": {
"<i>": "SC.Enumerable.groupBy.!ret.<i>"
},
"SC.Enumerator.!0.groupBy.!ret": {
"<i>": "SC.Enumerable.groupBy.!ret.<i>"
}
},
"Function": {
"prototype": {
"apply": {
"!type": "fn(this: ?, args: [?])",
"!doc": "Calls a function with a given this value and arguments provided as an array (or an array like object)."
},
"call": {
"!type": "fn(this: ?, args?: ?) -> !this.!ret",
"!doc": "Calls a function with a given this value and arguments provided individually."
},
"bind": {
"!type": "fn(this: ?, args?: ?) -> !custom:Function_bind",
"!doc": "Creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function was called."
},
"slice": "SC.copy",
"clone": "SC.copy",
"_pool": {
"<i>": {
"copy": "SC.copy",
"indexOf": "SC.indexOf",
"lastIndexOf": "SC.lastIndexOf",
"slice": "SC.copy",
"clone": "SC.copy",
"shift": "SC.shift",
"_pool": "SC._pool"
}
},
"copy": "SC.copy",
"indexOf": "SC.indexOf",
"lastIndexOf": "SC.lastIndexOf",
"shift": "SC.shift"
}
},
"Array": {
"prototype": {
"concat": {
"!type": "fn(other: [?]) -> !this",
"!doc": "Returns a new array comprised of this array joined with other array(s) and/or value(s)."
},
"join": {
"!type": "fn(separator?: string) -> string",
"!doc": "Joins all elements of an array into a string."
},
"splice": {
"!type": "fn(pos: number, amount: number)",
"!doc": "Changes the content of an array, adding new elements while removing old elements."
},
"push": {
"!type": "fn(newelt: ?) -> number",
"!span": "197319[6038:7]-197323[6038:11]",
"!doc": "@private"
},
"unshift": {
"!type": "fn(newelt: ?) -> number",
"!span": "197333[6038:21]-197340[6038:28]",
"!doc": "Adds one or more elements to the beginning of an array and returns the new length of the array."
},
"reverse": {
"!type": "fn()",
"!doc": "Reverses an array in place. The first array element becomes the last and the last becomes the first."
},
"sort": {
"!type": "fn(compare?: fn(a: ?, b: ?) -> number)",
"!doc": "Sorts the elements of an array in place and returns the array."
},
"indexOf": {
"!type": "fn(object: string|?, startAt: number) -> !1",
"!span": "167312[5066:9]-167319[5066:16]",
"!doc": "Returns the index for a particular object in the index."
},
"lastIndexOf": {
"!type": "fn(char: string, from?: number) -> number",
"!span": "168043[5089:9]-168054[5089:20]",
"!doc": "Returns the last index for a particular object in the index."
},
"every": {
"!type": "fn(test: fn(elt: ?, i: number) -> bool, context?: ?) -> bool",
"!doc": "Tests whether all elements in the array pass the test implemented by the provided function."
},
"some": {
"!type": "fn(test: fn(elt: ?, i: number) -> bool, context?: ?) -> bool",
"!doc": "Tests whether some element in the array passes the test implemented by the provided function."
},
"filter": {
"!type": "fn(callback: ?, target: ?) -> [?]",
"!span": "138669[4144:12]-139007[4155:5]",
"!doc": "Creates a new array with all elements that pass the test implemented by the provided function."
},
"forEach": {
"!type": "fn(f: fn(elt: ?, i: number), context?: ?)",
"!doc": "Executes a provided function once per array element."
},
"map": {
"!type": "fn(callback: fn(set: ?), target: ?) -> [?]",
"!span": "138326[4131:9]-138654[4142:5]",
"!doc": "Creates a new array with the results of calling a provided function on every element in this array."
},
"reduce": {
"!type": "fn(combine: fn(sum: ?, elt: ?, i: number), init?: ?) -> !0.!ret",
"!doc": "Apply a function against an accumulator and each value of the array (from left-to-right) as to reduce it to a single value."
},
"reduceRight": {
"!type": "fn(combine: fn(sum: ?, elt: ?, i: number), init?: ?) -> !0.!ret",
"!doc": "Apply a function simultaneously against two values of the array (from right-to-left) as to reduce it to a single value."
},
"length": {
"!type": "number",
"!doc": "reduce the length"
},
"isEnumerable": {
"!type": "bool",
"!span": "132252[3914:16]-132264[3914:28]"
},
"pop": "SC.shift",
"shift": "SC.shift",
"slice": "SC.copy",
"copy": "SC.copy",
"clone": "SC.copy",
"_pool": "SC._pool"
}
},
"String": {
"prototype": {
"charAt": {
"!type": "fn(i: number) -> string",
"!doc": "Returns the specified character from a string."
},
"charCodeAt": {
"!type": "fn(i: number) -> number",
"!doc": "Returns the numeric Unicode value of the character at the given index (except for unicode codepoints > 0x10000)."
},
"substring": {
"!type": "fn(from: number, to?: number) -> string",
"!doc": "Returns a subset of a string between one index and another, or through the end of the string."
},
"substr": {
"!type": "fn(from: number, length?: number) -> string",
"!doc": "Returns the characters in a string beginning at the specified location through the specified number of characters."
},
"trim": {
"!type": "fn() -> string",
"!doc": "Removes whitespace from both ends of the string."
},
"toUpperCase": {
"!type": "fn() -> string",
"!doc": "Returns the calling string value converted to uppercase."
},
"toLowerCase": {
"!type": "fn() -> string",
"!doc": "Returns the calling string value converted to lowercase."
},
"toLocaleUpperCase": {
"!type": "fn() -> string",
"!doc": "Returns the calling string value converted to upper case, according to any locale-specific case mappings."
},
"toLocaleLowerCase": {
"!type": "fn() -> string",
"!doc": "Returns the calling string value converted to lower case, according to any locale-specific case mappings."
},
"split": {
"!type": "fn(pattern: string) -> [string]",
"!doc": "Splits a String object into an array of strings by separating the string into substrings."
},
"concat": {
"!type": "fn(other: string) -> string",
"!doc": "Combines the text of two or more strings and returns a new string."
},
"localeCompare": {
"!type": "fn(other: string) -> number",
"!doc": "Returns a number indicating whether a reference string comes before or after or is the same as the given string in sort order."
},
"match": {
"!type": "fn(pattern: +RegExp) -> [string]",
"!doc": "Used to retrieve the matches when matching a string against a regular expression."
},
"replace": {
"!type": "fn(pattern: string|+RegExp, replacement: string) -> string",
"!doc": "Returns a new string with some or all matches of a pattern replaced by a replacement. The pattern can be a string or a RegExp, and the replacement can be a string or a function to be called for each match."
},
"search": {
"!type": "fn(pattern: +RegExp) -> number",
"!doc": "Executes the search for a match between a regular expression and this String object."
},
"length": {
"!type": "number",
"!doc": "Represents the length of a string."
},
"indexOf": "SC.indexOf",
"lastIndexOf": "SC.lastIndexOf",
"slice": "SC.copy",
"copy": "SC.copy",
"clone": "SC.copy",
"shift": "SC.shift",
"_pool": "SC._pool"
}
},
"Date": {
"now": {
"!type": "fn() -> number",
"!span": "40743[1239:7]-40746[1239:10]",
"!doc": "Returns the number of milliseconds elapsed since 1 January 1970 00:00:00 UTC."
}
},
"SC": {
"_detectBrowser": {
"!type": "fn(rawUserAgent: ?, language: ?) -> SC.browser",
"!span": "1205[24:3]-1219[24:17]"
},
"browser": {
"safari": {
"!type": "number",
"!span": "1765[35:4]-1771[35:10]"
},
"opera": {
"!type": "number",
"!span": "1829[36:4]-1834[36:9]"
},
"msie": {
"!type": "number",
"!span": "1886[37:4]-1890[37:8]"
},
"mozilla": {
"!type": "number",
"!span": "1970[38:4]-1977[38:11]"
},
"mobileSafari": {
"!type": "number",
"!span": "2073[39:4]-2085[39:16]"
},
"chrome": {
"!type": "number",
"!span": "2146[40:4]-2152[40:10]"
},
"windows": {
"!type": "bool",
"!span": "2206[41:4]-2213[41:11]"
},
"mac": {
"!type": "bool",
"!span": "2253[42:4]-2256[42:7]"
},
"current": {
"!type": "string",
"!span": "2426[46:10]-2433[46:17]"
},
"!span": "2610[50:3]-2617[50:10]"
},
"benchmarkPreloadEvents": {
"headStart": {
"!type": "number",
"!span": "3489[67:32]-3498[67:41]"
},
"!span": "3355[64:5]-3377[64:27]"
},
"setupBodyClassNames": {
"!type": "fn()",
"!span": "4146[81:3]-4165[81:22]",
"!doc": "sc_require(\"system/browser\");"
},
"_baseMixin": {
"!type": "fn(override: ?) -> ?",
"!span": "10185[223:3]-10195[223:13]",
"!doc": "@private Adds properties to a target object."
},
"mixin": {
"!type": "fn() -> Function.prototype|bool",
"!span": "11230[261:3]-11235[261:8]",
"!doc": "Adds properties to a target object."
},
"supplement": {
"!type": "fn() -> ?|bool",
"!span": "11792[279:3]-11802[279:13]",
"!doc": "Adds properties to a target object."
},
"ORDER_DEFINITION_MAPPING": {
"<i>": {
"!type": "number",
"!span": "21991[619:16]-22001[619:26]"
},
"!span": "21862[616:19]-21886[616:43]",
"!doc": "If we haven't yet generated a reverse-mapping of SC.ORDER_DEFINITION, do so now."
},
"copy": {
"!type": "fn(object: ?, deep: bool|number) -> SC.copy.!ret",
"!span": "318484[10001:32]-318488[10001:36]"
},
"ORDER_DEFINITION": {
"!type": "[?]",
"!span": "30302[924:3]-30318[924:19]",
"!doc": "@private Used by SC.compare"
},
"Function": {
"property": {
"!type": "fn(fn: ?, keys: [?]) -> !0",
"!span": "30797[942:2]-30805[942:10]"
},
"cacheable": {
"!type": "fn(fn: ?, aFlag: bool) -> !0",
"!span": "31037[951:2]-31046[951:11]"
},
"idempotent": {
"!type": "fn(fn: ?, aFlag: bool) -> !0",
"!span": "31270[958:2]-31280[958:12]"
},
"observes": {
"!type": "fn(fn: ?, propertyPaths: [?]) -> !0",
"!span": "31504[965:2]-31512[965:10]"
},
"dependentKeys": {
"!type": "[?]",
"!span": "31400[960:32]-31413[960:45]"
},
"!span": "30782[941:3]-30790[941:11]",
"!doc": "........................................"
},
"CoreString": {
"fmt": {
"!type": "fn(str: ?, formats: [?])",
"!span": "38206[1154:2]-38209[1154:5]"
},
"loc": {
"!type": "fn(str: ?, formats: [?])",
"!span": "38607[1163:2]-38610[1163:5]"
},
"w": {
"!type": "fn(str: ?) -> [?]",
"!span": "38725[1167:2]-38726[1167:3]"
},
"!span": "38189[1153:3]-38199[1153:13]"
},
"indexOf": {
"!type": "fn(object: string|?, startAt: number) -> !1",
"!span": "167312[5066:9]-167319[5066:16]"
},
"lastIndexOf": {
"!type": "fn(object: ?, startAt: number) -> !1",
"!span": "168043[5089:9]-168054[5089:20]"
},
"ObserverSet": {
"add": {
"!type": "fn(target: ?, method: ?, context: ?)",
"!span": "42033[1278:2]-42036[1278:5]",
"!doc": "Adds the named target/method observer to the set."
},
"remove": {
"!type": "fn(target: ?, method: ?) -> bool",
"!span": "42728[1299:2]-42734[1299:8]",
"!doc": "removes the named target/method observer from the set."
},
"invokeMethods": {
"!type": "fn()",
"!span": "43423[1323:2]-43436[1323:15]",
"!doc": "Invokes the target/method pairs in the receiver."
},
"clone": {
"!type": "fn() -> ?",
"!span": "43721[1337:2]-43726[1337:7]",
"!doc": "Returns a new instance of the set with the contents cloned."
},
"create": {
"!type": "fn() -> ?",
"!span": "44123[1354:2]-44129[1354:8]",
"!doc": "Creates a new instance of the observer set."
},
"getMembers": {
"!type": "fn() -> ?",
"!span": "44197[1358:2]-44207[1358:12]"
},
"constructor": {
"!type": "fn()",
"!span": "44264[1362:2]-44275[1362:13]",
"!doc": "@private",
"_members": {
"!span": "44299[1363:9]-44307[1363:17]"
},
"members": {
"!type": "[[?]]",
"!span": "44323[1364:9]-44330[1364:16]"
}
},
"!span": "41727[1268:3]-41738[1268:14]",
"!doc": "@namespace This private class is used to store information about obversers on a particular key.",
"slice": "SC.ObserverSet.clone"
},
"LOG_OBSERVERS": {
"!type": "bool",
"!span": "45245[1392:3]-45258[1392:16]",
"!doc": "Set to YES to have all observing activity logged to the console."
},
"Observable": {
"isObservable": {
"!type": "bool",
"!span": "49542[1525:2]-49554[1525:14]",
"!doc": "@private"
},
"automaticallyNotifiesObserversFor": {
"!type": "fn(key: ?) -> bool",
"!span": "50041[1540:2]-50074[1540:35]",
"!doc": "Determines whether observers should be automatically notified of changes to a key."
},
"get": {
"!type": "fn(key: ?) -> !this.<i>",
"!span": "51498[1585:2]-51501[1585:5]",
"!doc": "Retrieves the value of key from the object."
},
"set": {
"!type": "fn(key: ?, value: ?) -> !this",
"!span": "53869[1645:2]-53872[1645:5]",
"!doc": "Sets the key equal to value."
},
"unknownProperty": {
"!type": "fn(key: ?, value: ?) -> !1",
"!span": "56485[1725:2]-56500[1725:17]",
"!doc": "Called whenever you try to get or set an undefined property."
},
"beginPropertyChanges": {
"!type": "fn() -> !this",
"!span": "57094[1742:2]-57114[1742:22]",
"!doc": "Begins a grouping of property changes."
},
"endPropertyChanges": {
"!type": "fn() -> !this",
"!span": "57705[1759:2]-57723[1759:20]",
"!doc": "Ends a grouping of property changes."
},
"propertyWillChange": {
"!type": "fn(key: ?) -> !this",
"!span": "58754[1784:2]-58772[1784:20]",
"!doc": "Notify the observer system that a property is about to change."
},
"propertyDidChange": {
"!type": "fn(key: ?, value: ?, _keepCache: ?) -> !this",
"!span": "59598[1805:2]-59615[1805:19]",
"!doc": "Notify the observer system that a property has just changed."
},
"registerDependentKey": {
"!type": "fn(key: string, dependentKeys: [?]|string)",
"!span": "62088[1877:2]-62108[1877:22]",
"!doc": "Use this to indicate that one key changes if other keys it depends on change."
},
"_kvo_addCachedDependents": {
"!type": "fn(queue: [?], keys: [?], dependents: ?, seen: SC._pool.<i>)",
"!span": "63460[1920:2]-63484[1920:26]",
"!doc": "@private Helper method used by computeCachedDependents."
},
"_kvo_computeCachedDependentsFor": {
"!type": "fn(key: string) -> [?]",
"!span": "64492[1952:2]-64523[1952:33]",
"!doc": "@private Called by set() whenever it needs to determine which cached dependent keys to clear."
},
"_kvo_for": {
"!type": "fn(kvoKey: ?, type: ?) -> ?",
"!span": "65441[1979:2]-65449[1979:10]",
"!doc": ".........................................."
},
"addObserver": {
"!type": "fn(key: ?, target: ?, method: ?, context: ?) -> !this",
"!span": "68015[2048:2]-68026[2048:13]",
"!doc": "Adds an observer on a property."
},
"removeObserver": {
"!type": "fn(key: ?, target: ?, method: ?) -> !this",
"!span": "69841[2100:2]-69855[2100:16]",
"!doc": "Remove an observer you have previously registered on this object."
},
"hasObserverFor": {
"!type": "fn(key: string) -> bool",
"!span": "71820[2161:2]-71834[2161:16]",
"!doc": "Returns YES if the object currently has observers registered for a particular key."
},
"initObservable": {
"!type": "fn()",
"!span": "73041[2195:2]-73055[2195:16]",
"!doc": "This method will register any observers and computed properties saved on the object."
},
"observersForKey": {
"!type": "fn(key: ?) -> [?]",
"!span": "76119[2283:2]-76134[2283:17]",
"!doc": "Returns an array with all of the observers registered for the specified key."
},
"_notifyPropertyObservers": {
"!type": "fn(key: ?) -> bool",
"!span": "76403[2290:2]-76427[2290:26]",
"!doc": "this private method actually notifies the observers for any keys in the observer queue."
},
"bind": {
"!type": "fn(toKey: string, target: [?]|?, method: string|+Function) -> [!1]",
"!span": "83914[2474:2]-83918[2474:6]",
"!doc": "Manually add a new binding to an object."
},
"didChangeFor": {
"!type": "fn(context: string|?) -> bool",
"!span": "85702[2522:2]-85714[2522:14]",
"!doc": "didChangeFor allows you to determine if a property has changed since the last time the method was called."
},
"setIfChanged": {
"!type": "fn(key: ?, value: ?) -> !this",
"!span": "87611[2574:2]-87623[2574:14]",
"!doc": "Sets the property only if the passed value is different from the current value."
},
"getPath": {
"!type": "fn(path: ?)",
"!span": "88105[2594:2]-88112[2594:9]",
"!doc": "Navigates the property path, returning the value at that point."
},
"setPath": {
"!type": "fn(path: ?, value: ?) -> !this",
"!span": "88487[2607:2]-88494[2607:9]",
"!doc": "Navigates the property path, finally setting the value."
},
"setPathIfChanged": {
"!type": "fn(path: ?, value: ?) -> !this",
"!span": "89078[2625:2]-89094[2625:18]",
"!doc": "Navigates the property path, finally setting the value but only if the value does not match the current value."
},
"getEach": {
"!type": "fn() -> [?]",
"!span": "89672[2644:2]-89679[2644:9]",
"!doc": "Convenience method to get an array of properties."
},
"incrementProperty": {
"!type": "fn(key: ?, increment: number) -> number",
"!span": "90096[2661:2]-90113[2661:19]",
"!doc": "Increments the value of a property."
},
"decrementProperty": {
"!type": "fn(key: ?, increment: number) -> number",
"!span": "90460[2674:2]-90477[2674:19]",
"!doc": "Decrements the value of a property."
},
"toggleProperty": {
"!type": "fn(key: ?, value: bool, alt: bool) -> ?",
"!span": "90885[2688:2]-90899[2688:16]",
"!doc": "Inverts a property."
},
"notifyPropertyChange": {
"!type": "fn(key: ?, value: ?) -> !this",
"!span": "91616[2708:2]-91636[2708:22]",
"!doc": "Convenience method to call propertyWillChange/propertyDidChange."
},
"allPropertiesDidChange": {
"!type": "fn() -> !this",
"!span": "92222[2726:2]-92244[2726:24]",
"!doc": "Notifies all of observers of a property changes."
},
"addProbe": {
"!type": "fn(key: ?)",
"!span": "92375[2732:2]-92383[2732:10]"
},
"removeProbe": {
"!type": "fn(key: ?)",
"!span": "92442[2733:2]-92453[2733:13]"
},
"logProperty": {
"!type": "fn()",
"!span": "92638[2740:2]-92649[2740:13]",
"!doc": "Logs the named properties to the console."
},
"propertyRevision": {
"!type": "number",
"!span": "92905[2749:2]-92921[2749:18]"
},
"!span": "49460[1518:3]-49470[1518:13]",
"!doc": "@namespace Key-Value-Observing (KVO) simply allows one object to observe changes to a property on another object.",
"copy": "SC.copy",
"indexOf": "SC.indexOf",
"lastIndexOf": "SC.lastIndexOf",
"slice": "SC.copy",
"clone": "SC.copy",
"shift": "SC.shift",
"_pool": "SC._pool"
},
"logChange": {
"!type": "fn(target: ?, key: ?, value: ?)",
"!span": "92979[2754:3]-92988[2754:12]",
"!doc": "@private used by addProbe/removeProbe"
},
"shift": {
"!type": "fn() -> !this.<i>",
"!span": "197380[6041:7]-197385[6041:12]"
},
"Enumerator": {
"prototype": {
"nextObject": {
"!type": "fn() -> ?",
"!span": "94936[2814:2]-94946[2814:12]",
"!doc": "Returns the next object in the enumeration or undefined when complete."
},
"reset": {
"!type": "fn()",
"!span": "95511[2837:2]-95516[2837:7]",
"!doc": "Resets the enumerator to the beginning."
},
"destroy": {
"!type": "fn()",
"!span": "96061[2854:2]-96068[2854:9]",
"!doc": "Releases the enumerators enumerable object."
},
"!span": "94781[2807:14]-94790[2807:23]"
},
"create": {
"!type": "fn(enumerableObject: SC.Enumerable) -> +SC.Enumerator",
"!span": "96496[2868:14]-96502[2868:20]",
"!doc": "Use this method to manually create a new Enumerator object."
},
"_popContext": {
"!type": "fn() -> ?",
"!span": "96688[2875:14]-96699[2875:25]",
"!doc": "Private context caching methods."
},
"_pushContext": {
"!type": "fn(context: ?)",
"!span": "96822[2880:14]-96834[2880:26]"
},
"_contextCache": {
"!type": "[?]",
"!span": "96864[2881:7]-96877[2881:20]"
},
"!type": "fn(enumerableObject: SC.Enumerable) -> !this",
"!span": "94648[2801:3]-94658[2801:13]",
"!doc": "@class An object that iterates over all of the values in an object.",
"_index": {
"!type": "number",
"!span": "95228[2822:9]-95234[2822:15]"
}
},
"Enumerable": {
"isEnumerable": {
"!type": "bool",
"!span": "99241[2947:2]-99253[2947:14]",
"!doc": "Walk like a duck."
},
"nextObject": {
"!type": "fn(index: number, previousObject: ?, context: ?) -> !this.<i>",
"!span": "100830[2980:2]-100840[2980:12]",
"!doc": "Implement this method to make your class enumerable."
},
"enumerator": {
"!type": "fn() -> +SC.Enumerator",
"!span": "102478[3025:2]-102488[3025:12]",
"!doc": "Returns a new enumerator for this object."
},
"forEach": {
"!type": "fn(callback: fn(object: ?), target: ?) -> ?",
"!span": "103396[3051:2]-103403[3051:9]",
"!doc": "Iterates through the enumerable, calling the passed function on each item."
},
"getEach": {
"!type": "fn(key: string) -> [?]",
"!span": "104280[3077:2]-104287[3077:9]",
"!doc": "Retrieves the named value on each member object."
},
"setEach": {
"!type": "fn(key: string, value: ?) -> !this",
"!span": "104839[3093:2]-104846[3093:9]",
"!doc": "Sets the value on the named property for each member."
},
"map": {
"!type": "fn(callback: ?, target: ?) -> [?]",
"!span": "105941[3128:2]-105944[3128:5]",
"!doc": "Maps all of the items in the enumeration to another value, returning a new array."
},
"mapProperty": {
"!type": "fn(key: ?) -> [?]",
"!span": "106721[3153:2]-106732[3153:13]",
"!doc": "Similar to map, this specialized function returns the value of the named property on all items in the enumeration."
},
"filter": {
"!type": "fn(callback: ?, target: ?) -> [?]",
"!span": "107826[3185:2]-107832[3185:8]",
"!doc": "Returns an array with all of the items in the enumeration that the passed function returns YES for."
},
"sortProperty": {
"!type": "fn(key: string) -> [?]",
"!span": "108679[3211:2]-108691[3211:14]",
"!doc": "Returns an array sorted by the value of the passed key parameters."
},
"filterProperty": {
"!type": "fn(key: ?, value: ?) -> [?]",
"!span": "109711[3247:2]-109725[3247:16]",
"!doc": "Returns an array with just the items with the matched property."
},
"find": {
"!type": "fn(callback: ?, target: ?) -> ?",
"!span": "111302[3290:2]-111306[3290:6]",
"!doc": "Returns the first item in the array for which the callback returns YES."
},
"findProperty": {
"!type": "fn(key: ?, value: ?) -> ?",
"!span": "112268[3317:2]-112280[3317:14]",
"!doc": "Returns an the first item with a property matching the passed value."
},
"every": {
"!type": "fn(callback: ?, target: ?) -> bool",
"!span": "113836[3364:2]-113841[3364:7]",
"!doc": "Returns YES if the passed function returns YES for every item in the enumeration."
},
"everyProperty": {
"!type": "fn(key: ?, value: ?) -> bool|[?]",
"!span": "114716[3390:2]-114729[3390:15]",
"!doc": "Returns YES if the passed property resolves to true for all items in the enumerable."
},
"some": {
"!type": "fn(callback: ?, target: ?) -> bool|[?]",
"!span": "116297[3438:2]-116301[3438:6]",
"!doc": "Returns YES if the passed function returns true for any item in the enumeration."
},
"someProperty": {
"!type": "fn(key: ?, value: ?) -> bool",
"!span": "117168[3464:2]-117180[3464:14]",
"!doc": "Returns YES if the passed property resolves to true for any item in the enumerable."
},
"reduce": {
"!type": "fn(callback: ?, initialValue: ?, reducerProperty: string)",
"!span": "118987[3512:2]-118993[3512:8]",
"!doc": "This will combine the values of the enumerator into a single value."
},
"invoke": {
"!type": "fn(methodName: ?) -> [?]",
"!span": "120416[3554:2]-120422[3554:8]",
"!doc": "Invokes the named method on every object in the receiver that implements it."
},
"invokeWhile": {
"!type": "fn(targetValue: ?, methodName: ?) -> !0",
"!span": "121666[3593:2]-121677[3593:13]",
"!doc": "Invokes the passed method and optional arguments on the receiver elements as long as the methods return value matches the target value."
},
"toArray": {
"!type": "fn() -> [?]",
"!span": "122689[3628:2]-122696[3628:9]",
"!doc": "Simply converts the enumerable into a genuine array."
},
"groupBy": {
"!type": "fn(key: ?) -> [[?]]",
"!span": "123042[3641:2]-123049[3641:9]",
"!doc": "Converts an enumerable into a matrix, with inner arrays grouped based on a particular property of the elements of the enumerable."
},
"!span": "99165[2940:3]-99175[2940:13]",
"!doc": "@namespace This mixin defines the common interface implemented by enumerable objects in SproutCore.",
"copy": "SC.copy",
"indexOf": "SC.indexOf",
"lastIndexOf": "SC.lastIndexOf",
"slice": "SC.copy",
"clone": "SC.copy",
"shift": "SC.shift",
"_pool": "SC._pool"
},
"_buildReducerFor": {
"!type": "fn(reducerKey: string, reducerProperty: string)",
"!span": "123856[3671:3]-123872[3671:19]",
"!doc": "Build in a separate function to avoid unintential leaks through closures..."
},
"Reducers": {
"enumerableContentDidChange": {
"!type": "fn(start: number, length: number) -> !this",
"!span": "125365[3710:2]-125391[3710:28]",
"!doc": "Invoke this method when the contents of your enumerable has changed."
},
"reducedProperty": {
"!type": "fn(key: string, value: ?, generateProperty: bool) -> [?]|?",
"!span": "126635[3749:2]-126650[3749:17]",
"!doc": "Call this method from your unknownProperty() handler to implement automatic reduced properties."
},
"reduceMax": {
"!type": "fn(previousValue: ?, item: ?, index: ?, e: ?, reducerProperty: ?) -> !0",
"!span": "128226[3793:2]-128235[3793:11]",
"!doc": "Reducer for @max reduced property."
},
"reduceMaxObject": {
"!type": "fn(previousItem: ?, item: ?, index: ?, e: ?, reducerProperty: ?) -> !0",
"!span": "128581[3804:2]-128596[3804:17]",
"!doc": "Reducer for @maxObject reduced property."
},
"reduceMin": {
"!type": "fn(previousValue: ?, item: ?, index: ?, e: ?, reducerProperty: ?) -> !0",
"!span": "129300[3825:2]-129309[3825:11]",
"!doc": "Reducer for @min reduced property."
},
"reduceMinObject": {
"!type": "fn(previousItem: ?, item: ?, index: ?, e: ?, reducerProperty: ?) -> !0",
"!span": "129655[3836:2]-129670[3836:17]",
"!doc": "Reducer for @maxObject reduced property."
},
"reduceAverage": {
"!type": "fn(previousValue: ?, item: ?, index: ?, e: ?, reducerProperty: ?) -> number",
"!span": "130378[3857:2]-130391[3857:15]",
"!doc": "Reducer for @average reduced property."
},
"reduceSum": {
"!type": "fn(previousValue: ?, item: ?, index: ?, e: ?, reducerProperty: ?) -> !1.<i>",
"!span": "130801[3870:2]-130810[3870:11]",
"!doc": "Reducer for @sum reduced property."
},
"reduceStddev": {
"!type": "fn(previousValue: ?, item: ?, index: ?, e: ?, reducerProperty: ?) -> !0",
"!span": "131063[3877:2]-131075[3877:14]"
},
"reduceStddevsample": {
"!type": "fn(previousValue: ?, item: ?, index: ?, e: ?, reducerProperty: ?) -> !0",
"!span": "131594[3893:2]-131612[3893:20]"
},
"!span": "124471[3686:3]-124479[3686:11]",
"copy": "SC.copy",
"indexOf": "SC.indexOf",
"lastIndexOf": "SC.lastIndexOf",
"slice": "SC.copy",
"clone": "SC.copy",
"shift": "SC.shift",
"_pool": "SC._pool"
},
"RangeObserver": {
"isRangeObserver": {
"!type": "bool",
"!span": "142645[4263:2]-142660[4263:17]",
"!doc": "Walk like a duck."
},
"toString": {
"!type": "fn() -> string",
"!span": "142688[4266:2]-142696[4266:10]",
"!doc": "@private"
},
"create": {
"!type": "fn(source: SC.RangeObserver.create.!0, indexSet: SC._pool.<i>, target: ?, method: +Function|string, context: ?, isDeep: bool) -> SC.RangeObserver",
"!span": "143616[4287:2]-143622[4287:8]",
"!doc": "Creates a new range observer owned by the source."
},
"extend": {
"!type": "fn(attrs: ?) -> SC.RangeObserver",
"!span": "144255[4307:2]-144261[4307:8]",
"!doc": "Create subclasses for the RangeObserver."
},
"destroy": {
"!type": "fn(source: SC.RangeObserver.create.!0) -> !this",
"!span": "144582[4319:2]-144589[4319:9]",
"!doc": "Destroys an active ranger observer, cleaning up first."
},
"update": {
"!type": "fn(source: SC.RangeObserver.create.!0, indexSet: SC._pool.<i>) -> !this",
"!span": "144928[4332:2]-144934[4332:8]",
"!doc": "Updates the set of indexes the range observer applies to."
},
"beginObserving": {
"!type": "fn() -> !this",
"!span": "145390[4347:2]-145404[4347:16]",
"!doc": "Configures observing for each item in the current range."
},
"setupPending": {
"!type": "fn(object: ?) -> bool",
"!span": "146479[4381:2]-146491[4381:14]",
"!doc": "@private Called when an object that appears to need range observers has changed."
},
"endObserving": {
"!type": "fn() -> !this",
"!span": "147998[4435:2]-148010[4435:14]",
"!doc": "Remove observers for any objects currently begin observed."
},
"rangeDidChange": {
"!type": "fn(changes: SC._pool.<i>) -> !this",
"!span": "149101[4472:2]-149115[4472:16]",
"!doc": "Whenever the actual objects in the range changes, notify the delegate then begin observing again."
},
"objectPropertyDidChange": {
"!type": "fn(object: ?, key: string, value: ?, rev: ?)",
"!span": "149648[4489:2]-149671[4489:25]",
"!doc": "Whenever an object changes, notify the delegate @param {Object} the object that changed @param {String} key the property that changed @returns {SC.RangeObserver} receiver"
},
"isObserving": {
"!type": "bool",
"!span": "148537[4455:11]-148548[4455:22]"
},
"_beginObservingForEach": {
"!type": "fn(idx: ?)",
"!span": "145709[4356:18]-145731[4356:40]",
"!doc": "cache iterator function to keep things fast"
},
"!span": "142566[4256:3]-142579[4256:16]",
"!doc": "@class A RangeObserver is used by Arrays to automatically observe all of the objects in a particular range on the array."
},
"OUT_OF_RANGE_EXCEPTION": {
"!type": "string",
"!span": "150914[4521:3]-150936[4521:25]"
},
"Comparable": {
"isComparable": {
"!type": "bool",
"!span": "171803[5208:2]-171815[5208:14]",
"!doc": "walk like a duck."
},
"compare": {
"!type": "fn(a: ?, b: ?)",
"!span": "172210[5224:2]-172217[5224:9]",
"!doc": "Override to return the result of the comparison of the two parameters."
},
"!span": "171690[5201:3]-171700[5201:13]",
"!doc": "@namespace Implements some standard methods for comparing objects."
},
"Copyable": {
"isCopyable": {
"!type": "bool",
"!span": "173420[5261:2]-173430[5261:12]",
"!doc": "Walk like a duck."
},
"frozenCopy": {
"!type": "fn() -> !this",
"!span": "174230[5287:2]-174240[5287:12]",
"!doc": "If the object implements SC.Freezable, then this will return a new copy if the object is not frozen and the receiver if the object is frozen."
},
"!span": "173310[5254:3]-173318[5254:11]",
"!doc": "@namespace Impelements some standard methods for copying an object.",
"copy": "SC.copy",
"slice": "SC.copy",
"clone": "SC.copy",
"shift": "SC.shift",
"_pool": "SC._pool"
},
"DelegateSupport": {
"delegateFor": {
"!type": "fn(methodName: string) -> !this",
"!span": "176850[5358:2]-176861[5358:13]",
"!doc": "Selects the delegate that implements the specified method name."
},
"invokeDelegateMethod": {
"!type": "fn(delegate: ?, methodName: string, args: ?) -> ?",
"!span": "177702[5386:2]-177722[5386:22]",
"!doc": "Invokes the named method on the delegate that you pass."
},
"getDelegateProperty": {
"!type": "fn(key: string, delegate: ?) -> ?",
"!span": "178376[5403:2]-178395[5403:21]",
"!doc": "Search the named delegates for the passed property."
},
"!span": "176403[5345:3]-176418[5345:18]",
"!doc": "@namespace Support methods for the Delegate design pattern."
},
"FROZEN_ERROR": {
"!type": "+Error",
"!span": "179430[5436:3]-179442[5436:15]",
"!doc": "Standard Error that should be raised when you try to modify a frozen object."
},
"Freezable": {
"isFreezable": {
"!type": "bool",
"!span": "181363[5503:2]-181374[5503:13]",
"!doc": "Walk like a duck."
},
"isFrozen": {
"!type": "bool",
"!span": "181533[5511:2]-181541[5511:10]",
"!doc": "Set to YES when the object is frozen."
},
"freeze": {
"!type": "fn() -> !this",
"!span": "181718[5519:2]-181724[5519:8]",
"!doc": "Freezes the object."
},
"!span": "181288[5496:3]-181297[5496:12]",
"!doc": "@namespace The SC.Freezable mixin implements some basic methods for marking an object as frozen.",
"copy": "SC.copy",
"slice": "SC.copy",
"clone": "SC.copy",
"shift": "SC.shift",
"_pool": "SC._pool"
},
"_pool": {
"!type": "[?]",
"!span": "197558[6051:7]-197563[6051:12]"
},
"Observers": {
"queue": {
"!type": "[[?]]",
"!span": "199254[6107:2]-199259[6107:7]",
"!doc": "flush any observers that we tried to setup but didn't have a path yet"
},
"addObserver": {
"!type": "fn(propertyPath: ?, target: ?, method: ?, pathRoot: ?)",
"!span": "199398[6115:2]-199409[6115:13]",
"!doc": "@private Attempt to add the named observer."
},
"removeObserver": {
"!type": "fn(propertyPath: ?, target: ?, method: ?, pathRoot: ?)",
"!span": "200089[6141:2]-200103[6141:16]",
"!doc": "@private Remove the observer."
},
"addPendingRangeObserver": {
"!type": "fn(observer: SC.RangeObserver) -> !this",
"!span": "200705[6162:2]-200728[6162:25]",
"!doc": "@private Range Observers register here to indicate that they may potentially need to start observing."
},
"_TMP_OUT": {
"!type": "[?]",
"!span": "200894[6169:2]-200902[6169:10]"
},
"flush": {
"!type": "fn(object: ?)",
"!span": "200980[6174:2]-200985[6174:7]",
"!doc": "Flush the queue."
},
"isObservingSuspended": {
"!type": "number",
"!span": "202197[6218:2]-202217[6218:22]",
"!doc": "@private"
},
"objectHasPendingChanges": {
"!type": "fn(obj: ?)",
"!span": "202277[6223:2]-202300[6223:25]",
"!doc": "@private"
},
"suspendPropertyObserving": {
"!type": "fn()",
"!span": "202452[6229:2]-202476[6229:26]",
"!doc": "temporarily suspends all property change notifications."
},
"resumePropertyObserving": {
"!type": "fn()",
"!span": "202661[6236:2]-202684[6236:25]",
"!doc": "@private"
},
"!span": "199237[6105:3]-199246[6105:12]",
"!doc": "@namespace The private ObserverQueue is used to maintain a set of pending observers."
},
"BENCHMARK_OBJECTS": {
"!type": "bool",
"!span": "203791[6271:3]-203808[6271:20]",
"!doc": "globals $$sel"
},
"_object_extend": {
"!type": "fn(base: +SC.Object, ext: ?) -> !0",
"!span": "204608[6294:3]-204622[6294:17]",
"!doc": "@private Augments a base object by copying the properties from the extended hash."
},
"Object": {
"prototype": {
"_kvo_enabled": {
"!type": "bool",
"!span": "216804[6679:2]-216816[6679:14]"
},
"_object_init": {
"!type": "fn(extensions: [?]) -> !this",
"!span": "217116[6689:2]-217128[6689:14]",
"!doc": "@private This is the first method invoked on a new instance."
},
"mixin": {
"!type": "fn() -> !this",
"!span": "218596[6736:2]-218601[6736:7]",
"!doc": "You can call this method on an object to mixin one or more hashes of properties on the receiver object."
},
"init": {
"!type": "fn() -> !this",
"!span": "219375[6762:2]-219379[6762:6]",
"!doc": "This method is invoked automatically whenever a new object is instantiated."
},
"isDestroyed": {
"!type": "bool",
"!span": "219534[6772:2]-219545[6772:13]",
"!doc": "Set to NO once this object has been destroyed."
},
"destroy": {
"!type": "fn() -> !this",
"!span": "220099[6786:2]-220106[6786:9]",
"!doc": "Call this method when you are finished with an object to teardown its contents."
},
"isObject": {
"!type": "bool",
"!span": "220512[6802:2]-220520[6802:10]",
"!doc": "Walk like a duck."
},
"respondsTo": {
"!type": "fn(methodName: string) -> bool",
"!span": "220686[6810:2]-220696[6810:12]",
"!doc": "Returns YES if the named value is an executable function."
},
"tryToPerform": {
"!type": "fn(methodName: string, arg1: ?, arg2: ?) -> bool",
"!span": "221314[6827:2]-221326[6827:14]",
"!doc": "Attemps to invoke the named method, passing the included two arguments."
},
"superclass": {
"!type": "fn(args: ?) -> ?",
"!span": "222481[6867:2]-222491[6867:12]",
"!doc": "EXPERIMENTAL: You can use this to invoke a superclass implementation in any method."
},
"instanceOf": {
"!type": "fn(scClass: ?) -> bool",
"!span": "223135[6893:2]-223145[6893:12]",
"!doc": "returns YES if the receiver is an instance of the named class."
},
"kindOf": {
"!type": "fn(scClass: ?) -> bool",
"!span": "223672[6917:2]-223678[6917:8]",
"!doc": "Returns true if the receiver is an instance of the named class or any subclass of the named class."
},
"toString": {
"!type": "fn() -> !this._object_toString",
"!span": "223765[6920:2]-223773[6920:10]",
"!doc": "@private"
},
"awake": {
"!type": "fn(key: ?)",
"!span": "224335[6938:2]-224340[6938:7]",
"!doc": "Activates any outlet connections in object and syncs any bindings."
},
"invokeOnce": {
"!type": "fn(method: +Function|string) -> !this",
"!span": "225059[6960:2]-225069[6960:12]",
"!doc": "Invokes the passed method or method name one time during the runloop."
},
"invokeLast": {
"!type": "fn(method: +Function|string) -> !this",
"!span": "226652[7001:2]-226662[7001:12]",
"!doc": "Invokes the passed method once at the beginning of the next runloop, before any other methods (including events) are processed."
},
"concatenatedProperties": {
"!type": "[string]",
"!span": "227232[7017:2]-227254[7017:24]",
"!doc": "The properties named in this array will be concatenated in subclasses instead of replaced."
},
"!span": "216787[6677:10]-216796[6677:19]",
"!doc": "..........................................",
"constructor": "SC.Object",
"copy": "SC.copy",
"slice": "SC.copy"
},
"!type": "fn(props: ?) -> +SC.Object",
"!span": "210225[6462:3]-210231[6462:9]",
"!doc": "@class Root object for the SproutCore framework.",
"_bindings": {
"!type": "[string]",
"!span": "209087[6428:7]-209096[6428:16]",
"!doc": "copy bindings, observers, and properties"
},
"_observers": {
"!type": "[string]",
"!span": "209122[6429:7]-209132[6429:17]"
},
"_properties": {
"!type": "[string]",
"!span": "209160[6430:7]-209171[6430:18]"
},
"outlets": {
"!type": "[string]",
"!span": "209200[6431:7]-209207[6431:14]"
}
},
"kindOf": {
"!type": "fn(scObject: ?, scClass: ?) -> bool",
"!span": "230471[7124:3]-230477[7124:9]",
"!doc": "Same as the instance method, but lets you check kindOf without having to first check if kindOf exists as a method."
},
"instanceOf": {
"!type": "fn(scObject: ?, scClass: ?) -> bool",
"!span": "230071[7112:3]-230081[7112:13]",
"!doc": "Same as the instance method, but lets you check instanceOf without having to first check if instanceOf exists as a method."
},
"_object_foundObjectClassNames": {
"!type": "bool",
"!span": "227854[7039:5]-227883[7039:34]"
},
"_object_className": {
"!type": "fn(obj: fn(props: ?) -> +SC.Object) -> !0._object_className",
"!span": "230876[7135:3]-230893[7135:20]",
"!doc": "@private Returns the name of this class."
},
"_ChainObserver": {
"prototype": {
"isChainObserver": {
"!type": "bool",
"!span": "233453[7209:2]-233468[7209:17]"
},
"objectDidChange": {
"!type": "fn(newObject: ?)",
"!span": "234017[7229:2]-234032[7229:17]",
"!doc": "invoked when the source object changes."
},
"propertyDidChange": {
"!type": "fn()",
"!span": "234693[7248:2]-234710[7248:19]",
"!doc": "the observer method invoked when the observed property changes."
},
"destroyChain": {
"!type": "fn()",
"!span": "235476[7275:2]-235488[7275:14]",
"!doc": "teardown the chain..."
},
"!span": "233449[7208:30]-235886[7291:1]"
},
"createChain": {
"!type": "fn(rootObject: ?, path: ?, target: ?, method: ?, context: ?) -> +SC._ChainObserver",
"!span": "232659[7184:18]-232670[7184:29]",
"!doc": "This is the primary entry point."
},
"!type": "fn(property: ?)",
"!span": "232510[7179:3]-232524[7179:17]",
"!doc": "ChainObservers are used to automatically monitor a property several layers deep.",
"next": {
"!type": "+SC._ChainObserver",
"!span": "232918[7192:16]-232922[7192:20]",
"!doc": "create the chain and save it for later so we can tear it down if needed."
}
},
"ObservableProtocol": {
"propertyObserver": {
"!type": "fn(observer: ?, target: ?, key: ?, value: ?, revision: ?)",
"!span": "237655[7331:2]-237671[7331:18]",
"!doc": "Generic property observer called whenever a property on the receiver changes."
},
"!span": "236674[7308:3]-236692[7308:21]",
"!doc": "The SC.ObservableProtocol defines optional methods you can implement on your objects."
},
"SparseArrayDelegate": {
"sparseArrayDidRequestLength": {
"!type": "fn(sparseArray: ?)",
"!span": "239596[7378:2]-239623[7378:29]",
"!doc": "Invoked when an object requests the length of the sparse array and the length has not yet been set."
},
"sparseArrayDidRequestIndex": {
"!type": "fn(sparseArray: ?, index: number)",
"!span": "240688[7403:2]-240714[7403:28]",
"!doc": "Invoked when an object requests an index on the sparse array that has not yet been set."
},
"sparseArrayDidRequestRange": {
"!type": "fn(sparseArray: ?, range: ?)",
"!span": "241592[7427:2]-241618[7427:28]",
"!doc": "Alternative method invoked when an object requests an index on the sparse array that has not yet been set."
},
"sparseArrayDidRequestIndexOf": {
"!type": "fn(sparseArray: ?, object: ?)",
"!span": "242053[7441:2]-242081[7441:30]",
"!doc": "Optional delegate method you can use to determine the index of a particular object."
},
"sparseArrayShouldReplace": {
"!type": "fn(sparseArray: ?, idx: number, amt: number, objects: [?]) -> bool",
"!span": "242631[7456:2]-242655[7456:26]",
"!doc": "Optional delegate method invoked whenever the sparse array attempts to changes its contents."
},
"sparseArrayDidReset": {
"!type": "fn(sparseArray: ?)",
"!span": "242991[7468:2]-243010[7468:21]",
"!doc": "Invoked whenever the sparse array is reset."
},
"!span": "238790[7359:3]-238809[7359:22]",
"!doc": "@namespace Delegate that provides data for a sparse array."
},
"LOG_BINDINGS": {
"!type": "bool",
"!span": "243917[7491:3]-243929[7491:15]",
"!doc": "Debug parameter you can turn on."
},
"BENCHMARK_BINDING_NOTIFICATIONS": {
"!type": "bool",
"!span": "244054[7499:3]-244085[7499:34]",
"!doc": "Performance paramter."
},
"BENCHMARK_BINDING_SETUP": {
"!type": "bool",
"!span": "244216[7507:3]-244239[7507:26]",
"!doc": "Performance parameter."
},
"MULTIPLE_PLACEHOLDER": {
"!type": "string",
"!span": "244334[7514:3]-244354[7514:23]",
"!doc": "Default placeholder for multiple values in bindings."
},
"NULL_PLACEHOLDER": {
"!type": "string",
"!span": "244454[7521:3]-244470[7521:19]",
"!doc": "Default placeholder for null values in bindings."
},
"EMPTY_PLACEHOLDER": {
"!type": "string",
"!span": "244571[7528:3]-244588[7528:20]",
"!doc": "Default placeholder for empty values in bindings."
},
"Binding": {
"beget": {
"!type": "fn(fromPath: ?) -> SC.Binding",
"!span": "253633[7762:2]-253638[7762:7]",
"!doc": "This is the core method you use to create a new binding instance."
},
"builder": {
"!type": "fn() -> fn(fromProperty: ?)",
"!span": "253867[7772:2]-253874[7772:9]",
"!doc": "Returns a builder function for compatibility."
},
"from": {
"!type": "fn(propertyPath: [?]|?, root: ?) -> !this",
"!span": "254845[7794:2]-254849[7794:6]",
"!doc": "This will set \"from\" property path to the specified value."
},
"to": {
"!type": "fn(propertyPath: string, root: ?) -> !this",
"!span": "255712[7818:2]-255714[7818:4]",
"!doc": "This will set the \"to\" property path to the specified value."
},
"connect": {
"!type": "fn() -> !this",
"!span": "256223[7834:2]-256230[7834:9]",
"!doc": "Attempts to connect this binding instance so that it can receive and relay changes."
},
"_connect": {
"!type": "fn()",
"!span": "256730[7849:2]-256738[7849:10]",
"!doc": "@private Actually connects the binding."
},
"disconnect": {
"!type": "fn() -> !this",
"!span": "258734[7909:2]-258744[7909:12]",
"!doc": "Disconnects the binding instance."
},
"fromPropertyDidChange": {
"!type": "fn(target: ?, key: ?)",
"!span": "259394[7932:2]-259415[7932:23]",
"!doc": "Invoked whenever the value of the \"from\" property changes."
},
"toPropertyDidChange": {
"!type": "fn(target: ?, key: ?)",
"!span": "260205[7957:2]-260224[7957:21]",
"!doc": "Invoked whenever the value of the \"to\" property changes."
},
"_setBindingValue": {
"!type": "fn(source: ?, key: ?)",
"!span": "260781[7975:2]-260797[7975:18]",
"!doc": "@private Saves the source location for the binding value."
},
"_computeBindingValue": {
"!type": "fn()",
"!span": "261054[7984:2]-261074[7984:22]",
"!doc": "@private Updates the binding value from the current binding source if needed."
},
"_changePending": {
"!type": "bool",
"!span": "261926[8013:2]-261940[8013:16]"
},
"flushPendingChanges": {
"!type": "fn() -> bool",
"!span": "262092[8020:2]-262111[8020:21]",
"!doc": "Call this method on SC.Binding to flush all bindings with changed pending."
},
"applyBindingValue": {
"!type": "fn()",
"!span": "263771[8070:2]-263788[8070:19]",
"!doc": "This method is called at the end of the Run Loop to relay the changed binding value from one side to the other."
},
"sync": {
"!type": "fn() -> !this",
"!span": "265257[8109:2]-265261[8109:6]",
"!doc": "Calling this method on a binding will cause it to check the value of the from side of the binding matches the current expected value of the binding."
},
"_syncOnConnect": {
"!type": "bool",
"!span": "266212[8141:2]-266226[8141:16]",
"!doc": "set if you call sync() when the binding connection is still pending."
},
"_computeBindingTargets": {
"!type": "fn()",
"!span": "266235[8143:2]-266257[8143:24]"
},
"oneWay": {
"!type": "fn(fromPath: ?, aFlag: ?) -> SC.Binding",
"!span": "267904[8191:2]-267910[8191:8]",
"!doc": "Configures the binding as one way."
},
"transform": {
"!type": "fn(transformFunc: ?) -> !this",
"!span": "268869[8223:2]-268878[8223:11]",
"!doc": "Adds the specified transform function to the array of transform functions."
},
"resetTransforms": {
"!type": "fn() -> !this",
"!span": "269565[8247:2]-269580[8247:17]",
"!doc": "Resets the transforms for the binding."
},
"noError": {
"!type": "fn(fromPath: ?, aFlag: ?) -> SC.Binding",
"!span": "270174[8264:2]-270181[8264:9]",
"!doc": "Specifies that the binding should not return error objects."
},
"single": {
"!type": "fn(fromPath: ?, placeholder: string) -> SC.Binding",
"!span": "271242[8298:2]-271248[8298:8]",
"!doc": "Adds a transform to the chain that will allow only single values to pass."
},
"notEmpty": {
"!type": "fn(fromPath: ?, placeholder: string) -> SC.Binding",
"!span": "271945[8319:2]-271953[8319:10]",
"!doc": "Adds a transform that will return the placeholder value if the value is null, undefined, an empty array or an empty string."
},
"notNull": {
"!type": "fn(fromPath: ?, placeholder: string) -> SC.Binding",
"!span": "272592[8337:2]-272599[8337:9]",
"!doc": "Adds a transform that will return the placeholder value if the value is null."
},
"multiple": {
"!type": "fn(fromPath: ?) -> SC.Binding",
"!span": "273103[8352:2]-273111[8352:10]",
"!doc": "Adds a transform that will convert the passed value to an array."
},
"bool": {
"!type": "fn(fromPath: ?) -> SC.Binding",
"!span": "273601[8367:2]-273605[8367:6]",
"!doc": "Adds a transform to convert the value to a bool value."
},
"and": {
"!type": "fn(pathA: ?, pathB: ?)",
"!span": "274503[8390:2]-274506[8390:5]",
"!doc": "Adds a transform that forwards the logical 'AND' of values at 'pathA' and 'pathB' whenever either source changes."
},
"or": {
"!type": "fn(pathA: ?, pathB: ?)",
"!span": "275256[8414:2]-275258[8414:4]",
"!doc": "Adds a transform that forwards the 'OR' of values at 'pathA' and 'pathB' whenever either source changes."
},
"not": {
"!type": "fn(fromPath: ?) -> SC.Binding",
"!span": "275844[8436:2]-275847[8436:5]",
"!doc": "Adds a transform to convert the value to the inverse of a bool value."
},
"isNull": {
"!type": "fn(fromPath: ?) -> SC.Binding",
"!span": "276208[8449:2]-276214[8449:8]",
"!doc": "Adds a transform that will return YES if the value is null, NO otherwise."
},
"toString": {
"!type": "fn()",
"!span": "276388[8456:2]-276396[8456:10]"
},
"_toPropertyPath": {
"!type": "string",
"!span": "255847[7821:12]-255862[7821:27]"
},
"isConnected": {
"!type": "bool",
"!span": "256350[7837:9]-256361[7837:20]"
},
"_connectionPending": {
"!type": "bool",
"!span": "256379[7838:9]-256397[7838:27]"
},
"_isFlushing": {
"!type": "bool",
"!span": "262225[8024:9]-262236[8024:20]",
"!doc": "clean up"
},
"!span": "253206[7749:3]-253213[7749:10]",
"!doc": "@namespace A binding simply connects the properties of two objects so that whenever the value of one property changes, the other property will be changed also."
},
"binding": {
"!type": "fn(path: ?, root: ?) -> SC.Binding",
"!span": "276900[8473:3]-276907[8473:10]",
"!doc": "Shorthand method to define a binding."
},
"$error": {
"!type": "fn(description: string, label: ?, value: ?, c: ?) -> ?",
"!span": "284869[8759:3]-284875[8759:9]",
"!doc": "Shorthand form of the SC.Error.desc method."
},
"ok": {
"!type": "fn(ret: ?) -> bool",
"!span": "285107[8769:3]-285109[8769:5]",
"!doc": "Returns YES if the passed value is an error object or false."
},
"val": {
"!type": "fn(obj: ?) -> !0",
"!span": "285441[8783:3]-285444[8783:6]",
"!doc": "Returns the value of an object."
},
"LOGGER_LOG_DELIMITER": {
"!type": "string",
"!span": "319029[10017:3]-319049[10017:23]",
"!doc": "If {@link SC.Logger.format} is true, this delimiter will be put between arguments."
},
"LOGGER_LOG_ERROR": {
"!type": "string",
"!span": "319199[10025:3]-319215[10025:19]",
"!doc": "If {@link SC.Logger.error} falls back onto {@link SC.Logger.log}, this will be prepended to the output."
},
"LOGGER_LOG_INFO": {
"!type": "string",
"!span": "319369[10033:3]-319384[10033:18]",
"!doc": "If {@link SC.Logger.info} falls back onto {@link SC.Logger.log}, this will be prepended to the output."
},
"LOGGER_LOG_WARN": {
"!type": "string",
"!span": "319537[10041:3]-319552[10041:18]",
"!doc": "If {@link SC.Logger.warn} falls back onto {@link SC.Logger.log}, this will be prepended to the output."
},
"LOGGER_LOG_DEBUG": {
"!type": "string",
"!span": "319709[10049:3]-319725[10049:19]",
"!doc": "If {@link SC.Logger.debug} falls back onto {@link SC.Logger.log}, this will be prepended to the output."
},
"run": {
"!type": "fn(callback: +Function, target: ?, forceNested: ?)",
"!span": "339852[10745:3]-339855[10745:6]",
"!doc": "Executes a passed function in the context of a run loop."
},
"!span": "1145[22:4]-1147[22:6]",
"!doc": "@namespace The SproutCore namespace.",
"extend": "SC.mixin",
"clone": "SC.copy",
"slice": "SC.copy",
"$ok": "SC.ok",
"$val": "SC.val"
},
"require": {
"!type": "fn()",
"!span": "7921[159:4]-7928[159:11]",
"!doc": "These commands are used by the build tools to control load order."
},
"sc_resource": {
"!type": "fn()",
"!span": "8011[161:4]-8022[161:15]"
},
"YES": {
"!type": "bool",
"!span": "8343[170:4]-8346[170:7]",
"!doc": "........................................"
},
"NO": {
"!type": "bool",
"!span": "8360[171:4]-8362[171:6]"
},
"findClassNames": {
"!type": "fn()",
"!span": "227780[7036:9]-227794[7036:23]",
"!doc": "@private This is a way of performing brute-force introspection."
},
"sc_require": "require",
"SproutCore": "SC"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment