dist/ember-runtime.js in ember-source-0.0.9 vs dist/ember-runtime.js in ember-source-1.0.0.pre4.0

- old
+ new

@@ -1,7 +1,7 @@ -// Version: v1.0.0-rc.3-251-gcd5dfe3 -// Last commit: cd5dfe3 (2013-05-18 11:06:43 -0700) +// Version: v1.0.0-pre.4-228-gcabc05e +// Last commit: cabc05e (2013-02-21 10:31:18 -0500) (function() { /*global __fail__*/ @@ -138,23 +138,22 @@ no warnings will be shown in production. @method deprecateFunc @param {String} message A description of the deprecation. @param {Function} func The function to be deprecated. - @return {Function} a new function that wrapped the original function with a deprecation warning */ Ember.deprecateFunc = function(message, func) { return function() { Ember.deprecate(message); return func.apply(this, arguments); }; }; })(); -// Version: v1.0.0-rc.3-251-gcd5dfe3 -// Last commit: cd5dfe3 (2013-05-18 11:06:43 -0700) +// Version: v1.0.0-pre.4-228-gcabc05e +// Last commit: cabc05e (2013-02-21 10:31:18 -0500) (function() { var define, requireModule; @@ -210,11 +209,11 @@ The core Runtime framework is based on the jQuery API with a number of performance optimizations. @class Ember @static - @version 1.0.0-rc.3 + @version 1.0.0-pre.4 */ if ('undefined' === typeof Ember) { // Create core object. Make it act like an instance of Ember.Namespace so that // objects assigned to it are given a sane string representation. @@ -237,14 +236,14 @@ /** @property VERSION @type String - @default '1.0.0-rc.3' + @default '1.0.0-pre.4' @final */ -Ember.VERSION = '1.0.0-rc.3'; +Ember.VERSION = '1.0.0-pre.4'; /** Standard environmental variables. You can define these in a global `ENV` variable before loading Ember to control various configuration settings. @@ -297,19 +296,10 @@ @default Ember.EXTEND_PROTOTYPES */ Ember.SHIM_ES5 = (Ember.ENV.SHIM_ES5 === false) ? false : Ember.EXTEND_PROTOTYPES; /** - Determines whether Ember logs info about version of used libraries - - @property LOG_VERSION - @type Boolean - @default true -*/ -Ember.LOG_VERSION = (Ember.ENV.LOG_VERSION === false) ? false : true; - -/** Empty function. Useful for some operations. @method K @private @return {Object} @@ -401,79 +391,26 @@ Ember.handleErrors = function(func, context) { // Unfortunately in some browsers we lose the backtrace if we rethrow the existing error, // so in the event that we don't have an `onerror` handler we don't wrap in a try/catch if ('function' === typeof Ember.onerror) { try { - return func.call(context || this); + return func.apply(context || this); } catch (error) { Ember.onerror(error); } } else { - return func.call(context || this); + return func.apply(context || this); } }; Ember.merge = function(original, updates) { for (var prop in updates) { if (!updates.hasOwnProperty(prop)) { continue; } original[prop] = updates[prop]; } - return original; }; -/** - Returns true if the passed value is null or undefined. This avoids errors - from JSLint complaining about use of ==, which can be technically - confusing. - - ```javascript - Ember.isNone(); // true - Ember.isNone(null); // true - Ember.isNone(undefined); // true - Ember.isNone(''); // false - Ember.isNone([]); // false - Ember.isNone(function(){}); // false - ``` - - @method isNone - @for Ember - @param {Object} obj Value to test - @return {Boolean} -*/ -Ember.isNone = function(obj) { - return obj === null || obj === undefined; -}; -Ember.none = Ember.deprecateFunc("Ember.none is deprecated. Please use Ember.isNone instead.", Ember.isNone); - -/** - Verifies that a value is `null` or an empty string, empty array, - or empty function. - - Constrains the rules on `Ember.isNone` by returning false for empty - string and empty arrays. - - ```javascript - Ember.isEmpty(); // true - Ember.isEmpty(null); // true - Ember.isEmpty(undefined); // true - Ember.isEmpty(''); // true - Ember.isEmpty([]); // true - Ember.isEmpty('Adam Hawkins'); // false - Ember.isEmpty([0,1,2]); // false - ``` - - @method isEmpty - @for Ember - @param {Object} obj Value to test - @return {Boolean} -*/ -Ember.isEmpty = function(obj) { - return Ember.isNone(obj) || (obj.length === 0 && typeof obj !== 'function') || (typeof obj === 'object' && Ember.get(obj, 'length') === 0); -}; -Ember.empty = Ember.deprecateFunc("Ember.empty is deprecated. Please use Ember.isEmpty instead.", Ember.isEmpty) ; - - })(); (function() { @@ -498,17 +435,10 @@ @method create @for Ember */ Ember.create = Object.create; -// IE8 has Object.create but it couldn't treat property descripters. -if (Ember.create) { - if (Ember.create({a: 1}, {a: {value: 2}}).a !== 2) { - Ember.create = null; - } -} - // STUB_OBJECT_CREATE allows us to override other libraries that stub // Object.create different than we would prefer if (!Ember.create || Ember.ENV.STUB_OBJECT_CREATE) { var K = function() {}; @@ -640,112 +570,15 @@ })(); (function() { -/*jshint newcap:false*/ /** @module ember-metal */ -// NOTE: There is a bug in jshint that doesn't recognize `Object()` without `new` -// as being ok unless both `newcap:false` and not `use strict`. -// https://github.com/jshint/jshint/issues/392 -// Testing this is not ideal, but we want to use native functions -// if available, but not to use versions created by libraries like Prototype -var isNativeFunc = function(func) { - // This should probably work in all browsers likely to have ES5 array methods - return func && Function.prototype.toString.call(func).indexOf('[native code]') > -1; -}; - -// From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/map -var arrayMap = isNativeFunc(Array.prototype.map) ? Array.prototype.map : function(fun /*, thisp */) { - //"use strict"; - - if (this === void 0 || this === null) { - throw new TypeError(); - } - - var t = Object(this); - var len = t.length >>> 0; - if (typeof fun !== "function") { - throw new TypeError(); - } - - var res = new Array(len); - var thisp = arguments[1]; - for (var i = 0; i < len; i++) { - if (i in t) { - res[i] = fun.call(thisp, t[i], i, t); - } - } - - return res; -}; - -// From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/foreach -var arrayForEach = isNativeFunc(Array.prototype.forEach) ? Array.prototype.forEach : function(fun /*, thisp */) { - //"use strict"; - - if (this === void 0 || this === null) { - throw new TypeError(); - } - - var t = Object(this); - var len = t.length >>> 0; - if (typeof fun !== "function") { - throw new TypeError(); - } - - var thisp = arguments[1]; - for (var i = 0; i < len; i++) { - if (i in t) { - fun.call(thisp, t[i], i, t); - } - } -}; - -var arrayIndexOf = isNativeFunc(Array.prototype.indexOf) ? Array.prototype.indexOf : function (obj, fromIndex) { - if (fromIndex === null || fromIndex === undefined) { fromIndex = 0; } - else if (fromIndex < 0) { fromIndex = Math.max(0, this.length + fromIndex); } - for (var i = fromIndex, j = this.length; i < j; i++) { - if (this[i] === obj) { return i; } - } - return -1; -}; - -Ember.ArrayPolyfills = { - map: arrayMap, - forEach: arrayForEach, - indexOf: arrayIndexOf -}; - -if (Ember.SHIM_ES5) { - if (!Array.prototype.map) { - Array.prototype.map = arrayMap; - } - - if (!Array.prototype.forEach) { - Array.prototype.forEach = arrayForEach; - } - - if (!Array.prototype.indexOf) { - Array.prototype.indexOf = arrayIndexOf; - } -} - -})(); - - - -(function() { -/** -@module ember-metal -*/ - - var o_defineProperty = Ember.platform.defineProperty, o_create = Ember.create, // Used for guid generation... GUID_KEY = '__ember'+ (+ new Date()), uuid = 0, @@ -816,20 +649,20 @@ You can also use this method on DOM Element objects. @method guidFor @for Ember - @param {Object} obj any object, string, number, Element, or primitive + @param obj {Object} any object, string, number, Element, or primitive @return {String} the unique guid for this instance. */ Ember.guidFor = function guidFor(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 ret; + var cache, ret; var type = typeof obj; // Don't allow prototype changes to String etc. to change the guidFor switch(type) { case 'number': @@ -927,11 +760,11 @@ @private @param {Object} obj The object to retrieve meta for @param {Boolean} [writable=true] Pass `false` if you do not intend to modify the meta hash, allowing the method to avoid making an unnecessary copy. - @return {Object} the meta hash for an object + @return {Hash} */ Ember.meta = function meta(obj, writable) { var ret = obj[META_KEY]; if (writable===false) return ret || EMPTY_META; @@ -1079,11 +912,11 @@ ``` @method isArray @for Ember @param {Object} obj The object to test - @return {Boolean} true if the passed object is an array or Array-like + @return {Boolean} */ Ember.isArray = function(obj) { if (!obj || obj.setInterval) { return false; } if (Array.isArray && Array.isArray(obj)) { return true; } if (Ember.Array && Ember.Array.detect(obj)) { return true; } @@ -1138,11 +971,11 @@ @method tryInvoke @for Ember @param {Object} obj The object to check for the method @param {String} methodName The method name to check for @param {Array} [args] The arguments to pass to the method - @return {*} the return value of the invoked method or undefined if it cannot be invoked + @return {anything} the return value of the invoked method or undefined if it cannot be invoked */ Ember.tryInvoke = function(obj, methodName, args) { if (canInvoke(obj, methodName)) { return obj[methodName].apply(obj, args || []); } @@ -1166,14 +999,14 @@ Provides try { } finally { } functionality, while working around Safari's double finally bug. @method tryFinally @for Ember - @param {Function} tryable The function to run the try callback - @param {Function} finalizer The function to run the finally callback + @param {Function} function The function to run the try callback + @param {Function} function The function to run the finally callback @param [binding] - @return {*} The return value is the that of the finalizer, + @return {anything} The return value is the that of the finalizer, unless that valueis undefined, in which case it is the return value of the tryable */ if (needsFinallyFix) { @@ -1216,21 +1049,21 @@ Provides try { } catch finally { } functionality, while working around Safari's double finally bug. @method tryCatchFinally @for Ember - @param {Function} tryable The function to run the try callback - @param {Function} catchable The function to run the catchable callback - @param {Function} finalizer The function to run the finally callback + @param {Function} function The function to run the try callback + @param {Function} function The function to run the catchable callback + @param {Function} function The function to run the finally callback @param [binding] - @return {*} The return value is the that of the finalizer, + @return {anything} The return value is the that of the finalizer, unless that value is undefined, in which case it is the return value of the tryable. */ if (needsFinallyFix) { Ember.tryCatchFinally = function(tryable, catchable, finalizer, binding) { - var result, finalResult, finalError; + var result, finalResult, finalError, finalReturn; binding = binding || this; try { result = tryable.call(binding); @@ -1264,82 +1097,10 @@ return (finalResult === undefined) ? result : finalResult; }; } -// ........................................ -// TYPING & ARRAY MESSAGING -// - -var TYPE_MAP = {}; -var t = "Boolean Number String Function Array Date RegExp Object".split(" "); -Ember.ArrayPolyfills.forEach.call(t, function(name) { - TYPE_MAP[ "[object " + name + "]" ] = name.toLowerCase(); -}); - -var toString = Object.prototype.toString; - -/** - Returns a consistent 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 | Meaning | - |---------------|------------------------------------------------------| - | 'string' | String primitive | - | 'number' | Number primitive | - | 'boolean' | Boolean primitive | - | 'null' | Null value | - | 'undefined' | Undefined value | - | 'function' | A function | - | 'array' | An instance of Array | - | 'class' | An Ember class (created using Ember.Object.extend()) | - | 'instance' | An Ember object instance | - | 'error' | An instance of the Error object | - | 'object' | A JavaScript object not inheriting from Ember.Object | - - Examples: - - ```javascript - Ember.typeOf(); // 'undefined' - Ember.typeOf(null); // 'null' - Ember.typeOf(undefined); // 'undefined' - Ember.typeOf('michael'); // 'string' - Ember.typeOf(101); // 'number' - Ember.typeOf(true); // 'boolean' - Ember.typeOf(Ember.makeArray); // 'function' - Ember.typeOf([1,2,90]); // 'array' - Ember.typeOf(Ember.Object.extend()); // 'class' - Ember.typeOf(Ember.Object.create()); // 'instance' - Ember.typeOf(new Error('teamocil')); // 'error' - - // "normal" JavaScript object - Ember.typeOf({a: 'b'}); // 'object' - ``` - - @method typeOf - @for Ember - @param {Object} item the item to check - @return {String} the type -*/ -Ember.typeOf = function(item) { - var ret; - - ret = (item === null || item === undefined) ? String(item) : TYPE_MAP[toString.call(item)] || 'object'; - - if (ret === 'function') { - if (Ember.Object && Ember.Object.detect(item)) ret = 'class'; - } else if (ret === 'object') { - if (item instanceof Error) ret = 'error'; - else if (Ember.Object && item instanceof Ember.Object) ret = 'instance'; - else ret = 'object'; - } - - return ret; -}; })(); (function() { @@ -1516,27 +1277,21 @@ })(); (function() { -var map, forEach, indexOf, concat; -concat = Array.prototype.concat; -map = Array.prototype.map || Ember.ArrayPolyfills.map; -forEach = Array.prototype.forEach || Ember.ArrayPolyfills.forEach; -indexOf = Array.prototype.indexOf || Ember.ArrayPolyfills.indexOf; - var utils = Ember.EnumerableUtils = { map: function(obj, callback, thisArg) { - return obj.map ? obj.map.call(obj, callback, thisArg) : map.call(obj, callback, thisArg); + return obj.map ? obj.map.call(obj, callback, thisArg) : Array.prototype.map.call(obj, callback, thisArg); }, forEach: function(obj, callback, thisArg) { - return obj.forEach ? obj.forEach.call(obj, callback, thisArg) : forEach.call(obj, callback, thisArg); + return obj.forEach ? obj.forEach.call(obj, callback, thisArg) : Array.prototype.forEach.call(obj, callback, thisArg); }, indexOf: function(obj, element, index) { - return obj.indexOf ? obj.indexOf.call(obj, element, index) : indexOf.call(obj, element, index); + return obj.indexOf ? obj.indexOf.call(obj, element, index) : Array.prototype.indexOf.call(obj, element, index); }, indexesOf: function(obj, elements) { return elements === undefined ? [] : utils.map(elements, function(item) { return utils.indexOf(obj, item); @@ -1555,20 +1310,20 @@ replace: function(array, idx, amt, objects) { if (array.replace) { return array.replace(idx, amt, objects); } else { - var args = concat.apply([idx, amt], objects); + var args = Array.prototype.concat.apply([idx, amt], objects); return array.splice.apply(array, args); } }, intersection: function(array1, array2) { var intersection = []; - utils.forEach(array1, function(element) { - if (utils.indexOf(array2, element) >= 0) { + array1.forEach(function(element) { + if (array2.indexOf(element) >= 0) { intersection.push(element); } }); return intersection; @@ -1578,14 +1333,111 @@ })(); (function() { +/*jshint newcap:false*/ /** @module ember-metal */ +// NOTE: There is a bug in jshint that doesn't recognize `Object()` without `new` +// as being ok unless both `newcap:false` and not `use strict`. +// https://github.com/jshint/jshint/issues/392 + +// Testing this is not ideal, but we want to use native functions +// if available, but not to use versions created by libraries like Prototype +var isNativeFunc = function(func) { + // This should probably work in all browsers likely to have ES5 array methods + return func && Function.prototype.toString.call(func).indexOf('[native code]') > -1; +}; + +// From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/map +var arrayMap = isNativeFunc(Array.prototype.map) ? Array.prototype.map : function(fun /*, thisp */) { + //"use strict"; + + if (this === void 0 || this === null) { + throw new TypeError(); + } + + var t = Object(this); + var len = t.length >>> 0; + if (typeof fun !== "function") { + throw new TypeError(); + } + + var res = new Array(len); + var thisp = arguments[1]; + for (var i = 0; i < len; i++) { + if (i in t) { + res[i] = fun.call(thisp, t[i], i, t); + } + } + + return res; +}; + +// From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/foreach +var arrayForEach = isNativeFunc(Array.prototype.forEach) ? Array.prototype.forEach : function(fun /*, thisp */) { + //"use strict"; + + if (this === void 0 || this === null) { + throw new TypeError(); + } + + var t = Object(this); + var len = t.length >>> 0; + if (typeof fun !== "function") { + throw new TypeError(); + } + + var thisp = arguments[1]; + for (var i = 0; i < len; i++) { + if (i in t) { + fun.call(thisp, t[i], i, t); + } + } +}; + +var arrayIndexOf = isNativeFunc(Array.prototype.indexOf) ? Array.prototype.indexOf : function (obj, fromIndex) { + if (fromIndex === null || fromIndex === undefined) { fromIndex = 0; } + else if (fromIndex < 0) { fromIndex = Math.max(0, this.length + fromIndex); } + for (var i = fromIndex, j = this.length; i < j; i++) { + if (this[i] === obj) { return i; } + } + return -1; +}; + +Ember.ArrayPolyfills = { + map: arrayMap, + forEach: arrayForEach, + indexOf: arrayIndexOf +}; + +if (Ember.SHIM_ES5) { + if (!Array.prototype.map) { + Array.prototype.map = arrayMap; + } + + if (!Array.prototype.forEach) { + Array.prototype.forEach = arrayForEach; + } + + if (!Array.prototype.indexOf) { + Array.prototype.indexOf = arrayIndexOf; + } +} + +})(); + + + +(function() { +/** +@module ember-metal +*/ + /* JavaScript (before ES6) does not have a Map implementation. Objects, which are often used as dictionaries, may only have Strings as keys. Because Ember has a way to get a unique identifier for every object @@ -1711,16 +1563,16 @@ return guid in presenceSet; }, /** @method forEach - @param {Function} fn - @param self + @param {Function} function + @param target */ forEach: function(fn, self) { // allow mutation during iteration - var list = this.toArray(); + var list = this.list.slice(); for (var i = 0, j = list.length; i < j; i++) { fn.call(self, list[i]); } }, @@ -1739,11 +1591,11 @@ */ copy: function() { var set = new OrderedSet(); set.presenceSet = copy(this.presenceSet); - set.list = this.toArray(); + set.list = this.list.slice(); return set; } }; @@ -1783,12 +1635,12 @@ Map.prototype = { /** Retrieve the value associated with a given key. @method get - @param {*} key - @return {*} the value associated with the key, or `undefined` + @param {anything} key + @return {anything} the value associated with the key, or `undefined` */ get: function(key) { var values = this.values, guid = guidFor(key); @@ -1798,12 +1650,12 @@ /** Adds a value to the map. If a value for the given key has already been provided, the new value will replace the old value. @method set - @param {*} key - @param {*} value + @param {anything} key + @param {anything} value */ set: function(key, value) { var keys = this.keys, values = this.values, guid = guidFor(key); @@ -1814,22 +1666,24 @@ /** Removes a value from the map for an associated key. @method remove - @param {*} key + @param {anything} key @return {Boolean} true if an item was removed, false otherwise */ remove: function(key) { // don't use ES6 "delete" because it will be annoying // to use in browsers that are not ES6 friendly; var keys = this.keys, values = this.values, - guid = guidFor(key); + guid = guidFor(key), + value; if (values.hasOwnProperty(guid)) { keys.remove(key); + value = values[guid]; delete values[guid]; return true; } else { return false; } @@ -1837,11 +1691,11 @@ /** Check whether a key is present. @method has - @param {*} key + @param {anything} key @return {Boolean} true if the item was present, false otherwise */ has: function(key) { var values = this.values, guid = guidFor(key); @@ -1855,11 +1709,11 @@ The keys are guaranteed to be iterated over in insertion order. @method forEach @param {Function} callback - @param {*} self if passed, the `this` value inside the + @param {anything} self if passed, the `this` value inside the callback. By default, `this` is the map. */ forEach: function(callback, self) { var keys = this.keys, values = this.values; @@ -1884,23 +1738,23 @@ @namespace Ember @extends Ember.Map @private @constructor @param [options] - @param {*} [options.defaultValue] + @param {anything} [options.defaultValue] */ var MapWithDefault = Ember.MapWithDefault = function(options) { Map.call(this); this.defaultValue = options.defaultValue; }; /** @method create @static @param [options] - @param {*} [options.defaultValue] - @return {Ember.MapWithDefault|Ember.Map} If options are passed, returns + @param {anything} [options.defaultValue] + @return {Ember.MapWithDefault|Ember.Map} If options are passed, returns `Ember.MapWithDefault` otherwise returns `Ember.Map` */ MapWithDefault.create = function(options) { if (options) { return new MapWithDefault(options); @@ -1913,12 +1767,12 @@ /** Retrieve the value associated with a given key. @method get - @param {*} key - @return {*} the value associated with the key, or the default value + @param {anything} key + @return {anything} the value associated with the key, or the default value */ MapWithDefault.prototype.get = function(key) { var hasValue = this.has(key); if (hasValue) { @@ -1947,33 +1801,34 @@ (function() { /** @module ember-metal */ -var META_KEY = Ember.META_KEY, get; +var META_KEY = Ember.META_KEY, get, set; var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER; +var IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/; var IS_GLOBAL_PATH = /^([A-Z$]|([0-9][A-Z$])).*[\.\*]/; var HAS_THIS = /^this[\.\*]/; var FIRST_KEY = /^([^\.\*]+)/; // .......................................................... // GET AND SET // -// If we are on a platform that supports accessors we can use those. +// If we are on a platform that supports accessors we can get use those. // Otherwise simulate accessors by looking up the property directly on the // object. /** Gets the value of a property on an object. If the property is computed, the function will be invoked. If the property is not defined but the object implements the `unknownProperty` method then that will be invoked. If you plan to run on IE8 and older browsers then you should use this method anytime you want to retrieve a property on an object that you don't - know for sure is private. (Properties beginning with an underscore '_' + know for sure is private. (Properties beginning with an underscore '_' are considered private.) On all newer browsers, you only need to use this method to retrieve properties if the property might not be defined on the object and you want to respect the `unknownProperty` handler. Otherwise you can ignore this @@ -1997,16 +1852,17 @@ if (!keyName && 'string'===typeof obj) { keyName = obj; obj = null; } - Ember.assert("Cannot call get with '"+ keyName +"' on an undefined object.", obj !== undefined); - - if (obj === null || keyName.indexOf('.') !== -1) { + if (!obj || keyName.indexOf('.') !== -1) { + Ember.assert("Cannot call get with '"+ keyName +"' on an undefined object.", obj !== undefined); return getPath(obj, keyName); } + Ember.assert("You need to provide an object and key to `get`.", !!obj && keyName); + var meta = obj[META_KEY], desc = meta && meta.descs[keyName], ret; if (desc) { return desc.get(obj, keyName); } else { if (MANDATORY_SETTER && meta && meta.watching[keyName] > 0) { @@ -2022,783 +1878,19 @@ return ret; } }; -// Currently used only by Ember Data tests -if (Ember.config.overrideAccessors) { - Ember.get = get; - Ember.config.overrideAccessors(); - get = Ember.get; -} - -function firstKey(path) { - return path.match(FIRST_KEY)[0]; -} - -// assumes path is already normalized -function normalizeTuple(target, path) { - var hasThis = HAS_THIS.test(path), - isGlobal = !hasThis && IS_GLOBAL_PATH.test(path), - key; - - if (!target || isGlobal) target = Ember.lookup; - if (hasThis) path = path.slice(5); - - if (target === Ember.lookup) { - key = firstKey(path); - target = get(target, key); - path = path.slice(key.length+1); - } - - // must return some kind of path to be valid else other things will break. - if (!path || path.length===0) throw new Error('Invalid Path'); - - return [ target, path ]; -} - -var getPath = Ember._getPath = function(root, path) { - var hasThis, parts, tuple, idx, len; - - // If there is no root and path is a key name, return that - // property from the global object. - // E.g. get('Ember') -> Ember - if (root === null && path.indexOf('.') === -1) { return get(Ember.lookup, path); } - - // detect complicated paths and normalize them - hasThis = HAS_THIS.test(path); - - if (!root || hasThis) { - tuple = normalizeTuple(root, path); - root = tuple[0]; - path = tuple[1]; - tuple.length = 0; - } - - parts = path.split("."); - len = parts.length; - for (idx=0; root !== undefined && root !== null && idx<len; idx++) { - root = get(root, parts[idx], true); - if (root && root.isDestroyed) { return undefined; } - } - return root; -}; - /** - @private - - Normalizes a target/path pair to reflect that actual target/path that should - be observed, etc. This takes into account passing in global property - paths (i.e. a path beginning with a captial letter not defined on the - target) and * separators. - - @method normalizeTuple - @for Ember - @param {Object} target The current target. May be `null`. - @param {String} path A path on the target or a global property path. - @return {Array} a temporary array with the normalized target/path pair. -*/ -Ember.normalizeTuple = function(target, path) { - return normalizeTuple(target, path); -}; - -Ember.getWithDefault = function(root, key, defaultValue) { - var value = get(root, key); - - if (value === undefined) { return defaultValue; } - return value; -}; - - -Ember.get = get; -Ember.getPath = Ember.deprecateFunc('getPath is deprecated since get now supports paths', Ember.get); -})(); - - - -(function() { -/** -@module ember-metal -*/ - -var o_create = Ember.create, - metaFor = Ember.meta, - META_KEY = Ember.META_KEY, - /* listener flags */ - ONCE = 1, SUSPENDED = 2; - -/* - The event system uses a series of nested hashes to store listeners on an - object. When a listener is registered, or when an event arrives, these - hashes are consulted to determine which target and action pair to invoke. - - The hashes are stored in the object's meta hash, and look like this: - - // Object's meta hash - { - listeners: { // variable name: `listenerSet` - "foo:changed": [ // variable name: `actions` - [target, method, flags] - ] - } - } - -*/ - -function indexOf(array, target, method) { - var index = -1; - for (var i = 0, l = array.length; i < l; i++) { - if (target === array[i][0] && method === array[i][1]) { index = i; break; } - } - return index; -} - -function actionsFor(obj, eventName) { - var meta = metaFor(obj, true), - actions; - - if (!meta.listeners) { meta.listeners = {}; } - - if (!meta.hasOwnProperty('listeners')) { - // setup inherited copy of the listeners object - meta.listeners = o_create(meta.listeners); - } - - actions = meta.listeners[eventName]; - - // if there are actions, but the eventName doesn't exist in our listeners, then copy them from the prototype - if (actions && !meta.listeners.hasOwnProperty(eventName)) { - actions = meta.listeners[eventName] = meta.listeners[eventName].slice(); - } else if (!actions) { - actions = meta.listeners[eventName] = []; - } - - return actions; -} - -function actionsUnion(obj, eventName, otherActions) { - var meta = obj[META_KEY], - actions = meta && meta.listeners && meta.listeners[eventName]; - - if (!actions) { return; } - for (var i = actions.length - 1; i >= 0; i--) { - var target = actions[i][0], - method = actions[i][1], - flags = actions[i][2], - actionIndex = indexOf(otherActions, target, method); - - if (actionIndex === -1) { - otherActions.push([target, method, flags]); - } - } -} - -function actionsDiff(obj, eventName, otherActions) { - var meta = obj[META_KEY], - actions = meta && meta.listeners && meta.listeners[eventName], - diffActions = []; - - if (!actions) { return; } - for (var i = actions.length - 1; i >= 0; i--) { - var target = actions[i][0], - method = actions[i][1], - flags = actions[i][2], - actionIndex = indexOf(otherActions, target, method); - - if (actionIndex !== -1) { continue; } - - otherActions.push([target, method, flags]); - diffActions.push([target, method, flags]); - } - - return diffActions; -} - -/** - Add an event listener - - @method addListener - @for Ember - @param obj - @param {String} eventName - @param {Object|Function} targetOrMethod A target object or a function - @param {Function|String} method A function or the name of a function to be called on `target` - @param {Boolean} once A flag whether a function should only be called once -*/ -function addListener(obj, eventName, target, method, once) { - Ember.assert("You must pass at least an object and event name to Ember.addListener", !!obj && !!eventName); - - if (!method && 'function' === typeof target) { - method = target; - target = null; - } - - var actions = actionsFor(obj, eventName), - actionIndex = indexOf(actions, target, method), - flags = 0; - - if (once) flags |= ONCE; - - if (actionIndex !== -1) { return; } - - actions.push([target, method, flags]); - - if ('function' === typeof obj.didAddListener) { - obj.didAddListener(eventName, target, method); - } -} - -/** - Remove an event listener - - Arguments should match those passed to {{#crossLink "Ember/addListener"}}{{/crossLink}} - - @method removeListener - @for Ember - @param obj - @param {String} eventName - @param {Object|Function} targetOrMethod A target object or a function - @param {Function|String} method A function or the name of a function to be called on `target` -*/ -function removeListener(obj, eventName, target, method) { - Ember.assert("You must pass at least an object and event name to Ember.removeListener", !!obj && !!eventName); - - if (!method && 'function' === typeof target) { - method = target; - target = null; - } - - function _removeListener(target, method) { - var actions = actionsFor(obj, eventName), - actionIndex = indexOf(actions, target, method); - - // action doesn't exist, give up silently - if (actionIndex === -1) { return; } - - actions.splice(actionIndex, 1); - - if ('function' === typeof obj.didRemoveListener) { - obj.didRemoveListener(eventName, target, method); - } - } - - if (method) { - _removeListener(target, method); - } else { - var meta = obj[META_KEY], - actions = meta && meta.listeners && meta.listeners[eventName]; - - if (!actions) { return; } - for (var i = actions.length - 1; i >= 0; i--) { - _removeListener(actions[i][0], actions[i][1]); - } - } -} - -/** - @private - - Suspend listener during callback. - - This should only be used by the target of the event listener - when it is taking an action that would cause the event, e.g. - an object might suspend its property change listener while it is - setting that property. - - @method suspendListener - @for Ember - @param obj - @param {String} eventName - @param {Object|Function} targetOrMethod A target object or a function - @param {Function|String} method A function or the name of a function to be called on `target` - @param {Function} callback -*/ -function suspendListener(obj, eventName, target, method, callback) { - if (!method && 'function' === typeof target) { - method = target; - target = null; - } - - var actions = actionsFor(obj, eventName), - actionIndex = indexOf(actions, target, method), - action; - - if (actionIndex !== -1) { - action = actions[actionIndex].slice(); // copy it, otherwise we're modifying a shared object - action[2] |= SUSPENDED; // mark the action as suspended - actions[actionIndex] = action; // replace the shared object with our copy - } - - function tryable() { return callback.call(target); } - function finalizer() { if (action) { action[2] &= ~SUSPENDED; } } - - return Ember.tryFinally(tryable, finalizer); -} - -/** - @private - - Suspend listener during callback. - - This should only be used by the target of the event listener - when it is taking an action that would cause the event, e.g. - an object might suspend its property change listener while it is - setting that property. - - @method suspendListener - @for Ember - @param obj - @param {Array} eventName Array of event names - @param {Object|Function} targetOrMethod A target object or a function - @param {Function|String} method A function or the name of a function to be called on `target` - @param {Function} callback -*/ -function suspendListeners(obj, eventNames, target, method, callback) { - if (!method && 'function' === typeof target) { - method = target; - target = null; - } - - var suspendedActions = [], - eventName, actions, action, i, l; - - for (i=0, l=eventNames.length; i<l; i++) { - eventName = eventNames[i]; - actions = actionsFor(obj, eventName); - var actionIndex = indexOf(actions, target, method); - - if (actionIndex !== -1) { - action = actions[actionIndex].slice(); - action[2] |= SUSPENDED; - actions[actionIndex] = action; - suspendedActions.push(action); - } - } - - function tryable() { return callback.call(target); } - - function finalizer() { - for (i = 0, l = suspendedActions.length; i < l; i++) { - suspendedActions[i][2] &= ~SUSPENDED; - } - } - - return Ember.tryFinally(tryable, finalizer); -} - -/** - @private - - Return a list of currently watched events - - @method watchedEvents - @for Ember - @param obj -*/ -function watchedEvents(obj) { - var listeners = obj[META_KEY].listeners, ret = []; - - if (listeners) { - for(var eventName in listeners) { - if (listeners[eventName]) { ret.push(eventName); } - } - } - return ret; -} - -/** - @method sendEvent - @for Ember - @param obj - @param {String} eventName - @param {Array} params - @param {Array} actions - @return true -*/ -function sendEvent(obj, eventName, params, actions) { - // first give object a chance to handle it - if (obj !== Ember && 'function' === typeof obj.sendEvent) { - obj.sendEvent(eventName, params); - } - - if (!actions) { - var meta = obj[META_KEY]; - actions = meta && meta.listeners && meta.listeners[eventName]; - } - - if (!actions) { return; } - - for (var i = actions.length - 1; i >= 0; i--) { // looping in reverse for once listeners - var action = actions[i]; - if (!action) { continue; } - var target = action[0], method = action[1], flags = action[2]; - if (flags & SUSPENDED) { continue; } - if (flags & ONCE) { removeListener(obj, eventName, target, method); } - if (!target) { target = obj; } - if ('string' === typeof method) { method = target[method]; } - if (params) { - method.apply(target, params); - } else { - method.call(target); - } - } - return true; -} - -/** - @private - @method hasListeners - @for Ember - @param obj - @param {String} eventName -*/ -function hasListeners(obj, eventName) { - var meta = obj[META_KEY], - actions = meta && meta.listeners && meta.listeners[eventName]; - - return !!(actions && actions.length); -} - -/** - @private - @method listenersFor - @for Ember - @param obj - @param {String} eventName -*/ -function listenersFor(obj, eventName) { - var ret = []; - var meta = obj[META_KEY], - actions = meta && meta.listeners && meta.listeners[eventName]; - - if (!actions) { return ret; } - - for (var i = 0, l = actions.length; i < l; i++) { - var target = actions[i][0], - method = actions[i][1]; - ret.push([target, method]); - } - - return ret; -} - -Ember.addListener = addListener; -Ember.removeListener = removeListener; -Ember._suspendListener = suspendListener; -Ember._suspendListeners = suspendListeners; -Ember.sendEvent = sendEvent; -Ember.hasListeners = hasListeners; -Ember.watchedEvents = watchedEvents; -Ember.listenersFor = listenersFor; -Ember.listenersDiff = actionsDiff; -Ember.listenersUnion = actionsUnion; - -})(); - - - -(function() { -var guidFor = Ember.guidFor, - sendEvent = Ember.sendEvent; - -/* - this.observerSet = { - [senderGuid]: { // variable name: `keySet` - [keyName]: listIndex - } - }, - this.observers = [ - { - sender: obj, - keyName: keyName, - eventName: eventName, - listeners: [ - [target, method, flags] - ] - }, - ... - ] -*/ -var ObserverSet = Ember._ObserverSet = function() { - this.clear(); -}; - -ObserverSet.prototype.add = function(sender, keyName, eventName) { - var observerSet = this.observerSet, - observers = this.observers, - senderGuid = guidFor(sender), - keySet = observerSet[senderGuid], - index; - - if (!keySet) { - observerSet[senderGuid] = keySet = {}; - } - index = keySet[keyName]; - if (index === undefined) { - index = observers.push({ - sender: sender, - keyName: keyName, - eventName: eventName, - listeners: [] - }) - 1; - keySet[keyName] = index; - } - return observers[index].listeners; -}; - -ObserverSet.prototype.flush = function() { - var observers = this.observers, i, len, observer, sender; - this.clear(); - for (i=0, len=observers.length; i < len; ++i) { - observer = observers[i]; - sender = observer.sender; - if (sender.isDestroying || sender.isDestroyed) { continue; } - sendEvent(sender, observer.eventName, [sender, observer.keyName], observer.listeners); - } -}; - -ObserverSet.prototype.clear = function() { - this.observerSet = {}; - this.observers = []; -}; -})(); - - - -(function() { -var metaFor = Ember.meta, - guidFor = Ember.guidFor, - tryFinally = Ember.tryFinally, - sendEvent = Ember.sendEvent, - listenersUnion = Ember.listenersUnion, - listenersDiff = Ember.listenersDiff, - ObserverSet = Ember._ObserverSet, - beforeObserverSet = new ObserverSet(), - observerSet = new ObserverSet(), - deferred = 0; - -// .......................................................... -// PROPERTY CHANGES -// - -/** - This function is called just before an object property is about to change. - It will notify any before observers and prepare caches among other things. - - Normally you will not need to call this method directly but if for some - reason you can't directly watch a property you can invoke this method - manually along with `Ember.propertyDidChange()` which you should call just - after the property value changes. - - @method propertyWillChange - @for Ember - @param {Object} obj The object with the property that will change - @param {String} keyName The property key (or path) that will change. - @return {void} -*/ -var propertyWillChange = Ember.propertyWillChange = function(obj, keyName) { - var m = metaFor(obj, false), - watching = m.watching[keyName] > 0 || keyName === 'length', - proto = m.proto, - desc = m.descs[keyName]; - - if (!watching) { return; } - if (proto === obj) { return; } - if (desc && desc.willChange) { desc.willChange(obj, keyName); } - dependentKeysWillChange(obj, keyName, m); - chainsWillChange(obj, keyName, m); - notifyBeforeObservers(obj, keyName); -}; - -/** - This function is called just after an object property has changed. - It will notify any observers and clear caches among other things. - - Normally you will not need to call this method directly but if for some - reason you can't directly watch a property you can invoke this method - manually along with `Ember.propertyWilLChange()` which you should call just - before the property value changes. - - @method propertyDidChange - @for Ember - @param {Object} obj The object with the property that will change - @param {String} keyName The property key (or path) that will change. - @return {void} -*/ -var propertyDidChange = Ember.propertyDidChange = function(obj, keyName) { - var m = metaFor(obj, false), - watching = m.watching[keyName] > 0 || keyName === 'length', - proto = m.proto, - desc = m.descs[keyName]; - - if (proto === obj) { return; } - - // shouldn't this mean that we're watching this key? - if (desc && desc.didChange) { desc.didChange(obj, keyName); } - if (!watching && keyName !== 'length') { return; } - - dependentKeysDidChange(obj, keyName, m); - chainsDidChange(obj, keyName, m); - notifyObservers(obj, keyName); -}; - -var WILL_SEEN, DID_SEEN; - -// called whenever a property is about to change to clear the cache of any dependent keys (and notify those properties of changes, etc...) -function dependentKeysWillChange(obj, depKey, meta) { - if (obj.isDestroying) { return; } - - var seen = WILL_SEEN, top = !seen; - if (top) { seen = WILL_SEEN = {}; } - iterDeps(propertyWillChange, obj, depKey, seen, meta); - if (top) { WILL_SEEN = null; } -} - -// called whenever a property has just changed to update dependent keys -function dependentKeysDidChange(obj, depKey, meta) { - if (obj.isDestroying) { return; } - - var seen = DID_SEEN, top = !seen; - if (top) { seen = DID_SEEN = {}; } - iterDeps(propertyDidChange, obj, depKey, seen, meta); - if (top) { DID_SEEN = null; } -} - -function iterDeps(method, obj, depKey, seen, meta) { - var guid = guidFor(obj); - if (!seen[guid]) seen[guid] = {}; - if (seen[guid][depKey]) return; - seen[guid][depKey] = true; - - var deps = meta.deps; - deps = deps && deps[depKey]; - if (deps) { - for(var key in deps) { - var desc = meta.descs[key]; - if (desc && desc._suspended === obj) continue; - method(obj, key); - } - } -} - -var chainsWillChange = function(obj, keyName, m, arg) { - if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do - - var nodes = m.chainWatchers; - - nodes = nodes[keyName]; - if (!nodes) { return; } - - for(var i = 0, l = nodes.length; i < l; i++) { - nodes[i].willChange(arg); - } -}; - -var chainsDidChange = function(obj, keyName, m, arg) { - if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do - - var nodes = m.chainWatchers; - - nodes = nodes[keyName]; - if (!nodes) { return; } - - // looping in reverse because the chainWatchers array can be modified inside didChange - for (var i = nodes.length - 1; i >= 0; i--) { - nodes[i].didChange(arg); - } -}; - -Ember.overrideChains = function(obj, keyName, m) { - chainsDidChange(obj, keyName, m, true); -}; - -/** - @method beginPropertyChanges - @chainable -*/ -var beginPropertyChanges = Ember.beginPropertyChanges = function() { - deferred++; -}; - -/** - @method endPropertyChanges -*/ -var endPropertyChanges = Ember.endPropertyChanges = function() { - deferred--; - if (deferred<=0) { - beforeObserverSet.clear(); - observerSet.flush(); - } -}; - -/** - Make a series of property changes together in an - exception-safe way. - - ```javascript - Ember.changeProperties(function() { - obj1.set('foo', mayBlowUpWhenSet); - obj2.set('bar', baz); - }); - ``` - - @method changeProperties - @param {Function} callback - @param [binding] -*/ -Ember.changeProperties = function(cb, binding){ - beginPropertyChanges(); - tryFinally(cb, endPropertyChanges, binding); -}; - -var notifyBeforeObservers = function(obj, keyName) { - if (obj.isDestroying) { return; } - - var eventName = keyName + ':before', listeners, diff; - if (deferred) { - listeners = beforeObserverSet.add(obj, keyName, eventName); - diff = listenersDiff(obj, eventName, listeners); - sendEvent(obj, eventName, [obj, keyName], diff); - } else { - sendEvent(obj, eventName, [obj, keyName]); - } -}; - -var notifyObservers = function(obj, keyName) { - if (obj.isDestroying) { return; } - - var eventName = keyName + ':change', listeners; - if (deferred) { - listeners = observerSet.add(obj, keyName, eventName); - listenersUnion(obj, eventName, listeners); - } else { - sendEvent(obj, eventName, [obj, keyName]); - } -}; -})(); - - - -(function() { -// META_KEY -// _getPath -// propertyWillChange, propertyDidChange - -var META_KEY = Ember.META_KEY, - MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER, - IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/, - getPath = Ember._getPath; - -/** Sets the value of a property on an object, respecting computed properties and notifying observers and other listeners of the change. If the property is not defined but the object implements the `unknownProperty` method then that will be invoked as well. If you plan to run on IE8 and older browsers then you should use this method anytime you want to set a property on an object that you don't - know for sure is private. (Properties beginning with an underscore '_' + know for sure is private. (Properties beginning with an underscore '_' are considered private.) On all newer browsers, you only need to use this method to set properties if the property might not be defined on the object and you want to respect the `unknownProperty` handler. Otherwise you can ignore this @@ -2809,11 +1901,11 @@ @param {Object} obj The object to modify. @param {String} keyName The property key to set @param {Object} value The value to set @return {Object} the passed value. */ -var set = function set(obj, keyName, value, tolerant) { +set = function set(obj, keyName, value, tolerant) { if (typeof obj === 'string') { Ember.assert("Path '" + obj + "' must be global if no obj is given.", IS_GLOBAL.test(obj)); value = keyName; keyName = obj; obj = null; @@ -2865,15 +1957,69 @@ return value; }; // Currently used only by Ember Data tests if (Ember.config.overrideAccessors) { + Ember.get = get; Ember.set = set; Ember.config.overrideAccessors(); + get = Ember.get; set = Ember.set; } +function firstKey(path) { + return path.match(FIRST_KEY)[0]; +} + +// assumes path is already normalized +function normalizeTuple(target, path) { + var hasThis = HAS_THIS.test(path), + isGlobal = !hasThis && IS_GLOBAL_PATH.test(path), + key; + + if (!target || isGlobal) target = Ember.lookup; + if (hasThis) path = path.slice(5); + + if (target === Ember.lookup) { + key = firstKey(path); + target = get(target, key); + path = path.slice(key.length+1); + } + + // must return some kind of path to be valid else other things will break. + if (!path || path.length===0) throw new Error('Invalid Path'); + + return [ target, path ]; +} + +function getPath(root, path) { + var hasThis, parts, tuple, idx, len; + + // If there is no root and path is a key name, return that + // property from the global object. + // E.g. get('Ember') -> Ember + if (root === null && path.indexOf('.') === -1) { return get(Ember.lookup, path); } + + // detect complicated paths and normalize them + hasThis = HAS_THIS.test(path); + + if (!root || hasThis) { + tuple = normalizeTuple(root, path); + root = tuple[0]; + path = tuple[1]; + tuple.length = 0; + } + + parts = path.split("."); + len = parts.length; + for (idx=0; root && idx<len; idx++) { + root = get(root, parts[idx], true); + if (root && root.isDestroyed) { return undefined; } + } + return root; +} + function setPath(root, path, value, tolerant) { var keyName; // get the last part of the path keyName = path.slice(path.lastIndexOf('.') + 1); @@ -2897,10 +2043,39 @@ } return set(root, keyName, value); } +/** + @private + + Normalizes a target/path pair to reflect that actual target/path that should + be observed, etc. This takes into account passing in global property + paths (i.e. a path beginning with a captial letter not defined on the + target) and * separators. + + @method normalizeTuple + @for Ember + @param {Object} target The current target. May be `null`. + @param {String} path A path on the target or a global property path. + @return {Array} a temporary array with the normalized target/path pair. +*/ +Ember.normalizeTuple = function(target, path) { + return normalizeTuple(target, path); +}; + +Ember.getWithDefault = function(root, key, defaultValue) { + var value = get(root, key); + + if (value === undefined) { return defaultValue; } + return value; +}; + + +Ember.get = get; +Ember.getPath = Ember.deprecateFunc('getPath is deprecated since get now supports paths', Ember.get); + Ember.set = set; Ember.setPath = Ember.deprecateFunc('setPath is deprecated since set now supports paths', Ember.set); /** Error-tolerant form of `Ember.set`. Will not blow up if any part of the @@ -2910,49 +2085,67 @@ an object has been destroyed. @method trySet @for Ember @param {Object} obj The object to modify. - @param {String} path The property path to set + @param {String} keyName The property key to set @param {Object} value The value to set */ Ember.trySet = function(root, path, value) { return set(root, path, value, true); }; Ember.trySetPath = Ember.deprecateFunc('trySetPath has been renamed to trySet', Ember.trySet); +/** + Returns true if the provided path is global (e.g., `MyApp.fooController.bar`) + instead of local (`foo.bar.baz`). + + @method isGlobalPath + @for Ember + @private + @param {String} path + @return Boolean +*/ +Ember.isGlobalPath = function(path) { + return IS_GLOBAL.test(path); +}; + + })(); (function() { /** @module ember-metal */ -var META_KEY = Ember.META_KEY, +var GUID_KEY = Ember.GUID_KEY, + META_KEY = Ember.META_KEY, + EMPTY_META = Ember.EMPTY_META, metaFor = Ember.meta, + o_create = Ember.create, objectDefineProperty = Ember.platform.defineProperty; var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER; // .......................................................... // DESCRIPTOR // /** - Objects of this type can implement an interface to respond to requests to + Objects of this type can implement an interface to responds requests to get and set. The default implementation handles simple properties. You generally won't need to create or subclass this directly. @class Descriptor @namespace Ember @private @constructor */ -Ember.Descriptor = function() {}; +var Descriptor = Ember.Descriptor = function() {}; // .......................................................... // DEFINING PROPERTIES API // @@ -3008,11 +2201,11 @@ @param {Object} obj the object to define this property on. This may be a prototype. @param {String} keyName the name of the property @param {Ember.Descriptor} [desc] an instance of `Ember.Descriptor` (typically a computed property) or an ES5 descriptor. You must provide this or `data` but not both. - @param {*} [data] something other than a descriptor, that will + @param {anything} [data] something other than a descriptor, that will become the explicit value of this property. */ Ember.defineProperty = function(obj, keyName, desc, data, meta) { var descs, existingDesc, watching, value; @@ -3079,135 +2272,345 @@ })(); (function() { -var changeProperties = Ember.changeProperties, - set = Ember.set; +// Ember.tryFinally +/** +@module ember-metal +*/ +var AFTER_OBSERVERS = ':change'; +var BEFORE_OBSERVERS = ':before'; + +var guidFor = Ember.guidFor; + +var deferred = 0; + +/* + this.observerSet = { + [senderGuid]: { // variable name: `keySet` + [keyName]: listIndex + } + }, + this.observers = [ + { + sender: obj, + keyName: keyName, + eventName: eventName, + listeners: [ + [target, method, onceFlag, suspendedFlag] + ] + }, + ... + ] +*/ +function ObserverSet() { + this.clear(); +} + +ObserverSet.prototype.add = function(sender, keyName, eventName) { + var observerSet = this.observerSet, + observers = this.observers, + senderGuid = Ember.guidFor(sender), + keySet = observerSet[senderGuid], + index; + + if (!keySet) { + observerSet[senderGuid] = keySet = {}; + } + index = keySet[keyName]; + if (index === undefined) { + index = observers.push({ + sender: sender, + keyName: keyName, + eventName: eventName, + listeners: [] + }) - 1; + keySet[keyName] = index; + } + return observers[index].listeners; +}; + +ObserverSet.prototype.flush = function() { + var observers = this.observers, i, len, observer, sender; + this.clear(); + for (i=0, len=observers.length; i < len; ++i) { + observer = observers[i]; + sender = observer.sender; + if (sender.isDestroying || sender.isDestroyed) { continue; } + Ember.sendEvent(sender, observer.eventName, [sender, observer.keyName], observer.listeners); + } +}; + +ObserverSet.prototype.clear = function() { + this.observerSet = {}; + this.observers = []; +}; + +var beforeObserverSet = new ObserverSet(), observerSet = new ObserverSet(); + /** + @method beginPropertyChanges + @chainable +*/ +Ember.beginPropertyChanges = function() { + deferred++; +}; + +/** + @method endPropertyChanges +*/ +Ember.endPropertyChanges = function() { + deferred--; + if (deferred<=0) { + beforeObserverSet.clear(); + observerSet.flush(); + } +}; + +/** + Make a series of property changes together in an + exception-safe way. + + ```javascript + Ember.changeProperties(function() { + obj1.set('foo', mayBlowUpWhenSet); + obj2.set('bar', baz); + }); + ``` + + @method changeProperties + @param {Function} callback + @param [binding] +*/ +Ember.changeProperties = function(cb, binding){ + Ember.beginPropertyChanges(); + Ember.tryFinally(cb, Ember.endPropertyChanges, binding); +}; + +/** Set a list of properties on an object. These properties are set inside a single `beginPropertyChanges` and `endPropertyChanges` batch, so observers will be buffered. @method setProperties @param target @param {Hash} properties @return target */ Ember.setProperties = function(self, hash) { - changeProperties(function(){ + Ember.changeProperties(function(){ for(var prop in hash) { - if (hash.hasOwnProperty(prop)) { set(self, prop, hash[prop]); } + if (hash.hasOwnProperty(prop)) Ember.set(self, prop, hash[prop]); } }); return self; }; -})(); +function changeEvent(keyName) { + return keyName+AFTER_OBSERVERS; +} -(function() { -var metaFor = Ember.meta, // utils.js - typeOf = Ember.typeOf, // utils.js - MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER, - o_defineProperty = Ember.platform.defineProperty; +function beforeEvent(keyName) { + return keyName+BEFORE_OBSERVERS; +} -Ember.watchKey = function(obj, keyName) { - // can't watch length on Array - it is special... - if (keyName === 'length' && typeOf(obj) === 'array') { return; } +/** + @method addObserver + @param obj + @param {String} path + @param {Object|Function} targetOrMethod + @param {Function|String} [method] +*/ +Ember.addObserver = function(obj, path, target, method) { + Ember.addListener(obj, changeEvent(path), target, method); + Ember.watch(obj, path); + return this; +}; - var m = metaFor(obj), watching = m.watching, desc; +Ember.observersFor = function(obj, path) { + return Ember.listenersFor(obj, changeEvent(path)); +}; - // activate watching first time - if (!watching[keyName]) { - watching[keyName] = 1; - desc = m.descs[keyName]; - if (desc && desc.willWatch) { desc.willWatch(obj, keyName); } +/** + @method removeObserver + @param obj + @param {String} path + @param {Object|Function} targetOrMethod + @param {Function|String} [method] +*/ +Ember.removeObserver = function(obj, path, target, method) { + Ember.unwatch(obj, path); + Ember.removeListener(obj, changeEvent(path), target, method); + return this; +}; - if ('function' === typeof obj.willWatchProperty) { - obj.willWatchProperty(keyName); - } +/** + @method addBeforeObserver + @param obj + @param {String} path + @param {Object|Function} targetOrMethod + @param {Function|String} [method] +*/ +Ember.addBeforeObserver = function(obj, path, target, method) { + Ember.addListener(obj, beforeEvent(path), target, method); + Ember.watch(obj, path); + return this; +}; - if (MANDATORY_SETTER && keyName in obj) { - m.values[keyName] = obj[keyName]; - o_defineProperty(obj, keyName, { - configurable: true, - enumerable: true, - set: Ember.MANDATORY_SETTER_FUNCTION, - get: Ember.DEFAULT_GETTER_FUNCTION(keyName) - }); - } - } else { - watching[keyName] = (watching[keyName] || 0) + 1; - } +// Suspend observer during callback. +// +// This should only be used by the target of the observer +// while it is setting the observed path. +Ember._suspendBeforeObserver = function(obj, path, target, method, callback) { + return Ember._suspendListener(obj, beforeEvent(path), target, method, callback); }; +Ember._suspendObserver = function(obj, path, target, method, callback) { + return Ember._suspendListener(obj, changeEvent(path), target, method, callback); +}; -Ember.unwatchKey = function(obj, keyName) { - var m = metaFor(obj), watching = m.watching, desc; +var map = Ember.ArrayPolyfills.map; - if (watching[keyName] === 1) { - watching[keyName] = 0; - desc = m.descs[keyName]; +Ember._suspendBeforeObservers = function(obj, paths, target, method, callback) { + var events = map.call(paths, beforeEvent); + return Ember._suspendListeners(obj, events, target, method, callback); +}; - if (desc && desc.didUnwatch) { desc.didUnwatch(obj, keyName); } +Ember._suspendObservers = function(obj, paths, target, method, callback) { + var events = map.call(paths, changeEvent); + return Ember._suspendListeners(obj, events, target, method, callback); +}; - if ('function' === typeof obj.didUnwatchProperty) { - obj.didUnwatchProperty(keyName); - } +Ember.beforeObserversFor = function(obj, path) { + return Ember.listenersFor(obj, beforeEvent(path)); +}; - if (MANDATORY_SETTER && keyName in obj) { - o_defineProperty(obj, keyName, { - configurable: true, - enumerable: true, - writable: true, - value: m.values[keyName] - }); - delete m.values[keyName]; - } - } else if (watching[keyName] > 1) { - watching[keyName]--; +/** + @method removeBeforeObserver + @param obj + @param {String} path + @param {Object|Function} targetOrMethod + @param {Function|String} [method] +*/ +Ember.removeBeforeObserver = function(obj, path, target, method) { + Ember.unwatch(obj, path); + Ember.removeListener(obj, beforeEvent(path), target, method); + return this; +}; + +Ember.notifyBeforeObservers = function(obj, keyName) { + if (obj.isDestroying) { return; } + + var eventName = beforeEvent(keyName), listeners, listenersDiff; + if (deferred) { + listeners = beforeObserverSet.add(obj, keyName, eventName); + listenersDiff = Ember.listenersDiff(obj, eventName, listeners); + Ember.sendEvent(obj, eventName, [obj, keyName], listenersDiff); + } else { + Ember.sendEvent(obj, eventName, [obj, keyName]); } }; + +Ember.notifyObservers = function(obj, keyName) { + if (obj.isDestroying) { return; } + + var eventName = changeEvent(keyName), listeners; + if (deferred) { + listeners = observerSet.add(obj, keyName, eventName); + Ember.listenersUnion(obj, eventName, listeners); + } else { + Ember.sendEvent(obj, eventName, [obj, keyName]); + } +}; + })(); (function() { -var metaFor = Ember.meta, // utils.js - get = Ember.get, // property_get.js - normalizeTuple = Ember.normalizeTuple, // property_get.js +/** +@module ember-metal +*/ + +var guidFor = Ember.guidFor, // utils.js + metaFor = Ember.meta, // utils.js + get = Ember.get, // accessors.js + set = Ember.set, // accessors.js + normalizeTuple = Ember.normalizeTuple, // accessors.js + GUID_KEY = Ember.GUID_KEY, // utils.js + META_KEY = Ember.META_KEY, // utils.js + // circular reference observer depends on Ember.watch + // we should move change events to this file or its own property_events.js + notifyObservers = Ember.notifyObservers, // observer.js forEach = Ember.ArrayPolyfills.forEach, // array.js - warn = Ember.warn, - watchKey = Ember.watchKey, - unwatchKey = Ember.unwatchKey, - propertyWillChange = Ember.propertyWillChange, - propertyDidChange = Ember.propertyDidChange, - FIRST_KEY = /^([^\.\*]+)/; + FIRST_KEY = /^([^\.\*]+)/, + IS_PATH = /[\.\*]/; +var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER, +o_defineProperty = Ember.platform.defineProperty; + function firstKey(path) { return path.match(FIRST_KEY)[0]; } -var pendingQueue = []; +// returns true if the passed path is just a keyName +function isKeyName(path) { + return path==='*' || !IS_PATH.test(path); +} -// attempts to add the pendingQueue chains again. If some of them end up -// back in the queue and reschedule is true, schedules a timeout to try -// again. -Ember.flushPendingChains = function() { - if (pendingQueue.length === 0) { return; } // nothing to do +// .......................................................... +// DEPENDENT KEYS +// - var queue = pendingQueue; - pendingQueue = []; +function iterDeps(method, obj, depKey, seen, meta) { - forEach.call(queue, function(q) { q[0].add(q[1]); }); + var guid = guidFor(obj); + if (!seen[guid]) seen[guid] = {}; + if (seen[guid][depKey]) return; + seen[guid][depKey] = true; - warn('Watching an undefined global, Ember expects watched globals to be setup by the time the run loop is flushed, check for typos', pendingQueue.length === 0); -}; + var deps = meta.deps; + deps = deps && deps[depKey]; + if (deps) { + for(var key in deps) { + var desc = meta.descs[key]; + if (desc && desc._suspended === obj) continue; + method(obj, key); + } + } +} +var WILL_SEEN, DID_SEEN; + +// called whenever a property is about to change to clear the cache of any dependent keys (and notify those properties of changes, etc...) +function dependentKeysWillChange(obj, depKey, meta) { + if (obj.isDestroying) { return; } + + var seen = WILL_SEEN, top = !seen; + if (top) { seen = WILL_SEEN = {}; } + iterDeps(propertyWillChange, obj, depKey, seen, meta); + if (top) { WILL_SEEN = null; } +} + +// called whenever a property has just changed to update dependent keys +function dependentKeysDidChange(obj, depKey, meta) { + if (obj.isDestroying) { return; } + + var seen = DID_SEEN, top = !seen; + if (top) { seen = DID_SEEN = {}; } + iterDeps(propertyDidChange, obj, depKey, seen, meta); + if (top) { DID_SEEN = null; } +} + +// .......................................................... +// CHAIN +// + function addChainWatcher(obj, keyName, node) { if (!obj || ('object' !== typeof obj)) { return; } // nothing to do var m = metaFor(obj), nodes = m.chainWatchers; @@ -3215,14 +2618,14 @@ nodes = m.chainWatchers = {}; } if (!nodes[keyName]) { nodes[keyName] = []; } nodes[keyName].push(node); - watchKey(obj, keyName); + Ember.watch(obj, keyName); } -var removeChainWatcher = Ember.removeChainWatcher = function(obj, keyName, node) { +function removeChainWatcher(obj, keyName, node) { if (!obj || 'object' !== typeof obj) { return; } // nothing to do var m = metaFor(obj, false); if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do @@ -3232,21 +2635,38 @@ nodes = nodes[keyName]; for (var i = 0, l = nodes.length; i < l; i++) { if (nodes[i] === node) { nodes.splice(i, 1); } } } - unwatchKey(obj, keyName); -}; + Ember.unwatch(obj, keyName); +} +var pendingQueue = []; + +// attempts to add the pendingQueue chains again. If some of them end up +// back in the queue and reschedule is true, schedules a timeout to try +// again. +function flushPendingChains() { + if (pendingQueue.length === 0) { return; } // nothing to do + + var queue = pendingQueue; + pendingQueue = []; + + forEach.call(queue, function(q) { q[0].add(q[1]); }); + + Ember.warn('Watching an undefined global, Ember expects watched globals to be setup by the time the run loop is flushed, check for typos', pendingQueue.length === 0); +} + function isProto(pvalue) { return metaFor(pvalue, false).proto === pvalue; } // A ChainNode watches a single key on an object. If you provide a starting // value for the key then the node won't actually watch it. For a root node // pass null for parent and key and object for value. -var ChainNode = Ember._ChainNode = function(parent, key, value) { +var ChainNode = function(parent, key, value) { + var obj; this._parent = parent; this._key = key; // _watching is true when calling get(this._parent, this._key) will // return the value of this node. @@ -3414,24 +2834,24 @@ if (this._key) { path = this._key + '.' + path; } if (this._parent) { this._parent.chainWillChange(this, path, depth+1); } else { - if (depth > 1) { propertyWillChange(this.value(), path); } + if (depth > 1) { Ember.propertyWillChange(this.value(), path); } path = 'this.' + path; - if (this._paths[path] > 0) { propertyWillChange(this.value(), path); } + if (this._paths[path] > 0) { Ember.propertyWillChange(this.value(), path); } } }; ChainNodePrototype.chainDidChange = function(chain, path, depth) { if (this._key) { path = this._key + '.' + path; } if (this._parent) { this._parent.chainDidChange(this, path, depth+1); } else { - if (depth > 1) { propertyDidChange(this.value(), path); } + if (depth > 1) { Ember.propertyDidChange(this.value(), path); } path = 'this.' + path; - if (this._paths[path] > 0) { propertyDidChange(this.value(), path); } + if (this._paths[path] > 0) { Ember.propertyDidChange(this.value(), path); } } }; ChainNodePrototype.didChange = function(suppressEvent) { // invalidate my own value first. @@ -3463,28 +2883,10 @@ // and finally tell parent about my path changing... if (this._parent) { this._parent.chainDidChange(this, this._key, 1); } }; -Ember.finishChains = function(obj) { - var m = metaFor(obj, false), chains = m.chains; - if (chains) { - if (chains.value() !== obj) { - m.chains = chains = chains.copy(obj); - } - chains.didChange(true); - } -}; -})(); - - - -(function() { -var metaFor = Ember.meta, // utils.js - typeOf = Ember.typeOf, // utils.js - ChainNode = Ember._ChainNode; // chains.js - // get the chains for the current object. If the current object has // chains inherited from the proto they will be cloned and reconfigured for // the current object. function chainsFor(obj) { var m = metaFor(obj), ret = m.chains; @@ -3494,60 +2896,45 @@ ret = m.chains = ret.copy(obj); } return ret; } -Ember.watchPath = function(obj, keyPath) { - // can't watch length on Array - it is special... - if (keyPath === 'length' && typeOf(obj) === 'array') { return; } +Ember.overrideChains = function(obj, keyName, m) { + chainsDidChange(obj, keyName, m, true); +}; - var m = metaFor(obj), watching = m.watching; +function chainsWillChange(obj, keyName, m, arg) { + if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do - if (!watching[keyPath]) { // activate watching first time - watching[keyPath] = 1; - chainsFor(obj).add(keyPath); - } else { - watching[keyPath] = (watching[keyPath] || 0) + 1; - } -}; + var nodes = m.chainWatchers; -Ember.unwatchPath = function(obj, keyPath) { - var m = metaFor(obj), watching = m.watching; + nodes = nodes[keyName]; + if (!nodes) { return; } - if (watching[keyPath] === 1) { - watching[keyPath] = 0; - chainsFor(obj).remove(keyPath); - } else if (watching[keyPath] > 1) { - watching[keyPath]--; + for(var i = 0, l = nodes.length; i < l; i++) { + nodes[i].willChange(arg); } -}; -})(); +} +function chainsDidChange(obj, keyName, m, arg) { + if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do + var nodes = m.chainWatchers; -(function() { -/** -@module ember-metal -*/ + nodes = nodes[keyName]; + if (!nodes) { return; } -var metaFor = Ember.meta, // utils.js - GUID_KEY = Ember.GUID_KEY, // utils.js - META_KEY = Ember.META_KEY, // utils.js - removeChainWatcher = Ember.removeChainWatcher, - watchKey = Ember.watchKey, // watch_key.js - unwatchKey = Ember.unwatchKey, - watchPath = Ember.watchPath, // watch_path.js - unwatchPath = Ember.unwatchPath, - typeOf = Ember.typeOf, // utils.js - generateGuid = Ember.generateGuid, - IS_PATH = /[\.\*]/; - -// returns true if the passed path is just a keyName -function isKeyName(path) { - return path==='*' || !IS_PATH.test(path); + // looping in reverse because the chainWatchers array can be modified inside didChange + for (var i = nodes.length - 1; i >= 0; i--) { + nodes[i].didChange(arg); + } } +// .......................................................... +// WATCH +// + /** @private Starts watching a property on an object. Whenever the property changes, invokes `Ember.propertyWillChange` and `Ember.propertyDidChange`. This is the @@ -3558,37 +2945,88 @@ @method watch @for Ember @param obj @param {String} keyName */ -Ember.watch = function(obj, keyPath) { +Ember.watch = function(obj, keyName) { // can't watch length on Array - it is special... - if (keyPath === 'length' && typeOf(obj) === 'array') { return; } + if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; } - if (isKeyName(keyPath)) { - watchKey(obj, keyPath); - } else { - watchPath(obj, keyPath); + var m = metaFor(obj), watching = m.watching, desc; + + // activate watching first time + if (!watching[keyName]) { + watching[keyName] = 1; + if (isKeyName(keyName)) { + desc = m.descs[keyName]; + if (desc && desc.willWatch) { desc.willWatch(obj, keyName); } + + if ('function' === typeof obj.willWatchProperty) { + obj.willWatchProperty(keyName); + } + + if (MANDATORY_SETTER && keyName in obj) { + m.values[keyName] = obj[keyName]; + o_defineProperty(obj, keyName, { + configurable: true, + enumerable: true, + set: Ember.MANDATORY_SETTER_FUNCTION, + get: Ember.DEFAULT_GETTER_FUNCTION(keyName) + }); + } + } else { + chainsFor(obj).add(keyName); + } + + } else { + watching[keyName] = (watching[keyName] || 0) + 1; } + return this; }; Ember.isWatching = function isWatching(obj, key) { var meta = obj[META_KEY]; return (meta && meta.watching[key]) > 0; }; -Ember.watch.flushPending = Ember.flushPendingChains; +Ember.watch.flushPending = flushPendingChains; -Ember.unwatch = function(obj, keyPath) { +Ember.unwatch = function(obj, keyName) { // can't watch length on Array - it is special... - if (keyPath === 'length' && typeOf(obj) === 'array') { return; } + if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; } - if (isKeyName(keyPath)) { - unwatchKey(obj, keyPath); - } else { - unwatchPath(obj, keyPath); + var m = metaFor(obj), watching = m.watching, desc; + + if (watching[keyName] === 1) { + watching[keyName] = 0; + + if (isKeyName(keyName)) { + desc = m.descs[keyName]; + if (desc && desc.didUnwatch) { desc.didUnwatch(obj, keyName); } + + if ('function' === typeof obj.didUnwatchProperty) { + obj.didUnwatchProperty(keyName); + } + + if (MANDATORY_SETTER && keyName in obj) { + o_defineProperty(obj, keyName, { + configurable: true, + enumerable: true, + writable: true, + value: m.values[keyName] + }); + delete m.values[keyName]; + } + } else { + chainsFor(obj).remove(keyName); + } + + } else if (watching[keyName]>1) { + watching[keyName]--; } + + return this; }; /** @private @@ -3603,19 +3041,100 @@ Ember.rewatch = function(obj) { var m = metaFor(obj, false), chains = m.chains; // make sure the object has its own guid. if (GUID_KEY in obj && !obj.hasOwnProperty(GUID_KEY)) { - generateGuid(obj, 'ember'); + Ember.generateGuid(obj, 'ember'); } // make sure any chained watchers update. if (chains && chains.value() !== obj) { m.chains = chains.copy(obj); } + + return this; }; +Ember.finishChains = function(obj) { + var m = metaFor(obj, false), chains = m.chains; + if (chains) { + if (chains.value() !== obj) { + m.chains = chains = chains.copy(obj); + } + chains.didChange(true); + } +}; + +// .......................................................... +// PROPERTY CHANGES +// + +/** + This function is called just before an object property is about to change. + It will notify any before observers and prepare caches among other things. + + Normally you will not need to call this method directly but if for some + reason you can't directly watch a property you can invoke this method + manually along with `Ember.propertyDidChange()` which you should call just + after the property value changes. + + @method propertyWillChange + @for Ember + @param {Object} obj The object with the property that will change + @param {String} keyName The property key (or path) that will change. + @return {void} +*/ +function propertyWillChange(obj, keyName, value) { + var m = metaFor(obj, false), + watching = m.watching[keyName] > 0 || keyName === 'length', + proto = m.proto, + desc = m.descs[keyName]; + + if (!watching) { return; } + if (proto === obj) { return; } + if (desc && desc.willChange) { desc.willChange(obj, keyName); } + dependentKeysWillChange(obj, keyName, m); + chainsWillChange(obj, keyName, m); + Ember.notifyBeforeObservers(obj, keyName); +} + +Ember.propertyWillChange = propertyWillChange; + +/** + This function is called just after an object property has changed. + It will notify any observers and clear caches among other things. + + Normally you will not need to call this method directly but if for some + reason you can't directly watch a property you can invoke this method + manually along with `Ember.propertyWilLChange()` which you should call just + before the property value changes. + + @method propertyDidChange + @for Ember + @param {Object} obj The object with the property that will change + @param {String} keyName The property key (or path) that will change. + @return {void} +*/ +function propertyDidChange(obj, keyName) { + var m = metaFor(obj, false), + watching = m.watching[keyName] > 0 || keyName === 'length', + proto = m.proto, + desc = m.descs[keyName]; + + if (proto === obj) { return; } + + // shouldn't this mean that we're watching this key? + if (desc && desc.didChange) { desc.didChange(obj, keyName); } + if (!watching && keyName !== 'length') { return; } + + dependentKeysDidChange(obj, keyName, m); + chainsDidChange(obj, keyName, m); + Ember.notifyObservers(obj, keyName); +} + +Ember.propertyDidChange = propertyDidChange; + var NODE_STACK = []; /** Tears down the meta on an object so that it can be garbage collected. Multiple calls will have no effect. @@ -3670,10 +3189,11 @@ var get = Ember.get, set = Ember.set, metaFor = Ember.meta, + guidFor = Ember.guidFor, a_slice = [].slice, o_create = Ember.create, META_KEY = Ember.META_KEY, watch = Ember.watch, unwatch = Ember.unwatch; @@ -3691,11 +3211,11 @@ /* This function returns a map of unique dependencies for a given object and key. */ -function keysForDep(depsMeta, depKey) { +function keysForDep(obj, depsMeta, depKey) { var keys = depsMeta[depKey]; if (!keys) { // if there are no dependencies yet for a the given key // create a new empty list of dependencies for the key keys = depsMeta[depKey] = {}; @@ -3705,26 +3225,38 @@ keys = depsMeta[depKey] = o_create(keys); } return keys; } -function metaForDeps(meta) { - return keysForDep(meta, 'deps'); +/* return obj[META_KEY].deps */ +function metaForDeps(obj, meta) { + var deps = meta.deps; + // If the current object has no dependencies... + if (!deps) { + // initialize the dependencies with a pointer back to + // the current object + deps = meta.deps = {}; + } else if (!meta.hasOwnProperty('deps')) { + // otherwise if the dependencies are inherited from the + // object's superclass, clone the deps + deps = meta.deps = o_create(deps); + } + return deps; } function addDependentKeys(desc, obj, keyName, meta) { // the descriptor has a list of dependent keys, so // add all of its dependent keys. var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys; if (!depKeys) return; - depsMeta = metaForDeps(meta); + depsMeta = metaForDeps(obj, meta); for(idx = 0, len = depKeys.length; idx < len; idx++) { depKey = depKeys[idx]; // Lookup keys meta for depKey - keys = keysForDep(depsMeta, depKey); + keys = keysForDep(obj, depsMeta, depKey); // Increment the number of times depKey depends on keyName. keys[keyName] = (keys[keyName] || 0) + 1; // Watch the depKey watch(obj, depKey); } @@ -3734,16 +3266,16 @@ // the descriptor has a list of dependent keys, so // add all of its dependent keys. var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys; if (!depKeys) return; - depsMeta = metaForDeps(meta); + depsMeta = metaForDeps(obj, meta); for(idx = 0, len = depKeys.length; idx < len; idx++) { depKey = depKeys[idx]; // Lookup keys meta for depKey - keys = keysForDep(depsMeta, depKey); + keys = keysForDep(obj, depsMeta, depKey); // Increment the number of times depKey depends on keyName. keys[keyName] = (keys[keyName] || 0) - 1; // Watch the depKey unwatch(obj, depKey); } @@ -3759,47 +3291,40 @@ @extends Ember.Descriptor @constructor */ function ComputedProperty(func, opts) { this.func = func; - this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : true; this._dependentKeys = opts && opts.dependentKeys; - this._readOnly = opts && (opts.readOnly !== undefined || !!opts.readOnly); } Ember.ComputedProperty = ComputedProperty; ComputedProperty.prototype = new Ember.Descriptor(); var ComputedPropertyPrototype = ComputedProperty.prototype; -/* - Call on a computed property to explicitly change it's cacheable mode. +/** + Call on a computed property to set it into cacheable mode. When in this + mode the computed property will automatically cache the return value of + your function until one of the dependent keys changes. - Please use `.volatile` over this method. - ```javascript MyApp.president = Ember.Object.create({ fullName: function() { return this.get('firstName') + ' ' + this.get('lastName'); - // By default, Ember will return the value of this property - // without re-executing this function. + // After calculating the value of this function, Ember will + // return that value without re-executing this function until + // one of the dependent properties change. }.property('firstName', 'lastName') - - initials: function() { - return this.get('firstName')[0] + this.get('lastName')[0]; - - // This function will be executed every time this property - // is requested. - }.property('firstName', 'lastName').cacheable(false) }); ``` + Properties are cacheable by default. + @method cacheable @param {Boolean} aFlag optional set to `false` to disable caching - @return {Ember.ComputedProperty} this @chainable */ ComputedPropertyPrototype.cacheable = function(aFlag) { this._cacheable = aFlag !== false; return this; @@ -3816,41 +3341,17 @@ }.property().volatile() }); ``` @method volatile - @return {Ember.ComputedProperty} this @chainable */ ComputedPropertyPrototype.volatile = function() { return this.cacheable(false); }; /** - Call on a computed property to set it into read-only mode. When in this - mode the computed property will throw an error when set. - - ```javascript - MyApp.person = Ember.Object.create({ - guid: function() { - return 'guid-guid-guid'; - }.property().readOnly() - }); - - MyApp.person.set('guid', 'new-guid'); // will throw an exception - ``` - - @method readOnly - @return {Ember.ComputedProperty} this - @chainable -*/ -ComputedPropertyPrototype.readOnly = function(readOnly) { - this._readOnly = readOnly === undefined || !!readOnly; - return this; -}; - -/** Sets the dependent keys on this computed property. Pass any number of arguments containing key paths that this computed property depends on. ```javascript MyApp.president = Ember.Object.create({ @@ -3863,11 +3364,10 @@ }); ``` @method property @param {String} path* zero or more property paths - @return {Ember.ComputedProperty} this @chainable */ ComputedPropertyPrototype.property = function() { var args = []; for (var i = 0, l = arguments.length; i < l; i++) { @@ -3971,18 +3471,13 @@ oldSuspended = this._suspended, hadCachedValue = false, cache = meta.cache, cachedValue, ret; - if (this._readOnly) { - throw new Error('Cannot Set: ' + keyName + ' on: ' + obj.toString() ); - } - this._suspended = obj; try { - if (cacheable && cache.hasOwnProperty(keyName)) { cachedValue = cache[keyName]; hadCachedValue = true; } @@ -4055,10 +3550,11 @@ The function you pass will be used to both get and set property values. The function should accept two parameters, key and value. If value is not undefined you should set the value first. In either case return the current value of the property. + @method computed @for Ember @param {Function} func The computed property function. @return {Ember.ComputedProperty} property descriptor instance */ @@ -4068,14 +3564,10 @@ if (arguments.length > 1) { args = a_slice.call(arguments, 0, -1); func = a_slice.call(arguments, -1)[0]; } - if ( typeof func !== "function" ) { - throw new Error("Computed Property declared without a property function"); - } - var cp = new ComputedProperty(func); if (args) { cp.property.apply(cp, args); } @@ -4092,426 +3584,448 @@ @method cacheFor @for Ember @param {Object} obj the object whose property you want to check @param {String} key the name of the property whose cached value you want to return - @return {*} the cached value */ Ember.cacheFor = function cacheFor(obj, key) { var cache = metaFor(obj, false).cache; if (cache && key in cache) { return cache[key]; } }; -function getProperties(self, propertyNames) { - var ret = {}; - for(var i = 0; i < propertyNames.length; i++) { - ret[propertyNames[i]] = get(self, propertyNames[i]); - } - return ret; -} - -function registerComputed(name, macro) { - Ember.computed[name] = function(dependentKey) { - var args = a_slice.call(arguments); - return Ember.computed(dependentKey, function() { - return macro.apply(this, args); - }); - }; -} - -function registerComputedWithProperties(name, macro) { - Ember.computed[name] = function() { - var properties = a_slice.call(arguments); - - var computed = Ember.computed(function() { - return macro.apply(this, [getProperties(this, properties)]); - }); - - return computed.property.apply(computed, properties); - }; -} - /** - @method computed.empty + @method computed.not @for Ember @param {String} dependentKey - @return {Ember.ComputedProperty} computed property which negate - the original value for property */ -registerComputed('empty', function(dependentKey) { - return Ember.isEmpty(get(this, dependentKey)); -}); +Ember.computed.not = function(dependentKey) { + return Ember.computed(dependentKey, function(key) { + return !get(this, dependentKey); + }); +}; /** - @method computed.notEmpty + @method computed.empty @for Ember @param {String} dependentKey - @return {Ember.ComputedProperty} computed property which returns true if - original value for property is not empty. */ -registerComputed('notEmpty', function(dependentKey) { - return !Ember.isEmpty(get(this, dependentKey)); -}); +Ember.computed.empty = function(dependentKey) { + return Ember.computed(dependentKey, function(key) { + var val = get(this, dependentKey); + return val === undefined || val === null || val === '' || (Ember.isArray(val) && get(val, 'length') === 0); + }); +}; /** - @method computed.none + @method computed.bool @for Ember @param {String} dependentKey - @return {Ember.ComputedProperty} computed property which - rturns true if original value for property is null or undefined. */ -registerComputed('none', function(dependentKey) { - return Ember.isNone(get(this, dependentKey)); -}); +Ember.computed.bool = function(dependentKey) { + return Ember.computed(dependentKey, function(key) { + return !!get(this, dependentKey); + }); +}; /** - @method computed.not + @method computed.alias @for Ember @param {String} dependentKey - @return {Ember.ComputedProperty} computed property which returns - inverse of the original value for property */ -registerComputed('not', function(dependentKey) { - return !get(this, dependentKey); -}); +Ember.computed.alias = function(dependentKey) { + return Ember.computed(dependentKey, function(key, value){ + if (arguments.length === 1) { + return get(this, dependentKey); + } else { + set(this, dependentKey, value); + return value; + } + }); +}; -/** - @method computed.bool - @for Ember - @param {String} dependentKey - @return {Ember.ComputedProperty} computed property which convert - to boolean the original value for property -*/ -registerComputed('bool', function(dependentKey) { - return !!get(this, dependentKey); -}); +})(); -/** - @method computed.match - @for Ember - @param {String} dependentKey - @param {RegExp} regexp - @return {Ember.ComputedProperty} computed property which match - the original value for property against a given RegExp -*/ -registerComputed('match', function(dependentKey, regexp) { - var value = get(this, dependentKey); - return typeof value === 'string' ? !!value.match(regexp) : false; -}); -/** - @method computed.equal - @for Ember - @param {String} dependentKey - @param {String|Number|Object} value - @return {Ember.ComputedProperty} computed property which returns true if - the original value for property is equal to the given value. -*/ -registerComputed('equal', function(dependentKey, value) { - return get(this, dependentKey) === value; -}); +(function() { /** - @method computed.gt - @for Ember - @param {String} dependentKey - @param {Number} value - @return {Ember.ComputedProperty} computed property which returns true if - the original value for property is greater then given value. +@module ember-metal */ -registerComputed('gt', function(dependentKey, value) { - return get(this, dependentKey) > value; -}); -/** - @method computed.gte - @for Ember - @param {String} dependentKey - @param {Number} value - @return {Ember.ComputedProperty} computed property which returns true if - the original value for property is greater or equal then given value. -*/ -registerComputed('gte', function(dependentKey, value) { - return get(this, dependentKey) >= value; -}); +var o_create = Ember.create, + metaFor = Ember.meta, + metaPath = Ember.metaPath, + META_KEY = Ember.META_KEY; -/** - @method computed.lt - @for Ember - @param {String} dependentKey - @param {Number} value - @return {Ember.ComputedProperty} computed property which returns true if - the original value for property is less then given value. -*/ -registerComputed('lt', function(dependentKey, value) { - return get(this, dependentKey) < value; -}); +/* + The event system uses a series of nested hashes to store listeners on an + object. When a listener is registered, or when an event arrives, these + hashes are consulted to determine which target and action pair to invoke. -/** - @method computed.lte - @for Ember - @param {String} dependentKey - @param {Number} value - @return {Ember.ComputedProperty} computed property which returns true if - the original value for property is less or equal then given value. -*/ -registerComputed('lte', function(dependentKey, value) { - return get(this, dependentKey) <= value; -}); + The hashes are stored in the object's meta hash, and look like this: -/** - @method computed.and - @for Ember - @param {String} dependentKey, [dependentKey...] - @return {Ember.ComputedProperty} computed property which peforms - a logical `and` on the values of all the original values for properties. + // Object's meta hash + { + listeners: { // variable name: `listenerSet` + "foo:changed": [ // variable name: `actions` + [target, method, onceFlag, suspendedFlag] + ] + } + } + */ -registerComputedWithProperties('and', function(properties) { - for (var key in properties) { - if (properties.hasOwnProperty(key) && !properties[key]) { - return false; - } + +function indexOf(array, target, method) { + var index = -1; + for (var i = 0, l = array.length; i < l; i++) { + if (target === array[i][0] && method === array[i][1]) { index = i; break; } } - return true; -}); + return index; +} -/** - @method computed.or - @for Ember - @param {String} dependentKey, [dependentKey...] - @return {Ember.ComputedProperty} computed property which peforms - a logical `or` on the values of all the original values for properties. -*/ -registerComputedWithProperties('or', function(properties) { - for (var key in properties) { - if (properties.hasOwnProperty(key) && properties[key]) { - return true; +function actionsFor(obj, eventName) { + var meta = metaFor(obj, true), + actions; + + if (!meta.listeners) { meta.listeners = {}; } + + if (!meta.hasOwnProperty('listeners')) { + // setup inherited copy of the listeners object + meta.listeners = o_create(meta.listeners); + } + + actions = meta.listeners[eventName]; + + // if there are actions, but the eventName doesn't exist in our listeners, then copy them from the prototype + if (actions && !meta.listeners.hasOwnProperty(eventName)) { + actions = meta.listeners[eventName] = meta.listeners[eventName].slice(); + } else if (!actions) { + actions = meta.listeners[eventName] = []; + } + + return actions; +} + +function actionsUnion(obj, eventName, otherActions) { + var meta = obj[META_KEY], + actions = meta && meta.listeners && meta.listeners[eventName]; + + if (!actions) { return; } + for (var i = actions.length - 1; i >= 0; i--) { + var target = actions[i][0], + method = actions[i][1], + once = actions[i][2], + suspended = actions[i][3], + actionIndex = indexOf(otherActions, target, method); + + if (actionIndex === -1) { + otherActions.push([target, method, once, suspended]); } } - return false; -}); +} +function actionsDiff(obj, eventName, otherActions) { + var meta = obj[META_KEY], + actions = meta && meta.listeners && meta.listeners[eventName], + diffActions = []; + + if (!actions) { return; } + for (var i = actions.length - 1; i >= 0; i--) { + var target = actions[i][0], + method = actions[i][1], + once = actions[i][2], + suspended = actions[i][3], + actionIndex = indexOf(otherActions, target, method); + + if (actionIndex !== -1) { continue; } + + otherActions.push([target, method, once, suspended]); + diffActions.push([target, method, once, suspended]); + } + + return diffActions; +} + /** - @method computed.any + Add an event listener + + @method addListener @for Ember - @param {String} dependentKey, [dependentKey...] - @return {Ember.ComputedProperty} computed property which returns - the first truthy value of given list of properties. + @param obj + @param {String} eventName + @param {Object|Function} targetOrMethod A target object or a function + @param {Function|String} method A function or the name of a function to be called on `target` */ -registerComputedWithProperties('any', function(properties) { - for (var key in properties) { - if (properties.hasOwnProperty(key) && properties[key]) { - return properties[key]; - } +function addListener(obj, eventName, target, method, once) { + Ember.assert("You must pass at least an object and event name to Ember.addListener", !!obj && !!eventName); + + if (!method && 'function' === typeof target) { + method = target; + target = null; } - return null; -}); + var actions = actionsFor(obj, eventName), + actionIndex = indexOf(actions, target, method); + + if (actionIndex !== -1) { return; } + + actions.push([target, method, once, undefined]); + + if ('function' === typeof obj.didAddListener) { + obj.didAddListener(eventName, target, method); + } +} + /** - @method computed.map + Remove an event listener + + Arguments should match those passed to {{#crossLink "Ember/addListener"}}{{/crossLink}} + + @method removeListener @for Ember - @param {String} dependentKey, [dependentKey...] - @return {Ember.ComputedProperty} computed property which maps - values of all passed properties in to an array. + @param obj + @param {String} eventName + @param {Object|Function} targetOrMethod A target object or a function + @param {Function|String} method A function or the name of a function to be called on `target` */ -registerComputedWithProperties('map', function(properties) { - var res = []; - for (var key in properties) { - if (properties.hasOwnProperty(key)) { - if (Ember.isNone(properties[key])) { - res.push(null); - } else { - res.push(properties[key]); - } +function removeListener(obj, eventName, target, method) { + Ember.assert("You must pass at least an object and event name to Ember.removeListener", !!obj && !!eventName); + + if (!method && 'function' === typeof target) { + method = target; + target = null; + } + + function _removeListener(target, method, once) { + var actions = actionsFor(obj, eventName), + actionIndex = indexOf(actions, target, method); + + // action doesn't exist, give up silently + if (actionIndex === -1) { return; } + + actions.splice(actionIndex, 1); + + if ('function' === typeof obj.didRemoveListener) { + obj.didRemoveListener(eventName, target, method); } } - return res; -}); -/** - @method computed.alias - @for Ember - @param {String} dependentKey - @return {Ember.ComputedProperty} computed property which creates an - alias to the original value for property. -*/ -Ember.computed.alias = function(dependentKey) { - return Ember.computed(dependentKey, function(key, value){ - if (arguments.length > 1) { - set(this, dependentKey, value); - return value; - } else { - return get(this, dependentKey); + if (method) { + _removeListener(target, method); + } else { + var meta = obj[META_KEY], + actions = meta && meta.listeners && meta.listeners[eventName]; + + if (!actions) { return; } + for (var i = actions.length - 1; i >= 0; i--) { + _removeListener(actions[i][0], actions[i][1]); } - }); -}; + } +} /** - @method computed.oneWay - @for Ember - @param {String} dependentKey - @return {Ember.ComputedProperty} computed property which creates an - one way computed property to the original value for property. + @private - Where `computed.alias` aliases `get` and `set`, and allows for bidirectional - data flow, `computed.oneWay` only provides an aliased `get`. The `set` will - not mutate the upstream property, rather causes the current property to - become the value set. This causes the downstream property to permentantly - diverge from the upstream property. + Suspend listener during callback. - ```javascript - User = Ember.Object.extend({ - firstName: null, - lastName: null, - nickName: Ember.computed.oneWay('firstName') - }); + This should only be used by the target of the event listener + when it is taking an action that would cause the event, e.g. + an object might suspend its property change listener while it is + setting that property. - user = User.create({ - firstName: 'Teddy', - lastName: 'Zeenny' - }); + @method suspendListener + @for Ember + @param obj + @param {String} eventName + @param {Object|Function} targetOrMethod A target object or a function + @param {Function|String} method A function or the name of a function to be called on `target` + @param {Function} callback +*/ +function suspendListener(obj, eventName, target, method, callback) { + if (!method && 'function' === typeof target) { + method = target; + target = null; + } - user.get('nickName'); - # 'Teddy' + var actions = actionsFor(obj, eventName), + actionIndex = indexOf(actions, target, method), + action; - user.set('nickName', 'TeddyBear'); - # 'TeddyBear' + if (actionIndex !== -1) { + action = actions[actionIndex].slice(); // copy it, otherwise we're modifying a shared object + action[3] = true; // mark the action as suspended + actions[actionIndex] = action; // replace the shared object with our copy + } - user.get('firstName'); - # 'Teddy' - ``` -*/ -Ember.computed.oneWay = function(dependentKey) { - return Ember.computed(dependentKey, function() { - return get(this, dependentKey); - }); -}; + function tryable() { return callback.call(target); } + function finalizer() { if (action) { action[3] = undefined; } } + return Ember.tryFinally(tryable, finalizer); +} /** - @method computed.defaultTo + @private + + Suspend listener during callback. + + This should only be used by the target of the event listener + when it is taking an action that would cause the event, e.g. + an object might suspend its property change listener while it is + setting that property. + + @method suspendListener @for Ember - @param {String} defaultPath - @return {Ember.ComputedProperty} computed property which acts like - a standard getter and setter, but defaults to the value from `defaultPath`. + @param obj + @param {Array} eventName Array of event names + @param {Object|Function} targetOrMethod A target object or a function + @param {Function|String} method A function or the name of a function to be called on `target` + @param {Function} callback */ -Ember.computed.defaultTo = function(defaultPath) { - return Ember.computed(function(key, newValue, cachedValue) { - if (arguments.length === 1) { - return cachedValue != null ? cachedValue : get(this, defaultPath); +function suspendListeners(obj, eventNames, target, method, callback) { + if (!method && 'function' === typeof target) { + method = target; + target = null; + } + + var suspendedActions = [], + eventName, actions, action, i, l; + + for (i=0, l=eventNames.length; i<l; i++) { + eventName = eventNames[i]; + actions = actionsFor(obj, eventName); + var actionIndex = indexOf(actions, target, method); + + if (actionIndex !== -1) { + action = actions[actionIndex].slice(); + action[3] = true; + actions[actionIndex] = action; + suspendedActions.push(action); } - return newValue != null ? newValue : get(this, defaultPath); - }); -}; + } -})(); + function tryable() { return callback.call(target); } + function finalizer() { + for (i = 0, l = suspendedActions.length; i < l; i++) { + suspendedActions[i][3] = undefined; + } + } + return Ember.tryFinally(tryable, finalizer); +} -(function() { -// Ember.tryFinally /** -@module ember-metal -*/ + @private -var AFTER_OBSERVERS = ':change'; -var BEFORE_OBSERVERS = ':before'; + Return a list of currently watched events -function changeEvent(keyName) { - return keyName+AFTER_OBSERVERS; -} + @method watchedEvents + @for Ember + @param obj +*/ +function watchedEvents(obj) { + var listeners = obj[META_KEY].listeners, ret = []; -function beforeEvent(keyName) { - return keyName+BEFORE_OBSERVERS; + if (listeners) { + for(var eventName in listeners) { + if (listeners[eventName]) { ret.push(eventName); } + } + } + return ret; } /** - @method addObserver + @method sendEvent + @for Ember @param obj - @param {String} path - @param {Object|Function} targetOrMethod - @param {Function|String} [method] + @param {String} eventName + @param {Array} params + @return true */ -Ember.addObserver = function(obj, path, target, method) { - Ember.addListener(obj, changeEvent(path), target, method); - Ember.watch(obj, path); - return this; -}; +function sendEvent(obj, eventName, params, actions) { + // first give object a chance to handle it + if (obj !== Ember && 'function' === typeof obj.sendEvent) { + obj.sendEvent(eventName, params); + } -Ember.observersFor = function(obj, path) { - return Ember.listenersFor(obj, changeEvent(path)); -}; + if (!actions) { + var meta = obj[META_KEY]; + actions = meta && meta.listeners && meta.listeners[eventName]; + } + if (!actions) { return; } + + for (var i = actions.length - 1; i >= 0; i--) { // looping in reverse for once listeners + if (!actions[i] || actions[i][3] === true) { continue; } + + var target = actions[i][0], + method = actions[i][1], + once = actions[i][2]; + + if (once) { removeListener(obj, eventName, target, method); } + if (!target) { target = obj; } + if ('string' === typeof method) { method = target[method]; } + if (params) { + method.apply(target, params); + } else { + method.apply(target); + } + } + return true; +} + /** - @method removeObserver + @private + @method hasListeners + @for Ember @param obj - @param {String} path - @param {Object|Function} targetOrMethod - @param {Function|String} [method] + @param {String} eventName */ -Ember.removeObserver = function(obj, path, target, method) { - Ember.unwatch(obj, path); - Ember.removeListener(obj, changeEvent(path), target, method); - return this; -}; +function hasListeners(obj, eventName) { + var meta = obj[META_KEY], + actions = meta && meta.listeners && meta.listeners[eventName]; + return !!(actions && actions.length); +} + /** - @method addBeforeObserver + @private + @method listenersFor + @for Ember @param obj - @param {String} path - @param {Object|Function} targetOrMethod - @param {Function|String} [method] + @param {String} eventName */ -Ember.addBeforeObserver = function(obj, path, target, method) { - Ember.addListener(obj, beforeEvent(path), target, method); - Ember.watch(obj, path); - return this; -}; +function listenersFor(obj, eventName) { + var ret = []; + var meta = obj[META_KEY], + actions = meta && meta.listeners && meta.listeners[eventName]; -// Suspend observer during callback. -// -// This should only be used by the target of the observer -// while it is setting the observed path. -Ember._suspendBeforeObserver = function(obj, path, target, method, callback) { - return Ember._suspendListener(obj, beforeEvent(path), target, method, callback); -}; + if (!actions) { return ret; } -Ember._suspendObserver = function(obj, path, target, method, callback) { - return Ember._suspendListener(obj, changeEvent(path), target, method, callback); -}; + for (var i = 0, l = actions.length; i < l; i++) { + var target = actions[i][0], + method = actions[i][1]; + ret.push([target, method]); + } -var map = Ember.ArrayPolyfills.map; + return ret; +} -Ember._suspendBeforeObservers = function(obj, paths, target, method, callback) { - var events = map.call(paths, beforeEvent); - return Ember._suspendListeners(obj, events, target, method, callback); -}; +Ember.addListener = addListener; +Ember.removeListener = removeListener; +Ember._suspendListener = suspendListener; +Ember._suspendListeners = suspendListeners; +Ember.sendEvent = sendEvent; +Ember.hasListeners = hasListeners; +Ember.watchedEvents = watchedEvents; +Ember.listenersFor = listenersFor; +Ember.listenersDiff = actionsDiff; +Ember.listenersUnion = actionsUnion; -Ember._suspendObservers = function(obj, paths, target, method, callback) { - var events = map.call(paths, changeEvent); - return Ember._suspendListeners(obj, events, target, method, callback); -}; - -Ember.beforeObserversFor = function(obj, path) { - return Ember.listenersFor(obj, beforeEvent(path)); -}; - -/** - @method removeBeforeObserver - @param obj - @param {String} path - @param {Object|Function} targetOrMethod - @param {Function|String} [method] -*/ -Ember.removeBeforeObserver = function(obj, path, target, method) { - Ember.unwatch(obj, path); - Ember.removeListener(obj, beforeEvent(path), target, method); - return this; -}; })(); (function() { @@ -4554,10 +4068,12 @@ // .......................................................... // RUNLOOP // +var timerMark; // used by timers... + /** Ember RunLoop (Private) @class RunLoop @namespace Ember @@ -4684,10 +4200,12 @@ idx++; } } + timerMark = null; + return this; } }; @@ -4707,11 +4225,11 @@ libraries or plugins, you should probably wrap all of your code inside this call. ```javascript Ember.run(function(){ - // code to be execute within a RunLoop + // code to be execute within a RunLoop }); ``` @class run @namespace Ember @@ -4723,11 +4241,12 @@ then it will be looked up on the passed target. @param {Object} [args*] Any additional arguments you wish to pass to the method. @return {Object} return value from invoking the passed function. */ Ember.run = function(target, method) { - var args = arguments; + var loop, + args = arguments; run.begin(); function tryable() { if (target || method) { return invoke(target, method, args, 2); @@ -4741,15 +4260,15 @@ /** Begins a new RunLoop. Any deferred actions invoked after the begin will be buffered until you invoke a matching call to `Ember.run.end()`. This is - a lower-level way to use a RunLoop instead of using `Ember.run()`. + an lower-level way to use a RunLoop instead of using `Ember.run()`. ```javascript Ember.run.begin(); - // code to be execute within a RunLoop + // code to be execute within a RunLoop Ember.run.end(); ``` @method begin @return {void} @@ -4763,11 +4282,11 @@ `Ember.run.begin()` to flush any deferred actions. This is a lower-level way to use a RunLoop instead of using `Ember.run()`. ```javascript Ember.run.begin(); - // code to be execute within a RunLoop + // code to be execute within a RunLoop Ember.run.end(); ``` @method end @return {void} @@ -4787,38 +4306,38 @@ simply adding the queue name to this array. Normally you should not need to inspect or modify this property. @property queues @type Array - @default ['sync', 'actions', 'destroy'] + @default ['sync', 'actions', 'destroy', 'timers'] */ -Ember.run.queues = ['sync', 'actions', 'destroy']; +Ember.run.queues = ['sync', 'actions', 'destroy', 'timers']; /** Adds the passed target/method and any optional arguments to the named queue to be executed at the end of the RunLoop. If you have not already started a RunLoop when calling this method one will be started for you automatically. At the end of a RunLoop, any methods scheduled in this way will be invoked. Methods will be invoked in an order matching the named queues defined in - the `Ember.run.queues` property. + the `run.queues` property. ```javascript + Ember.run.schedule('timers', this, function(){ + // this will be executed at the end of the RunLoop, when timers are run + console.log("scheduled on timers queue"); + }); + Ember.run.schedule('sync', this, function(){ - // this will be executed in the first RunLoop queue, when bindings are synced + // this will be executed at the end of the RunLoop, when bindings are synced console.log("scheduled on sync queue"); }); - Ember.run.schedule('actions', this, function(){ - // this will be executed in the 'actions' queue, after bindings have synced. - console.log("scheduled on actions queue"); - }); - // Note the functions will be run in order based on the run queues order. Output would be: // scheduled on sync queue - // scheduled on actions queue + // scheduled on timers queue ``` @method schedule @param {String} queue The name of the queue to schedule against. Default queues are 'sync' and 'actions' @@ -4840,11 +4359,11 @@ if (run.currentRunLoop) { run.end(); } } // Used by global test teardown Ember.run.hasScheduledTimers = function() { - return !!(scheduledAutorun || scheduledLater); + return !!(scheduledAutorun || scheduledLater || scheduledNext); }; // Used by global test teardown Ember.run.cancelTimers = function () { if (scheduledAutorun) { @@ -4853,10 +4372,14 @@ } if (scheduledLater) { clearTimeout(scheduledLater); scheduledLater = null; } + if (scheduledNext) { + clearTimeout(scheduledNext); + scheduledNext = null; + } timers = {}; }; /** Begins a new RunLoop if necessary and schedules a timer to flush the @@ -4887,12 +4410,11 @@ Immediately flushes any events scheduled in the 'sync' queue. Bindings use this queue so this method is a useful way to immediately force all bindings in the application to sync. You should call this method anytime you need any changed state to propagate - throughout the app immediately without repainting the UI (which happens - in the later 'render' queue added by the `ember-views` package). + throughout the app immediately without repainting the UI. ```javascript Ember.run.sync(); ``` @@ -4908,51 +4430,29 @@ // TIMERS // var timers = {}; // active timers... -function sortByExpires(timerA, timerB) { - var a = timerA.expires, - b = timerB.expires; - - if (a > b) { return 1; } - if (a < b) { return -1; } - return 0; -} - -var scheduledLater, scheduledLaterExpires; +var scheduledLater; function invokeLaterTimers() { scheduledLater = null; - run(function() { - var now = (+ new Date()), earliest = -1; - var timersToBeInvoked = []; - for (var key in timers) { - if (!timers.hasOwnProperty(key)) { continue; } - var timer = timers[key]; - if (timer && timer.expires) { - if (now >= timer.expires) { - delete timers[key]; - timersToBeInvoked.push(timer); - } else { - if (earliest < 0 || (timer.expires < earliest)) { earliest = timer.expires; } - } + var now = (+ new Date()), earliest = -1; + for (var key in timers) { + if (!timers.hasOwnProperty(key)) { continue; } + var timer = timers[key]; + if (timer && timer.expires) { + if (now >= timer.expires) { + delete timers[key]; + invoke(timer.target, timer.method, timer.args, 2); + } else { + if (earliest<0 || (timer.expires < earliest)) earliest=timer.expires; } } + } - forEach.call(timersToBeInvoked.sort(sortByExpires), function(timer) { - invoke(timer.target, timer.method, timer.args, 2); - }); - - // schedule next timeout to fire when the earliest timer expires - if (earliest > 0) { - // To allow overwrite `setTimeout` as stub from test code. - // The assignment to `window.setTimeout` doesn't equal to `setTimeout` in older IE. - // So `window` is required. - scheduledLater = window.setTimeout(invokeLaterTimers, earliest - now); - scheduledLaterExpires = earliest; - } - }); + // schedule next timeout to fire... + if (earliest > 0) { scheduledLater = setTimeout(invokeLaterTimers, earliest-(+ new Date())); } } /** Invokes the passed target/method and optional arguments after a specified period if time. The last parameter of this method must always be a number @@ -4973,11 +4473,12 @@ @param {Object} [target] target of method to invoke @param {Function|String} method The method to invoke. If you pass a string it will be resolved on the target at the time the method is invoked. @param {Object} [args*] Optional arguments to pass to the timeout. - @param {Number} wait Number of milliseconds to wait. + @param {Number} wait + Number of milliseconds to wait. @return {String} a string you can use to cancel the timer in {{#crossLink "Ember/run.cancel"}}{{/crossLink}} later. */ Ember.run.later = function(target, method) { var args, expires, timer, guid, wait; @@ -4995,23 +4496,11 @@ expires = (+ new Date()) + wait; timer = { target: target, method: method, expires: expires, args: args }; guid = Ember.guidFor(timer); timers[guid] = timer; - - if(scheduledLater && expires < scheduledLaterExpires) { - // Cancel later timer (then reschedule earlier timer below) - clearTimeout(scheduledLater); - scheduledLater = null; - } - - if (!scheduledLater) { - // Schedule later timers to be run. - scheduledLater = setTimeout(invokeLaterTimers, wait); - scheduledLaterExpires = expires; - } - + run.once(timers, invokeLaterTimers); return guid; }; function invokeOnceTimer(guid, onceTimers) { if (onceTimers[this.tguid]) { delete onceTimers[this.tguid][this.mguid]; } @@ -5047,134 +4536,87 @@ return guid; } /** - Schedule a function to run one time during the current RunLoop. This is equivalent - to calling `scheduleOnce` with the "actions" queue. + Schedules an item to run one time during the current RunLoop. Calling + this method with the same target/method combination will have no effect. - @method once - @param {Object} [target] The target of the method to invoke. - @param {Function|String} method The method to invoke. - If you pass a string it will be resolved on the - target at the time the method is invoked. - @param {Object} [args*] Optional arguments to pass to the timeout. - @return {Object} timer -*/ -Ember.run.once = function(target, method) { - return scheduleOnce('actions', target, method, slice.call(arguments, 2)); -}; - -/** - Schedules a function to run one time in a given queue of the current RunLoop. - Calling this method with the same queue/target/method combination will have - no effect (past the initial call). - Note that although you can pass optional arguments these will not be considered when looking for duplicates. New arguments will replace previous calls. ```javascript Ember.run(function(){ - var sayHi = function() { console.log('hi'); } - Ember.run.scheduleOnce('afterRender', myContext, sayHi); - Ember.run.scheduleOnce('afterRender', myContext, sayHi); - // doFoo will only be executed once, in the afterRender queue of the RunLoop + var doFoo = function() { foo(); } + Ember.run.once(myContext, doFoo); + Ember.run.once(myContext, doFoo); + // doFoo will only be executed once at the end of the RunLoop }); ``` - Also note that passing an anonymous function to `Ember.run.scheduleOnce` will - not prevent additional calls with an identical anonymous function from - scheduling the items multiple times, e.g.: - - ```javascript - function scheduleIt() { - Ember.run.scheduleOnce('actions', myContext, function() { console.log("Closure"); }); - } - scheduleIt(); - scheduleIt(); - // "Closure" will print twice, even though we're using `Ember.run.scheduleOnce`, - // because the function we pass to it is anonymous and won't match the - // previously scheduled operation. - ``` - - Available queues, and their order, can be found at `Ember.run.queues` - - @method scheduleOnce - @param {String} [queue] The name of the queue to schedule against. Default queues are 'sync' and 'actions'. - @param {Object} [target] The target of the method to invoke. + @method once + @param {Object} [target] target of method to invoke @param {Function|String} method The method to invoke. If you pass a string it will be resolved on the target at the time the method is invoked. @param {Object} [args*] Optional arguments to pass to the timeout. @return {Object} timer */ -Ember.run.scheduleOnce = function(queue, target, method) { +Ember.run.once = function(target, method) { + return scheduleOnce('actions', target, method, slice.call(arguments, 2)); +}; + +Ember.run.scheduleOnce = function(queue, target, method, args) { return scheduleOnce(queue, target, method, slice.call(arguments, 3)); }; +var scheduledNext; +function invokeNextTimers() { + scheduledNext = null; + for(var key in timers) { + if (!timers.hasOwnProperty(key)) { continue; } + var timer = timers[key]; + if (timer.next) { + delete timers[key]; + invoke(timer.target, timer.method, timer.args, 2); + } + } +} + /** - Schedules an item to run from within a separate run loop, after - control has been returned to the system. This is equivalent to calling - `Ember.run.later` with a wait time of 1ms. + Schedules an item to run after control has been returned to the system. + This is often equivalent to calling `setTimeout(function() {}, 1)`. ```javascript Ember.run.next(myContext, function(){ - // code to be executed in the next run loop, which will be scheduled after the current one + // code to be executed in the next RunLoop, which will be scheduled after the current one }); ``` - Multiple operations scheduled with `Ember.run.next` will coalesce - into the same later run loop, along with any other operations - scheduled by `Ember.run.later` that expire right around the same - time that `Ember.run.next` operations will fire. - - Note that there are often alternatives to using `Ember.run.next`. - For instance, if you'd like to schedule an operation to happen - after all DOM element operations have completed within the current - run loop, you can make use of the `afterRender` run loop queue (added - by the `ember-views` package, along with the preceding `render` queue - where all the DOM element operations happen). Example: - - ```javascript - App.MyCollectionView = Ember.CollectionView.extend({ - didInsertElement: function() { - Ember.run.scheduleOnce('afterRender', this, 'processChildElements'); - }, - processChildElements: function() { - // ... do something with collectionView's child view - // elements after they've finished rendering, which - // can't be done within the CollectionView's - // `didInsertElement` hook because that gets run - // before the child elements have been added to the DOM. - } - }); - ``` - - One benefit of the above approach compared to using `Ember.run.next` is - that you will be able to perform DOM/CSS operations before unprocessed - elements are rendered to the screen, which may prevent flickering or - other artifacts caused by delaying processing until after rendering. - - The other major benefit to the above approach is that `Ember.run.next` - introduces an element of non-determinism, which can make things much - harder to test, due to its reliance on `setTimeout`; it's much harder - to guarantee the order of scheduled operations when they are scheduled - outside of the current run loop, i.e. with `Ember.run.next`. - @method next @param {Object} [target] target of method to invoke @param {Function|String} method The method to invoke. If you pass a string it will be resolved on the target at the time the method is invoked. @param {Object} [args*] Optional arguments to pass to the timeout. @return {Object} timer */ -Ember.run.next = function() { - var args = slice.call(arguments); - args.push(1); // 1 millisecond wait - return run.later.apply(this, args); +Ember.run.next = function(target, method) { + var guid, + timer = { + target: target, + method: method, + args: slice.call(arguments), + next: true + }; + + guid = Ember.guidFor(timer); + timers[guid] = timer; + + if (!scheduledNext) { scheduledNext = setTimeout(invokeNextTimers, 1); } + return guid; }; /** Cancels a scheduled item. Must be a value returned by `Ember.run.later()`, `Ember.run.once()`, or `Ember.run.next()`. @@ -5208,13 +4650,12 @@ (function() { // Ember.Logger -// get -// set -// guidFor, meta +// get, set, trySet +// guidFor, isArray, meta // addObserver, removeObserver // Ember.run.schedule /** @module ember-metal */ @@ -5236,26 +4677,13 @@ Ember.LOG_BINDINGS = false || !!Ember.ENV.LOG_BINDINGS; var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, - IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/; + isGlobalPath = Ember.isGlobalPath; -/** - Returns true if the provided path is global (e.g., `MyApp.fooController.bar`) - instead of local (`foo.bar.baz`). - @method isGlobalPath - @for Ember - @private - @param {String} path - @return Boolean -*/ -var isGlobalPath = Ember.isGlobalPath = function(path) { - return IS_GLOBAL.test(path); -}; - function getWithGlobals(obj, path) { return get(isGlobalPath(path) ? Ember.lookup : obj, path); } // .......................................................... @@ -5299,11 +4727,11 @@ The binding will search for the property path starting at the root object you pass when you `connect()` the binding. It follows the same rules as `get()` - see that method for more information. @method from - @param {String} path the property path to connect to + @param {String} propertyPath the property path to connect to @return {Ember.Binding} `this` */ from: function(path) { this._from = path; return this; @@ -5317,11 +4745,11 @@ The binding will search for the property path starting at the root object you pass when you `connect()` the binding. It follows the same rules as `get()` - see that method for more information. @method to - @param {String|Tuple} path A property path or tuple + @param {String|Tuple} propertyPath A property path or tuple @return {Ember.Binding} `this` */ to: function(path) { this._to = path; return this; @@ -5339,14 +4767,10 @@ oneWay: function() { this._oneWay = true; return this; }, - /** - @method toString - @return {String} string representation of binding - */ toString: function() { var oneWay = this._oneWay ? '[oneWay]' : ''; return "Ember.Binding<" + guidFor(this) + ">(" + this._from + " -> " + this._to + ")" + oneWay; }, @@ -5542,11 +4966,11 @@ detection. Properties ending in a `Binding` suffix will be converted to `Ember.Binding` instances. The value of this property should be a string representing a path to another object or a custom binding instanced created using Binding helpers - (see "One Way Bindings"): + (see "Customizing Your Bindings"): ``` valueBinding: "MyApp.someController.title" ``` @@ -5575,11 +4999,11 @@ 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). + to monitor it for changes. (such as in the example above). ## Adding Bindings Manually 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 @@ -5693,10 +5117,11 @@ var Mixin, REQUIRED, Alias, a_map = Ember.ArrayPolyfills.map, a_indexOf = Ember.ArrayPolyfills.indexOf, a_forEach = Ember.ArrayPolyfills.forEach, a_slice = [].slice, + EMPTY_META = {}, // dummy for non-writable meta o_create = Ember.create, defineProperty = Ember.defineProperty, guidFor = Ember.guidFor; function mixinsMeta(obj) { @@ -5842,11 +5267,11 @@ descs[key] = undefined; values[key] = value; } } -function mergeMixins(mixins, m, descs, values, base, keys) { +function mergeMixins(mixins, m, descs, values, base) { var mixin, props, key, concats, meta; function removeKeys(keyName) { delete descs[keyName]; delete values[keyName]; @@ -5863,23 +5288,30 @@ meta = Ember.meta(base); concats = concatenatedProperties(props, values, base); for (key in props) { if (!props.hasOwnProperty(key)) { continue; } - keys.push(key); addNormalizedProperty(base, key, props[key], meta, descs, values, concats); } // manually copy toString() because some JS engines do not enumerate it if (props.hasOwnProperty('toString')) { base.toString = props.toString; } } else if (mixin.mixins) { - mergeMixins(mixin.mixins, m, descs, values, base, keys); + mergeMixins(mixin.mixins, m, descs, values, base); if (mixin._without) { a_forEach.call(mixin._without, removeKeys); } } } } +function writableReq(obj) { + var m = Ember.meta(obj), req = m.required; + if (!req || !m.hasOwnProperty('required')) { + req = m.required = req ? o_create(req) : {}; + } + return req; +} + var IS_BINDING = Ember.IS_BINDING = /^.+Binding$/; function detectBinding(obj, key, value, m) { if (IS_BINDING.test(key)) { var bindings = m.bindings; @@ -5958,23 +5390,22 @@ updateObservers(obj, key, observer, '__ember_observes__', 'addObserver'); } function applyMixin(obj, mixins, partial) { var descs = {}, values = {}, m = Ember.meta(obj), - key, value, desc, keys = []; + key, value, desc; // Go through all mixins and hashes passed in, and: // // * Handle concatenated properties // * Set up _super wrapping if necessary // * Set up computed property descriptors // * Copying `toString` in broken browsers - mergeMixins(mixins, mixinsMeta(obj), descs, values, obj, keys); + mergeMixins(mixins, mixinsMeta(obj), descs, values, obj); - for(var i = 0, l = keys.length; i < l; i++) { - key = keys[i]; - if (key === 'constructor' || !values.hasOwnProperty(key)) { continue; } + for(key in values) { + if (key === 'contructor' || !values.hasOwnProperty(key)) { continue; } desc = descs[key]; value = values[key]; if (desc === REQUIRED) { continue; } @@ -6024,11 +5455,11 @@ }, isEditing: false }); // Mix mixins into classes by passing them as the first arguments to - // .extend. + // .extend or .create. App.CommentView = Ember.View.extend(App.Editable, { template: Ember.Handlebars.compile('{{#if isEditing}}...{{else}}...{{/if}}') }); commentView = App.CommentView.create(); @@ -6043,16 +5474,10 @@ */ Ember.Mixin = function() { return initMixin(this, arguments); }; Mixin = Ember.Mixin; -Mixin.prototype = { - properties: null, - mixins: null, - ownerConstructor: null -}; - Mixin._apply = applyMixin; Mixin.applyPartial = function(obj) { var args = a_slice.call(arguments, 1); return applyMixin(obj, args, true); @@ -6061,42 +5486,10 @@ Mixin.finishPartial = finishPartial; Ember.anyUnprocessedMixins = false; /** - Creates an instance of a class. Accepts either no arguments, or an object - containing values to initialize the newly instantiated object with. - - ```javascript - App.Person = Ember.Object.extend({ - helloWorld: function() { - alert("Hi, my name is " + this.get('name')); - } - }); - - var tom = App.Person.create({ - name: 'Tom Dale' - }); - - tom.helloWorld(); // alerts "Hi, my name is Tom Dale". - ``` - - `create` will call the `init` function if defined during - `Ember.AnyObject.extend` - - If no arguments are passed to `create`, it will not set values to the new - instance during initialization: - - ```javascript - var noName = App.Person.create(); - noName.helloWorld(); // alerts undefined - ``` - - NOTE: For performance reasons, you cannot declare methods or computed - properties during `create`. You should instead declare methods and computed - properties when using `extend`. - @method create @static @param arguments* */ Mixin.create = function() { @@ -6273,11 +5666,11 @@ */ Ember.alias = function(methodName) { return new Alias(methodName); }; -Ember.alias = Ember.deprecateFunc("Ember.alias is deprecated. Please use Ember.aliasMethod or Ember.computed.alias instead.", Ember.alias); +Ember.deprecateFunc("Ember.alias is deprecated. Please use Ember.aliasMethod or Ember.computed.alias instead.", Ember.alias); /** Makes a method available via an additional name. ```javascript @@ -6361,73 +5754,30 @@ */ })(); (function() { -define("rsvp/all", - ["rsvp/defer","exports"], - function(__dependency1__, __exports__) { +define("rsvp", + [], + function() { "use strict"; - var defer = __dependency1__.defer; - - function all(promises) { - var results = [], deferred = defer(), remaining = promises.length; - - if (remaining === 0) { - deferred.resolve([]); - } - - var resolver = function(index) { - return function(value) { - resolveAll(index, value); - }; - }; - - var resolveAll = function(index, value) { - results[index] = value; - if (--remaining === 0) { - deferred.resolve(results); - } - }; - - var rejectAll = function(error) { - deferred.reject(error); - }; - - for (var i = 0; i < promises.length; i++) { - if (promises[i] && typeof promises[i].then === 'function') { - promises[i].then(resolver(i), rejectAll); - } else { - resolveAll(i, promises[i]); - } - } - return deferred.promise; - } - - __exports__.all = all; - }); - -define("rsvp/async", - ["exports"], - function(__exports__) { - "use strict"; var browserGlobal = (typeof window !== 'undefined') ? window : {}; - var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver; - var async; + var MutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver; + var RSVP, async; if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') { async = function(callback, binding) { process.nextTick(function() { callback.call(binding); }); }; - } else if (BrowserMutationObserver) { + } else if (MutationObserver) { var queue = []; - var observer = new BrowserMutationObserver(function() { + var observer = new MutationObserver(function() { var toProcess = queue.slice(); queue = []; toProcess.forEach(function(tuple) { var callback = tuple[0], binding = tuple[1]; @@ -6454,51 +5804,10 @@ callback.call(binding); }, 1); }; } - - __exports__.async = async; - }); - -define("rsvp/config", - ["rsvp/async","exports"], - function(__dependency1__, __exports__) { - "use strict"; - var async = __dependency1__.async; - - var config = {}; - config.async = async; - - __exports__.config = config; - }); - -define("rsvp/defer", - ["rsvp/promise","exports"], - function(__dependency1__, __exports__) { - "use strict"; - var Promise = __dependency1__.Promise; - - function defer() { - var deferred = {}; - - var promise = new Promise(function(resolve, reject) { - deferred.resolve = resolve; - deferred.reject = reject; - }); - - deferred.promise = promise; - return deferred; - } - - __exports__.defer = defer; - }); - -define("rsvp/events", - ["exports"], - function(__exports__) { - "use strict"; var Event = function(type, options) { this.type = type; for (var option in options) { if (!options.hasOwnProperty(option)) { continue; } @@ -6589,169 +5898,24 @@ } } } }; - - __exports__.EventTarget = EventTarget; - }); - -define("rsvp/hash", - ["rsvp/defer","exports"], - function(__dependency1__, __exports__) { - "use strict"; - var defer = __dependency1__.defer; - - function size(object) { - var size = 0; - - for (var prop in object) { - size++; - } - - return size; - } - - function hash(promises) { - var results = {}, deferred = defer(), remaining = size(promises); - - if (remaining === 0) { - deferred.resolve({}); - } - - var resolver = function(prop) { - return function(value) { - resolveAll(prop, value); - }; - }; - - var resolveAll = function(prop, value) { - results[prop] = value; - if (--remaining === 0) { - deferred.resolve(results); - } - }; - - var rejectAll = function(error) { - deferred.reject(error); - }; - - for (var prop in promises) { - if (promises[prop] && typeof promises[prop].then === 'function') { - promises[prop].then(resolver(prop), rejectAll); - } else { - resolveAll(prop, promises[prop]); - } - } - - return deferred.promise; - } - - __exports__.hash = hash; - }); - -define("rsvp/node", - ["rsvp/promise","rsvp/all","exports"], - function(__dependency1__, __dependency2__, __exports__) { - "use strict"; - var Promise = __dependency1__.Promise; - var all = __dependency2__.all; - - function makeNodeCallbackFor(resolve, reject) { - return function (error, value) { - if (error) { - reject(error); - } else if (arguments.length > 2) { - resolve(Array.prototype.slice.call(arguments, 1)); - } else { - resolve(value); - } - }; - } - - function denodeify(nodeFunc) { - return function() { - var nodeArgs = Array.prototype.slice.call(arguments), resolve, reject; - - var promise = new Promise(function(nodeResolve, nodeReject) { - resolve = nodeResolve; - reject = nodeReject; - }); - - all(nodeArgs).then(function(nodeArgs) { - nodeArgs.push(makeNodeCallbackFor(resolve, reject)); - - try { - nodeFunc.apply(this, nodeArgs); - } catch(e) { - reject(e); - } - }); - - return promise; - }; - } - - __exports__.denodeify = denodeify; - }); - -define("rsvp/promise", - ["rsvp/config","rsvp/events","exports"], - function(__dependency1__, __dependency2__, __exports__) { - "use strict"; - var config = __dependency1__.config; - var EventTarget = __dependency2__.EventTarget; - - function objectOrFunction(x) { - return isFunction(x) || (typeof x === "object" && x !== null); - } - - function isFunction(x){ - return typeof x === "function"; - } - - var Promise = function(resolver) { - var promise = this, - resolved = false; - - if (typeof resolver !== 'function') { - throw new TypeError('You must pass a resolver function as the sole argument to the promise constructor'); - } - - if (!(promise instanceof Promise)) { - return new Promise(resolver); - } - - var resolvePromise = function(value) { - if (resolved) { return; } - resolved = true; - resolve(promise, value); - }; - - var rejectPromise = function(value) { - if (resolved) { return; } - resolved = true; - reject(promise, value); - }; - + var Promise = function() { this.on('promise:resolved', function(event) { this.trigger('success', { detail: event.detail }); }, this); this.on('promise:failed', function(event) { this.trigger('error', { detail: event.detail }); }, this); - - try { - resolver(resolvePromise, rejectPromise); - } catch(e) { - rejectPromise(e); - } }; + var noop = function() {}; + var invokeCallback = function(type, promise, callback, event) { - var hasCallback = isFunction(callback), + var hasCallback = typeof callback === 'function', value, error, succeeded, failed; if (hasCallback) { try { value = callback(event.detail); @@ -6763,38 +5927,38 @@ } else { value = event.detail; succeeded = true; } - if (handleThenable(promise, value)) { - return; + if (value && typeof value.then === 'function') { + value.then(function(value) { + promise.resolve(value); + }, function(error) { + promise.reject(error); + }); } else if (hasCallback && succeeded) { - resolve(promise, value); + promise.resolve(value); } else if (failed) { - reject(promise, error); - } else if (type === 'resolve') { - resolve(promise, value); - } else if (type === 'reject') { - reject(promise, value); + promise.reject(error); + } else { + promise[type](value); } }; Promise.prototype = { - constructor: Promise, - then: function(done, fail) { - var thenPromise = new Promise(function() {}); + var thenPromise = new Promise(); - if (this.isFulfilled) { - config.async(function() { - invokeCallback('resolve', thenPromise, done, { detail: this.fulfillmentValue }); + if (this.isResolved) { + RSVP.async(function() { + invokeCallback('resolve', thenPromise, done, { detail: this.resolvedValue }); }, this); } if (this.isRejected) { - config.async(function() { - invokeCallback('reject', thenPromise, fail, { detail: this.rejectedReason }); + RSVP.async(function() { + invokeCallback('reject', thenPromise, fail, { detail: this.rejectedValue }); }, this); } this.on('promise:resolved', function(event) { invokeCallback('resolve', thenPromise, done, event); @@ -6803,175 +5967,94 @@ this.on('promise:failed', function(event) { invokeCallback('reject', thenPromise, fail, event); }); return thenPromise; - } - }; + }, - EventTarget.mixin(Promise.prototype); + resolve: function(value) { + resolve(this, value); - function resolve(promise, value) { - if (promise === value) { - fulfill(promise, value); - } else if (!handleThenable(promise, value)) { - fulfill(promise, value); - } - } + this.resolve = noop; + this.reject = noop; + }, - function handleThenable(promise, value) { - var then = null; + reject: function(value) { + reject(this, value); - if (objectOrFunction(value)) { - try { - then = value.then; - } catch(e) { - reject(promise, e); - return true; - } - - if (isFunction(then)) { - try { - then.call(value, function(val) { - if (value !== val) { - resolve(promise, val); - } else { - fulfill(promise, val); - } - }, function(val) { - reject(promise, val); - }); - } catch (e) { - reject(promise, e); - } - return true; - } + this.resolve = noop; + this.reject = noop; } + }; - return false; - } - - function fulfill(promise, value) { - config.async(function() { + function resolve(promise, value) { + RSVP.async(function() { promise.trigger('promise:resolved', { detail: value }); - promise.isFulfilled = true; - promise.fulfillmentValue = value; + promise.isResolved = true; + promise.resolvedValue = value; }); } function reject(promise, value) { - config.async(function() { + RSVP.async(function() { promise.trigger('promise:failed', { detail: value }); promise.isRejected = true; - promise.rejectedReason = value; + promise.rejectedValue = value; }); } + function all(promises) { + var i, results = []; + var allPromise = new Promise(); + var remaining = promises.length; - __exports__.Promise = Promise; - }); + if (remaining === 0) { + allPromise.resolve([]); + } -define("rsvp/reject", - ["rsvp/promise","exports"], - function(__dependency1__, __exports__) { - "use strict"; - var Promise = __dependency1__.Promise; + var resolver = function(index) { + return function(value) { + resolve(index, value); + }; + }; + var resolve = function(index, value) { + results[index] = value; + if (--remaining === 0) { + allPromise.resolve(results); + } + }; - function objectOrFunction(x) { - return typeof x === "function" || (typeof x === "object" && x !== null); - } + var reject = function(error) { + allPromise.reject(error); + }; - - function reject(reason) { - return new Promise(function (resolve, reject) { - reject(reason); - }); + for (i = 0; i < remaining; i++) { + promises[i].then(resolver(i), reject); + } + return allPromise; } + EventTarget.mixin(Promise.prototype); - __exports__.reject = reject; + RSVP = { async: async, Promise: Promise, Event: Event, EventTarget: EventTarget, all: all, raiseOnUncaughtExceptions: true }; + return RSVP; }); -define("rsvp/resolve", - ["rsvp/promise","exports"], - function(__dependency1__, __exports__) { - "use strict"; - var Promise = __dependency1__.Promise; - - - function objectOrFunction(x) { - return typeof x === "function" || (typeof x === "object" && x !== null); - } - - function resolve(thenable){ - var promise = new Promise(function(resolve, reject){ - var then; - - try { - if ( objectOrFunction(thenable) ) { - then = thenable.then; - - if (typeof then === "function") { - then.call(thenable, resolve, reject); - } else { - resolve(thenable); - } - - } else { - resolve(thenable); - } - - } catch(error) { - reject(error); - } - }); - - return promise; - } - - - __exports__.resolve = resolve; - }); - -define("rsvp", - ["rsvp/events","rsvp/promise","rsvp/node","rsvp/all","rsvp/hash","rsvp/defer","rsvp/config","rsvp/resolve","rsvp/reject","exports"], - function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) { - "use strict"; - var EventTarget = __dependency1__.EventTarget; - var Promise = __dependency2__.Promise; - var denodeify = __dependency3__.denodeify; - var all = __dependency4__.all; - var hash = __dependency5__.hash; - var defer = __dependency6__.defer; - var config = __dependency7__.config; - var resolve = __dependency8__.resolve; - var reject = __dependency9__.reject; - - function configure(name, value) { - config[name] = value; - } - - - __exports__.Promise = Promise; - __exports__.EventTarget = EventTarget; - __exports__.all = all; - __exports__.hash = hash; - __exports__.defer = defer; - __exports__.denodeify = denodeify; - __exports__.configure = configure; - __exports__.resolve = resolve; - __exports__.reject = reject; - }); - })(); (function() { define("container", [], function() { + var objectCreate = Object.create || function(parent) { + function F() {} + F.prototype = parent; + return new F(); + }; + function InheritingDict(parent) { this.parent = parent; this.dict = {}; } @@ -7042,47 +6125,38 @@ }, register: function(type, name, factory, options) { var fullName; + if (type.indexOf(':') !== -1){ options = factory; factory = name; fullName = type; } else { - Ember.deprecate('register("'+type +'", "'+ name+'") is now deprecated in-favour of register("'+type+':'+name+'");', false); + Ember.deprecate('register("'+type +'", "'+ name+'") is now deprecated in-favour of register("'+type+':'+name+'");', true); fullName = type + ":" + name; } - var normalizedName = this.normalize(fullName); - - this.registry.set(normalizedName, factory); - this._options.set(normalizedName, options || {}); + this.registry.set(fullName, factory); + this._options.set(fullName, options || {}); }, resolve: function(fullName) { return this.resolver(fullName) || this.registry.get(fullName); }, - normalize: function(fullName) { - return fullName; - }, - - lookup: function(fullName, options) { - fullName = this.normalize(fullName); - - options = options || {}; - - if (this.cache.has(fullName) && options.singleton !== false) { + lookup: function(fullName) { + if (this.cache.has(fullName)) { return this.cache.get(fullName); } var value = instantiate(this, fullName); if (!value) { return; } - if (isSingleton(this, fullName) && options.singleton !== false) { + if (isSingleton(this, fullName)) { this.cache.set(fullName, value); } return value; }, @@ -7196,19 +6270,18 @@ return options[optionName]; } } function factoryFor(container, fullName) { - var name = container.normalize(fullName); - return container.resolve(name); + return container.resolve(fullName); } function instantiate(container, fullName) { var factory = factoryFor(container, fullName); var splitName = fullName.split(":"), - type = splitName[0], + type = splitName[0], name = splitName[1], value; if (option(container, fullName, 'instantiate') === false) { return factory; } @@ -7255,11 +6328,135 @@ @submodule ember-runtime */ var indexOf = Ember.EnumerableUtils.indexOf; +// ........................................ +// TYPING & ARRAY MESSAGING +// + +var TYPE_MAP = {}; +var t = "Boolean Number String Function Array Date RegExp Object".split(" "); +Ember.ArrayPolyfills.forEach.call(t, function(name) { + TYPE_MAP[ "[object " + name + "]" ] = name.toLowerCase(); +}); + +var toString = Object.prototype.toString; + /** + Returns a consistent 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 | Meaning | + |---------------|------------------------------------------------------| + | 'string' | String primitive | + | 'number' | Number primitive | + | 'boolean' | Boolean primitive | + | 'null' | Null value | + | 'undefined' | Undefined value | + | 'function' | A function | + | 'array' | An instance of Array | + | 'class' | A Ember class (created using Ember.Object.extend()) | + | 'instance' | A Ember object instance | + | 'error' | An instance of the Error object | + | 'object' | A JavaScript object not inheriting from Ember.Object | + + Examples: + + ```javascript + Ember.typeOf(); // 'undefined' + Ember.typeOf(null); // 'null' + Ember.typeOf(undefined); // 'undefined' + Ember.typeOf('michael'); // 'string' + Ember.typeOf(101); // 'number' + Ember.typeOf(true); // 'boolean' + Ember.typeOf(Ember.makeArray); // 'function' + Ember.typeOf([1,2,90]); // 'array' + Ember.typeOf(Ember.Object.extend()); // 'class' + Ember.typeOf(Ember.Object.create()); // 'instance' + Ember.typeOf(new Error('teamocil')); // 'error' + + // "normal" JavaScript object + Ember.typeOf({a: 'b'}); // 'object' + ``` + + @method typeOf + @for Ember + @param item {Object} the item to check + @return {String} the type +*/ +Ember.typeOf = function(item) { + var ret; + + ret = (item === null || item === undefined) ? String(item) : TYPE_MAP[toString.call(item)] || 'object'; + + if (ret === 'function') { + if (Ember.Object && Ember.Object.detect(item)) ret = 'class'; + } else if (ret === 'object') { + if (item instanceof Error) ret = 'error'; + else if (Ember.Object && item instanceof Ember.Object) ret = 'instance'; + else ret = 'object'; + } + + return ret; +}; + +/** + Returns true if the passed value is null or undefined. This avoids errors + from JSLint complaining about use of ==, which can be technically + confusing. + + ```javascript + Ember.isNone(); // true + Ember.isNone(null); // true + Ember.isNone(undefined); // true + Ember.isNone(''); // false + Ember.isNone([]); // false + Ember.isNone(function(){}); // false + ``` + + @method isNone + @for Ember + @param {Object} obj Value to test + @return {Boolean} +*/ +Ember.isNone = function(obj) { + return obj === null || obj === undefined; +}; +Ember.none = Ember.deprecateFunc("Ember.none is deprecated. Please use Ember.isNone instead.", Ember.isNone); + +/** + Verifies that a value is `null` or an empty string, empty array, + or empty function. + + Constrains the rules on `Ember.isNone` by returning false for empty + string and empty arrays. + + ```javascript + Ember.isEmpty(); // true + Ember.isEmpty(null); // true + Ember.isEmpty(undefined); // true + Ember.isEmpty(''); // true + Ember.isEmpty([]); // true + Ember.isEmpty('Adam Hawkins'); // false + Ember.isEmpty([0,1,2]); // false + ``` + + @method isEmpty + @for Ember + @param {Object} obj Value to test + @return {Boolean} +*/ +Ember.isEmpty = function(obj) { + return obj === null || obj === undefined || (obj.length === 0 && typeof obj !== 'function') || (typeof obj === 'object' && Ember.get(obj, 'length') === 0); +}; +Ember.empty = Ember.deprecateFunc("Ember.empty is deprecated. Please use Ember.isEmpty instead.", Ember.isEmpty) ; + +/** 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, @@ -7419,11 +6616,11 @@ If the passed object implements the `clone()` method, then this function will simply call that method and return the result. @method copy @for Ember - @param {Object} obj The object to clone + @param {Object} object The object to clone @param {Boolean} deep If true, a deep copy of the object is made @return {Object} The cloned object */ Ember.copy = function(obj, deep) { // fast paths @@ -7534,11 +6731,11 @@ @namespace Ember @extends Error @constructor */ Ember.Error = function() { - var tmp = Error.apply(this, arguments); + var tmp = Error.prototype.constructor.apply(this, arguments); // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. for (var idx = 0; idx < errorProps.length; idx++) { this[errorProps[idx]] = tmp[errorProps[idx]]; } @@ -7550,24 +6747,10 @@ (function() { /** - Expose RSVP implementation - - @class RSVP - @namespace Ember - @constructor -*/ -Ember.RSVP = requireModule('rsvp'); - -})(); - - - -(function() { -/** @module ember @submodule ember-runtime */ var STRING_DASHERIZE_REGEXP = (/[ _]/g); @@ -7613,12 +6796,11 @@ "Hello %@ %@".fmt('John', 'Doe'); // "Hello John Doe" "Hello %@2, %@1".fmt('John', 'Doe'); // "Hello Doe, John" ``` @method fmt - @param {String} str The string to format - @param {Array} formats An array of parameters to interpolate into string. + @param {Object...} [args] @return {String} formatted string */ fmt: function(str, formats) { // first, replace any ORDERED replacements. var idx = 0; // the current index for non-numerical replacements @@ -7711,43 +6893,39 @@ @param {String} str The string to dasherize. @return {String} the dasherized string. */ dasherize: function(str) { var cache = STRING_DASHERIZE_CACHE, - hit = cache.hasOwnProperty(str), - ret; + ret = cache[str]; - if (hit) { - return cache[str]; + if (ret) { + return ret; } else { ret = Ember.String.decamelize(str).replace(STRING_DASHERIZE_REGEXP,'-'); cache[str] = ret; } return ret; }, /** - Returns the lowerCamelCase form of a string. + Returns the lowerCaseCamel form of a string. ```javascript 'innerHTML'.camelize(); // 'innerHTML' 'action_name'.camelize(); // 'actionName' 'css-class-name'.camelize(); // 'cssClassName' 'my favorite items'.camelize(); // 'myFavoriteItems' - 'My Favorite Items'.camelize(); // 'myFavoriteItems' ``` @method camelize @param {String} str The string to camelize. @return {String} the camelized string. */ camelize: function(str) { return str.replace(STRING_CAMELIZE_REGEXP, function(match, separator, chr) { return chr ? chr.toUpperCase() : ''; - }).replace(/^([A-Z])/, function(match, separator, chr) { - return match.toLowerCase(); }); }, /** Returns the UpperCamelCase form of a string. @@ -7755,11 +6933,11 @@ ```javascript 'innerHTML'.classify(); // 'InnerHTML' 'action_name'.classify(); // 'ActionName' 'css-class-name'.classify(); // 'CssClassName' 'my favorite items'.classify(); // 'MyFavoriteItems' - ``` + ``` @method classify @param {String} str the string to classify @return {String} the classified string */ @@ -7796,16 +6974,14 @@ }, /** Returns the Capitalized form of a string - ```javascript - 'innerHTML'.capitalize() // 'InnerHTML' - 'action_name'.capitalize() // 'Action_name' - 'css-class-name'.capitalize() // 'Css-class-name' - 'my favorite items'.capitalize() // 'My favorite items' - ``` + 'innerHTML'.capitalize() => 'InnerHTML' + 'action_name'.capitalize() => 'Action_name' + 'css-class-name'.capitalize() => 'Css-class-name' + 'my favorite items'.capitalize() => 'My favorite items' @method capitalize @param {String} str @return {String} */ @@ -8145,11 +7321,12 @@ @class Enumerable @namespace Ember @extends Ember.Mixin @since Ember 0.9 */ -Ember.Enumerable = Ember.Mixin.create({ +Ember.Enumerable = Ember.Mixin.create( + /** @scope Ember.Enumerable.prototype */ { // compatibility isEnumerable: true, /** @@ -8178,11 +7355,11 @@ The default implementation of this method simply looks up the index. This works great on any Array-like objects. @method nextObject @param {Number} index the current index of the iteration - @param {Object} previousObject the value returned by the last call to + @param {Object} previousObject the value returned by the last call to `nextObject`. @param {Object} context a context object you can use to maintain state. @return {Object} the next object in the iteration or undefined */ nextObject: Ember.required(Function), @@ -8197,14 +7374,14 @@ contains only one object, this method should always return that object. If your enumerable is empty, this method should return `undefined`. ```javascript var arr = ["a", "b", "c"]; - arr.get('firstObject'); // "a" + arr.firstObject(); // "a" var arr = []; - arr.get('firstObject'); // undefined + arr.firstObject(); // undefined ``` @property firstObject @return {Object} the object or undefined */ @@ -8223,14 +7400,14 @@ contains only one object, this method should always return that object. If your enumerable is empty, this method should return `undefined`. ```javascript var arr = ["a", "b", "c"]; - arr.get('lastObject'); // "c" + arr.lastObject(); // "c" var arr = []; - arr.get('lastObject'); // undefined + arr.lastObject(); // undefined ``` @property lastObject @return {Object} the last object or undefined */ @@ -8359,11 +7536,11 @@ @param {Function} callback The callback to execute @param {Object} [target] The target object to use @return {Array} The mapped array. */ map: function(callback, target) { - var ret = Ember.A([]); + var ret = []; this.forEach(function(x, idx, i) { ret[idx] = callback.call(target, x, idx,i); }); return ret ; }, @@ -8409,11 +7586,11 @@ @param {Function} callback The callback to execute @param {Object} [target] The target object to use @return {Array} A filtered array. */ filter: function(callback, target) { - var ret = Ember.A([]); + var ret = []; this.forEach(function(x, idx, i) { if (callback.call(target, x, idx, i)) ret.push(x); }); return ret ; }, @@ -8698,11 +7875,11 @@ @param {String} methodName the name of the method @param {Object...} args optional arguments to pass as well. @return {Array} return values from calling invoke. */ invoke: function(methodName) { - var args, ret = Ember.A([]); + var args, ret = []; if (arguments.length>1) args = a_slice.call(arguments, 1); this.forEach(function(x, idx) { var method = x && x[methodName]; if ('function' === typeof method) { @@ -8719,29 +7896,27 @@ @method toArray @return {Array} the enumerable as an array. */ toArray: function() { - var ret = Ember.A([]); + var ret = []; this.forEach(function(o, idx) { ret[idx] = o; }); return ret ; }, /** - Returns a copy of the array with all null and undefined elements removed. + Returns a copy of the array with all null elements removed. ```javascript - var arr = ["a", null, "c", undefined]; + var arr = ["a", null, "c", null]; arr.compact(); // ["a", "c"] ``` @method compact - @return {Array} the array without null and undefined elements. + @return {Array} the array without null elements. */ - compact: function() { - return this.filter(function(value) { return value != null; }); - }, + compact: function() { return this.without(null); }, /** Returns a new enumerable that excludes the passed value. The default implementation returns an array regardless of the receiver type unless the receiver does not contain the value. @@ -8755,11 +7930,11 @@ @param {Object} value @return {Ember.Enumerable} */ without: function(value) { if (!this.contains(value)) return this; // nothing to do - var ret = Ember.A([]); + var ret = [] ; this.forEach(function(k) { if (k !== value) ret[ret.length] = k; }) ; return ret ; }, @@ -8775,11 +7950,11 @@ @method uniq @return {Ember.Enumerable} */ uniq: function() { - var ret = Ember.A([]); + var ret = []; this.forEach(function(k){ if (a_indexOf(ret, k)<0) ret.push(k); }); return ret; }, @@ -8792,11 +7967,10 @@ For plain enumerables, this property is read only. `Ember.Array` overrides this method. @property [] @type Ember.Array - @return this */ '[]': Ember.computed(function(key, value) { return this; }), @@ -8807,13 +7981,12 @@ /** Registers an enumerable observer. Must implement `Ember.EnumerableObserver` mixin. @method addEnumerableObserver - @param {Object} target - @param {Hash} [opts] - @return this + @param target {Object} + @param opts {Hash} */ addEnumerableObserver: function(target, opts) { var willChange = (opts && opts.willChange) || 'enumerableWillChange', didChange = (opts && opts.didChange) || 'enumerableDidChange'; @@ -8827,13 +8000,12 @@ /** Removes a registered enumerable observer. @method removeEnumerableObserver - @param {Object} target - @param {Hash} [opts] - @return this + @param target {Object} + @param [opts] {Hash} */ removeEnumerableObserver: function(target, opts) { var willChange = (opts && opts.willChange) || 'enumerableWillChange', didChange = (opts && opts.didChange) || 'enumerableDidChange'; @@ -8908,11 +8080,11 @@ @param {Ember.Enumerable|Number} adding An enumerable of the objects to be added or the number of items to be added. @chainable */ enumerableContentDidChange: function(removing, adding) { - var removeCnt, addCnt, hasDelta; + var notify = this.propertyDidChange, removeCnt, addCnt, hasDelta; if ('number' === typeof removing) removeCnt = removing; else if (removing) removeCnt = get(removing, 'length'); else removeCnt = removing = -1; @@ -8946,12 +8118,14 @@ // .......................................................... // HELPERS // -var get = Ember.get, set = Ember.set, isNone = Ember.isNone, map = Ember.EnumerableUtils.map, cacheFor = Ember.cacheFor; +var get = Ember.get, set = Ember.set, meta = Ember.meta, map = Ember.EnumerableUtils.map, cacheFor = Ember.cacheFor; +function none(obj) { return obj===null || obj===undefined; } + // .......................................................... // ARRAY // /** This module implements Observer-friendly Array-like behavior. This mixin is @@ -8969,11 +8143,11 @@ 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 of an array changes by changing the syntax of the property to + membership if an array changes by changing the syntax of the property to `.observes('*myProperty.[]')`. To support `Ember.Array` in your own class, you must override two primitives to use it: `replace()` and `objectAt()`. @@ -8986,10 +8160,13 @@ @uses Ember.Enumerable @since Ember 0.9.0 */ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.prototype */ { + // compatibility + isSCArray: true, + /** Your array must support the `length` property. Your replace methods should set this property whenever it changes. @property {Number} length @@ -9014,11 +8191,10 @@ arr.objectAt(5); // undefined ``` @method objectAt @param {Number} idx The index of the item to return. - @return {*} item at index or undefined */ objectAt: function(idx) { if ((idx < 0) || (idx>=get(this, 'length'))) return undefined ; return get(this, idx); }, @@ -9032,11 +8208,10 @@ arr.objectsAt([2, 3, 4]); // ["c", "d", undefined] ``` @method objectsAt @param {Array} indexes An array of indexes of items to return. - @return {Array} */ objectsAt: function(indexes) { var self = this; return map(indexes, function(idx){ return self.objectAt(idx); }); }, @@ -9052,11 +8227,10 @@ array, it will replace the current content. This property overrides the default property defined in `Ember.Enumerable`. @property [] - @return this */ '[]': Ember.computed(function(key, value) { if (value !== undefined) this.replace(0, get(this, 'length'), value) ; return this ; }), @@ -9086,23 +8260,19 @@ arr.slice(0, 2); // ['red', 'green'] arr.slice(1, 100); // ['green', 'blue'] ``` @method slice - @param {Integer} beginIndex (Optional) index to begin slicing from. - @param {Integer} endIndex (Optional) index to end the slice at. + @param beginIndex {Integer} (Optional) index to begin slicing from. + @param endIndex {Integer} (Optional) index to end the slice at. @return {Array} New array with specified slice */ slice: function(beginIndex, endIndex) { - var ret = Ember.A([]); + var ret = []; var length = get(this, 'length') ; - if (isNone(beginIndex)) beginIndex = 0 ; - if (isNone(endIndex) || (endIndex > length)) endIndex = length ; - - if (beginIndex < 0) beginIndex = length + beginIndex; - if (endIndex < 0) endIndex = length + endIndex; - + if (none(beginIndex)) beginIndex = 0 ; + if (none(endIndex) || (endIndex > length)) endIndex = length ; while(beginIndex < endIndex) { ret[ret.length] = this.objectAt(beginIndex++) ; } return ret ; }, @@ -9196,11 +8366,11 @@ target. @method addArrayObserver @param {Object} target The observer object. @param {Hash} opts Optional hash of configuration options including - `willChange` and `didChange` option. + `willChange`, `didChange`, and a `context` option. @return {Ember.Array} receiver */ addArrayObserver: function(target, opts) { var willChange = (opts && opts.willChange) || 'arrayWillChange', didChange = (opts && opts.didChange) || 'arrayDidChange'; @@ -9218,12 +8388,10 @@ registered. Calling this method multiple times with the same object will have no effect. @method removeArrayObserver @param {Object} target The object observing the array. - @param {Hash} opts Optional hash of configuration options including - `willChange` and `didChange` option. @return {Ember.Array} receiver */ removeArrayObserver: function(target, opts) { var willChange = (opts && opts.willChange) || 'arrayWillChange', didChange = (opts && opts.didChange) || 'arrayDidChange'; @@ -9252,13 +8420,13 @@ invalidate any related properties. Pass the starting index of the change as well as a delta of the amounts to change. @method arrayContentWillChange @param {Number} startIdx The starting index in the array that will change. - @param {Number} removeAmt The number of items that will be removed. If you + @param {Number} removeAmt The number of items that will be removed. If you pass `null` assumes 0 - @param {Number} addAmt The number of items that will be added. If you + @param {Number} addAmt The number of items that will be added If you pass `null` assumes 0. @return {Ember.Array} receiver */ arrayContentWillChange: function(startIdx, removeAmt, addAmt) { @@ -9288,24 +8456,10 @@ this.enumerableContentWillChange(removing, addAmt); return this; }, - /** - If you are implementing an object that supports `Ember.Array`, call this - method just after the array content changes to notify any observers and - invalidate any related properties. Pass the starting index of the change - as well as a delta of the amounts to change. - - @method arrayContentDidChange - @param {Number} startIdx The starting index in the array that did change. - @param {Number} removeAmt The number of items that were removed. If you - pass `null` assumes 0 - @param {Number} addAmt The number of items that were added. If you - pass `null` assumes 0. - @return {Ember.Array} receiver - */ arrayContentDidChange: function(startIdx, removeAmt, addAmt) { // if no args are passed assume everything changes if (startIdx===undefined) { startIdx = 0; @@ -9442,18 +8596,19 @@ @class Copyable @namespace Ember @extends Ember.Mixin @since Ember 0.9 */ -Ember.Copyable = Ember.Mixin.create(/** @scope Ember.Copyable.prototype */ { +Ember.Copyable = Ember.Mixin.create( +/** @scope Ember.Copyable.prototype */ { /** Override to return a copy of the receiver. Default implementation raises an exception. @method copy - @param {Boolean} deep if `true`, a deep copy of the object should be made + @param deep {Boolean} if `true`, a deep copy of the object should be made @return {Object} copy of receiver */ copy: Ember.required(Function), /** @@ -9547,11 +8702,12 @@ @class Freezable @namespace Ember @extends Ember.Mixin @since Ember 0.9 */ -Ember.Freezable = Ember.Mixin.create(/** @scope Ember.Freezable.prototype */ { +Ember.Freezable = Ember.Mixin.create( +/** @scope Ember.Freezable.prototype */ { /** Set to `true` when the object is frozen. Use this property to detect whether your object is frozen or not. @@ -9600,20 +8756,20 @@ ## Adding Objects To add an object to an enumerable, use the `addObject()` method. This method will only add the object to the enumerable if the object is not - already present and is of a type supported by the enumerable. + already present and the object if of a type supported by the enumerable. ```javascript set.addObject(contact); ``` ## Removing Objects - To remove an object from an enumerable, use the `removeObject()` method. This - will only remove the object if it is present in the enumerable, otherwise + To remove an object form an enumerable, use the `removeObject()` method. This + will only remove the object if it is already in the enumerable, otherwise this method has no effect. ```javascript set.removeObject(contact); ``` @@ -9627,20 +8783,21 @@ @class MutableEnumerable @namespace Ember @extends Ember.Mixin @uses Ember.Enumerable */ -Ember.MutableEnumerable = Ember.Mixin.create(Ember.Enumerable, { +Ember.MutableEnumerable = Ember.Mixin.create(Ember.Enumerable, + /** @scope Ember.MutableEnumerable.prototype */ { /** __Required.__ You must implement this method to apply this mixin. Attempts to add the passed object to the receiver if the object is not already present in the collection. If the object is present, this method has no effect. - If the passed object is of a type not supported by the receiver, + If the passed object is of a type not supported by the receiver then this method should raise an exception. @method addObject @param {Object} object The object to add to the enumerable. @return {Object} the passed object @@ -9663,25 +8820,25 @@ /** __Required.__ You must implement this method to apply this mixin. Attempts to remove the passed object from the receiver collection if the - object is present in the collection. If the object is not present, + object is in present in the collection. If the object is not present, this method has no effect. - If the passed object is of a type not supported by the receiver, + If the passed object is of a type not supported by the receiver then this method should raise an exception. @method removeObject @param {Object} object The object to remove from the enumerable. @return {Object} the passed object */ removeObject: Ember.required(Function), /** - Removes each object in the passed enumerable from the receiver. + Removes each objects in the passed enumerable from the receiver. @method removeObjects @param {Ember.Enumerable} objects the objects to remove @return {Object} receiver */ @@ -9712,11 +8869,11 @@ // .......................................................... // HELPERS // -var get = Ember.get, set = Ember.set; +var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach; /** This mixin defines the API for modifying array-like objects. These methods can be applied only to a collection that keeps its items in an ordered set. @@ -9728,25 +8885,26 @@ @namespace Ember @extends Ember.Mixin @uses Ember.Array @uses Ember.MutableEnumerable */ -Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,/** @scope Ember.MutableArray.prototype */ { +Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable, + /** @scope Ember.MutableArray.prototype */ { /** __Required.__ You must implement this method to apply this mixin. This is one of the primitives you must implement to support `Ember.Array`. You should replace amt objects started at idx with the objects in the passed array. You should also call `this.enumerableContentDidChange()` @method replace - @param {Number} idx Starting index in the array to replace. If + @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 + @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 + @param {Array} objects An array of zero or more objects that should be inserted into the array at *idx* */ replace: Ember.required(), /** @@ -9781,11 +8939,10 @@ ``` @method insertAt @param {Number} idx index of insert the object at. @param {Object} object object to insert - @return this */ insertAt: function(idx, object) { if (idx > get(this, 'length')) throw new Error(OUT_OF_RANGE_EXCEPTION) ; this.replace(idx, 0, [object]) ; return this ; @@ -9834,12 +8991,11 @@ colors.pushObject("black"); // ["red", "green", "blue", "black"] colors.pushObject(["yellow", "orange"]); // ["red", "green", "blue", "black", ["yellow", "orange"]] ``` @method pushObject - @param {*} obj object to push - @return {*} the same obj passed as param + @param {anything} obj object to push */ pushObject: function(obj) { this.insertAt(get(this, 'length'), obj) ; return obj ; }, @@ -9914,12 +9070,11 @@ colors.unshiftObject("yellow"); // ["yellow", "red", "green", "blue"] colors.unshiftObject(["black", "white"]); // [["black", "white"], "yellow", "red", "green", "blue"] ``` @method unshiftObject - @param {*} obj object to unshift - @return {*} the same obj passed as param + @param {anything} obj object to unshift */ unshiftObject: function(obj) { this.insertAt(0, obj) ; return obj ; }, @@ -9999,21 +9154,22 @@ return this ; } }); + })(); (function() { /** @module ember @submodule ember-runtime */ -var get = Ember.get, set = Ember.set; +var get = Ember.get, set = Ember.set, defineProperty = Ember.defineProperty; /** ## Overview This mixin provides properties and property observing functionality, core @@ -10046,11 +9202,11 @@ 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: ```javascript - Ember.Object.extend({ + Ember.Object.create({ valueObserver: function() { // Executes whenever the "value" property changes }.observes('value') }); ``` @@ -10065,12 +9221,12 @@ ```javascript object.addObserver('propertyKey', targetObject, targetAction) ``` - This will call the `targetAction` method on the `targetObject` whenever - the value of the `propertyKey` changes. + This will call the `targetAction` method on the `targetObject` to be called + whenever the value of the `propertyKey` changes. Note that if `propertyKey` is a computed property, the observer will be called when any of the property dependencies are changed, even if the resulting value of the computed property is unchanged. This is necessary because computed properties are not computed until `get` is called. @@ -10114,11 +9270,11 @@ If this method returns any value other than `undefined`, it will be returned instead. This allows you to implement "virtual" properties that are not defined upfront. @method get - @param {String} keyName The property to retrieve + @param {String} key The property to retrieve @return {Object} The property value or undefined. */ get: function(keyName) { return get(this, keyName); }, @@ -10195,11 +9351,11 @@ ```javascript record.set('firstName', 'Charles').set('lastName', 'Jolley'); ``` @method set - @param {String} keyName The property to set + @param {String} key The property to set @param {Object} value The value to set or `null`. @return {Ember.Observable} */ set: function(keyName, value) { set(this, keyName, value); @@ -10272,11 +9428,11 @@ 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. @method propertyWillChange - @param {String} keyName The property key that is about to change. + @param {String} key The property key that is about to change. @return {Ember.Observable} */ propertyWillChange: function(keyName){ Ember.propertyWillChange(this, keyName); return this; @@ -10326,12 +9482,12 @@ /** Adds an observer on a property. This is the core method used to register an observer for a property. - Once you call this method, any time the key's value is set, your observer - will be notified. Note that the observers are triggered any time the + 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. @@ -10452,15 +9608,15 @@ team.incrementProperty('score', 2); ``` @method incrementProperty @param {String} keyName The name of the property to increment - @param {Number} increment The amount to increment by. Defaults to 1 - @return {Number} The new property value + @param {Object} increment The amount to increment by. Defaults to 1 + @return {Object} The new property value */ incrementProperty: function(keyName, increment) { - if (Ember.isNone(increment)) { increment = 1; } + if (!increment) { increment = 1; } set(this, keyName, (get(this, keyName) || 0)+increment); return get(this, keyName); }, /** @@ -10471,25 +9627,25 @@ orc.decrementProperty('health', 5); ``` @method decrementProperty @param {String} keyName The name of the property to decrement - @param {Number} decrement The amount to decrement by. Defaults to 1 - @return {Number} The new property value + @param {Object} increment The amount to decrement by. Defaults to 1 + @return {Object} The new property value */ - decrementProperty: function(keyName, decrement) { - if (Ember.isNone(decrement)) { decrement = 1; } - set(this, keyName, (get(this, keyName) || 0)-decrement); + decrementProperty: function(keyName, increment) { + if (!increment) { increment = 1; } + set(this, keyName, (get(this, keyName) || 0)-increment); return get(this, keyName); }, /** Set the value of a boolean property to the opposite of it's current value. ```javascript - starship.toggleProperty('warpDriveEngaged'); + starship.toggleProperty('warpDriveEnaged'); ``` @method toggleProperty @param {String} keyName The name of the property to toggle @return {Object} The new property value @@ -10517,10 +9673,11 @@ observersForKey: function(keyName) { return Ember.observersFor(this, keyName); } }); + })(); (function() { @@ -10530,27 +9687,17 @@ */ var get = Ember.get, set = Ember.set; /** -`Ember.TargetActionSupport` is a mixin that can be included in a class -to add a `triggerAction` method with semantics similar to the Handlebars -`{{action}}` helper. In normal Ember usage, the `{{action}}` helper is -usually the best choice. This mixin is most often useful when you are -doing more complex event handling in View objects. - -See also `Ember.ViewTargetActionSupport`, which has -view-aware defaults for target and actionContext. - @class TargetActionSupport @namespace Ember @extends Ember.Mixin */ Ember.TargetActionSupport = Ember.Mixin.create({ target: null, action: null, - actionContext: null, targetObject: Ember.computed(function() { var target = get(this, 'target'); if (Ember.typeOf(target) === "string") { @@ -10560,90 +9707,25 @@ } else { return target; } }).property('target'), - actionContextObject: Ember.computed(function() { - var actionContext = get(this, 'actionContext'); + triggerAction: function() { + var action = get(this, 'action'), + target = get(this, 'targetObject'); - if (Ember.typeOf(actionContext) === "string") { - var value = get(this, actionContext); - if (value === undefined) { value = get(Ember.lookup, actionContext); } - return value; - } else { - return actionContext; - } - }).property('actionContext'), - - /** - Send an "action" with an "actionContext" to a "target". The action, actionContext - and target will be retrieved from properties of the object. For example: - - ```javascript - App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, { - target: Ember.computed.alias('controller'), - action: 'save', - actionContext: Ember.computed.alias('context'), - click: function(){ - this.triggerAction(); // Sends the `save` action, along with the current context - // to the current controller - } - }); - ``` - - The `target`, `action`, and `actionContext` can be provided as properties of - an optional object argument to `triggerAction` as well. - - ```javascript - App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, { - click: function(){ - this.triggerAction({ - action: 'save', - target: this.get('controller'), - actionContext: this.get('context'), - }); // Sends the `save` action, along with the current context - // to the current controller - } - }); - ``` - - The `actionContext` defaults to the object you mixing `TargetActionSupport` into. - But `target` and `action` must be specified either as properties or with the argument - to `triggerAction`, or a combination: - - ```javascript - App.SaveButtonView = Ember.View.extend(Ember.TargetActionSupport, { - target: Ember.computed.alias('controller'), - click: function(){ - this.triggerAction({ - action: 'save' - }); // Sends the `save` action, along with a reference to `this`, - // to the current controller - } - }); - ``` - - @method triggerAction - @param opts {Hash} (optional, with the optional keys action, target and/or actionContext) - @return {Boolean} true if the action was sent successfully and did not return false - */ - triggerAction: function(opts) { - opts = opts || {}; - var action = opts['action'] || get(this, 'action'), - target = opts['target'] || get(this, 'targetObject'), - actionContext = opts['actionContext'] || get(this, 'actionContextObject') || this; - if (target && action) { var ret; - if (target.send) { - ret = target.send.apply(target, [action, actionContext]); + if (typeof target.send === 'function') { + ret = target.send(action, this); } else { - Ember.assert("The action '" + action + "' did not exist on " + target, typeof target[action] === 'function'); - ret = target[action].apply(target, [actionContext]); + if (typeof action === 'string') { + action = target[action]; + } + ret = action.call(target, this); } - if (ret !== false) ret = true; return ret; } else { return false; @@ -10681,20 +9763,10 @@ person.greet(); // outputs: 'Our person has greeted' ``` - You can also chain multiple event subscriptions: - - ```javascript - person.on('greet', function() { - console.log('Our person has greeted'); - }).one('greet', function() { - console.log('Offer one-time special'); - }).off('event', this, forgetThis); - ``` - @class Evented @namespace Ember @extends Ember.Mixin */ Ember.Evented = Ember.Mixin.create({ @@ -10715,15 +9787,13 @@ @method on @param {String} name The name of the event @param {Object} [target] The "this" binding for the callback @param {Function} method The callback to execute - @return this */ on: function(name, target, method) { Ember.addListener(this, name, target, method); - return this; }, /** Subscribes a function to a named event and then cancels the subscription after the first time the event is triggered. It is good to use ``one`` when @@ -10735,20 +9805,18 @@ @method one @param {String} name The name of the event @param {Object} [target] The "this" binding for the callback @param {Function} method The callback to execute - @return this */ one: function(name, target, method) { if (!method) { method = target; target = null; } Ember.addListener(this, name, target, method, true); - return this; }, /** Triggers a named event for the object. Any additional arguments will be passed as parameters to the functions that are subscribed to the @@ -10785,15 +9853,13 @@ @method off @param {String} name The name of the event @param {Object} target The target of the subscription @param {Function} method The function of the subscription - @return this */ off: function(name, target, method) { Ember.removeListener(this, name, target, method); - return this; }, /** Checks to see if object has any subscriptions for named event. @@ -10811,20 +9877,21 @@ (function() { var RSVP = requireModule("rsvp"); -RSVP.configure('async', function(callback, binding) { +RSVP.async = function(callback, binding) { Ember.run.schedule('actions', binding, callback); -}); +}; /** @module ember @submodule ember-runtime */ -var get = Ember.get; +var get = Ember.get, + slice = Array.prototype.slice; /** @class Deferred @namespace Ember @extends Ember.Mixin @@ -10835,57 +9902,35 @@ @method then @param {Function} doneCallback a callback function to be called when done @param {Function} failCallback a callback function to be called when failed */ - then: function(resolve, reject) { - var deferred, promise, entity; - - entity = this; - deferred = get(this, '_deferred'); - promise = deferred.promise; - - function fulfillmentHandler(fulfillment) { - if (fulfillment === promise) { - return resolve(entity); - } else { - return resolve(fulfillment); - } - } - - return promise.then(resolve && fulfillmentHandler, reject); + then: function(doneCallback, failCallback) { + var promise = get(this, 'promise'); + return promise.then.apply(promise, arguments); }, /** Resolve a Deferred object and call any `doneCallbacks` with the given args. @method resolve */ resolve: function(value) { - var deferred, promise; - - deferred = get(this, '_deferred'); - promise = deferred.promise; - - if (value === this){ - deferred.resolve(promise); - } else { - deferred.resolve(value); - } + get(this, 'promise').resolve(value); }, /** Reject a Deferred object and call any `failCallbacks` with the given args. @method reject */ reject: function(value) { - get(this, '_deferred').reject(value); + get(this, 'promise').reject(value); }, - _deferred: Ember.computed(function() { - return RSVP.defer(); + promise: Ember.computed(function() { + return new RSVP.Promise(); }) }); })(); @@ -10918,10 +9963,11 @@ var set = Ember.set, get = Ember.get, o_create = Ember.create, o_defineProperty = Ember.platform.defineProperty, + a_slice = Array.prototype.slice, GUID_KEY = Ember.GUID_KEY, guidFor = Ember.guidFor, generateGuid = Ember.generateGuid, meta = Ember.meta, rewatch = Ember.rewatch, @@ -10971,13 +10017,10 @@ var concatenatedProperties = this.concatenatedProperties; for (var i = 0, l = props.length; i < l; i++) { var properties = props[i]; - - Ember.assert("Ember.Object.create no longer supports mixing in other definitions, use createWithMixins instead.", !(properties instanceof Ember.Mixin)); - for (var keyName in properties) { if (!properties.hasOwnProperty(keyName)) { continue; } var value = properties[keyName], IS_BINDING = Ember.IS_BINDING; @@ -11068,39 +10111,10 @@ return this; }, isInstance: true, - /** - An overridable method called when objects are instantiated. By default, - does nothing unless it is overridden during class definition. - - Example: - - ```javascript - App.Person = Ember.Object.extend({ - init: function() { - this._super(); - alert('Name is ' + this.get('name')); - } - }); - - var steve = App.Person.create({ - name: "Steve" - }); - - // alerts 'Name is Steve'. - ``` - - NOTE: If you do override `init` for a framework class like `Ember.View` or - `Ember.ArrayController`, be sure to call `this._super()` in your - `init` declaration! If you don't, Ember may not have an opportunity to - do important setup work, and you'll see strange behavior in your - application. - - @method init - */ init: function() {}, /** Defines the properties that will be concatenated from the superclass (instead of overridden). @@ -11141,18 +10155,18 @@ }) view.get('someNonConcatenatedProperty'); // ['baz'] view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz'] ``` Adding a single property that is not an array will just add it in the array: - + ```javascript var view = App.FooBarView.create({ classNames: 'baz' }) view.get('classNames'); // ['ember-view', 'bar', 'foo', 'baz'] ``` - + Using the `concatenatedProperties` property, we can tell to Ember that mix the content of the properties. In `Ember.View` the `classNameBindings` and `attributeBindings` properties are also concatenated, in addition to `classNames`. @@ -11165,26 +10179,16 @@ @default null */ concatenatedProperties: null, /** - Destroyed object property flag. - - if this property is `true` the observers and bindings were already - removed by the effect of calling the `destroy()` method. - @property isDestroyed @default false */ isDestroyed: false, /** - Destruction scheduled flag. The `destroy()` method has been called. - - The object stays intact until the end of the run loop at which point - the `isDestroyed` flag is set. - @property isDestroying @default false */ isDestroying: false, @@ -11205,28 +10209,28 @@ if (this._didCallDestroy) { return; } this.isDestroying = true; this._didCallDestroy = true; + if (this.willDestroy) { this.willDestroy(); } + schedule('destroy', this, this._scheduledDestroy); return this; }, - willDestroy: Ember.K, - /** @private Invoked by the run loop to actually destroy the object. This is scheduled for execution by the `destroy` method. @method _scheduledDestroy */ _scheduledDestroy: function() { - if (this.willDestroy) { this.willDestroy(); } destroy(this); - this.isDestroyed = true; + set(this, 'isDestroyed', true); + if (this.didDestroy) { this.didDestroy(); } }, bind: function(to, from) { if (!(from instanceof Ember.Binding)) { from = Ember.Binding.from(from); } @@ -11257,11 +10261,11 @@ toStringExtension: function(){ return this.get('fullName'); } }); teacher = App.Teacher.create() - teacher.toString(); //=> "<App.Teacher:ember1026:Tom Dale>" + teacher.toString(); // #=> "<App.Teacher:ember1026:Tom Dale>" @method toString @return {String} string representation */ toString: function toString() { @@ -11436,11 +10440,468 @@ /** @module ember @submodule ember-runtime */ +var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, none = Ember.isNone; + /** + 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 guarantee as to their order. + + All Sets are observable via the Enumerable Observer API - which works + on any enumerable object including both Sets and Arrays. + + ## Creating a Set + + You can create a set like you would most objects using + `new Ember.Set()`. 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 `Ember.Set#copy()`. + + ```javascript + // creates a new empty set + var foundNames = new Ember.Set(); + + // creates a set with four names in it. + var names = new Ember.Set(["Charles", "Tom", "Juan", "Alex"]); // :P + + // creates a copy of the names set. + var namesCopy = new Ember.Set(names); + + // same as above. + var anotherNamesCopy = names.copy(); + ``` + + ## 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. + + Unlike arrays, 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: 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 + `Ember.Set#contains()`. + + ## Observing changes + + When using `Ember.Set`, you can observe the `"[]"` property to be + alerted whenever the content changes. You can also add an enumerable + observer to the set to be notified of specific objects that are added and + removed from the set. See `Ember.Enumerable` for more information on + enumerables. + + 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. + + ## Other Methods + + `Ember.Set` primary implements other mixin APIs. For a complete reference + on the methods you will use with `Ember.Set`, please consult these mixins. + The most useful ones will be `Ember.Enumerable` and + `Ember.MutableEnumerable` which implement most of the common iterator + methods you are used to on Array. + + Note that you can also use the `Ember.Copyable` and `Ember.Freezable` + APIs on `Ember.Set` as well. Once a set is frozen it can no longer be + modified. The benefit of this is that when you call `frozenCopy()` on it, + Ember will avoid making copies of the set. This allows you to write + code that can know with certainty when the underlying set data will or + will not be modified. + + @class Set + @namespace Ember + @extends Ember.CoreObject + @uses Ember.MutableEnumerable + @uses Ember.Copyable + @uses Ember.Freezable + @since Ember 0.9 +*/ +Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Ember.Freezable, + /** @scope Ember.Set.prototype */ { + + // .......................................................... + // IMPLEMENT ENUMERABLE APIS + // + + /** + This property will change as the number of objects in the set changes. + + @property length + @type number + @default 0 + */ + length: 0, + + /** + Clears the set. This is useful if you want to reuse an existing set + without having to recreate it. + + ```javascript + var colors = new Ember.Set(["red", "green", "blue"]); + colors.length; // 3 + colors.clear(); + colors.length; // 0 + ``` + + @method clear + @return {Ember.Set} An empty Set + */ + clear: function() { + if (this.isFrozen) { throw new Error(Ember.FROZEN_ERROR); } + + var len = get(this, 'length'); + if (len === 0) { return this; } + + var guid; + + this.enumerableContentWillChange(len, 0); + Ember.propertyWillChange(this, 'firstObject'); + Ember.propertyWillChange(this, 'lastObject'); + + for (var i=0; i < len; i++){ + guid = guidFor(this[i]); + delete this[guid]; + delete this[i]; + } + + set(this, 'length', 0); + + Ember.propertyDidChange(this, 'firstObject'); + Ember.propertyDidChange(this, 'lastObject'); + this.enumerableContentDidChange(len, 0); + + return this; + }, + + /** + Returns true if the passed object is also an enumerable that contains the + same objects as the receiver. + + ```javascript + var colors = ["red", "green", "blue"], + same_colors = new Ember.Set(colors); + + same_colors.isEqual(colors); // true + same_colors.isEqual(["purple", "brown"]); // false + ``` + + @method isEqual + @param {Ember.Set} obj the other object. + @return {Boolean} + */ + isEqual: function(obj) { + // fail fast + if (!Ember.Enumerable.detect(obj)) return false; + + var loc = get(this, 'length'); + if (get(obj, 'length') !== loc) return false; + + while(--loc >= 0) { + if (!obj.contains(this[loc])) return false; + } + + return true; + }, + + /** + Adds an object to the set. Only non-`null` objects can be added to a set + and those can only be added once. If the object is already in the set or + the passed value is null this method will have no effect. + + This is an alias for `Ember.MutableEnumerable.addObject()`. + + ```javascript + var colors = new Ember.Set(); + colors.add("blue"); // ["blue"] + colors.add("blue"); // ["blue"] + colors.add("red"); // ["blue", "red"] + colors.add(null); // ["blue", "red"] + colors.add(undefined); // ["blue", "red"] + ``` + + @method add + @param {Object} obj The object to add. + @return {Ember.Set} The set itself. + */ + add: Ember.aliasMethod('addObject'), + + /** + Removes the object from the set if it is found. If you pass a `null` value + or an object that is already not in the set, this method will have no + effect. This is an alias for `Ember.MutableEnumerable.removeObject()`. + + ```javascript + var colors = new Ember.Set(["red", "green", "blue"]); + colors.remove("red"); // ["blue", "green"] + colors.remove("purple"); // ["blue", "green"] + colors.remove(null); // ["blue", "green"] + ``` + + @method remove + @param {Object} obj The object to remove + @return {Ember.Set} The set itself. + */ + remove: Ember.aliasMethod('removeObject'), + + /** + Removes the last element from the set and returns it, or `null` if it's empty. + + ```javascript + var colors = new Ember.Set(["green", "blue"]); + colors.pop(); // "blue" + colors.pop(); // "green" + colors.pop(); // null + ``` + + @method pop + @return {Object} The removed object from the set or null. + */ + pop: function() { + if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR); + var obj = this.length > 0 ? this[this.length-1] : null; + this.remove(obj); + return obj; + }, + + /** + Inserts the given object on to the end of the set. It returns + the set itself. + + This is an alias for `Ember.MutableEnumerable.addObject()`. + + ```javascript + var colors = new Ember.Set(); + colors.push("red"); // ["red"] + colors.push("green"); // ["red", "green"] + colors.push("blue"); // ["red", "green", "blue"] + ``` + + @method push + @return {Ember.Set} The set itself. + */ + push: Ember.aliasMethod('addObject'), + + /** + Removes the last element from the set and returns it, or `null` if it's empty. + + This is an alias for `Ember.Set.pop()`. + + ```javascript + var colors = new Ember.Set(["green", "blue"]); + colors.shift(); // "blue" + colors.shift(); // "green" + colors.shift(); // null + ``` + + @method shift + @return {Object} The removed object from the set or null. + */ + shift: Ember.aliasMethod('pop'), + + /** + Inserts the given object on to the end of the set. It returns + the set itself. + + This is an alias of `Ember.Set.push()` + + ```javascript + var colors = new Ember.Set(); + colors.unshift("red"); // ["red"] + colors.unshift("green"); // ["red", "green"] + colors.unshift("blue"); // ["red", "green", "blue"] + ``` + + @method unshift + @return {Ember.Set} The set itself. + */ + unshift: Ember.aliasMethod('push'), + + /** + Adds each object in the passed enumerable to the set. + + This is an alias of `Ember.MutableEnumerable.addObjects()` + + ```javascript + var colors = new Ember.Set(); + colors.addEach(["red", "green", "blue"]); // ["red", "green", "blue"] + ``` + + @method addEach + @param {Ember.Enumerable} objects the objects to add. + @return {Ember.Set} The set itself. + */ + addEach: Ember.aliasMethod('addObjects'), + + /** + Removes each object in the passed enumerable to the set. + + This is an alias of `Ember.MutableEnumerable.removeObjects()` + + ```javascript + var colors = new Ember.Set(["red", "green", "blue"]); + colors.removeEach(["red", "blue"]); // ["green"] + ``` + + @method removeEach + @param {Ember.Enumerable} objects the objects to remove. + @return {Ember.Set} The set itself. + */ + removeEach: Ember.aliasMethod('removeObjects'), + + // .......................................................... + // PRIVATE ENUMERABLE SUPPORT + // + + init: function(items) { + this._super(); + if (items) this.addObjects(items); + }, + + // implement Ember.Enumerable + nextObject: function(idx) { + return this[idx]; + }, + + // more optimized version + firstObject: Ember.computed(function() { + return this.length > 0 ? this[0] : undefined; + }), + + // more optimized version + lastObject: Ember.computed(function() { + return this.length > 0 ? this[this.length-1] : undefined; + }), + + // implements Ember.MutableEnumerable + addObject: function(obj) { + if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR); + if (none(obj)) return this; // nothing to do + + var guid = guidFor(obj), + idx = this[guid], + len = get(this, 'length'), + added ; + + if (idx>=0 && idx<len && (this[idx] === obj)) return this; // added + + added = [obj]; + + this.enumerableContentWillChange(null, added); + Ember.propertyWillChange(this, 'lastObject'); + + len = get(this, 'length'); + this[guid] = len; + this[len] = obj; + set(this, 'length', len+1); + + Ember.propertyDidChange(this, 'lastObject'); + this.enumerableContentDidChange(null, added); + + return this; + }, + + // implements Ember.MutableEnumerable + removeObject: function(obj) { + if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR); + if (none(obj)) return this; // nothing to do + + var guid = guidFor(obj), + idx = this[guid], + len = get(this, 'length'), + isFirst = idx === 0, + isLast = idx === len-1, + last, removed; + + + if (idx>=0 && idx<len && (this[idx] === obj)) { + removed = [obj]; + + this.enumerableContentWillChange(removed, null); + if (isFirst) { Ember.propertyWillChange(this, 'firstObject'); } + if (isLast) { Ember.propertyWillChange(this, 'lastObject'); } + + // swap items - basically move the item to the end so it can be removed + if (idx < len-1) { + last = this[len-1]; + this[idx] = last; + this[guidFor(last)] = idx; + } + + delete this[guid]; + delete this[len-1]; + set(this, 'length', len-1); + + if (isFirst) { Ember.propertyDidChange(this, 'firstObject'); } + if (isLast) { Ember.propertyDidChange(this, 'lastObject'); } + this.enumerableContentDidChange(removed, null); + } + + return this; + }, + + // optimized version + contains: function(obj) { + return this[guidFor(obj)]>=0; + }, + + copy: function() { + var C = this.constructor, ret = new C(), loc = get(this, 'length'); + set(ret, 'length', loc); + while(--loc>=0) { + ret[loc] = this[loc]; + ret[guidFor(this[loc])] = loc; + } + return ret; + }, + + toString: function() { + var len = this.length, idx, array = []; + for(idx = 0; idx < len; idx++) { + array[idx] = this[idx]; + } + return "Ember.Set<%@>".fmt(array.join(',')); + } + +}); + +})(); + + + +(function() { +/** +@module ember +@submodule ember-runtime +*/ + +/** `Ember.Object` is the main base class for all Ember objects. It is a subclass of `Ember.CoreObject` with the `Ember.Observable` mixin applied. For details, see the documentation for each of these. @class Object @@ -11508,32 +10969,20 @@ } }); Namespace.reopenClass({ NAMESPACES: [Ember], - NAMESPACES_BY_ID: {}, PROCESSED: false, - processAll: processAllNamespaces, - byName: function(name) { - if (!Ember.BOOTED) { - processAllNamespaces(); - } - - return NAMESPACES_BY_ID[name]; - } + processAll: processAllNamespaces }); -var NAMESPACES_BY_ID = Namespace.NAMESPACES_BY_ID; - var hasOwnProp = ({}).hasOwnProperty, guidFor = Ember.guidFor; function processNamespace(paths, root, seen) { var idx = paths.length; - NAMESPACES_BY_ID[paths.join('.')] = root; - // Loop over all of the keys in the namespace, looking for classes for(var key in root) { if (!hasOwnProp.call(root, key)) { continue; } var obj = root[key]; @@ -11570,11 +11019,11 @@ if (Namespace.PROCESSED) { return; } for (var prop in lookup) { // These don't raise exceptions but can cause warnings - if (prop === "parent" || prop === "top" || prop === "frameElement" || prop === "webkitStorageInfo") { continue; } + if (prop === "parent" || prop === "top" || prop === "frameElement") { continue; } // get(window.globalStorage, 'isNamespace') would try to read the storage for domain isNamespace and cause exception in Firefox. // globalStorage is a storage obsoleted by the WhatWG storage specification. See https://developer.mozilla.org/en/DOM/Storage#globalStorage if (prop === "globalStorage" && lookup.StorageList && lookup.globalStorage instanceof lookup.StorageList) { continue; } // Unfortunately, some versions of IE don't support window.hasOwnProperty @@ -11629,19 +11078,16 @@ return ret; } function processAllNamespaces() { - var unprocessedNamespaces = !Namespace.PROCESSED, - unprocessedMixins = Ember.anyUnprocessedMixins; - - if (unprocessedNamespaces) { + if (!Namespace.PROCESSED) { findNamespaces(); Namespace.PROCESSED = true; } - if (unprocessedNamespaces || unprocessedMixins) { + if (Ember.anyUnprocessedMixins) { var namespaces = Namespace.NAMESPACES, namespace; for (var i=0, l=namespaces.length; i<l; i++) { namespace = namespaces[i]; processNamespace([namespace.toString()], namespace, {}); } @@ -11659,24 +11105,54 @@ })(); (function() { +/** +@module ember +@submodule ember-runtime +*/ + +/** + Defines a namespace that will contain an executable application. This is + very similar to a normal namespace except that it is expected to include at + least a 'ready' function which can be run to initialize the application. + + Currently `Ember.Application` is very similar to `Ember.Namespace.` However, + this class may be augmented by additional frameworks so it is important to + use this instance when building new applications. + + # Example Usage + + ```javascript + MyApp = Ember.Application.create({ + VERSION: '1.0.0', + store: Ember.Store.create().from(Ember.fixtures) + }); + + MyApp.ready = function() { + //..init code goes here... + } + ``` + + @class Application + @namespace Ember + @extends Ember.Namespace +*/ Ember.Application = Ember.Namespace.extend(); + })(); (function() { /** @module ember @submodule ember-runtime */ -var OUT_OF_RANGE_EXCEPTION = "Index out of range"; -var EMPTY = []; var get = Ember.get, set = Ember.set; /** An ArrayProxy wraps any other object that implements `Ember.Array` and/or @@ -11714,11 +11190,12 @@ @class ArrayProxy @namespace Ember @extends Ember.Object @uses Ember.MutableArray */ -Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray,/** @scope Ember.ArrayProxy.prototype */ { +Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray, +/** @scope Ember.ArrayProxy.prototype */ { /** The content array. Must be an object that implements `Ember.Array` and/or `Ember.MutableArray.` @@ -11877,103 +11354,16 @@ var arrangedContent = get(this, 'arrangedContent'); return arrangedContent ? get(arrangedContent, 'length') : 0; // No dependencies since Enumerable notifies length of change }), - _replace: function(idx, amt, objects) { - var content = get(this, 'content'); - Ember.assert('The content property of '+ this.constructor + ' should be set before modifying it', content); - if (content) this.replaceContent(idx, amt, objects); + replace: function(idx, amt, objects) { + Ember.assert('The content property of '+ this.constructor + ' should be set before modifying it', this.get('content')); + if (get(this, 'content')) this.replaceContent(idx, amt, objects); return this; }, - replace: function() { - if (get(this, 'arrangedContent') === get(this, 'content')) { - this._replace.apply(this, arguments); - } else { - throw new Ember.Error("Using replace on an arranged ArrayProxy is not allowed."); - } - }, - - _insertAt: function(idx, object) { - if (idx > get(this, 'content.length')) throw new Error(OUT_OF_RANGE_EXCEPTION); - this._replace(idx, 0, [object]); - return this; - }, - - insertAt: function(idx, object) { - if (get(this, 'arrangedContent') === get(this, 'content')) { - return this._insertAt(idx, object); - } else { - throw new Ember.Error("Using insertAt on an arranged ArrayProxy is not allowed."); - } - }, - - removeAt: function(start, len) { - if ('number' === typeof start) { - var content = get(this, 'content'), - arrangedContent = get(this, 'arrangedContent'), - indices = [], i; - - if ((start < 0) || (start >= get(this, 'length'))) { - throw new Error(OUT_OF_RANGE_EXCEPTION); - } - - if (len === undefined) len = 1; - - // Get a list of indices in original content to remove - for (i=start; i<start+len; i++) { - // Use arrangedContent here so we avoid confusion with objects transformed by objectAtContent - indices.push(content.indexOf(arrangedContent.objectAt(i))); - } - - // Replace in reverse order since indices will change - indices.sort(function(a,b) { return b - a; }); - - Ember.beginPropertyChanges(); - for (i=0; i<indices.length; i++) { - this._replace(indices[i], 1, EMPTY); - } - Ember.endPropertyChanges(); - } - - return this ; - }, - - pushObject: function(obj) { - this._insertAt(get(this, 'content.length'), obj) ; - return obj ; - }, - - pushObjects: function(objects) { - this._replace(get(this, 'length'), 0, objects); - return this; - }, - - setObjects: function(objects) { - if (objects.length === 0) return this.clear(); - - var len = get(this, 'length'); - this._replace(0, len, objects); - return this; - }, - - unshiftObject: function(obj) { - this._insertAt(0, obj) ; - return obj ; - }, - - unshiftObjects: function(objects) { - this._replace(0, 0, objects); - return this; - }, - - slice: function() { - var arr = this.toArray(); - return arr.slice.apply(arr, arguments); - }, - arrangedContentArrayWillChange: function(item, idx, removedCnt, addedCnt) { this.arrayContentWillChange(idx, removedCnt, addedCnt); }, arrangedContentArrayDidChange: function(item, idx, removedCnt, addedCnt) { @@ -11990,10 +11380,11 @@ this._teardownArrangedContent(); this._teardownContent(); } }); + })(); (function() { @@ -12089,11 +11480,12 @@ @class ObjectProxy @namespace Ember @extends Ember.Object */ -Ember.ObjectProxy = Ember.Object.extend(/** @scope Ember.ObjectProxy.prototype */ { +Ember.ObjectProxy = Ember.Object.extend( +/** @scope Ember.ObjectProxy.prototype */ { /** The object whose properties will be forwarded. @property content @type Ember.Object @@ -12132,29 +11524,10 @@ Ember.assert(fmt("Cannot delegate set('%@', %@) to the 'content' property of object proxy %@: its 'content' is undefined.", [key, value, this]), content); return set(content, key, value); } }); -Ember.ObjectProxy.reopenClass({ - create: function () { - var mixin, prototype, i, l, properties, keyName; - if (arguments.length) { - prototype = this.proto(); - for (i = 0, l = arguments.length; i < l; i++) { - properties = arguments[i]; - for (keyName in properties) { - if (!properties.hasOwnProperty(keyName) || keyName in prototype) { continue; } - if (!mixin) mixin = {}; - mixin[keyName] = null; - } - } - if (mixin) this._initMixins([mixin]); - } - return this._super.apply(this, arguments); - } -}); - })(); (function() { @@ -12198,11 +11571,11 @@ var item = content.objectAt(loc); if (item) { Ember.addBeforeObserver(item, keyName, proxy, 'contentKeyWillChange'); Ember.addObserver(item, keyName, proxy, 'contentKeyDidChange'); - // keep track of the index each item was found at so we can map + // keep track of the indicies each item was found at so we can map // it back when the obj changes. guid = guidFor(item); if (!objects[guid]) objects[guid] = []; objects[guid].push(loc); } @@ -12255,11 +11628,11 @@ You can directly access mapped properties by simply requesting them. The `unknownProperty` handler will generate an EachArray of each item. @method unknownProperty @param keyName {String} - @param value {*} + @param value {anything} */ unknownProperty: function(keyName, value) { var ret; ret = new EachArray(this._content, keyName, this); Ember.defineProperty(this, keyName, null, ret); @@ -12270,11 +11643,11 @@ // .......................................................... // ARRAY CHANGES // Invokes whenever the content array itself changes. arrayWillChange: function(content, idx, removedCnt, addedCnt) { - var keys = this._keys, key, lim; + var keys = this._keys, key, array, lim; lim = removedCnt>0 ? idx+removedCnt : -1; Ember.beginPropertyChanges(this); for(key in keys) { @@ -12288,11 +11661,11 @@ Ember.propertyWillChange(this._content, '@each'); Ember.endPropertyChanges(this); }, arrayDidChange: function(content, idx, removedCnt, addedCnt) { - var keys = this._keys, key, lim; + var keys = this._keys, key, array, lim; lim = addedCnt>0 ? idx+addedCnt : -1; Ember.beginPropertyChanges(this); for(key in keys) { @@ -12471,20 +11844,21 @@ } /** The NativeArray mixin contains the properties needed to to make the native Array support Ember.MutableArray and all of its dependent APIs. Unless you - have `Ember.EXTEND_PROTOTYPES` or `Ember.EXTEND_PROTOTYPES.Array` set to + have `Ember.EXTEND_PROTOTYPES or `Ember.EXTEND_PROTOTYPES.Array` set to false, this will be applied automatically. Otherwise you can apply the mixin at anytime by calling `Ember.NativeArray.activate`. @class NativeArray @namespace Ember @extends Ember.Mixin @uses Ember.MutableArray - @uses Ember.Observable + @uses Ember.MutableEnumerable @uses Ember.Copyable + @uses Ember.Freezable */ Ember.NativeArray = NativeArray; /** Creates an `Ember.NativeArray` from an Array like object. @@ -12522,489 +11896,31 @@ })(); (function() { -/** -@module ember -@submodule ember-runtime -*/ - -var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, isNone = Ember.isNone, fmt = Ember.String.fmt; - -/** - 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 guarantee as to their order. - - All Sets are observable via the Enumerable Observer API - which works - on any enumerable object including both Sets and Arrays. - - ## Creating a Set - - You can create a set like you would most objects using - `new Ember.Set()`. 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 `Ember.Set#copy()`. - - ```javascript - // creates a new empty set - var foundNames = new Ember.Set(); - - // creates a set with four names in it. - var names = new Ember.Set(["Charles", "Tom", "Juan", "Alex"]); // :P - - // creates a copy of the names set. - var namesCopy = new Ember.Set(names); - - // same as above. - var anotherNamesCopy = names.copy(); - ``` - - ## 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. - - Unlike arrays, 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: 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 - `Ember.Set#contains()`. - - ## Observing changes - - When using `Ember.Set`, you can observe the `"[]"` property to be - alerted whenever the content changes. You can also add an enumerable - observer to the set to be notified of specific objects that are added and - removed from the set. See `Ember.Enumerable` for more information on - enumerables. - - 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. - - ## Other Methods - - `Ember.Set` primary implements other mixin APIs. For a complete reference - on the methods you will use with `Ember.Set`, please consult these mixins. - The most useful ones will be `Ember.Enumerable` and - `Ember.MutableEnumerable` which implement most of the common iterator - methods you are used to on Array. - - Note that you can also use the `Ember.Copyable` and `Ember.Freezable` - APIs on `Ember.Set` as well. Once a set is frozen it can no longer be - modified. The benefit of this is that when you call `frozenCopy()` on it, - Ember will avoid making copies of the set. This allows you to write - code that can know with certainty when the underlying set data will or - will not be modified. - - @class Set - @namespace Ember - @extends Ember.CoreObject - @uses Ember.MutableEnumerable - @uses Ember.Copyable - @uses Ember.Freezable - @since Ember 0.9 -*/ -Ember.Set = Ember.CoreObject.extend(Ember.MutableEnumerable, Ember.Copyable, Ember.Freezable, - /** @scope Ember.Set.prototype */ { - - // .......................................................... - // IMPLEMENT ENUMERABLE APIS - // - - /** - This property will change as the number of objects in the set changes. - - @property length - @type number - @default 0 - */ - length: 0, - - /** - Clears the set. This is useful if you want to reuse an existing set - without having to recreate it. - - ```javascript - var colors = new Ember.Set(["red", "green", "blue"]); - colors.length; // 3 - colors.clear(); - colors.length; // 0 - ``` - - @method clear - @return {Ember.Set} An empty Set - */ - clear: function() { - if (this.isFrozen) { throw new Error(Ember.FROZEN_ERROR); } - - var len = get(this, 'length'); - if (len === 0) { return this; } - - var guid; - - this.enumerableContentWillChange(len, 0); - Ember.propertyWillChange(this, 'firstObject'); - Ember.propertyWillChange(this, 'lastObject'); - - for (var i=0; i < len; i++){ - guid = guidFor(this[i]); - delete this[guid]; - delete this[i]; - } - - set(this, 'length', 0); - - Ember.propertyDidChange(this, 'firstObject'); - Ember.propertyDidChange(this, 'lastObject'); - this.enumerableContentDidChange(len, 0); - - return this; - }, - - /** - Returns true if the passed object is also an enumerable that contains the - same objects as the receiver. - - ```javascript - var colors = ["red", "green", "blue"], - same_colors = new Ember.Set(colors); - - same_colors.isEqual(colors); // true - same_colors.isEqual(["purple", "brown"]); // false - ``` - - @method isEqual - @param {Ember.Set} obj the other object. - @return {Boolean} - */ - isEqual: function(obj) { - // fail fast - if (!Ember.Enumerable.detect(obj)) return false; - - var loc = get(this, 'length'); - if (get(obj, 'length') !== loc) return false; - - while(--loc >= 0) { - if (!obj.contains(this[loc])) return false; - } - - return true; - }, - - /** - Adds an object to the set. Only non-`null` objects can be added to a set - and those can only be added once. If the object is already in the set or - the passed value is null this method will have no effect. - - This is an alias for `Ember.MutableEnumerable.addObject()`. - - ```javascript - var colors = new Ember.Set(); - colors.add("blue"); // ["blue"] - colors.add("blue"); // ["blue"] - colors.add("red"); // ["blue", "red"] - colors.add(null); // ["blue", "red"] - colors.add(undefined); // ["blue", "red"] - ``` - - @method add - @param {Object} obj The object to add. - @return {Ember.Set} The set itself. - */ - add: Ember.aliasMethod('addObject'), - - /** - Removes the object from the set if it is found. If you pass a `null` value - or an object that is already not in the set, this method will have no - effect. This is an alias for `Ember.MutableEnumerable.removeObject()`. - - ```javascript - var colors = new Ember.Set(["red", "green", "blue"]); - colors.remove("red"); // ["blue", "green"] - colors.remove("purple"); // ["blue", "green"] - colors.remove(null); // ["blue", "green"] - ``` - - @method remove - @param {Object} obj The object to remove - @return {Ember.Set} The set itself. - */ - remove: Ember.aliasMethod('removeObject'), - - /** - Removes the last element from the set and returns it, or `null` if it's empty. - - ```javascript - var colors = new Ember.Set(["green", "blue"]); - colors.pop(); // "blue" - colors.pop(); // "green" - colors.pop(); // null - ``` - - @method pop - @return {Object} The removed object from the set or null. - */ - pop: function() { - if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR); - var obj = this.length > 0 ? this[this.length-1] : null; - this.remove(obj); - return obj; - }, - - /** - Inserts the given object on to the end of the set. It returns - the set itself. - - This is an alias for `Ember.MutableEnumerable.addObject()`. - - ```javascript - var colors = new Ember.Set(); - colors.push("red"); // ["red"] - colors.push("green"); // ["red", "green"] - colors.push("blue"); // ["red", "green", "blue"] - ``` - - @method push - @return {Ember.Set} The set itself. - */ - push: Ember.aliasMethod('addObject'), - - /** - Removes the last element from the set and returns it, or `null` if it's empty. - - This is an alias for `Ember.Set.pop()`. - - ```javascript - var colors = new Ember.Set(["green", "blue"]); - colors.shift(); // "blue" - colors.shift(); // "green" - colors.shift(); // null - ``` - - @method shift - @return {Object} The removed object from the set or null. - */ - shift: Ember.aliasMethod('pop'), - - /** - Inserts the given object on to the end of the set. It returns - the set itself. - - This is an alias of `Ember.Set.push()` - - ```javascript - var colors = new Ember.Set(); - colors.unshift("red"); // ["red"] - colors.unshift("green"); // ["red", "green"] - colors.unshift("blue"); // ["red", "green", "blue"] - ``` - - @method unshift - @return {Ember.Set} The set itself. - */ - unshift: Ember.aliasMethod('push'), - - /** - Adds each object in the passed enumerable to the set. - - This is an alias of `Ember.MutableEnumerable.addObjects()` - - ```javascript - var colors = new Ember.Set(); - colors.addEach(["red", "green", "blue"]); // ["red", "green", "blue"] - ``` - - @method addEach - @param {Ember.Enumerable} objects the objects to add. - @return {Ember.Set} The set itself. - */ - addEach: Ember.aliasMethod('addObjects'), - - /** - Removes each object in the passed enumerable to the set. - - This is an alias of `Ember.MutableEnumerable.removeObjects()` - - ```javascript - var colors = new Ember.Set(["red", "green", "blue"]); - colors.removeEach(["red", "blue"]); // ["green"] - ``` - - @method removeEach - @param {Ember.Enumerable} objects the objects to remove. - @return {Ember.Set} The set itself. - */ - removeEach: Ember.aliasMethod('removeObjects'), - - // .......................................................... - // PRIVATE ENUMERABLE SUPPORT - // - - init: function(items) { - this._super(); - if (items) this.addObjects(items); - }, - - // implement Ember.Enumerable - nextObject: function(idx) { - return this[idx]; - }, - - // more optimized version - firstObject: Ember.computed(function() { - return this.length > 0 ? this[0] : undefined; - }), - - // more optimized version - lastObject: Ember.computed(function() { - return this.length > 0 ? this[this.length-1] : undefined; - }), - - // implements Ember.MutableEnumerable - addObject: function(obj) { - if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR); - if (isNone(obj)) return this; // nothing to do - - var guid = guidFor(obj), - idx = this[guid], - len = get(this, 'length'), - added ; - - if (idx>=0 && idx<len && (this[idx] === obj)) return this; // added - - added = [obj]; - - this.enumerableContentWillChange(null, added); - Ember.propertyWillChange(this, 'lastObject'); - - len = get(this, 'length'); - this[guid] = len; - this[len] = obj; - set(this, 'length', len+1); - - Ember.propertyDidChange(this, 'lastObject'); - this.enumerableContentDidChange(null, added); - - return this; - }, - - // implements Ember.MutableEnumerable - removeObject: function(obj) { - if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR); - if (isNone(obj)) return this; // nothing to do - - var guid = guidFor(obj), - idx = this[guid], - len = get(this, 'length'), - isFirst = idx === 0, - isLast = idx === len-1, - last, removed; - - - if (idx>=0 && idx<len && (this[idx] === obj)) { - removed = [obj]; - - this.enumerableContentWillChange(removed, null); - if (isFirst) { Ember.propertyWillChange(this, 'firstObject'); } - if (isLast) { Ember.propertyWillChange(this, 'lastObject'); } - - // swap items - basically move the item to the end so it can be removed - if (idx < len-1) { - last = this[len-1]; - this[idx] = last; - this[guidFor(last)] = idx; - } - - delete this[guid]; - delete this[len-1]; - set(this, 'length', len-1); - - if (isFirst) { Ember.propertyDidChange(this, 'firstObject'); } - if (isLast) { Ember.propertyDidChange(this, 'lastObject'); } - this.enumerableContentDidChange(removed, null); - } - - return this; - }, - - // optimized version - contains: function(obj) { - return this[guidFor(obj)]>=0; - }, - - copy: function() { - var C = this.constructor, ret = new C(), loc = get(this, 'length'); - set(ret, 'length', loc); - while(--loc>=0) { - ret[loc] = this[loc]; - ret[guidFor(this[loc])] = loc; - } - return ret; - }, - - toString: function() { - var len = this.length, idx, array = []; - for(idx = 0; idx < len; idx++) { - array[idx] = this[idx]; - } - return fmt("Ember.Set<%@>", [array.join(',')]); - } - -}); - -})(); - - - -(function() { var DeferredMixin = Ember.DeferredMixin, // mixins/deferred + EmberObject = Ember.Object, // system/object get = Ember.get; var Deferred = Ember.Object.extend(DeferredMixin); Deferred.reopenClass({ promise: function(callback, binding) { var deferred = Deferred.create(); callback.call(binding, deferred); - return deferred; + return get(deferred, 'promise'); } }); Ember.Deferred = Deferred; })(); (function() { -var forEach = Ember.ArrayPolyfills.forEach; - /** @module ember @submodule ember-runtime */ @@ -13033,14 +11949,16 @@ @for Ember @param name {String} name of hook @param object {Object} object to pass to callbacks */ Ember.runLoadHooks = function(name, object) { + var hooks; + loaded[name] = object; - if (loadHooks[name]) { - forEach.call(loadHooks[name], function(callback) { + if (hooks = loadHooks[name]) { + loadHooks[name].forEach(function(callback) { callback(object); }); } }; @@ -13110,12 +12028,10 @@ */ target: null, container: null, - parentController: null, - store: null, model: Ember.computed.alias('content'), send: function(actionName) { @@ -13305,10 +12221,11 @@ var isSorted = get(this, 'isSorted'), sortProperties = get(this, 'sortProperties'); if (isSorted) { var addedObjects = array.slice(idx, idx+addedCount); + var arrangedContent = get(this, 'arrangedContent'); forEach(addedObjects, function(item) { this.insertItemSorted(item); forEach(sortProperties, function(sortProperty) { @@ -13374,12 +12291,12 @@ /** @module ember @submodule ember-runtime */ -var get = Ember.get, set = Ember.set, forEach = Ember.EnumerableUtils.forEach, - replace = Ember.EnumerableUtils.replace; +var get = Ember.get, set = Ember.set, isGlobalPath = Ember.isGlobalPath, + forEach = Ember.EnumerableUtils.forEach, replace = Ember.EnumerableUtils.replace; /** `Ember.ArrayController` provides a way for you to publish a collection of objects so that you can easily bind to the collection from a Handlebars `#each` helper, an `Ember.CollectionView`, or other controllers. @@ -13454,14 +12371,10 @@ } } }); ``` - The itemController instances will have a `parentController` property set to - either the the `parentController` property of the `ArrayController` - or to the `ArrayController` instance itself. - @class ArrayController @namespace Ember @extends Ember.ArrayProxy @uses Ember.SortableMixin @uses Ember.ControllerMixin @@ -13497,22 +12410,21 @@ } } }); ``` - @method lookupItemController - @param {Object} object - @return {String} + @method + @type String + @default null */ lookupItemController: function(object) { return get(this, 'itemController'); }, objectAtContent: function(idx) { var length = get(this, 'length'), - arrangedContent = get(this,'arrangedContent'), - object = arrangedContent && arrangedContent.objectAt(idx); + object = get(this,'arrangedContent').objectAt(idx); if (idx >= 0 && idx < length) { var controllerClass = this.lookupItemController(object); if (controllerClass) { return this.controllerAt(idx, object, controllerClass); @@ -13528,66 +12440,67 @@ return object; }, arrangedContentDidChange: function() { this._super(); - this._resetSubControllers(); + this._resetSubContainers(); }, arrayContentDidChange: function(idx, removedCnt, addedCnt) { - var subControllers = get(this, '_subControllers'), - subControllersToRemove = subControllers.slice(idx, idx+removedCnt); + var subContainers = get(this, 'subContainers'), + subContainersToRemove = subContainers.slice(idx, idx+removedCnt); - forEach(subControllersToRemove, function(subController) { - if (subController) { subController.destroy(); } + forEach(subContainersToRemove, function(subContainer) { + if (subContainer) { subContainer.destroy(); } }); - replace(subControllers, idx, removedCnt, new Array(addedCnt)); + replace(subContainers, idx, removedCnt, new Array(addedCnt)); - // The shadow array of subcontrollers must be updated before we trigger + // The shadow array of subcontainers must be updated before we trigger // observers, otherwise observers will get the wrong subcontainer when // calling `objectAt` this._super(idx, removedCnt, addedCnt); }, init: function() { - if (!this.get('content')) { Ember.defineProperty(this, 'content', undefined, Ember.A()); } this._super(); - this.set('_subControllers', Ember.A()); + if (!this.get('content')) { this.set('content', Ember.A()); } + this._resetSubContainers(); }, controllerAt: function(idx, object, controllerClass) { var container = get(this, 'container'), - subControllers = get(this, '_subControllers'), - subController = subControllers[idx]; + subContainers = get(this, 'subContainers'), + subContainer = subContainers[idx], + controller; - if (!subController) { - subController = container.lookup("controller:" + controllerClass, { singleton: false }); - subControllers[idx] = subController; + if (!subContainer) { + subContainer = subContainers[idx] = container.child(); } - if (!subController) { + controller = subContainer.lookup("controller:" + controllerClass); + if (!controller) { throw new Error('Could not resolve itemController: "' + controllerClass + '"'); } - subController.set('target', this); - subController.set('parentController', get(this, 'parentController') || this); - subController.set('content', object); + controller.set('target', this); + controller.set('content', object); - return subController; + return controller; }, - _subControllers: null, + subContainers: null, - _resetSubControllers: function() { - var subControllers = get(this, '_subControllers'); - if (subControllers) { - forEach(subControllers, function(subController) { - if (subController) { subController.destroy(); } + _resetSubContainers: function() { + var subContainers = get(this, 'subContainers'); + + if (subContainers) { + forEach(subContainers, function(subContainer) { + if (subContainer) { subContainer.destroy(); } }); } - this.set('_subControllers', Ember.A()); + this.set('subContainers', Ember.A()); } }); })();