dist/ember-runtime.js in ember-source-1.0.0.rc2.3 vs dist/ember-runtime.js in ember-source-1.0.0.rc3
- old
+ new
@@ -1,7 +1,7 @@
-// Version: v1.0.0-rc.2-2-gd22a11b
-// Last commit: d22a11b (2013-07-25 20:15:17 -0400)
+// Version: v1.0.0-rc.3
+// Last commit: 96e97b5 (2013-04-21 02:07:10 -0400)
(function() {
/*global __fail__*/
@@ -149,12 +149,12 @@
};
};
})();
-// Version: v1.0.0-rc.2-2-gd22a11b
-// Last commit: d22a11b (2013-07-25 20:15:17 -0400)
+// Version: v1.0.0-rc.3
+// Last commit: 96e97b5 (2013-04-21 02:07:10 -0400)
(function() {
var define, requireModule;
@@ -210,11 +210,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.2
+ @version 1.0.0-rc.3
*/
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 +237,14 @@
/**
@property VERSION
@type String
- @default '1.0.0-rc.2'
+ @default '1.0.0-rc.3'
@final
*/
-Ember.VERSION = '1.0.0-rc.2';
+Ember.VERSION = '1.0.0-rc.3';
/**
Standard environmental variables. You can define these in a global `ENV`
variable before loading Ember to control various configuration
settings.
@@ -633,15 +633,112 @@
})();
(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,
@@ -1034,11 +1131,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 {anything} the return value of the invoked method or undefined if it cannot be invoked
+ @return {*} 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 || []);
}
@@ -1065,11 +1162,11 @@
@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 [binding]
- @return {anything} The return value is the that of the finalizer,
+ @return {*} 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) {
@@ -1116,11 +1213,11 @@
@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 [binding]
- @return {anything} The return value is the that of the finalizer,
+ @return {*} 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) {
@@ -1160,10 +1257,82 @@
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() {
@@ -1340,21 +1509,27 @@
})();
(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) : Array.prototype.map.call(obj, callback, thisArg);
+ return obj.map ? obj.map.call(obj, callback, thisArg) : map.call(obj, callback, thisArg);
},
forEach: function(obj, callback, thisArg) {
- return obj.forEach ? obj.forEach.call(obj, callback, thisArg) : Array.prototype.forEach.call(obj, callback, thisArg);
+ return obj.forEach ? obj.forEach.call(obj, callback, thisArg) : forEach.call(obj, callback, thisArg);
},
indexOf: function(obj, element, index) {
- return obj.indexOf ? obj.indexOf.call(obj, element, index) : Array.prototype.indexOf.call(obj, element, index);
+ return obj.indexOf ? obj.indexOf.call(obj, element, index) : indexOf.call(obj, element, index);
},
indexesOf: function(obj, elements) {
return elements === undefined ? [] : utils.map(elements, function(item) {
return utils.indexOf(obj, item);
@@ -1373,20 +1548,20 @@
replace: function(array, idx, amt, objects) {
if (array.replace) {
return array.replace(idx, amt, objects);
} else {
- var args = Array.prototype.concat.apply([idx, amt], objects);
+ var args = concat.apply([idx, amt], objects);
return array.splice.apply(array, args);
}
},
intersection: function(array1, array2) {
var intersection = [];
- array1.forEach(function(element) {
- if (array2.indexOf(element) >= 0) {
+ utils.forEach(array1, function(element) {
+ if (utils.indexOf(array2, element) >= 0) {
intersection.push(element);
}
});
return intersection;
@@ -1396,111 +1571,14 @@
})();
(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
@@ -1631,11 +1709,11 @@
@param {Function} fn
@param self
*/
forEach: function(fn, self) {
// allow mutation during iteration
- var list = this.list.slice();
+ var list = this.toArray();
for (var i = 0, j = list.length; i < j; i++) {
fn.call(self, list[i]);
}
},
@@ -1654,11 +1732,11 @@
*/
copy: function() {
var set = new OrderedSet();
set.presenceSet = copy(this.presenceSet);
- set.list = this.list.slice();
+ set.list = this.toArray();
return set;
}
};
@@ -1698,12 +1776,12 @@
Map.prototype = {
/**
Retrieve the value associated with a given key.
@method get
- @param {anything} key
- @return {anything} the value associated with the key, or `undefined`
+ @param {*} key
+ @return {*} the value associated with the key, or `undefined`
*/
get: function(key) {
var values = this.values,
guid = guidFor(key);
@@ -1713,12 +1791,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 {anything} key
- @param {anything} value
+ @param {*} key
+ @param {*} value
*/
set: function(key, value) {
var keys = this.keys,
values = this.values,
guid = guidFor(key);
@@ -1729,24 +1807,22 @@
/**
Removes a value from the map for an associated key.
@method remove
- @param {anything} key
+ @param {*} 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),
- value;
+ guid = guidFor(key);
if (values.hasOwnProperty(guid)) {
keys.remove(key);
- value = values[guid];
delete values[guid];
return true;
} else {
return false;
}
@@ -1754,11 +1830,11 @@
/**
Check whether a key is present.
@method has
- @param {anything} key
+ @param {*} key
@return {Boolean} true if the item was present, false otherwise
*/
has: function(key) {
var values = this.values,
guid = guidFor(key);
@@ -1772,11 +1848,11 @@
The keys are guaranteed to be iterated over in insertion order.
@method forEach
@param {Function} callback
- @param {anything} self if passed, the `this` value inside the
+ @param {*} 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;
@@ -1801,22 +1877,22 @@
@namespace Ember
@extends Ember.Map
@private
@constructor
@param [options]
- @param {anything} [options.defaultValue]
+ @param {*} [options.defaultValue]
*/
var MapWithDefault = Ember.MapWithDefault = function(options) {
Map.call(this);
this.defaultValue = options.defaultValue;
};
/**
@method create
@static
@param [options]
- @param {anything} [options.defaultValue]
+ @param {*} [options.defaultValue]
@return {Ember.MapWithDefault|Ember.Map} If options are passed, returns
`Ember.MapWithDefault` otherwise returns `Ember.Map`
*/
MapWithDefault.create = function(options) {
if (options) {
@@ -1830,12 +1906,12 @@
/**
Retrieve the value associated with a given key.
@method get
- @param {anything} key
- @return {anything} the value associated with the key, or the default value
+ @param {*} key
+ @return {*} the value associated with the key, or the default value
*/
MapWithDefault.prototype.get = function(key) {
var hasValue = this.has(key);
if (hasValue) {
@@ -1864,15 +1940,14 @@
(function() {
/**
@module ember-metal
*/
-var META_KEY = Ember.META_KEY, get, set;
+var META_KEY = Ember.META_KEY, get;
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 = /^([^\.\*]+)/;
// ..........................................................
@@ -1941,11 +2016,774 @@
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 && 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;
+
+/*
+ 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, onceFlag, suspendedFlag]
+ ]
+ }
+ }
+
+*/
+
+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],
+ once = actions[i][2],
+ suspended = actions[i][3],
+ actionIndex = indexOf(otherActions, target, method);
+
+ if (actionIndex === -1) {
+ otherActions.push([target, method, once, suspended]);
+ }
+ }
+}
+
+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;
+}
+
+/**
+ 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);
+
+ if (actionIndex !== -1) { return; }
+
+ actions.push([target, method, once, undefined]);
+
+ 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, 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);
+ }
+ }
+
+ 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[3] = true; // 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[3] = undefined; } }
+
+ 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[3] = true;
+ 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][3] = undefined;
+ }
+ }
+
+ 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
+ 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.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, onceFlag, suspendedFlag]
+ ]
+ },
+ ...
+ ]
+*/
+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 changePropertiess
+ @param {Function} callback
+ @param [binding]
+*/
+var changeProperties = 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.
@@ -1964,11 +2802,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.
*/
-set = function set(obj, keyName, value, tolerant) {
+var 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;
@@ -2020,69 +2858,15 @@
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);
@@ -2106,39 +2890,10 @@
}
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
@@ -2156,25 +2911,10 @@
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() {
@@ -2191,11 +2931,11 @@
// ..........................................................
// DESCRIPTOR
//
/**
- Objects of this type can implement an interface to responds requests to
+ Objects of this type can implement an interface to respond to requests to
get and set. The default implementation handles simple properties.
You generally won't need to create or subclass this directly.
@class Descriptor
@@ -2261,11 +3001,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 {anything} [data] something other than a descriptor, that will
+ @param {*} [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;
@@ -2332,344 +3072,135 @@
})();
(function() {
-// Ember.tryFinally
-/**
-@module ember-metal
-*/
+var changeProperties = Ember.changeProperties,
+ set = Ember.set;
-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) {
- Ember.changeProperties(function(){
+ changeProperties(function(){
for(var prop in hash) {
- if (hash.hasOwnProperty(prop)) Ember.set(self, prop, hash[prop]);
+ if (hash.hasOwnProperty(prop)) { set(self, prop, hash[prop]); }
}
});
return self;
};
+})();
-function changeEvent(keyName) {
- return keyName+AFTER_OBSERVERS;
-}
-function beforeEvent(keyName) {
- return keyName+BEFORE_OBSERVERS;
-}
+(function() {
+var metaFor = Ember.meta, // utils.js
+ typeOf = Ember.typeOf, // utils.js
+ MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
+ o_defineProperty = Ember.platform.defineProperty;
-/**
- @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;
-};
+Ember.watchKey = function(obj, keyName) {
+ // can't watch length on Array - it is special...
+ if (keyName === 'length' && typeOf(obj) === 'array') { return; }
-Ember.observersFor = function(obj, path) {
- return Ember.listenersFor(obj, changeEvent(path));
-};
+ var m = metaFor(obj), watching = m.watching, desc;
-/**
- @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;
-};
+ // activate watching first time
+ if (!watching[keyName]) {
+ watching[keyName] = 1;
+ desc = m.descs[keyName];
+ if (desc && desc.willWatch) { desc.willWatch(obj, 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 ('function' === typeof obj.willWatchProperty) {
+ obj.willWatchProperty(keyName);
+ }
-// 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 (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;
+ }
};
-Ember._suspendObserver = function(obj, path, target, method, callback) {
- return Ember._suspendListener(obj, changeEvent(path), target, method, callback);
-};
-var map = Ember.ArrayPolyfills.map;
+Ember.unwatchKey = function(obj, keyName) {
+ var m = metaFor(obj), watching = m.watching, desc;
-Ember._suspendBeforeObservers = function(obj, paths, target, method, callback) {
- var events = map.call(paths, beforeEvent);
- return Ember._suspendListeners(obj, events, target, method, callback);
-};
+ if (watching[keyName] === 1) {
+ watching[keyName] = 0;
+ desc = m.descs[keyName];
-Ember._suspendObservers = function(obj, paths, target, method, callback) {
- var events = map.call(paths, changeEvent);
- return Ember._suspendListeners(obj, events, target, method, callback);
-};
+ if (desc && desc.didUnwatch) { desc.didUnwatch(obj, keyName); }
-Ember.beforeObserversFor = function(obj, path) {
- return Ember.listenersFor(obj, beforeEvent(path));
-};
+ if ('function' === typeof obj.didUnwatchProperty) {
+ obj.didUnwatchProperty(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]);
+ 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]--;
}
};
-
-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() {
-/**
-@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
+var metaFor = Ember.meta, // utils.js
+ get = Ember.get, // property_get.js
+ normalizeTuple = Ember.normalizeTuple, // property_get.js
forEach = Ember.ArrayPolyfills.forEach, // array.js
- FIRST_KEY = /^([^\.\*]+)/,
- IS_PATH = /[\.\*]/;
+ warn = Ember.warn,
+ watchKey = Ember.watchKey,
+ unwatchKey = Ember.unwatchKey,
+ propertyWillChange = Ember.propertyWillChange,
+ propertyDidChange = Ember.propertyDidChange,
+ FIRST_KEY = /^([^\.\*]+)/;
-var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
-o_defineProperty = Ember.platform.defineProperty;
-
function firstKey(path) {
return path.match(FIRST_KEY)[0];
}
-// returns true if the passed path is just a keyName
-function isKeyName(path) {
- return path==='*' || !IS_PATH.test(path);
-}
+var pendingQueue = [];
-// ..........................................................
-// DEPENDENT KEYS
-//
+// 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
-function iterDeps(method, obj, depKey, seen, meta) {
+ var queue = pendingQueue;
+ pendingQueue = [];
- var guid = guidFor(obj);
- if (!seen[guid]) seen[guid] = {};
- if (seen[guid][depKey]) return;
- seen[guid][depKey] = true;
+ forEach.call(queue, function(q) { q[0].add(q[1]); });
- 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);
- }
- }
-}
+ 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 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;
@@ -2677,14 +3208,14 @@
nodes = m.chainWatchers = {};
}
if (!nodes[keyName]) { nodes[keyName] = []; }
nodes[keyName].push(node);
- Ember.watch(obj, keyName);
+ watchKey(obj, keyName);
}
-function removeChainWatcher(obj, keyName, node) {
+var removeChainWatcher = Ember.removeChainWatcher = function(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
@@ -2694,38 +3225,21 @@
nodes = nodes[keyName];
for (var i = 0, l = nodes.length; i < l; i++) {
if (nodes[i] === node) { nodes.splice(i, 1); }
}
}
- Ember.unwatch(obj, keyName);
-}
+ unwatchKey(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 = function(parent, key, value) {
- var obj;
+var ChainNode = Ember._ChainNode = function(parent, key, value) {
this._parent = parent;
this._key = key;
// _watching is true when calling get(this._parent, this._key) will
// return the value of this node.
@@ -2893,24 +3407,24 @@
if (this._key) { path = this._key + '.' + path; }
if (this._parent) {
this._parent.chainWillChange(this, path, depth+1);
} else {
- if (depth > 1) { Ember.propertyWillChange(this.value(), path); }
+ if (depth > 1) { propertyWillChange(this.value(), path); }
path = 'this.' + path;
- if (this._paths[path] > 0) { Ember.propertyWillChange(this.value(), path); }
+ if (this._paths[path] > 0) { 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) { Ember.propertyDidChange(this.value(), path); }
+ if (depth > 1) { propertyDidChange(this.value(), path); }
path = 'this.' + path;
- if (this._paths[path] > 0) { Ember.propertyDidChange(this.value(), path); }
+ if (this._paths[path] > 0) { propertyDidChange(this.value(), path); }
}
};
ChainNodePrototype.didChange = function(suppressEvent) {
// invalidate my own value first.
@@ -2942,10 +3456,28 @@
// 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;
@@ -2955,45 +3487,60 @@
ret = m.chains = ret.copy(obj);
}
return ret;
}
-Ember.overrideChains = function(obj, keyName, m) {
- chainsDidChange(obj, keyName, m, true);
-};
+Ember.watchPath = function(obj, keyPath) {
+ // can't watch length on Array - it is special...
+ if (keyPath === 'length' && typeOf(obj) === 'array') { return; }
-function chainsWillChange(obj, keyName, m, arg) {
- if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
+ var m = metaFor(obj), watching = m.watching;
- var nodes = m.chainWatchers;
+ if (!watching[keyPath]) { // activate watching first time
+ watching[keyPath] = 1;
+ chainsFor(obj).add(keyPath);
+ } else {
+ watching[keyPath] = (watching[keyPath] || 0) + 1;
+ }
+};
- nodes = nodes[keyName];
- if (!nodes) { return; }
+Ember.unwatchPath = function(obj, keyPath) {
+ var m = metaFor(obj), watching = m.watching, desc;
- for(var i = 0, l = nodes.length; i < l; i++) {
- nodes[i].willChange(arg);
+ if (watching[keyPath] === 1) {
+ watching[keyPath] = 0;
+ chainsFor(obj).remove(keyPath);
+ } else if (watching[keyPath] > 1) {
+ watching[keyPath]--;
}
-}
+};
+})();
-function chainsDidChange(obj, keyName, m, arg) {
- if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
- var nodes = m.chainWatchers;
- nodes = nodes[keyName];
- if (!nodes) { return; }
+(function() {
+/**
+@module ember-metal
+*/
- // 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);
- }
+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);
}
-// ..........................................................
-// WATCH
-//
-
/**
@private
Starts watching a property on an object. Whenever the property changes,
invokes `Ember.propertyWillChange` and `Ember.propertyDidChange`. This is the
@@ -3004,88 +3551,37 @@
@method watch
@for Ember
@param obj
@param {String} keyName
*/
-Ember.watch = function(obj, keyName) {
+Ember.watch = function(obj, keyPath) {
// can't watch length on Array - it is special...
- if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; }
+ if (keyPath === 'length' && typeOf(obj) === 'array') { return; }
- 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;
+ if (isKeyName(keyPath)) {
+ watchKey(obj, keyPath);
+ } else {
+ watchPath(obj, keyPath);
}
- return this;
};
Ember.isWatching = function isWatching(obj, key) {
var meta = obj[META_KEY];
return (meta && meta.watching[key]) > 0;
};
-Ember.watch.flushPending = flushPendingChains;
+Ember.watch.flushPending = Ember.flushPendingChains;
-Ember.unwatch = function(obj, keyName) {
+Ember.unwatch = function(obj, keyPath) {
// can't watch length on Array - it is special...
- if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; }
+ if (keyPath === 'length' && typeOf(obj) === 'array') { return; }
- 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]--;
+ if (isKeyName(keyPath)) {
+ unwatchKey(obj, keyPath);
+ } else {
+ unwatchPath(obj, keyPath);
}
-
- return this;
};
/**
@private
@@ -3100,100 +3596,19 @@
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)) {
- Ember.generateGuid(obj, '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) {
- 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.
@@ -3269,11 +3684,11 @@
/*
This function returns a map of unique dependencies for a
given object and key.
*/
-function keysForDep(obj, depsMeta, depKey) {
+function keysForDep(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] = {};
@@ -3283,26 +3698,26 @@
keys = depsMeta[depKey] = o_create(keys);
}
return keys;
}
-function metaForDeps(obj, meta) {
- return keysForDep(obj, meta, 'deps');
+function metaForDeps(meta) {
+ return keysForDep(meta, '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(obj, meta);
+ depsMeta = metaForDeps(meta);
for(idx = 0, len = depKeys.length; idx < len; idx++) {
depKey = depKeys[idx];
// Lookup keys meta for depKey
- keys = keysForDep(obj, depsMeta, depKey);
+ keys = keysForDep(depsMeta, depKey);
// Increment the number of times depKey depends on keyName.
keys[keyName] = (keys[keyName] || 0) + 1;
// Watch the depKey
watch(obj, depKey);
}
@@ -3312,16 +3727,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(obj, meta);
+ depsMeta = metaForDeps(meta);
for(idx = 0, len = depKeys.length; idx < len; idx++) {
depKey = depKeys[idx];
// Lookup keys meta for depKey
- keys = keysForDep(obj, depsMeta, depKey);
+ keys = keysForDep(depsMeta, depKey);
// Increment the number of times depKey depends on keyName.
keys[keyName] = (keys[keyName] || 0) - 1;
// Watch the depKey
unwatch(obj, depKey);
}
@@ -3348,29 +3763,33 @@
Ember.ComputedProperty = ComputedProperty;
ComputedProperty.prototype = new Ember.Descriptor();
var ComputedPropertyPrototype = ComputedProperty.prototype;
-/**
- 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.
+/*
+ Call on a computed property to explicitly change it's cacheable mode.
+ Please use `.volatile` over this method.
+
```javascript
MyApp.president = Ember.Object.create({
fullName: function() {
return this.get('firstName') + ' ' + this.get('lastName');
- // After calculating the value of this function, Ember will
- // return that value without re-executing this function until
- // one of the dependent properties change.
+ // By default, Ember will return the value of this property
+ // without re-executing this function.
}.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
*/
@@ -3667,11 +4086,11 @@
@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 {any} the cached value
+ @return {*} the cached value
*/
Ember.cacheFor = function cacheFor(obj, key) {
var cache = metaFor(obj, false).cache;
if (cache && key in cache) {
@@ -3930,11 +4349,10 @@
@return {Ember.ComputedProperty} computed property which acts like
a standard getter and setter, but defaults to the value from `defaultPath`.
*/
Ember.computed.defaultTo = function(defaultPath) {
return Ember.computed(function(key, newValue, cachedValue) {
- var result;
if (arguments.length === 1) {
return cachedValue != null ? cachedValue : get(this, defaultPath);
}
return newValue != null ? newValue : get(this, defaultPath);
});
@@ -3943,385 +4361,111 @@
})();
(function() {
+// Ember.tryFinally
/**
@module ember-metal
*/
-var o_create = Ember.create,
- metaFor = Ember.meta,
- META_KEY = Ember.META_KEY;
+var AFTER_OBSERVERS = ':change';
+var BEFORE_OBSERVERS = ':before';
-/*
- 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.
+var guidFor = Ember.guidFor;
- 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, onceFlag, suspendedFlag]
- ]
- }
- }
-
-*/
-
-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 changeEvent(keyName) {
+ return keyName+AFTER_OBSERVERS;
}
-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 beforeEvent(keyName) {
+ return keyName+BEFORE_OBSERVERS;
}
-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]);
- }
- }
-}
-
-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;
-}
-
/**
- Add an event listener
-
- @method addListener
- @for Ember
+ @method addObserver
@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
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
*/
-function addListener(obj, eventName, target, method, once) {
- Ember.assert("You must pass at least an object and event name to Ember.addListener", !!obj && !!eventName);
+Ember.addObserver = function(obj, path, target, method) {
+ Ember.addListener(obj, changeEvent(path), target, method);
+ Ember.watch(obj, path);
+ return this;
+};
- if (!method && 'function' === typeof target) {
- method = target;
- target = null;
- }
+Ember.observersFor = function(obj, path) {
+ return Ember.listenersFor(obj, changeEvent(path));
+};
- 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);
- }
-}
-
/**
- Remove an event listener
-
- Arguments should match those passed to {{#crossLink "Ember/addListener"}}{{/crossLink}}
-
- @method removeListener
- @for Ember
+ @method removeObserver
@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 {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
*/
-function removeListener(obj, eventName, target, method) {
- Ember.assert("You must pass at least an object and event name to Ember.removeListener", !!obj && !!eventName);
+Ember.removeObserver = function(obj, path, target, method) {
+ Ember.unwatch(obj, path);
+ Ember.removeListener(obj, changeEvent(path), target, method);
+ return this;
+};
- 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);
- }
- }
-
- 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
+ @method addBeforeObserver
@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
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
*/
-function suspendListener(obj, eventName, target, method, callback) {
- if (!method && 'function' === typeof target) {
- method = target;
- target = null;
- }
+Ember.addBeforeObserver = function(obj, path, target, method) {
+ Ember.addListener(obj, beforeEvent(path), target, method);
+ Ember.watch(obj, path);
+ return this;
+};
- var actions = actionsFor(obj, eventName),
- actionIndex = indexOf(actions, target, method),
- action;
+// 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 (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
- }
+Ember._suspendObserver = function(obj, path, target, method, callback) {
+ return Ember._suspendListener(obj, changeEvent(path), target, method, callback);
+};
- function tryable() { return callback.call(target); }
- function finalizer() { if (action) { action[3] = undefined; } }
+var map = Ember.ArrayPolyfills.map;
- return Ember.tryFinally(tryable, finalizer);
-}
+Ember._suspendBeforeObservers = function(obj, paths, target, method, callback) {
+ var events = map.call(paths, beforeEvent);
+ return Ember._suspendListeners(obj, events, target, method, callback);
+};
-/**
- @private
+Ember._suspendObservers = function(obj, paths, target, method, callback) {
+ var events = map.call(paths, changeEvent);
+ return Ember._suspendListeners(obj, events, target, method, callback);
+};
- Suspend listener during callback.
+Ember.beforeObserversFor = function(obj, path) {
+ return Ember.listenersFor(obj, beforeEvent(path));
+};
- 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[3] = true;
- 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][3] = undefined;
- }
- }
-
- return Ember.tryFinally(tryable, finalizer);
-}
-
/**
- @private
-
- Return a list of currently watched events
-
- @method watchedEvents
- @for Ember
+ @method removeBeforeObserver
@param obj
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
*/
-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
- 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.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;
-
+Ember.removeBeforeObserver = function(obj, path, target, method) {
+ Ember.unwatch(obj, path);
+ Ember.removeListener(obj, beforeEvent(path), target, method);
+ return this;
+};
})();
(function() {
@@ -4364,12 +4508,10 @@
// ..........................................................
// RUNLOOP
//
-var timerMark; // used by timers...
-
/**
Ember RunLoop (Private)
@class RunLoop
@namespace Ember
@@ -4496,12 +4638,10 @@
idx++;
}
}
- timerMark = null;
-
return this;
}
};
@@ -4613,11 +4753,11 @@
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 `run.queues` property.
+ the `Ember.run.queues` property.
```javascript
Ember.run.schedule('sync', this, function(){
// this will be executed in the first RunLoop queue, when bindings are synced
console.log("scheduled on sync queue");
@@ -4844,54 +4984,70 @@
return guid;
}
/**
- Schedules an item to run one time during the current RunLoop. Calling
- this method with the same target/method combination will have no effect.
+ Schedule a function to run one time during the current RunLoop. This is equivalent
+ to calling `scheduleOnce` with the "actions" queue.
+ @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 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
+ 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
});
```
- Also note that passing an anonymous function to `Ember.run.once` will
+ 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.once(myContext, function() { console.log("Closure"); });
+ Ember.run.scheduleOnce('actions', myContext, function() { console.log("Closure"); });
}
scheduleIt();
scheduleIt();
- // "Closure" will print twice, even though we're using `Ember.run.once`,
+ // "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.
```
- @method once
- @param {Object} [target] target of method to invoke
+ 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.
@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));
-};
-
-Ember.run.scheduleOnce = function(queue, target, method, args) {
+Ember.run.scheduleOnce = function(queue, target, method) {
return scheduleOnce(queue, target, method, slice.call(arguments, 3));
};
/**
Schedules an item to run from within a separate run loop, after
@@ -4989,12 +5145,13 @@
(function() {
// Ember.Logger
-// get, set, trySet
-// guidFor, isArray, meta
+// get
+// set
+// guidFor, meta
// addObserver, removeObserver
// Ember.run.schedule
/**
@module ember-metal
*/
@@ -5016,13 +5173,26 @@
Ember.LOG_BINDINGS = false || !!Ember.ENV.LOG_BINDINGS;
var get = Ember.get,
set = Ember.set,
guidFor = Ember.guidFor,
- isGlobalPath = Ember.isGlobalPath;
+ IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/;
+/**
+ 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);
}
// ..........................................................
@@ -5309,11 +5479,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 "Customizing Your Bindings"):
+ (see "One Way Bindings"):
```
valueBinding: "MyApp.someController.title"
```
@@ -5609,11 +5779,11 @@
descs[key] = undefined;
values[key] = value;
}
}
-function mergeMixins(mixins, m, descs, values, base) {
+function mergeMixins(mixins, m, descs, values, base, keys) {
var mixin, props, key, concats, meta;
function removeKeys(keyName) {
delete descs[keyName];
delete values[keyName];
@@ -5630,17 +5800,18 @@
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);
+ mergeMixins(mixin.mixins, m, descs, values, base, keys);
if (mixin._without) { a_forEach.call(mixin._without, removeKeys); }
}
}
}
@@ -5732,22 +5903,23 @@
updateObservers(obj, key, observer, '__ember_observes__', 'addObserver');
}
function applyMixin(obj, mixins, partial) {
var descs = {}, values = {}, m = Ember.meta(obj),
- key, value, desc;
+ key, value, desc, keys = [];
// 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);
+ mergeMixins(mixins, mixinsMeta(obj), descs, values, obj, keys);
- for(key in values) {
- if (key === 'contructor' || !values.hasOwnProperty(key)) { continue; }
+ for(var i = 0, l = keys.length; i < l; i++) {
+ key = keys[i];
+ if (key === 'constructor' || !values.hasOwnProperty(key)) { continue; }
desc = descs[key];
value = values[key];
if (desc === REQUIRED) { continue; }
@@ -5797,11 +5969,11 @@
},
isEditing: false
});
// Mix mixins into classes by passing them as the first arguments to
- // .extend or .create.
+ // .extend.
App.CommentView = Ember.View.extend(App.Editable, {
template: Ember.Handlebars.compile('{{#if isEditing}}...{{else}}...{{/if}}')
});
commentView = App.CommentView.create();
@@ -5816,10 +5988,16 @@
*/
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);
@@ -6040,11 +6218,11 @@
*/
Ember.alias = function(methodName) {
return new Alias(methodName);
};
-Ember.deprecateFunc("Ember.alias is deprecated. Please use Ember.aliasMethod or Ember.computed.alias instead.", Ember.alias);
+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
@@ -6655,11 +6833,11 @@
function instantiate(container, fullName) {
var factory = factoryFor(container, fullName);
var splitName = fullName.split(":"),
- type = splitName[0], name = splitName[1],
+ type = splitName[0],
value;
if (option(container, fullName, 'instantiate') === false) {
return factory;
}
@@ -6706,84 +6884,11 @@
@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' | 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;
-};
-
-/**
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,
@@ -7137,11 +7242,12 @@
"Hello %@ %@".fmt('John', 'Doe'); // "Hello John Doe"
"Hello %@2, %@1".fmt('John', 'Doe'); // "Hello Doe, John"
```
@method fmt
- @param {Object...} [args]
+ @param {String} str The string to format
+ @param {Array} formats An array of parameters to interpolate into string.
@return {String} formatted string
*/
fmt: function(str, formats) {
// first, replace any ORDERED replacements.
var idx = 0; // the current index for non-numerical replacements
@@ -8540,11 +8646,11 @@
arr.objectAt(5); // undefined
```
@method objectAt
@param {Number} idx The index of the item to return.
- @return {any} item at index or undefined
+ @return {*} item at index or undefined
*/
objectAt: function(idx) {
if ((idx < 0) || (idx>=get(this, 'length'))) return undefined ;
return get(this, idx);
},
@@ -9114,20 +9220,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 the object if of a type supported by the enumerable.
+ already present and is of a type supported by the enumerable.
```javascript
set.addObject(contact);
```
## Removing Objects
- 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
+ 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
this method has no effect.
```javascript
set.removeObject(contact);
```
@@ -9150,11 +9256,11 @@
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
@@ -9177,25 +9283,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 in present in the collection. If the object is not present,
+ object is 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 objects in the passed enumerable from the receiver.
+ Removes each object in the passed enumerable from the receiver.
@method removeObjects
@param {Ember.Enumerable} objects the objects to remove
@return {Object} receiver
*/
@@ -9349,12 +9455,12 @@
colors.pushObject("black"); // ["red", "green", "blue", "black"]
colors.pushObject(["yellow", "orange"]); // ["red", "green", "blue", "black", ["yellow", "orange"]]
```
@method pushObject
- @param {anything} obj object to push
- @return {any} the same obj passed as param
+ @param {*} obj object to push
+ @return {*} the same obj passed as param
*/
pushObject: function(obj) {
this.insertAt(get(this, 'length'), obj) ;
return obj ;
},
@@ -9429,12 +9535,12 @@
colors.unshiftObject("yellow"); // ["yellow", "red", "green", "blue"]
colors.unshiftObject(["black", "white"]); // [["black", "white"], "yellow", "red", "green", "blue"]
```
@method unshiftObject
- @param {anything} obj object to unshift
- @return {any} the same obj passed as param
+ @param {*} obj object to unshift
+ @return {*} the same obj passed as param
*/
unshiftObject: function(obj) {
this.insertAt(0, obj) ;
return obj ;
},
@@ -10047,17 +10153,27 @@
*/
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") {
@@ -10067,25 +10183,90 @@
} else {
return target;
}
}).property('target'),
- triggerAction: function() {
- var action = get(this, 'action'),
- target = get(this, 'targetObject');
+ actionContextObject: Ember.computed(function() {
+ var actionContext = get(this, 'actionContext');
+ 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 (typeof target.send === 'function') {
- ret = target.send(action, this);
+ if (target.send) {
+ ret = target.send.apply(target, [action, actionContext]);
} else {
- if (typeof action === 'string') {
- action = target[action];
- }
- ret = action.call(target, this);
+ Ember.assert("The action '" + action + "' did not exist on " + target, typeof target[action] === 'function');
+ ret = target[action].apply(target, [actionContext]);
}
+
if (ret !== false) ret = true;
return ret;
} else {
return false;
@@ -10391,10 +10572,13 @@
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;
@@ -10512,12 +10696,10 @@
`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() {},
/**
@@ -10624,28 +10806,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);
- set(this, 'isDestroyed', true);
-
+ this.isDestroyed = true;
if (this.didDestroy) { this.didDestroy(); }
},
bind: function(to, from) {
if (!(from instanceof Ember.Binding)) { from = Ember.Binding.from(from); }
@@ -10989,11 +11171,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") { continue; }
+ if (prop === "parent" || prop === "top" || prop === "frameElement" || prop === "webkitStorageInfo") { 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
@@ -11677,11 +11859,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 {anything}
+ @param value {*}
*/
unknownProperty: function(keyName, value) {
var ret;
ret = new EachArray(this._content, keyName, this);
Ember.defineProperty(this, keyName, null, ret);
@@ -11901,13 +12083,12 @@
@class NativeArray
@namespace Ember
@extends Ember.Mixin
@uses Ember.MutableArray
- @uses Ember.MutableEnumerable
+ @uses Ember.Observable
@uses Ember.Copyable
- @uses Ember.Freezable
*/
Ember.NativeArray = NativeArray;
/**
Creates an `Ember.NativeArray` from an Array like object.
@@ -12914,13 +13095,13 @@
}
}
});
```
- @method
- @type String
- @default null
+ @method lookupItemController
+ @param {Object} object
+ @return {String}
*/
lookupItemController: function(object) {
return get(this, 'itemController');
},
@@ -12965,12 +13146,12 @@
// calling `objectAt`
this._super(idx, removedCnt, addedCnt);
},
init: function() {
- this._super();
if (!this.get('content')) { Ember.defineProperty(this, 'content', undefined, Ember.A()); }
+ this._super();
this.set('_subControllers', Ember.A());
},
controllerAt: function(idx, object, controllerClass) {
var container = get(this, 'container'),
@@ -12994,13 +13175,14 @@
_subControllers: null,
_resetSubControllers: function() {
var subControllers = get(this, '_subControllers');
-
- forEach(subControllers, function(subController) {
- if (subController) { subController.destroy(); }
- });
+ if (subControllers) {
+ forEach(subControllers, function(subController) {
+ if (subController) { subController.destroy(); }
+ });
+ }
this.set('_subControllers', Ember.A());
}
});