diff --git a/app/assets/javascripts/external_development/ember.js b/app/assets/javascripts/external_development/ember.js
index a49cfe8514a..9e1168043ed 100644
--- a/app/assets/javascripts/external_development/ember.js
+++ b/app/assets/javascripts/external_development/ember.js
@@ -1,5 +1,5 @@
-// Version: v1.0.0-pre.2-950-g656fa6e
-// Last commit: 656fa6e (2013-03-29 13:40:38 -0700)
+// Version: v1.0.0-rc.3
+// Last commit: 96e97b5 (2013-04-21 02:07:10 -0400)
(function() {
@@ -151,8 +151,8 @@ Ember.deprecateFunc = function(message, func) {
})();
-// Version: v1.0.0-pre.2-950-g656fa6e
-// Last commit: 656fa6e (2013-03-29 13:40:38 -0700)
+// Version: v1.0.0-rc.3
+// Last commit: 96e97b5 (2013-04-21 02:07:10 -0400)
(function() {
@@ -212,7 +212,7 @@ var define, requireModule;
@class Ember
@static
- @version 1.0.0-rc.2
+ @version 1.0.0-rc.3
*/
if ('undefined' === typeof Ember) {
@@ -239,10 +239,10 @@ Ember.toString = function() { return "Ember"; };
/**
@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`
@@ -634,6 +634,103 @@ if (Ember.ENV.MANDATORY_SETTER && !platform.hasPropertyAccessors) {
+(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
@@ -1036,7 +1133,7 @@ Ember.canInvoke = canInvoke;
@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)) {
@@ -1067,7 +1164,7 @@ var needsFinallyFix = (function() {
@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
*/
@@ -1118,7 +1215,7 @@ if (needsFinallyFix) {
@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.
*/
@@ -1162,6 +1259,78 @@ if (needsFinallyFix) {
};
}
+// ........................................
+// 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;
+};
})();
@@ -1342,17 +1511,23 @@ Ember.subscribe = Ember.Instrumentation.subscribe;
(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) {
@@ -1375,7 +1550,7 @@ var utils = Ember.EnumerableUtils = {
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);
}
},
@@ -1383,8 +1558,8 @@ var utils = Ember.EnumerableUtils = {
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);
}
});
@@ -1397,103 +1572,6 @@ var utils = Ember.EnumerableUtils = {
-(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
@@ -1633,7 +1711,7 @@ OrderedSet.prototype = {
*/
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]);
@@ -1656,7 +1734,7 @@ OrderedSet.prototype = {
var set = new OrderedSet();
set.presenceSet = copy(this.presenceSet);
- set.list = this.list.slice();
+ set.list = this.toArray();
return set;
}
@@ -1700,8 +1778,8 @@ 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,
@@ -1715,8 +1793,8 @@ Map.prototype = {
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,
@@ -1731,7 +1809,7 @@ Map.prototype = {
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) {
@@ -1739,12 +1817,10 @@ Map.prototype = {
// 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 {
@@ -1756,7 +1832,7 @@ Map.prototype = {
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) {
@@ -1774,7 +1850,7 @@ Map.prototype = {
@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) {
@@ -1803,7 +1879,7 @@ Map.prototype = {
@private
@constructor
@param [options]
- @param {anything} [options.defaultValue]
+ @param {*} [options.defaultValue]
*/
var MapWithDefault = Ember.MapWithDefault = function(options) {
Map.call(this);
@@ -1814,7 +1890,7 @@ var MapWithDefault = Ember.MapWithDefault = function(options) {
@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`
*/
@@ -1832,8 +1908,8 @@ MapWithDefault.prototype = Ember.create(Map.prototype);
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);
@@ -1866,11 +1942,10 @@ MapWithDefault.prototype.copy = 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 = /^([^\.\*]+)/;
@@ -1943,90 +2018,11 @@ get = function get(obj, keyName) {
}
};
-/**
- Sets the value of a property on an object, respecting computed properties
- and notifying observers and other listeners of the change. If the
- property is not defined but the object implements the `unknownProperty`
- method then that will be invoked as well.
-
- If you plan to run on IE8 and older browsers then you should use this
- method anytime you want to set a property on an object that you don't
- know for sure is private. (Properties beginning with an underscore '_'
- are considered private.)
-
- On all newer browsers, you only need to use this method to set
- properties if the property might not be defined on the object and you want
- to respect the `unknownProperty` handler. Otherwise you can ignore this
- method.
-
- @method set
- @for Ember
- @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) {
- 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;
- }
-
- if (!obj || keyName.indexOf('.') !== -1) {
- return setPath(obj, keyName, value, tolerant);
- }
-
- Ember.assert("You need to provide an object and key to `set`.", !!obj && keyName !== undefined);
- Ember.assert('calling set on destroyed object', !obj.isDestroyed);
-
- var meta = obj[META_KEY], desc = meta && meta.descs[keyName],
- isUnknown, currentValue;
- if (desc) {
- desc.set(obj, keyName, value);
- } else {
- isUnknown = 'object' === typeof obj && !(keyName in obj);
-
- // setUnknownProperty is called if `obj` is an object,
- // the property does not already exist, and the
- // `setUnknownProperty` method exists on the object
- if (isUnknown && 'function' === typeof obj.setUnknownProperty) {
- obj.setUnknownProperty(keyName, value);
- } else if (meta && meta.watching[keyName] > 0) {
- if (MANDATORY_SETTER) {
- currentValue = meta.values[keyName];
- } else {
- currentValue = obj[keyName];
- }
- // only trigger a change if the value has changed
- if (value !== currentValue) {
- Ember.propertyWillChange(obj, keyName);
- if (MANDATORY_SETTER) {
- if (currentValue === undefined && !(keyName in obj)) {
- Ember.defineProperty(obj, keyName, null, value); // setup mandatory setter
- } else {
- meta.values[keyName] = value;
- }
- } else {
- obj[keyName] = value;
- }
- Ember.propertyDidChange(obj, keyName);
- }
- } else {
- obj[keyName] = value;
- }
- }
- 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) {
@@ -2054,7 +2050,7 @@ function normalizeTuple(target, path) {
return [ target, path ];
}
-function getPath(root, 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
@@ -2079,34 +2075,7 @@ function getPath(root, path) {
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);
-
- // get the first part of the part
- path = path.slice(0, path.length-(keyName.length+1));
-
- // unless the path is this, look up the first part to
- // get the root
- if (path !== 'this') {
- root = getPath(root, path);
- }
-
- if (!keyName || keyName.length === 0) {
- throw new Error('You passed an empty path');
- }
-
- if (!root) {
- if (tolerant) { return; }
- else { throw new Error('Object in path '+path+' could not be found or was destroyed.'); }
- }
-
- return set(root, keyName, value);
-}
+};
/**
@private
@@ -2136,1810 +2105,6 @@ Ember.getWithDefault = function(root, key, defaultValue) {
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
- chain is `undefined`, `null`, or destroyed.
-
- This is primarily used when syncing bindings, which may try to update after
- an object has been destroyed.
-
- @method trySet
- @for Ember
- @param {Object} obj The object to modify.
- @param {String} path The property path to set
- @param {Object} value The value to set
-*/
-Ember.trySet = function(root, path, value) {
- return set(root, path, value, true);
-};
-Ember.trySetPath = Ember.deprecateFunc('trySetPath has been renamed to trySet', Ember.trySet);
-
-/**
- Returns true if the provided path is global (e.g., `MyApp.fooController.bar`)
- instead of local (`foo.bar.baz`).
-
- @method isGlobalPath
- @for Ember
- @private
- @param {String} path
- @return Boolean
-*/
-Ember.isGlobalPath = function(path) {
- return IS_GLOBAL.test(path);
-};
-
-
-})();
-
-
-
-(function() {
-/**
-@module ember-metal
-*/
-
-var META_KEY = Ember.META_KEY,
- metaFor = Ember.meta,
- objectDefineProperty = Ember.platform.defineProperty;
-
-var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
-
-// ..........................................................
-// DESCRIPTOR
-//
-
-/**
- Objects of this type can implement an interface to responds requests to
- get and set. The default implementation handles simple properties.
-
- You generally won't need to create or subclass this directly.
-
- @class Descriptor
- @namespace Ember
- @private
- @constructor
-*/
-var Descriptor = Ember.Descriptor = function() {};
-
-// ..........................................................
-// DEFINING PROPERTIES API
-//
-
-var MANDATORY_SETTER_FUNCTION = Ember.MANDATORY_SETTER_FUNCTION = function(value) {
- Ember.assert("You must use Ember.set() to access this property (of " + this + ")", false);
-};
-
-var DEFAULT_GETTER_FUNCTION = Ember.DEFAULT_GETTER_FUNCTION = function(name) {
- return function() {
- var meta = this[META_KEY];
- return meta && meta.values[name];
- };
-};
-
-/**
- @private
-
- NOTE: This is a low-level method used by other parts of the API. You almost
- never want to call this method directly. Instead you should use
- `Ember.mixin()` to define new properties.
-
- Defines a property on an object. This method works much like the ES5
- `Object.defineProperty()` method except that it can also accept computed
- properties and other special descriptors.
-
- Normally this method takes only three parameters. However if you pass an
- instance of `Ember.Descriptor` as the third param then you can pass an
- optional value as the fourth parameter. This is often more efficient than
- creating new descriptor hashes for each property.
-
- ## Examples
-
- ```javascript
- // ES5 compatible mode
- Ember.defineProperty(contact, 'firstName', {
- writable: true,
- configurable: false,
- enumerable: true,
- value: 'Charles'
- });
-
- // define a simple property
- Ember.defineProperty(contact, 'lastName', undefined, 'Jolley');
-
- // define a computed property
- Ember.defineProperty(contact, 'fullName', Ember.computed(function() {
- return this.firstName+' '+this.lastName;
- }).property('firstName', 'lastName'));
- ```
-
- @method defineProperty
- @for Ember
- @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
- become the explicit value of this property.
-*/
-Ember.defineProperty = function(obj, keyName, desc, data, meta) {
- var descs, existingDesc, watching, value;
-
- if (!meta) meta = metaFor(obj);
- descs = meta.descs;
- existingDesc = meta.descs[keyName];
- watching = meta.watching[keyName] > 0;
-
- if (existingDesc instanceof Ember.Descriptor) {
- existingDesc.teardown(obj, keyName);
- }
-
- if (desc instanceof Ember.Descriptor) {
- value = desc;
-
- descs[keyName] = desc;
- if (MANDATORY_SETTER && watching) {
- objectDefineProperty(obj, keyName, {
- configurable: true,
- enumerable: true,
- writable: true,
- value: undefined // make enumerable
- });
- } else {
- obj[keyName] = undefined; // make enumerable
- }
- desc.setup(obj, keyName);
- } else {
- descs[keyName] = undefined; // shadow descriptor in proto
- if (desc == null) {
- value = data;
-
- if (MANDATORY_SETTER && watching) {
- meta.values[keyName] = data;
- objectDefineProperty(obj, keyName, {
- configurable: true,
- enumerable: true,
- set: MANDATORY_SETTER_FUNCTION,
- get: DEFAULT_GETTER_FUNCTION(keyName)
- });
- } else {
- obj[keyName] = data;
- }
- } else {
- value = desc;
-
- // compatibility with ES5
- objectDefineProperty(obj, keyName, desc);
- }
- }
-
- // if key is being watched, override chains that
- // were initialized with the prototype
- if (watching) { Ember.overrideChains(obj, keyName, meta); }
-
- // The `value` passed to the `didDefineProperty` hook is
- // either the descriptor or data, whichever was passed.
- if (obj.didDefineProperty) { obj.didDefineProperty(obj, keyName, value); }
-
- return this;
-};
-
-
-})();
-
-
-
-(function() {
-// Ember.tryFinally
-/**
-@module ember-metal
-*/
-
-var AFTER_OBSERVERS = ':change';
-var BEFORE_OBSERVERS = ':before';
-
-var guidFor = Ember.guidFor;
-
-var deferred = 0;
-
-/*
- this.observerSet = {
- [senderGuid]: { // variable name: `keySet`
- [keyName]: listIndex
- }
- },
- this.observers = [
- {
- sender: obj,
- keyName: keyName,
- eventName: eventName,
- listeners: [
- [target, method, onceFlag, suspendedFlag]
- ]
- },
- ...
- ]
-*/
-function ObserverSet() {
- this.clear();
-}
-
-ObserverSet.prototype.add = function(sender, keyName, eventName) {
- var observerSet = this.observerSet,
- observers = this.observers,
- senderGuid = Ember.guidFor(sender),
- keySet = observerSet[senderGuid],
- index;
-
- if (!keySet) {
- observerSet[senderGuid] = keySet = {};
- }
- index = keySet[keyName];
- if (index === undefined) {
- index = observers.push({
- sender: sender,
- keyName: keyName,
- eventName: eventName,
- listeners: []
- }) - 1;
- keySet[keyName] = index;
- }
- return observers[index].listeners;
-};
-
-ObserverSet.prototype.flush = function() {
- var observers = this.observers, i, len, observer, sender;
- this.clear();
- for (i=0, len=observers.length; i < len; ++i) {
- observer = observers[i];
- sender = observer.sender;
- if (sender.isDestroying || sender.isDestroyed) { continue; }
- Ember.sendEvent(sender, observer.eventName, [sender, observer.keyName], observer.listeners);
- }
-};
-
-ObserverSet.prototype.clear = function() {
- this.observerSet = {};
- this.observers = [];
-};
-
-var beforeObserverSet = new ObserverSet(), observerSet = new ObserverSet();
-
-/**
- @method beginPropertyChanges
- @chainable
-*/
-Ember.beginPropertyChanges = function() {
- deferred++;
-};
-
-/**
- @method endPropertyChanges
-*/
-Ember.endPropertyChanges = function() {
- deferred--;
- if (deferred<=0) {
- beforeObserverSet.clear();
- observerSet.flush();
- }
-};
-
-/**
- Make a series of property changes together in an
- exception-safe way.
-
- ```javascript
- Ember.changeProperties(function() {
- obj1.set('foo', mayBlowUpWhenSet);
- obj2.set('bar', baz);
- });
- ```
-
- @method changeProperties
- @param {Function} callback
- @param [binding]
-*/
-Ember.changeProperties = function(cb, binding){
- Ember.beginPropertyChanges();
- Ember.tryFinally(cb, Ember.endPropertyChanges, binding);
-};
-
-/**
- Set a list of properties on an object. These properties are set inside
- a single `beginPropertyChanges` and `endPropertyChanges` batch, so
- observers will be buffered.
-
- @method setProperties
- @param target
- @param {Hash} properties
- @return target
-*/
-Ember.setProperties = function(self, hash) {
- Ember.changeProperties(function(){
- for(var prop in hash) {
- if (hash.hasOwnProperty(prop)) Ember.set(self, prop, hash[prop]);
- }
- });
- return self;
-};
-
-
-function changeEvent(keyName) {
- return keyName+AFTER_OBSERVERS;
-}
-
-function beforeEvent(keyName) {
- return keyName+BEFORE_OBSERVERS;
-}
-
-/**
- @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.observersFor = function(obj, path) {
- return Ember.listenersFor(obj, changeEvent(path));
-};
-
-/**
- @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;
-};
-
-/**
- @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;
-};
-
-// Suspend observer during callback.
-//
-// This should only be used by the target of the observer
-// while it is setting the observed path.
-Ember._suspendBeforeObserver = function(obj, path, target, method, callback) {
- return Ember._suspendListener(obj, beforeEvent(path), target, method, callback);
-};
-
-Ember._suspendObserver = function(obj, path, target, method, callback) {
- return Ember._suspendListener(obj, changeEvent(path), target, method, callback);
-};
-
-var map = Ember.ArrayPolyfills.map;
-
-Ember._suspendBeforeObservers = function(obj, paths, target, method, callback) {
- var events = map.call(paths, beforeEvent);
- return Ember._suspendListeners(obj, events, target, method, callback);
-};
-
-Ember._suspendObservers = function(obj, paths, target, method, callback) {
- var events = map.call(paths, changeEvent);
- return Ember._suspendListeners(obj, events, target, method, callback);
-};
-
-Ember.beforeObserversFor = function(obj, path) {
- return Ember.listenersFor(obj, beforeEvent(path));
-};
-
-/**
- @method removeBeforeObserver
- @param obj
- @param {String} path
- @param {Object|Function} targetOrMethod
- @param {Function|String} [method]
-*/
-Ember.removeBeforeObserver = function(obj, path, target, method) {
- Ember.unwatch(obj, path);
- Ember.removeListener(obj, beforeEvent(path), target, method);
- return this;
-};
-
-Ember.notifyBeforeObservers = function(obj, keyName) {
- if (obj.isDestroying) { return; }
-
- var eventName = beforeEvent(keyName), listeners, listenersDiff;
- if (deferred) {
- listeners = beforeObserverSet.add(obj, keyName, eventName);
- listenersDiff = Ember.listenersDiff(obj, eventName, listeners);
- Ember.sendEvent(obj, eventName, [obj, keyName], listenersDiff);
- } else {
- Ember.sendEvent(obj, eventName, [obj, keyName]);
- }
-};
-
-Ember.notifyObservers = function(obj, keyName) {
- if (obj.isDestroying) { return; }
-
- var eventName = changeEvent(keyName), listeners;
- if (deferred) {
- listeners = observerSet.add(obj, keyName, eventName);
- Ember.listenersUnion(obj, eventName, listeners);
- } else {
- Ember.sendEvent(obj, eventName, [obj, keyName]);
- }
-};
-
-})();
-
-
-
-(function() {
-/**
-@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
- forEach = Ember.ArrayPolyfills.forEach, // array.js
- FIRST_KEY = /^([^\.\*]+)/,
- IS_PATH = /[\.\*]/;
-
-var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
-o_defineProperty = Ember.platform.defineProperty;
-
-function firstKey(path) {
- return path.match(FIRST_KEY)[0];
-}
-
-// returns true if the passed path is just a keyName
-function isKeyName(path) {
- return path==='*' || !IS_PATH.test(path);
-}
-
-// ..........................................................
-// DEPENDENT KEYS
-//
-
-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 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;
-
- if (!m.hasOwnProperty('chainWatchers')) {
- nodes = m.chainWatchers = {};
- }
-
- if (!nodes[keyName]) { nodes[keyName] = []; }
- nodes[keyName].push(node);
- Ember.watch(obj, keyName);
-}
-
-function removeChainWatcher(obj, keyName, node) {
- if (!obj || 'object' !== typeof obj) { return; } // nothing to do
-
- var m = metaFor(obj, false);
- if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
-
- var nodes = m.chainWatchers;
-
- if (nodes[keyName]) {
- 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);
-}
-
-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;
- this._parent = parent;
- this._key = key;
-
- // _watching is true when calling get(this._parent, this._key) will
- // return the value of this node.
- //
- // It is false for the root of a chain (because we have no parent)
- // and for global paths (because the parent node is the object with
- // the observer on it)
- this._watching = value===undefined;
-
- this._value = value;
- this._paths = {};
- if (this._watching) {
- this._object = parent.value();
- if (this._object) { addChainWatcher(this._object, this._key, this); }
- }
-
- // Special-case: the EachProxy relies on immediate evaluation to
- // establish its observers.
- //
- // TODO: Replace this with an efficient callback that the EachProxy
- // can implement.
- if (this._parent && this._parent._key === '@each') {
- this.value();
- }
-};
-
-var ChainNodePrototype = ChainNode.prototype;
-
-ChainNodePrototype.value = function() {
- if (this._value === undefined && this._watching) {
- var obj = this._parent.value();
- this._value = (obj && !isProto(obj)) ? get(obj, this._key) : undefined;
- }
- return this._value;
-};
-
-ChainNodePrototype.destroy = function() {
- if (this._watching) {
- var obj = this._object;
- if (obj) { removeChainWatcher(obj, this._key, this); }
- this._watching = false; // so future calls do nothing
- }
-};
-
-// copies a top level object only
-ChainNodePrototype.copy = function(obj) {
- var ret = new ChainNode(null, null, obj),
- paths = this._paths, path;
- for (path in paths) {
- if (paths[path] <= 0) { continue; } // this check will also catch non-number vals.
- ret.add(path);
- }
- return ret;
-};
-
-// called on the root node of a chain to setup watchers on the specified
-// path.
-ChainNodePrototype.add = function(path) {
- var obj, tuple, key, src, paths;
-
- paths = this._paths;
- paths[path] = (paths[path] || 0) + 1;
-
- obj = this.value();
- tuple = normalizeTuple(obj, path);
-
- // the path was a local path
- if (tuple[0] && tuple[0] === obj) {
- path = tuple[1];
- key = firstKey(path);
- path = path.slice(key.length+1);
-
- // global path, but object does not exist yet.
- // put into a queue and try to connect later.
- } else if (!tuple[0]) {
- pendingQueue.push([this, path]);
- tuple.length = 0;
- return;
-
- // global path, and object already exists
- } else {
- src = tuple[0];
- key = path.slice(0, 0-(tuple[1].length+1));
- path = tuple[1];
- }
-
- tuple.length = 0;
- this.chain(key, path, src);
-};
-
-// called on the root node of a chain to teardown watcher on the specified
-// path
-ChainNodePrototype.remove = function(path) {
- var obj, tuple, key, src, paths;
-
- paths = this._paths;
- if (paths[path] > 0) { paths[path]--; }
-
- obj = this.value();
- tuple = normalizeTuple(obj, path);
- if (tuple[0] === obj) {
- path = tuple[1];
- key = firstKey(path);
- path = path.slice(key.length+1);
- } else {
- src = tuple[0];
- key = path.slice(0, 0-(tuple[1].length+1));
- path = tuple[1];
- }
-
- tuple.length = 0;
- this.unchain(key, path);
-};
-
-ChainNodePrototype.count = 0;
-
-ChainNodePrototype.chain = function(key, path, src) {
- var chains = this._chains, node;
- if (!chains) { chains = this._chains = {}; }
-
- node = chains[key];
- if (!node) { node = chains[key] = new ChainNode(this, key, src); }
- node.count++; // count chains...
-
- // chain rest of path if there is one
- if (path && path.length>0) {
- key = firstKey(path);
- path = path.slice(key.length+1);
- node.chain(key, path); // NOTE: no src means it will observe changes...
- }
-};
-
-ChainNodePrototype.unchain = function(key, path) {
- var chains = this._chains, node = chains[key];
-
- // unchain rest of path first...
- if (path && path.length>1) {
- key = firstKey(path);
- path = path.slice(key.length+1);
- node.unchain(key, path);
- }
-
- // delete node if needed.
- node.count--;
- if (node.count<=0) {
- delete chains[node._key];
- node.destroy();
- }
-
-};
-
-ChainNodePrototype.willChange = function() {
- var chains = this._chains;
- if (chains) {
- for(var key in chains) {
- if (!chains.hasOwnProperty(key)) { continue; }
- chains[key].willChange();
- }
- }
-
- if (this._parent) { this._parent.chainWillChange(this, this._key, 1); }
-};
-
-ChainNodePrototype.chainWillChange = function(chain, path, depth) {
- 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); }
- path = 'this.' + path;
- if (this._paths[path] > 0) { Ember.propertyWillChange(this.value(), path); }
- }
-};
-
-ChainNodePrototype.chainDidChange = function(chain, path, depth) {
- if (this._key) { path = this._key + '.' + path; }
- if (this._parent) {
- this._parent.chainDidChange(this, path, depth+1);
- } else {
- if (depth > 1) { Ember.propertyDidChange(this.value(), path); }
- path = 'this.' + path;
- if (this._paths[path] > 0) { Ember.propertyDidChange(this.value(), path); }
- }
-};
-
-ChainNodePrototype.didChange = function(suppressEvent) {
- // invalidate my own value first.
- if (this._watching) {
- var obj = this._parent.value();
- if (obj !== this._object) {
- removeChainWatcher(this._object, this._key, this);
- this._object = obj;
- addChainWatcher(obj, this._key, this);
- }
- this._value = undefined;
-
- // Special-case: the EachProxy relies on immediate evaluation to
- // establish its observers.
- if (this._parent && this._parent._key === '@each')
- this.value();
- }
-
- // then notify chains...
- var chains = this._chains;
- if (chains) {
- for(var key in chains) {
- if (!chains.hasOwnProperty(key)) { continue; }
- chains[key].didChange(suppressEvent);
- }
- }
-
- if (suppressEvent) { return; }
-
- // and finally tell parent about my path changing...
- if (this._parent) { this._parent.chainDidChange(this, this._key, 1); }
-};
-
-// 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;
- if (!ret) {
- ret = m.chains = new ChainNode(null, null, obj);
- } else if (ret.value() !== obj) {
- ret = m.chains = ret.copy(obj);
- }
- return ret;
-}
-
-Ember.overrideChains = function(obj, keyName, m) {
- chainsDidChange(obj, keyName, m, true);
-};
-
-function chainsWillChange(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);
- }
-}
-
-function chainsDidChange(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);
- }
-}
-
-// ..........................................................
-// WATCH
-//
-
-/**
- @private
-
- Starts watching a property on an object. Whenever the property changes,
- invokes `Ember.propertyWillChange` and `Ember.propertyDidChange`. This is the
- primitive used by observers and dependent keys; usually you will never call
- this method directly but instead use higher level methods like
- `Ember.addObserver()`
-
- @method watch
- @for Ember
- @param obj
- @param {String} keyName
-*/
-Ember.watch = function(obj, keyName) {
- // can't watch length on Array - it is special...
- if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; }
-
- var m = metaFor(obj), watching = m.watching, desc;
-
- // activate watching first time
- if (!watching[keyName]) {
- watching[keyName] = 1;
- if (isKeyName(keyName)) {
- desc = m.descs[keyName];
- if (desc && desc.willWatch) { desc.willWatch(obj, keyName); }
-
- if ('function' === typeof obj.willWatchProperty) {
- obj.willWatchProperty(keyName);
- }
-
- if (MANDATORY_SETTER && keyName in obj) {
- m.values[keyName] = obj[keyName];
- o_defineProperty(obj, keyName, {
- configurable: true,
- enumerable: true,
- set: Ember.MANDATORY_SETTER_FUNCTION,
- get: Ember.DEFAULT_GETTER_FUNCTION(keyName)
- });
- }
- } else {
- chainsFor(obj).add(keyName);
- }
-
- } else {
- watching[keyName] = (watching[keyName] || 0) + 1;
- }
- return this;
-};
-
-Ember.isWatching = function isWatching(obj, key) {
- var meta = obj[META_KEY];
- return (meta && meta.watching[key]) > 0;
-};
-
-Ember.watch.flushPending = flushPendingChains;
-
-Ember.unwatch = function(obj, keyName) {
- // can't watch length on Array - it is special...
- if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; }
-
- var m = metaFor(obj), watching = m.watching, desc;
-
- if (watching[keyName] === 1) {
- watching[keyName] = 0;
-
- if (isKeyName(keyName)) {
- desc = m.descs[keyName];
- if (desc && desc.didUnwatch) { desc.didUnwatch(obj, keyName); }
-
- if ('function' === typeof obj.didUnwatchProperty) {
- obj.didUnwatchProperty(keyName);
- }
-
- if (MANDATORY_SETTER && keyName in obj) {
- o_defineProperty(obj, keyName, {
- configurable: true,
- enumerable: true,
- writable: true,
- value: m.values[keyName]
- });
- delete m.values[keyName];
- }
- } else {
- chainsFor(obj).remove(keyName);
- }
-
- } else if (watching[keyName]>1) {
- watching[keyName]--;
- }
-
- return this;
-};
-
-/**
- @private
-
- Call on an object when you first beget it from another object. This will
- setup any chained watchers on the object instance as needed. This method is
- safe to call multiple times.
-
- @method rewatch
- @for Ember
- @param obj
-*/
-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');
- }
-
- // 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.
-
- @method destroy
- @for Ember
- @param {Object} obj the object to destroy
- @return {void}
-*/
-Ember.destroy = function (obj) {
- var meta = obj[META_KEY], node, nodes, key, nodeObject;
- if (meta) {
- obj[META_KEY] = null;
- // remove chainWatchers to remove circular references that would prevent GC
- node = meta.chains;
- if (node) {
- NODE_STACK.push(node);
- // process tree
- while (NODE_STACK.length > 0) {
- node = NODE_STACK.pop();
- // push children
- nodes = node._chains;
- if (nodes) {
- for (key in nodes) {
- if (nodes.hasOwnProperty(key)) {
- NODE_STACK.push(nodes[key]);
- }
- }
- }
- // remove chainWatcher in node object
- if (node._watching) {
- nodeObject = node._object;
- if (nodeObject) {
- removeChainWatcher(nodeObject, node._key, node);
- }
- }
- }
- }
- }
-};
-
-})();
-
-
-
-(function() {
-/**
-@module ember-metal
-*/
-
-Ember.warn("The CP_DEFAULT_CACHEABLE flag has been removed and computed properties are always cached by default. Use `volatile` if you don't want caching.", Ember.ENV.CP_DEFAULT_CACHEABLE !== false);
-
-
-var get = Ember.get,
- set = Ember.set,
- metaFor = Ember.meta,
- a_slice = [].slice,
- o_create = Ember.create,
- META_KEY = Ember.META_KEY,
- watch = Ember.watch,
- unwatch = Ember.unwatch;
-
-// ..........................................................
-// DEPENDENT KEYS
-//
-
-// data structure:
-// meta.deps = {
-// 'depKey': {
-// 'keyName': count,
-// }
-// }
-
-/*
- This function returns a map of unique dependencies for a
- given object and key.
-*/
-function keysForDep(obj, depsMeta, depKey) {
- var keys = depsMeta[depKey];
- if (!keys) {
- // if there are no dependencies yet for a the given key
- // create a new empty list of dependencies for the key
- keys = depsMeta[depKey] = {};
- } else if (!depsMeta.hasOwnProperty(depKey)) {
- // otherwise if the dependency list is inherited from
- // a superclass, clone the hash
- keys = depsMeta[depKey] = o_create(keys);
- }
- return keys;
-}
-
-function metaForDeps(obj, meta) {
- return keysForDep(obj, 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);
-
- for(idx = 0, len = depKeys.length; idx < len; idx++) {
- depKey = depKeys[idx];
- // Lookup keys meta for depKey
- keys = keysForDep(obj, depsMeta, depKey);
- // Increment the number of times depKey depends on keyName.
- keys[keyName] = (keys[keyName] || 0) + 1;
- // Watch the depKey
- watch(obj, depKey);
- }
-}
-
-function removeDependentKeys(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);
-
- for(idx = 0, len = depKeys.length; idx < len; idx++) {
- depKey = depKeys[idx];
- // Lookup keys meta for depKey
- keys = keysForDep(obj, depsMeta, depKey);
- // Increment the number of times depKey depends on keyName.
- keys[keyName] = (keys[keyName] || 0) - 1;
- // Watch the depKey
- unwatch(obj, depKey);
- }
-}
-
-// ..........................................................
-// COMPUTED PROPERTY
-//
-
-/**
- @class ComputedProperty
- @namespace Ember
- @extends Ember.Descriptor
- @constructor
-*/
-function ComputedProperty(func, opts) {
- this.func = func;
-
- this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : true;
- this._dependentKeys = opts && opts.dependentKeys;
- this._readOnly = opts && (opts.readOnly !== undefined || !!opts.readOnly);
-}
-
-Ember.ComputedProperty = ComputedProperty;
-ComputedProperty.prototype = new Ember.Descriptor();
-
-var ComputedPropertyPrototype = ComputedProperty.prototype;
-
-/**
- Call on a computed property to 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.
-
- ```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.
- }.property('firstName', 'lastName')
- });
- ```
-
- Properties are cacheable by default.
-
- @method cacheable
- @param {Boolean} aFlag optional set to `false` to disable caching
- @return {Ember.ComputedProperty} this
- @chainable
-*/
-ComputedPropertyPrototype.cacheable = function(aFlag) {
- this._cacheable = aFlag !== false;
- return this;
-};
-
-/**
- Call on a computed property to set it into non-cached mode. When in this
- mode the computed property will not automatically cache the return value.
-
- ```javascript
- MyApp.outsideService = Ember.Object.create({
- value: function() {
- return OutsideService.getValue();
- }.property().volatile()
- });
- ```
-
- @method volatile
- @return {Ember.ComputedProperty} this
- @chainable
-*/
-ComputedPropertyPrototype.volatile = function() {
- return this.cacheable(false);
-};
-
-/**
- Call on a computed property to set it into read-only mode. When in this
- mode the computed property will throw an error when set.
-
- ```javascript
- MyApp.person = Ember.Object.create({
- guid: function() {
- return 'guid-guid-guid';
- }.property().readOnly()
- });
-
- MyApp.person.set('guid', 'new-guid'); // will throw an exception
- ```
-
- @method readOnly
- @return {Ember.ComputedProperty} this
- @chainable
-*/
-ComputedPropertyPrototype.readOnly = function(readOnly) {
- this._readOnly = readOnly === undefined || !!readOnly;
- return this;
-};
-
-/**
- Sets the dependent keys on this computed property. Pass any number of
- arguments containing key paths that this computed property depends on.
-
- ```javascript
- MyApp.president = Ember.Object.create({
- fullName: Ember.computed(function() {
- return this.get('firstName') + ' ' + this.get('lastName');
-
- // Tell Ember that this computed property depends on firstName
- // and lastName
- }).property('firstName', 'lastName')
- });
- ```
-
- @method property
- @param {String} path* zero or more property paths
- @return {Ember.ComputedProperty} this
- @chainable
-*/
-ComputedPropertyPrototype.property = function() {
- var args = [];
- for (var i = 0, l = arguments.length; i < l; i++) {
- args.push(arguments[i]);
- }
- this._dependentKeys = args;
- return this;
-};
-
-/**
- In some cases, you may want to annotate computed properties with additional
- metadata about how they function or what values they operate on. For example,
- computed property functions may close over variables that are then no longer
- available for introspection.
-
- You can pass a hash of these values to a computed property like this:
-
- ```
- person: function() {
- var personId = this.get('personId');
- return App.Person.create({ id: personId });
- }.property().meta({ type: App.Person })
- ```
-
- The hash that you pass to the `meta()` function will be saved on the
- computed property descriptor under the `_meta` key. Ember runtime
- exposes a public API for retrieving these values from classes,
- via the `metaForProperty()` function.
-
- @method meta
- @param {Hash} meta
- @chainable
-*/
-
-ComputedPropertyPrototype.meta = function(meta) {
- if (arguments.length === 0) {
- return this._meta || {};
- } else {
- this._meta = meta;
- return this;
- }
-};
-
-/* impl descriptor API */
-ComputedPropertyPrototype.willWatch = function(obj, keyName) {
- // watch already creates meta for this instance
- var meta = obj[META_KEY];
- Ember.assert('watch should have setup meta to be writable', meta.source === obj);
- if (!(keyName in meta.cache)) {
- addDependentKeys(this, obj, keyName, meta);
- }
-};
-
-ComputedPropertyPrototype.didUnwatch = function(obj, keyName) {
- var meta = obj[META_KEY];
- Ember.assert('unwatch should have setup meta to be writable', meta.source === obj);
- if (!(keyName in meta.cache)) {
- // unwatch already creates meta for this instance
- removeDependentKeys(this, obj, keyName, meta);
- }
-};
-
-/* impl descriptor API */
-ComputedPropertyPrototype.didChange = function(obj, keyName) {
- // _suspended is set via a CP.set to ensure we don't clear
- // the cached value set by the setter
- if (this._cacheable && this._suspended !== obj) {
- var meta = metaFor(obj);
- if (keyName in meta.cache) {
- delete meta.cache[keyName];
- if (!meta.watching[keyName]) {
- removeDependentKeys(this, obj, keyName, meta);
- }
- }
- }
-};
-
-/* impl descriptor API */
-ComputedPropertyPrototype.get = function(obj, keyName) {
- var ret, cache, meta;
- if (this._cacheable) {
- meta = metaFor(obj);
- cache = meta.cache;
- if (keyName in cache) { return cache[keyName]; }
- ret = cache[keyName] = this.func.call(obj, keyName);
- if (!meta.watching[keyName]) {
- addDependentKeys(this, obj, keyName, meta);
- }
- } else {
- ret = this.func.call(obj, keyName);
- }
- return ret;
-};
-
-/* impl descriptor API */
-ComputedPropertyPrototype.set = function(obj, keyName, value) {
- var cacheable = this._cacheable,
- func = this.func,
- meta = metaFor(obj, cacheable),
- watched = meta.watching[keyName],
- oldSuspended = this._suspended,
- hadCachedValue = false,
- cache = meta.cache,
- cachedValue, ret;
-
- if (this._readOnly) {
- throw new Error('Cannot Set: ' + keyName + ' on: ' + obj.toString() );
- }
-
- this._suspended = obj;
-
- try {
-
- if (cacheable && cache.hasOwnProperty(keyName)) {
- cachedValue = cache[keyName];
- hadCachedValue = true;
- }
-
- // Check if the CP has been wrapped
- if (func.wrappedFunction) { func = func.wrappedFunction; }
-
- // For backwards-compatibility with computed properties
- // that check for arguments.length === 2 to determine if
- // they are being get or set, only pass the old cached
- // value if the computed property opts into a third
- // argument.
- if (func.length === 3) {
- ret = func.call(obj, keyName, value, cachedValue);
- } else if (func.length === 2) {
- ret = func.call(obj, keyName, value);
- } else {
- Ember.defineProperty(obj, keyName, null, cachedValue);
- Ember.set(obj, keyName, value);
- return;
- }
-
- if (hadCachedValue && cachedValue === ret) { return; }
-
- if (watched) { Ember.propertyWillChange(obj, keyName); }
-
- if (hadCachedValue) {
- delete cache[keyName];
- }
-
- if (cacheable) {
- if (!watched && !hadCachedValue) {
- addDependentKeys(this, obj, keyName, meta);
- }
- cache[keyName] = ret;
- }
-
- if (watched) { Ember.propertyDidChange(obj, keyName); }
- } finally {
- this._suspended = oldSuspended;
- }
- return ret;
-};
-
-/* called when property is defined */
-ComputedPropertyPrototype.setup = function(obj, keyName) {
- var meta = obj[META_KEY];
- if (meta && meta.watching[keyName]) {
- addDependentKeys(this, obj, keyName, metaFor(obj));
- }
-};
-
-/* called before property is overridden */
-ComputedPropertyPrototype.teardown = function(obj, keyName) {
- var meta = metaFor(obj);
-
- if (meta.watching[keyName] || keyName in meta.cache) {
- removeDependentKeys(this, obj, keyName, meta);
- }
-
- if (this._cacheable) { delete meta.cache[keyName]; }
-
- return null; // no value to restore
-};
-
-
-/**
- This helper returns a new property descriptor that wraps the passed
- computed property function. You can use this helper to define properties
- with mixins or via `Ember.defineProperty()`.
-
- The function you pass will be used to both get and set property values.
- The function should accept two parameters, key and value. If value is not
- undefined you should set the value first. In either case return the
- current value of the property.
-
- @method computed
- @for Ember
- @param {Function} func The computed property function.
- @return {Ember.ComputedProperty} property descriptor instance
-*/
-Ember.computed = function(func) {
- var args;
-
- if (arguments.length > 1) {
- args = a_slice.call(arguments, 0, -1);
- func = a_slice.call(arguments, -1)[0];
- }
-
- if ( typeof func !== "function" ) {
- throw new Error("Computed Property declared without a property function");
- }
-
- var cp = new ComputedProperty(func);
-
- if (args) {
- cp.property.apply(cp, args);
- }
-
- return cp;
-};
-
-/**
- Returns the cached value for a property, if one exists.
- This can be useful for peeking at the value of a computed
- property that is generated lazily, without accidentally causing
- it to be created.
-
- @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
-*/
-Ember.cacheFor = function cacheFor(obj, key) {
- var cache = metaFor(obj, false).cache;
-
- if (cache && key in cache) {
- return cache[key];
- }
-};
-
-function getProperties(self, propertyNames) {
- var ret = {};
- for(var i = 0; i < propertyNames.length; i++) {
- ret[propertyNames[i]] = get(self, propertyNames[i]);
- }
- return ret;
-}
-
-function registerComputed(name, macro) {
- Ember.computed[name] = function(dependentKey) {
- var args = a_slice.call(arguments);
- return Ember.computed(dependentKey, function() {
- return macro.apply(this, args);
- });
- };
-}
-
-function registerComputedWithProperties(name, macro) {
- Ember.computed[name] = function() {
- var properties = a_slice.call(arguments);
-
- var computed = Ember.computed(function() {
- return macro.apply(this, [getProperties(this, properties)]);
- });
-
- return computed.property.apply(computed, properties);
- };
-}
-
-/**
- @method computed.empty
- @for Ember
- @param {String} dependentKey
- @return {Ember.ComputedProperty} computed property which negate
- the original value for property
-*/
-registerComputed('empty', function(dependentKey) {
- return Ember.isEmpty(get(this, dependentKey));
-});
-
-/**
- @method computed.notEmpty
- @for Ember
- @param {String} dependentKey
- @return {Ember.ComputedProperty} computed property which returns true if
- original value for property is not empty.
-*/
-registerComputed('notEmpty', function(dependentKey) {
- return !Ember.isEmpty(get(this, dependentKey));
-});
-
-/**
- @method computed.none
- @for Ember
- @param {String} dependentKey
- @return {Ember.ComputedProperty} computed property which
- rturns true if original value for property is null or undefined.
-*/
-registerComputed('none', function(dependentKey) {
- return Ember.isNone(get(this, dependentKey));
-});
-
-/**
- @method computed.not
- @for Ember
- @param {String} dependentKey
- @return {Ember.ComputedProperty} computed property which returns
- inverse of the original value for property
-*/
-registerComputed('not', function(dependentKey) {
- return !get(this, dependentKey);
-});
-
-/**
- @method computed.bool
- @for Ember
- @param {String} dependentKey
- @return {Ember.ComputedProperty} computed property which convert
- to boolean the original value for property
-*/
-registerComputed('bool', function(dependentKey) {
- return !!get(this, dependentKey);
-});
-
-/**
- @method computed.match
- @for Ember
- @param {String} dependentKey
- @param {RegExp} regexp
- @return {Ember.ComputedProperty} computed property which match
- the original value for property against a given RegExp
-*/
-registerComputed('match', function(dependentKey, regexp) {
- var value = get(this, dependentKey);
- return typeof value === 'string' ? !!value.match(regexp) : false;
-});
-
-/**
- @method computed.equal
- @for Ember
- @param {String} dependentKey
- @param {String|Number|Object} value
- @return {Ember.ComputedProperty} computed property which returns true if
- the original value for property is equal to the given value.
-*/
-registerComputed('equal', function(dependentKey, value) {
- return get(this, dependentKey) === value;
-});
-
-/**
- @method computed.gt
- @for Ember
- @param {String} dependentKey
- @param {Number} value
- @return {Ember.ComputedProperty} computed property which returns true if
- the original value for property is greater then given value.
-*/
-registerComputed('gt', function(dependentKey, value) {
- return get(this, dependentKey) > value;
-});
-
-/**
- @method computed.gte
- @for Ember
- @param {String} dependentKey
- @param {Number} value
- @return {Ember.ComputedProperty} computed property which returns true if
- the original value for property is greater or equal then given value.
-*/
-registerComputed('gte', function(dependentKey, value) {
- return get(this, dependentKey) >= value;
-});
-
-/**
- @method computed.lt
- @for Ember
- @param {String} dependentKey
- @param {Number} value
- @return {Ember.ComputedProperty} computed property which returns true if
- the original value for property is less then given value.
-*/
-registerComputed('lt', function(dependentKey, value) {
- return get(this, dependentKey) < value;
-});
-
-/**
- @method computed.lte
- @for Ember
- @param {String} dependentKey
- @param {Number} value
- @return {Ember.ComputedProperty} computed property which returns true if
- the original value for property is less or equal then given value.
-*/
-registerComputed('lte', function(dependentKey, value) {
- return get(this, dependentKey) <= value;
-});
-
-/**
- @method computed.and
- @for Ember
- @param {String} dependentKey, [dependentKey...]
- @return {Ember.ComputedProperty} computed property which peforms
- a logical `and` on the values of all the original values for properties.
-*/
-registerComputedWithProperties('and', function(properties) {
- for (var key in properties) {
- if (properties.hasOwnProperty(key) && !properties[key]) {
- return false;
- }
- }
- return true;
-});
-
-/**
- @method computed.or
- @for Ember
- @param {String} dependentKey, [dependentKey...]
- @return {Ember.ComputedProperty} computed property which peforms
- a logical `or` on the values of all the original values for properties.
-*/
-registerComputedWithProperties('or', function(properties) {
- for (var key in properties) {
- if (properties.hasOwnProperty(key) && properties[key]) {
- return true;
- }
- }
- return false;
-});
-
-/**
- @method computed.any
- @for Ember
- @param {String} dependentKey, [dependentKey...]
- @return {Ember.ComputedProperty} computed property which returns
- the first trouthy value of given list of properties.
-*/
-registerComputedWithProperties('any', function(properties) {
- for (var key in properties) {
- if (properties.hasOwnProperty(key) && properties[key]) {
- return properties[key];
- }
- }
- return null;
-});
-
-/**
- @method computed.map
- @for Ember
- @param {String} dependentKey, [dependentKey...]
- @return {Ember.ComputedProperty} computed property which maps
- values of all passed properties in to an array.
-*/
-registerComputedWithProperties('map', function(properties) {
- var res = [];
- for (var key in properties) {
- if (properties.hasOwnProperty(key)) {
- if (Ember.isNone(properties[key])) {
- res.push(null);
- } else {
- res.push(properties[key]);
- }
- }
- }
- return res;
-});
-
-/**
- @method computed.alias
- @for Ember
- @param {String} dependentKey
- @return {Ember.ComputedProperty} computed property which creates an
- alias to the original value for property.
-*/
-Ember.computed.alias = function(dependentKey) {
- return Ember.computed(dependentKey, function(key, value){
- if (arguments.length > 1) {
- set(this, dependentKey, value);
- return value;
- } else {
- return get(this, dependentKey);
- }
- });
-};
-
-/**
- @method computed.defaultTo
- @for Ember
- @param {String} defaultPath
- @return {Ember.ComputedProperty} computed property which acts like
- a standard getter and setter, but defaults to the value from `defaultPath`.
-*/
-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);
- });
-};
-
})();
@@ -4324,6 +2489,1985 @@ 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.
+
+ If you plan to run on IE8 and older browsers then you should use this
+ method anytime you want to set a property on an object that you don't
+ know for sure is private. (Properties beginning with an underscore '_'
+ are considered private.)
+
+ On all newer browsers, you only need to use this method to set
+ properties if the property might not be defined on the object and you want
+ to respect the `unknownProperty` handler. Otherwise you can ignore this
+ method.
+
+ @method set
+ @for Ember
+ @param {Object} obj The object to modify.
+ @param {String} keyName The property key to set
+ @param {Object} value The value to set
+ @return {Object} the passed value.
+*/
+var set = function set(obj, keyName, value, tolerant) {
+ 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;
+ }
+
+ if (!obj || keyName.indexOf('.') !== -1) {
+ return setPath(obj, keyName, value, tolerant);
+ }
+
+ Ember.assert("You need to provide an object and key to `set`.", !!obj && keyName !== undefined);
+ Ember.assert('calling set on destroyed object', !obj.isDestroyed);
+
+ var meta = obj[META_KEY], desc = meta && meta.descs[keyName],
+ isUnknown, currentValue;
+ if (desc) {
+ desc.set(obj, keyName, value);
+ } else {
+ isUnknown = 'object' === typeof obj && !(keyName in obj);
+
+ // setUnknownProperty is called if `obj` is an object,
+ // the property does not already exist, and the
+ // `setUnknownProperty` method exists on the object
+ if (isUnknown && 'function' === typeof obj.setUnknownProperty) {
+ obj.setUnknownProperty(keyName, value);
+ } else if (meta && meta.watching[keyName] > 0) {
+ if (MANDATORY_SETTER) {
+ currentValue = meta.values[keyName];
+ } else {
+ currentValue = obj[keyName];
+ }
+ // only trigger a change if the value has changed
+ if (value !== currentValue) {
+ Ember.propertyWillChange(obj, keyName);
+ if (MANDATORY_SETTER) {
+ if (currentValue === undefined && !(keyName in obj)) {
+ Ember.defineProperty(obj, keyName, null, value); // setup mandatory setter
+ } else {
+ meta.values[keyName] = value;
+ }
+ } else {
+ obj[keyName] = value;
+ }
+ Ember.propertyDidChange(obj, keyName);
+ }
+ } else {
+ obj[keyName] = value;
+ }
+ }
+ return value;
+};
+
+// Currently used only by Ember Data tests
+if (Ember.config.overrideAccessors) {
+ Ember.set = set;
+ Ember.config.overrideAccessors();
+ set = Ember.set;
+}
+
+function setPath(root, path, value, tolerant) {
+ var keyName;
+
+ // get the last part of the path
+ keyName = path.slice(path.lastIndexOf('.') + 1);
+
+ // get the first part of the part
+ path = path.slice(0, path.length-(keyName.length+1));
+
+ // unless the path is this, look up the first part to
+ // get the root
+ if (path !== 'this') {
+ root = getPath(root, path);
+ }
+
+ if (!keyName || keyName.length === 0) {
+ throw new Error('You passed an empty path');
+ }
+
+ if (!root) {
+ if (tolerant) { return; }
+ else { throw new Error('Object in path '+path+' could not be found or was destroyed.'); }
+ }
+
+ return set(root, keyName, value);
+}
+
+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
+ chain is `undefined`, `null`, or destroyed.
+
+ This is primarily used when syncing bindings, which may try to update after
+ an object has been destroyed.
+
+ @method trySet
+ @for Ember
+ @param {Object} obj The object to modify.
+ @param {String} path The property path to set
+ @param {Object} value The value to set
+*/
+Ember.trySet = function(root, path, value) {
+ return set(root, path, value, true);
+};
+Ember.trySetPath = Ember.deprecateFunc('trySetPath has been renamed to trySet', Ember.trySet);
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+var META_KEY = Ember.META_KEY,
+ metaFor = Ember.meta,
+ objectDefineProperty = Ember.platform.defineProperty;
+
+var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
+
+// ..........................................................
+// DESCRIPTOR
+//
+
+/**
+ Objects of this type can implement an interface to respond to requests to
+ get and set. The default implementation handles simple properties.
+
+ You generally won't need to create or subclass this directly.
+
+ @class Descriptor
+ @namespace Ember
+ @private
+ @constructor
+*/
+var Descriptor = Ember.Descriptor = function() {};
+
+// ..........................................................
+// DEFINING PROPERTIES API
+//
+
+var MANDATORY_SETTER_FUNCTION = Ember.MANDATORY_SETTER_FUNCTION = function(value) {
+ Ember.assert("You must use Ember.set() to access this property (of " + this + ")", false);
+};
+
+var DEFAULT_GETTER_FUNCTION = Ember.DEFAULT_GETTER_FUNCTION = function(name) {
+ return function() {
+ var meta = this[META_KEY];
+ return meta && meta.values[name];
+ };
+};
+
+/**
+ @private
+
+ NOTE: This is a low-level method used by other parts of the API. You almost
+ never want to call this method directly. Instead you should use
+ `Ember.mixin()` to define new properties.
+
+ Defines a property on an object. This method works much like the ES5
+ `Object.defineProperty()` method except that it can also accept computed
+ properties and other special descriptors.
+
+ Normally this method takes only three parameters. However if you pass an
+ instance of `Ember.Descriptor` as the third param then you can pass an
+ optional value as the fourth parameter. This is often more efficient than
+ creating new descriptor hashes for each property.
+
+ ## Examples
+
+ ```javascript
+ // ES5 compatible mode
+ Ember.defineProperty(contact, 'firstName', {
+ writable: true,
+ configurable: false,
+ enumerable: true,
+ value: 'Charles'
+ });
+
+ // define a simple property
+ Ember.defineProperty(contact, 'lastName', undefined, 'Jolley');
+
+ // define a computed property
+ Ember.defineProperty(contact, 'fullName', Ember.computed(function() {
+ return this.firstName+' '+this.lastName;
+ }).property('firstName', 'lastName'));
+ ```
+
+ @method defineProperty
+ @for Ember
+ @param {Object} obj the object to define this property on. This may be a prototype.
+ @param {String} keyName the name of the property
+ @param {Ember.Descriptor} [desc] an instance of `Ember.Descriptor` (typically a
+ computed property) or an ES5 descriptor.
+ You must provide this or `data` but not both.
+ @param {*} [data] something other than a descriptor, that will
+ become the explicit value of this property.
+*/
+Ember.defineProperty = function(obj, keyName, desc, data, meta) {
+ var descs, existingDesc, watching, value;
+
+ if (!meta) meta = metaFor(obj);
+ descs = meta.descs;
+ existingDesc = meta.descs[keyName];
+ watching = meta.watching[keyName] > 0;
+
+ if (existingDesc instanceof Ember.Descriptor) {
+ existingDesc.teardown(obj, keyName);
+ }
+
+ if (desc instanceof Ember.Descriptor) {
+ value = desc;
+
+ descs[keyName] = desc;
+ if (MANDATORY_SETTER && watching) {
+ objectDefineProperty(obj, keyName, {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: undefined // make enumerable
+ });
+ } else {
+ obj[keyName] = undefined; // make enumerable
+ }
+ desc.setup(obj, keyName);
+ } else {
+ descs[keyName] = undefined; // shadow descriptor in proto
+ if (desc == null) {
+ value = data;
+
+ if (MANDATORY_SETTER && watching) {
+ meta.values[keyName] = data;
+ objectDefineProperty(obj, keyName, {
+ configurable: true,
+ enumerable: true,
+ set: MANDATORY_SETTER_FUNCTION,
+ get: DEFAULT_GETTER_FUNCTION(keyName)
+ });
+ } else {
+ obj[keyName] = data;
+ }
+ } else {
+ value = desc;
+
+ // compatibility with ES5
+ objectDefineProperty(obj, keyName, desc);
+ }
+ }
+
+ // if key is being watched, override chains that
+ // were initialized with the prototype
+ if (watching) { Ember.overrideChains(obj, keyName, meta); }
+
+ // The `value` passed to the `didDefineProperty` hook is
+ // either the descriptor or data, whichever was passed.
+ if (obj.didDefineProperty) { obj.didDefineProperty(obj, keyName, value); }
+
+ return this;
+};
+
+
+})();
+
+
+
+(function() {
+var changeProperties = Ember.changeProperties,
+ set = Ember.set;
+
+/**
+ Set a list of properties on an object. These properties are set inside
+ a single `beginPropertyChanges` and `endPropertyChanges` batch, so
+ observers will be buffered.
+
+ @method setProperties
+ @param target
+ @param {Hash} properties
+ @return target
+*/
+Ember.setProperties = function(self, hash) {
+ changeProperties(function(){
+ for(var prop in hash) {
+ if (hash.hasOwnProperty(prop)) { set(self, prop, hash[prop]); }
+ }
+ });
+ return self;
+};
+})();
+
+
+
+(function() {
+var metaFor = Ember.meta, // utils.js
+ typeOf = Ember.typeOf, // utils.js
+ MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
+ o_defineProperty = Ember.platform.defineProperty;
+
+Ember.watchKey = function(obj, keyName) {
+ // can't watch length on Array - it is special...
+ if (keyName === 'length' && typeOf(obj) === 'array') { return; }
+
+ var m = metaFor(obj), watching = m.watching, desc;
+
+ // activate watching first time
+ if (!watching[keyName]) {
+ watching[keyName] = 1;
+ 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 {
+ watching[keyName] = (watching[keyName] || 0) + 1;
+ }
+};
+
+
+Ember.unwatchKey = function(obj, keyName) {
+ var m = metaFor(obj), watching = m.watching, desc;
+
+ if (watching[keyName] === 1) {
+ watching[keyName] = 0;
+ 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 if (watching[keyName] > 1) {
+ watching[keyName]--;
+ }
+};
+})();
+
+
+
+(function() {
+var metaFor = Ember.meta, // utils.js
+ get = Ember.get, // property_get.js
+ normalizeTuple = Ember.normalizeTuple, // property_get.js
+ forEach = Ember.ArrayPolyfills.forEach, // array.js
+ warn = Ember.warn,
+ watchKey = Ember.watchKey,
+ unwatchKey = Ember.unwatchKey,
+ propertyWillChange = Ember.propertyWillChange,
+ propertyDidChange = Ember.propertyDidChange,
+ FIRST_KEY = /^([^\.\*]+)/;
+
+function firstKey(path) {
+ return path.match(FIRST_KEY)[0];
+}
+
+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.
+Ember.flushPendingChains = function() {
+ if (pendingQueue.length === 0) { return; } // nothing to do
+
+ var queue = pendingQueue;
+ pendingQueue = [];
+
+ forEach.call(queue, function(q) { q[0].add(q[1]); });
+
+ 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 addChainWatcher(obj, keyName, node) {
+ if (!obj || ('object' !== typeof obj)) { return; } // nothing to do
+
+ var m = metaFor(obj), nodes = m.chainWatchers;
+
+ if (!m.hasOwnProperty('chainWatchers')) {
+ nodes = m.chainWatchers = {};
+ }
+
+ if (!nodes[keyName]) { nodes[keyName] = []; }
+ nodes[keyName].push(node);
+ watchKey(obj, keyName);
+}
+
+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
+
+ var nodes = m.chainWatchers;
+
+ if (nodes[keyName]) {
+ nodes = nodes[keyName];
+ for (var i = 0, l = nodes.length; i < l; i++) {
+ if (nodes[i] === node) { nodes.splice(i, 1); }
+ }
+ }
+ unwatchKey(obj, keyName);
+};
+
+function isProto(pvalue) {
+ return metaFor(pvalue, false).proto === pvalue;
+}
+
+// A ChainNode watches a single key on an object. If you provide a starting
+// value for the key then the node won't actually watch it. For a root node
+// pass null for parent and key and object for value.
+var ChainNode = Ember._ChainNode = function(parent, key, value) {
+ this._parent = parent;
+ this._key = key;
+
+ // _watching is true when calling get(this._parent, this._key) will
+ // return the value of this node.
+ //
+ // It is false for the root of a chain (because we have no parent)
+ // and for global paths (because the parent node is the object with
+ // the observer on it)
+ this._watching = value===undefined;
+
+ this._value = value;
+ this._paths = {};
+ if (this._watching) {
+ this._object = parent.value();
+ if (this._object) { addChainWatcher(this._object, this._key, this); }
+ }
+
+ // Special-case: the EachProxy relies on immediate evaluation to
+ // establish its observers.
+ //
+ // TODO: Replace this with an efficient callback that the EachProxy
+ // can implement.
+ if (this._parent && this._parent._key === '@each') {
+ this.value();
+ }
+};
+
+var ChainNodePrototype = ChainNode.prototype;
+
+ChainNodePrototype.value = function() {
+ if (this._value === undefined && this._watching) {
+ var obj = this._parent.value();
+ this._value = (obj && !isProto(obj)) ? get(obj, this._key) : undefined;
+ }
+ return this._value;
+};
+
+ChainNodePrototype.destroy = function() {
+ if (this._watching) {
+ var obj = this._object;
+ if (obj) { removeChainWatcher(obj, this._key, this); }
+ this._watching = false; // so future calls do nothing
+ }
+};
+
+// copies a top level object only
+ChainNodePrototype.copy = function(obj) {
+ var ret = new ChainNode(null, null, obj),
+ paths = this._paths, path;
+ for (path in paths) {
+ if (paths[path] <= 0) { continue; } // this check will also catch non-number vals.
+ ret.add(path);
+ }
+ return ret;
+};
+
+// called on the root node of a chain to setup watchers on the specified
+// path.
+ChainNodePrototype.add = function(path) {
+ var obj, tuple, key, src, paths;
+
+ paths = this._paths;
+ paths[path] = (paths[path] || 0) + 1;
+
+ obj = this.value();
+ tuple = normalizeTuple(obj, path);
+
+ // the path was a local path
+ if (tuple[0] && tuple[0] === obj) {
+ path = tuple[1];
+ key = firstKey(path);
+ path = path.slice(key.length+1);
+
+ // global path, but object does not exist yet.
+ // put into a queue and try to connect later.
+ } else if (!tuple[0]) {
+ pendingQueue.push([this, path]);
+ tuple.length = 0;
+ return;
+
+ // global path, and object already exists
+ } else {
+ src = tuple[0];
+ key = path.slice(0, 0-(tuple[1].length+1));
+ path = tuple[1];
+ }
+
+ tuple.length = 0;
+ this.chain(key, path, src);
+};
+
+// called on the root node of a chain to teardown watcher on the specified
+// path
+ChainNodePrototype.remove = function(path) {
+ var obj, tuple, key, src, paths;
+
+ paths = this._paths;
+ if (paths[path] > 0) { paths[path]--; }
+
+ obj = this.value();
+ tuple = normalizeTuple(obj, path);
+ if (tuple[0] === obj) {
+ path = tuple[1];
+ key = firstKey(path);
+ path = path.slice(key.length+1);
+ } else {
+ src = tuple[0];
+ key = path.slice(0, 0-(tuple[1].length+1));
+ path = tuple[1];
+ }
+
+ tuple.length = 0;
+ this.unchain(key, path);
+};
+
+ChainNodePrototype.count = 0;
+
+ChainNodePrototype.chain = function(key, path, src) {
+ var chains = this._chains, node;
+ if (!chains) { chains = this._chains = {}; }
+
+ node = chains[key];
+ if (!node) { node = chains[key] = new ChainNode(this, key, src); }
+ node.count++; // count chains...
+
+ // chain rest of path if there is one
+ if (path && path.length>0) {
+ key = firstKey(path);
+ path = path.slice(key.length+1);
+ node.chain(key, path); // NOTE: no src means it will observe changes...
+ }
+};
+
+ChainNodePrototype.unchain = function(key, path) {
+ var chains = this._chains, node = chains[key];
+
+ // unchain rest of path first...
+ if (path && path.length>1) {
+ key = firstKey(path);
+ path = path.slice(key.length+1);
+ node.unchain(key, path);
+ }
+
+ // delete node if needed.
+ node.count--;
+ if (node.count<=0) {
+ delete chains[node._key];
+ node.destroy();
+ }
+
+};
+
+ChainNodePrototype.willChange = function() {
+ var chains = this._chains;
+ if (chains) {
+ for(var key in chains) {
+ if (!chains.hasOwnProperty(key)) { continue; }
+ chains[key].willChange();
+ }
+ }
+
+ if (this._parent) { this._parent.chainWillChange(this, this._key, 1); }
+};
+
+ChainNodePrototype.chainWillChange = function(chain, path, depth) {
+ if (this._key) { path = this._key + '.' + path; }
+
+ if (this._parent) {
+ this._parent.chainWillChange(this, path, depth+1);
+ } else {
+ if (depth > 1) { propertyWillChange(this.value(), path); }
+ path = 'this.' + 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) { propertyDidChange(this.value(), path); }
+ path = 'this.' + path;
+ if (this._paths[path] > 0) { propertyDidChange(this.value(), path); }
+ }
+};
+
+ChainNodePrototype.didChange = function(suppressEvent) {
+ // invalidate my own value first.
+ if (this._watching) {
+ var obj = this._parent.value();
+ if (obj !== this._object) {
+ removeChainWatcher(this._object, this._key, this);
+ this._object = obj;
+ addChainWatcher(obj, this._key, this);
+ }
+ this._value = undefined;
+
+ // Special-case: the EachProxy relies on immediate evaluation to
+ // establish its observers.
+ if (this._parent && this._parent._key === '@each')
+ this.value();
+ }
+
+ // then notify chains...
+ var chains = this._chains;
+ if (chains) {
+ for(var key in chains) {
+ if (!chains.hasOwnProperty(key)) { continue; }
+ chains[key].didChange(suppressEvent);
+ }
+ }
+
+ if (suppressEvent) { return; }
+
+ // 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;
+ if (!ret) {
+ ret = m.chains = new ChainNode(null, null, obj);
+ } else if (ret.value() !== obj) {
+ ret = m.chains = ret.copy(obj);
+ }
+ return ret;
+}
+
+Ember.watchPath = function(obj, keyPath) {
+ // can't watch length on Array - it is special...
+ if (keyPath === 'length' && typeOf(obj) === 'array') { return; }
+
+ var m = metaFor(obj), watching = m.watching;
+
+ if (!watching[keyPath]) { // activate watching first time
+ watching[keyPath] = 1;
+ chainsFor(obj).add(keyPath);
+ } else {
+ watching[keyPath] = (watching[keyPath] || 0) + 1;
+ }
+};
+
+Ember.unwatchPath = function(obj, keyPath) {
+ var m = metaFor(obj), watching = m.watching, desc;
+
+ if (watching[keyPath] === 1) {
+ watching[keyPath] = 0;
+ chainsFor(obj).remove(keyPath);
+ } else if (watching[keyPath] > 1) {
+ watching[keyPath]--;
+ }
+};
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+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);
+}
+
+/**
+ @private
+
+ Starts watching a property on an object. Whenever the property changes,
+ invokes `Ember.propertyWillChange` and `Ember.propertyDidChange`. This is the
+ primitive used by observers and dependent keys; usually you will never call
+ this method directly but instead use higher level methods like
+ `Ember.addObserver()`
+
+ @method watch
+ @for Ember
+ @param obj
+ @param {String} keyName
+*/
+Ember.watch = function(obj, keyPath) {
+ // can't watch length on Array - it is special...
+ if (keyPath === 'length' && typeOf(obj) === 'array') { return; }
+
+ if (isKeyName(keyPath)) {
+ watchKey(obj, keyPath);
+ } else {
+ watchPath(obj, keyPath);
+ }
+};
+
+Ember.isWatching = function isWatching(obj, key) {
+ var meta = obj[META_KEY];
+ return (meta && meta.watching[key]) > 0;
+};
+
+Ember.watch.flushPending = Ember.flushPendingChains;
+
+Ember.unwatch = function(obj, keyPath) {
+ // can't watch length on Array - it is special...
+ if (keyPath === 'length' && typeOf(obj) === 'array') { return; }
+
+ if (isKeyName(keyPath)) {
+ unwatchKey(obj, keyPath);
+ } else {
+ unwatchPath(obj, keyPath);
+ }
+};
+
+/**
+ @private
+
+ Call on an object when you first beget it from another object. This will
+ setup any chained watchers on the object instance as needed. This method is
+ safe to call multiple times.
+
+ @method rewatch
+ @for Ember
+ @param obj
+*/
+Ember.rewatch = function(obj) {
+ var m = metaFor(obj, false), chains = m.chains;
+
+ // make sure the object has its own guid.
+ if (GUID_KEY in obj && !obj.hasOwnProperty(GUID_KEY)) {
+ generateGuid(obj, 'ember');
+ }
+
+ // make sure any chained watchers update.
+ if (chains && chains.value() !== obj) {
+ m.chains = chains.copy(obj);
+ }
+};
+
+var NODE_STACK = [];
+
+/**
+ Tears down the meta on an object so that it can be garbage collected.
+ Multiple calls will have no effect.
+
+ @method destroy
+ @for Ember
+ @param {Object} obj the object to destroy
+ @return {void}
+*/
+Ember.destroy = function (obj) {
+ var meta = obj[META_KEY], node, nodes, key, nodeObject;
+ if (meta) {
+ obj[META_KEY] = null;
+ // remove chainWatchers to remove circular references that would prevent GC
+ node = meta.chains;
+ if (node) {
+ NODE_STACK.push(node);
+ // process tree
+ while (NODE_STACK.length > 0) {
+ node = NODE_STACK.pop();
+ // push children
+ nodes = node._chains;
+ if (nodes) {
+ for (key in nodes) {
+ if (nodes.hasOwnProperty(key)) {
+ NODE_STACK.push(nodes[key]);
+ }
+ }
+ }
+ // remove chainWatcher in node object
+ if (node._watching) {
+ nodeObject = node._object;
+ if (nodeObject) {
+ removeChainWatcher(nodeObject, node._key, node);
+ }
+ }
+ }
+ }
+ }
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+Ember.warn("The CP_DEFAULT_CACHEABLE flag has been removed and computed properties are always cached by default. Use `volatile` if you don't want caching.", Ember.ENV.CP_DEFAULT_CACHEABLE !== false);
+
+
+var get = Ember.get,
+ set = Ember.set,
+ metaFor = Ember.meta,
+ a_slice = [].slice,
+ o_create = Ember.create,
+ META_KEY = Ember.META_KEY,
+ watch = Ember.watch,
+ unwatch = Ember.unwatch;
+
+// ..........................................................
+// DEPENDENT KEYS
+//
+
+// data structure:
+// meta.deps = {
+// 'depKey': {
+// 'keyName': count,
+// }
+// }
+
+/*
+ This function returns a map of unique dependencies for a
+ given object and key.
+*/
+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] = {};
+ } else if (!depsMeta.hasOwnProperty(depKey)) {
+ // otherwise if the dependency list is inherited from
+ // a superclass, clone the hash
+ keys = depsMeta[depKey] = o_create(keys);
+ }
+ return keys;
+}
+
+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(meta);
+
+ for(idx = 0, len = depKeys.length; idx < len; idx++) {
+ depKey = depKeys[idx];
+ // Lookup keys meta for 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);
+ }
+}
+
+function removeDependentKeys(desc, obj, keyName, meta) {
+ // the descriptor has a list of dependent keys, so
+ // add all of its dependent keys.
+ var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
+ if (!depKeys) return;
+
+ depsMeta = metaForDeps(meta);
+
+ for(idx = 0, len = depKeys.length; idx < len; idx++) {
+ depKey = depKeys[idx];
+ // Lookup keys meta for 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);
+ }
+}
+
+// ..........................................................
+// COMPUTED PROPERTY
+//
+
+/**
+ @class ComputedProperty
+ @namespace Ember
+ @extends Ember.Descriptor
+ @constructor
+*/
+function ComputedProperty(func, opts) {
+ this.func = func;
+
+ this._cacheable = (opts && opts.cacheable !== undefined) ? opts.cacheable : true;
+ this._dependentKeys = opts && opts.dependentKeys;
+ this._readOnly = opts && (opts.readOnly !== undefined || !!opts.readOnly);
+}
+
+Ember.ComputedProperty = ComputedProperty;
+ComputedProperty.prototype = new Ember.Descriptor();
+
+var ComputedPropertyPrototype = ComputedProperty.prototype;
+
+/*
+ Call on a computed property to explicitly change it's cacheable mode.
+
+ Please use `.volatile` over this method.
+
+ ```javascript
+ MyApp.president = Ember.Object.create({
+ fullName: function() {
+ return this.get('firstName') + ' ' + this.get('lastName');
+
+ // By default, Ember will return the value of this property
+ // without re-executing this function.
+ }.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)
+ });
+ ```
+
+ @method cacheable
+ @param {Boolean} aFlag optional set to `false` to disable caching
+ @return {Ember.ComputedProperty} this
+ @chainable
+*/
+ComputedPropertyPrototype.cacheable = function(aFlag) {
+ this._cacheable = aFlag !== false;
+ return this;
+};
+
+/**
+ Call on a computed property to set it into non-cached mode. When in this
+ mode the computed property will not automatically cache the return value.
+
+ ```javascript
+ MyApp.outsideService = Ember.Object.create({
+ value: function() {
+ return OutsideService.getValue();
+ }.property().volatile()
+ });
+ ```
+
+ @method volatile
+ @return {Ember.ComputedProperty} this
+ @chainable
+*/
+ComputedPropertyPrototype.volatile = function() {
+ return this.cacheable(false);
+};
+
+/**
+ Call on a computed property to set it into read-only mode. When in this
+ mode the computed property will throw an error when set.
+
+ ```javascript
+ MyApp.person = Ember.Object.create({
+ guid: function() {
+ return 'guid-guid-guid';
+ }.property().readOnly()
+ });
+
+ MyApp.person.set('guid', 'new-guid'); // will throw an exception
+ ```
+
+ @method readOnly
+ @return {Ember.ComputedProperty} this
+ @chainable
+*/
+ComputedPropertyPrototype.readOnly = function(readOnly) {
+ this._readOnly = readOnly === undefined || !!readOnly;
+ return this;
+};
+
+/**
+ Sets the dependent keys on this computed property. Pass any number of
+ arguments containing key paths that this computed property depends on.
+
+ ```javascript
+ MyApp.president = Ember.Object.create({
+ fullName: Ember.computed(function() {
+ return this.get('firstName') + ' ' + this.get('lastName');
+
+ // Tell Ember that this computed property depends on firstName
+ // and lastName
+ }).property('firstName', 'lastName')
+ });
+ ```
+
+ @method property
+ @param {String} path* zero or more property paths
+ @return {Ember.ComputedProperty} this
+ @chainable
+*/
+ComputedPropertyPrototype.property = function() {
+ var args = [];
+ for (var i = 0, l = arguments.length; i < l; i++) {
+ args.push(arguments[i]);
+ }
+ this._dependentKeys = args;
+ return this;
+};
+
+/**
+ In some cases, you may want to annotate computed properties with additional
+ metadata about how they function or what values they operate on. For example,
+ computed property functions may close over variables that are then no longer
+ available for introspection.
+
+ You can pass a hash of these values to a computed property like this:
+
+ ```
+ person: function() {
+ var personId = this.get('personId');
+ return App.Person.create({ id: personId });
+ }.property().meta({ type: App.Person })
+ ```
+
+ The hash that you pass to the `meta()` function will be saved on the
+ computed property descriptor under the `_meta` key. Ember runtime
+ exposes a public API for retrieving these values from classes,
+ via the `metaForProperty()` function.
+
+ @method meta
+ @param {Hash} meta
+ @chainable
+*/
+
+ComputedPropertyPrototype.meta = function(meta) {
+ if (arguments.length === 0) {
+ return this._meta || {};
+ } else {
+ this._meta = meta;
+ return this;
+ }
+};
+
+/* impl descriptor API */
+ComputedPropertyPrototype.willWatch = function(obj, keyName) {
+ // watch already creates meta for this instance
+ var meta = obj[META_KEY];
+ Ember.assert('watch should have setup meta to be writable', meta.source === obj);
+ if (!(keyName in meta.cache)) {
+ addDependentKeys(this, obj, keyName, meta);
+ }
+};
+
+ComputedPropertyPrototype.didUnwatch = function(obj, keyName) {
+ var meta = obj[META_KEY];
+ Ember.assert('unwatch should have setup meta to be writable', meta.source === obj);
+ if (!(keyName in meta.cache)) {
+ // unwatch already creates meta for this instance
+ removeDependentKeys(this, obj, keyName, meta);
+ }
+};
+
+/* impl descriptor API */
+ComputedPropertyPrototype.didChange = function(obj, keyName) {
+ // _suspended is set via a CP.set to ensure we don't clear
+ // the cached value set by the setter
+ if (this._cacheable && this._suspended !== obj) {
+ var meta = metaFor(obj);
+ if (keyName in meta.cache) {
+ delete meta.cache[keyName];
+ if (!meta.watching[keyName]) {
+ removeDependentKeys(this, obj, keyName, meta);
+ }
+ }
+ }
+};
+
+/* impl descriptor API */
+ComputedPropertyPrototype.get = function(obj, keyName) {
+ var ret, cache, meta;
+ if (this._cacheable) {
+ meta = metaFor(obj);
+ cache = meta.cache;
+ if (keyName in cache) { return cache[keyName]; }
+ ret = cache[keyName] = this.func.call(obj, keyName);
+ if (!meta.watching[keyName]) {
+ addDependentKeys(this, obj, keyName, meta);
+ }
+ } else {
+ ret = this.func.call(obj, keyName);
+ }
+ return ret;
+};
+
+/* impl descriptor API */
+ComputedPropertyPrototype.set = function(obj, keyName, value) {
+ var cacheable = this._cacheable,
+ func = this.func,
+ meta = metaFor(obj, cacheable),
+ watched = meta.watching[keyName],
+ oldSuspended = this._suspended,
+ hadCachedValue = false,
+ cache = meta.cache,
+ cachedValue, ret;
+
+ if (this._readOnly) {
+ throw new Error('Cannot Set: ' + keyName + ' on: ' + obj.toString() );
+ }
+
+ this._suspended = obj;
+
+ try {
+
+ if (cacheable && cache.hasOwnProperty(keyName)) {
+ cachedValue = cache[keyName];
+ hadCachedValue = true;
+ }
+
+ // Check if the CP has been wrapped
+ if (func.wrappedFunction) { func = func.wrappedFunction; }
+
+ // For backwards-compatibility with computed properties
+ // that check for arguments.length === 2 to determine if
+ // they are being get or set, only pass the old cached
+ // value if the computed property opts into a third
+ // argument.
+ if (func.length === 3) {
+ ret = func.call(obj, keyName, value, cachedValue);
+ } else if (func.length === 2) {
+ ret = func.call(obj, keyName, value);
+ } else {
+ Ember.defineProperty(obj, keyName, null, cachedValue);
+ Ember.set(obj, keyName, value);
+ return;
+ }
+
+ if (hadCachedValue && cachedValue === ret) { return; }
+
+ if (watched) { Ember.propertyWillChange(obj, keyName); }
+
+ if (hadCachedValue) {
+ delete cache[keyName];
+ }
+
+ if (cacheable) {
+ if (!watched && !hadCachedValue) {
+ addDependentKeys(this, obj, keyName, meta);
+ }
+ cache[keyName] = ret;
+ }
+
+ if (watched) { Ember.propertyDidChange(obj, keyName); }
+ } finally {
+ this._suspended = oldSuspended;
+ }
+ return ret;
+};
+
+/* called when property is defined */
+ComputedPropertyPrototype.setup = function(obj, keyName) {
+ var meta = obj[META_KEY];
+ if (meta && meta.watching[keyName]) {
+ addDependentKeys(this, obj, keyName, metaFor(obj));
+ }
+};
+
+/* called before property is overridden */
+ComputedPropertyPrototype.teardown = function(obj, keyName) {
+ var meta = metaFor(obj);
+
+ if (meta.watching[keyName] || keyName in meta.cache) {
+ removeDependentKeys(this, obj, keyName, meta);
+ }
+
+ if (this._cacheable) { delete meta.cache[keyName]; }
+
+ return null; // no value to restore
+};
+
+
+/**
+ This helper returns a new property descriptor that wraps the passed
+ computed property function. You can use this helper to define properties
+ with mixins or via `Ember.defineProperty()`.
+
+ The function you pass will be used to both get and set property values.
+ The function should accept two parameters, key and value. If value is not
+ undefined you should set the value first. In either case return the
+ current value of the property.
+
+ @method computed
+ @for Ember
+ @param {Function} func The computed property function.
+ @return {Ember.ComputedProperty} property descriptor instance
+*/
+Ember.computed = function(func) {
+ var args;
+
+ if (arguments.length > 1) {
+ args = a_slice.call(arguments, 0, -1);
+ func = a_slice.call(arguments, -1)[0];
+ }
+
+ if ( typeof func !== "function" ) {
+ throw new Error("Computed Property declared without a property function");
+ }
+
+ var cp = new ComputedProperty(func);
+
+ if (args) {
+ cp.property.apply(cp, args);
+ }
+
+ return cp;
+};
+
+/**
+ Returns the cached value for a property, if one exists.
+ This can be useful for peeking at the value of a computed
+ property that is generated lazily, without accidentally causing
+ it to be created.
+
+ @method cacheFor
+ @for Ember
+ @param {Object} obj the object whose property you want to check
+ @param {String} key the name of the property whose cached value you want
+ to return
+ @return {*} the cached value
+*/
+Ember.cacheFor = function cacheFor(obj, key) {
+ var cache = metaFor(obj, false).cache;
+
+ if (cache && key in cache) {
+ return cache[key];
+ }
+};
+
+function getProperties(self, propertyNames) {
+ var ret = {};
+ for(var i = 0; i < propertyNames.length; i++) {
+ ret[propertyNames[i]] = get(self, propertyNames[i]);
+ }
+ return ret;
+}
+
+function registerComputed(name, macro) {
+ Ember.computed[name] = function(dependentKey) {
+ var args = a_slice.call(arguments);
+ return Ember.computed(dependentKey, function() {
+ return macro.apply(this, args);
+ });
+ };
+}
+
+function registerComputedWithProperties(name, macro) {
+ Ember.computed[name] = function() {
+ var properties = a_slice.call(arguments);
+
+ var computed = Ember.computed(function() {
+ return macro.apply(this, [getProperties(this, properties)]);
+ });
+
+ return computed.property.apply(computed, properties);
+ };
+}
+
+/**
+ @method computed.empty
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which negate
+ the original value for property
+*/
+registerComputed('empty', function(dependentKey) {
+ return Ember.isEmpty(get(this, dependentKey));
+});
+
+/**
+ @method computed.notEmpty
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which returns true if
+ original value for property is not empty.
+*/
+registerComputed('notEmpty', function(dependentKey) {
+ return !Ember.isEmpty(get(this, dependentKey));
+});
+
+/**
+ @method computed.none
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which
+ rturns true if original value for property is null or undefined.
+*/
+registerComputed('none', function(dependentKey) {
+ return Ember.isNone(get(this, dependentKey));
+});
+
+/**
+ @method computed.not
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which returns
+ inverse of the original value for property
+*/
+registerComputed('not', function(dependentKey) {
+ return !get(this, dependentKey);
+});
+
+/**
+ @method computed.bool
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which convert
+ to boolean the original value for property
+*/
+registerComputed('bool', function(dependentKey) {
+ return !!get(this, dependentKey);
+});
+
+/**
+ @method computed.match
+ @for Ember
+ @param {String} dependentKey
+ @param {RegExp} regexp
+ @return {Ember.ComputedProperty} computed property which match
+ the original value for property against a given RegExp
+*/
+registerComputed('match', function(dependentKey, regexp) {
+ var value = get(this, dependentKey);
+ return typeof value === 'string' ? !!value.match(regexp) : false;
+});
+
+/**
+ @method computed.equal
+ @for Ember
+ @param {String} dependentKey
+ @param {String|Number|Object} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is equal to the given value.
+*/
+registerComputed('equal', function(dependentKey, value) {
+ return get(this, dependentKey) === value;
+});
+
+/**
+ @method computed.gt
+ @for Ember
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is greater then given value.
+*/
+registerComputed('gt', function(dependentKey, value) {
+ return get(this, dependentKey) > value;
+});
+
+/**
+ @method computed.gte
+ @for Ember
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is greater or equal then given value.
+*/
+registerComputed('gte', function(dependentKey, value) {
+ return get(this, dependentKey) >= value;
+});
+
+/**
+ @method computed.lt
+ @for Ember
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is less then given value.
+*/
+registerComputed('lt', function(dependentKey, value) {
+ return get(this, dependentKey) < value;
+});
+
+/**
+ @method computed.lte
+ @for Ember
+ @param {String} dependentKey
+ @param {Number} value
+ @return {Ember.ComputedProperty} computed property which returns true if
+ the original value for property is less or equal then given value.
+*/
+registerComputed('lte', function(dependentKey, value) {
+ return get(this, dependentKey) <= value;
+});
+
+/**
+ @method computed.and
+ @for Ember
+ @param {String} dependentKey, [dependentKey...]
+ @return {Ember.ComputedProperty} computed property which peforms
+ a logical `and` on the values of all the original values for properties.
+*/
+registerComputedWithProperties('and', function(properties) {
+ for (var key in properties) {
+ if (properties.hasOwnProperty(key) && !properties[key]) {
+ return false;
+ }
+ }
+ return true;
+});
+
+/**
+ @method computed.or
+ @for Ember
+ @param {String} dependentKey, [dependentKey...]
+ @return {Ember.ComputedProperty} computed property which peforms
+ a logical `or` on the values of all the original values for properties.
+*/
+registerComputedWithProperties('or', function(properties) {
+ for (var key in properties) {
+ if (properties.hasOwnProperty(key) && properties[key]) {
+ return true;
+ }
+ }
+ return false;
+});
+
+/**
+ @method computed.any
+ @for Ember
+ @param {String} dependentKey, [dependentKey...]
+ @return {Ember.ComputedProperty} computed property which returns
+ the first trouthy value of given list of properties.
+*/
+registerComputedWithProperties('any', function(properties) {
+ for (var key in properties) {
+ if (properties.hasOwnProperty(key) && properties[key]) {
+ return properties[key];
+ }
+ }
+ return null;
+});
+
+/**
+ @method computed.map
+ @for Ember
+ @param {String} dependentKey, [dependentKey...]
+ @return {Ember.ComputedProperty} computed property which maps
+ values of all passed properties in to an array.
+*/
+registerComputedWithProperties('map', function(properties) {
+ var res = [];
+ for (var key in properties) {
+ if (properties.hasOwnProperty(key)) {
+ if (Ember.isNone(properties[key])) {
+ res.push(null);
+ } else {
+ res.push(properties[key]);
+ }
+ }
+ }
+ return res;
+});
+
+/**
+ @method computed.alias
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which creates an
+ alias to the original value for property.
+*/
+Ember.computed.alias = function(dependentKey) {
+ return Ember.computed(dependentKey, function(key, value){
+ if (arguments.length > 1) {
+ set(this, dependentKey, value);
+ return value;
+ } else {
+ return get(this, dependentKey);
+ }
+ });
+};
+
+/**
+ @method computed.defaultTo
+ @for Ember
+ @param {String} defaultPath
+ @return {Ember.ComputedProperty} computed property which acts like
+ a standard getter and setter, but defaults to the value from `defaultPath`.
+*/
+Ember.computed.defaultTo = function(defaultPath) {
+ return Ember.computed(function(key, newValue, cachedValue) {
+ if (arguments.length === 1) {
+ return cachedValue != null ? cachedValue : get(this, defaultPath);
+ }
+ return newValue != null ? newValue : get(this, defaultPath);
+ });
+};
+
+})();
+
+
+
+(function() {
+// Ember.tryFinally
+/**
+@module ember-metal
+*/
+
+var AFTER_OBSERVERS = ':change';
+var BEFORE_OBSERVERS = ':before';
+
+var guidFor = Ember.guidFor;
+
+function changeEvent(keyName) {
+ return keyName+AFTER_OBSERVERS;
+}
+
+function beforeEvent(keyName) {
+ return keyName+BEFORE_OBSERVERS;
+}
+
+/**
+ @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.observersFor = function(obj, path) {
+ return Ember.listenersFor(obj, changeEvent(path));
+};
+
+/**
+ @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;
+};
+
+/**
+ @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;
+};
+
+// Suspend observer during callback.
+//
+// This should only be used by the target of the observer
+// while it is setting the observed path.
+Ember._suspendBeforeObserver = function(obj, path, target, method, callback) {
+ return Ember._suspendListener(obj, beforeEvent(path), target, method, callback);
+};
+
+Ember._suspendObserver = function(obj, path, target, method, callback) {
+ return Ember._suspendListener(obj, changeEvent(path), target, method, callback);
+};
+
+var map = Ember.ArrayPolyfills.map;
+
+Ember._suspendBeforeObservers = function(obj, paths, target, method, callback) {
+ var events = map.call(paths, beforeEvent);
+ return Ember._suspendListeners(obj, events, target, method, callback);
+};
+
+Ember._suspendObservers = function(obj, paths, target, method, callback) {
+ var events = map.call(paths, changeEvent);
+ return Ember._suspendListeners(obj, events, target, method, callback);
+};
+
+Ember.beforeObserversFor = function(obj, path) {
+ return Ember.listenersFor(obj, beforeEvent(path));
+};
+
+/**
+ @method removeBeforeObserver
+ @param obj
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
+*/
+Ember.removeBeforeObserver = function(obj, path, target, method) {
+ Ember.unwatch(obj, path);
+ Ember.removeListener(obj, beforeEvent(path), target, method);
+ return this;
+};
+})();
+
+
+
(function() {
// Ember.Logger
// Ember.watch.flushPending
@@ -4366,8 +4510,6 @@ function invoke(target, method, args, ignore) {
// RUNLOOP
//
-var timerMark; // used by timers...
-
/**
Ember RunLoop (Private)
@@ -4498,8 +4640,6 @@ RunLoop.prototype = {
}
}
- timerMark = null;
-
return this;
}
@@ -4615,7 +4755,7 @@ Ember.run.queues = ['sync', 'actions', 'destroy'];
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(){
@@ -4846,39 +4986,11 @@ function scheduleOnce(queue, target, method, args) {
}
/**
- Schedules an item to run one time during the current RunLoop. Calling
- this method with the same target/method combination will have no effect.
-
- 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
- });
- ```
-
- Also note that passing an anonymous function to `Ember.run.once` 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"); });
- }
- scheduleIt();
- scheduleIt();
- // "Closure" will print twice, even though we're using `Ember.run.once`,
- // because the function we pass to it is anonymous and won't match the
- // previously scheduled operation.
- ```
+ 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] target of method to invoke
+ @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.
@@ -4889,7 +5001,51 @@ Ember.run.once = function(target, method) {
return scheduleOnce('actions', target, method, slice.call(arguments, 2));
};
-Ember.run.scheduleOnce = function(queue, target, method, args) {
+/**
+ Schedules a function to run one time in a given queue of the current RunLoop.
+ Calling this method with the same queue/target/method combination will have
+ no effect (past the initial call).
+
+ Note that although you can pass optional arguments these will not be
+ considered when looking for duplicates. New arguments will replace previous
+ calls.
+
+ ```javascript
+ Ember.run(function(){
+ var sayHi = function() { console.log('hi'); }
+ Ember.run.scheduleOnce('afterRender', myContext, sayHi);
+ Ember.run.scheduleOnce('afterRender', myContext, sayHi);
+ // doFoo will only be executed once, in the afterRender queue of the RunLoop
+ });
+ ```
+
+ Also note that passing an anonymous function to `Ember.run.scheduleOnce` will
+ not prevent additional calls with an identical anonymous function from
+ scheduling the items multiple times, e.g.:
+
+ ```javascript
+ function scheduleIt() {
+ Ember.run.scheduleOnce('actions', myContext, function() { console.log("Closure"); });
+ }
+ scheduleIt();
+ scheduleIt();
+ // "Closure" will print twice, even though we're using `Ember.run.scheduleOnce`,
+ // because the function we pass to it is anonymous and won't match the
+ // previously scheduled operation.
+ ```
+
+ Available queues, and their order, can be found at `Ember.run.queues`
+
+ @method scheduleOnce
+ @param {String} [queue] The name of the queue to schedule against. Default queues are 'sync' and 'actions'.
+ @param {Object} [target] The target of the method to invoke.
+ @param {Function|String} method The method to invoke.
+ If you pass a string it will be resolved on the
+ target at the time the method is invoked.
+ @param {Object} [args*] Optional arguments to pass to the timeout.
+ @return {Object} timer
+*/
+Ember.run.scheduleOnce = function(queue, target, method) {
return scheduleOnce(queue, target, method, slice.call(arguments, 3));
};
@@ -4991,8 +5147,9 @@ Ember.run.cancel = function(timer) {
(function() {
// Ember.Logger
-// get, set, trySet
-// guidFor, isArray, meta
+// get
+// set
+// guidFor, meta
// addObserver, removeObserver
// Ember.run.schedule
/**
@@ -5018,8 +5175,21 @@ 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);
@@ -5311,7 +5481,7 @@ mixinProperties(Binding, {
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"
@@ -5611,7 +5781,7 @@ function addNormalizedProperty(base, key, value, meta, descs, values, concats) {
}
}
-function mergeMixins(mixins, m, descs, values, base) {
+function mergeMixins(mixins, m, descs, values, base, keys) {
var mixin, props, key, concats, meta;
function removeKeys(keyName) {
@@ -5632,13 +5802,14 @@ function mergeMixins(mixins, m, descs, 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); }
}
}
@@ -5734,7 +5905,7 @@ function replaceObservers(obj, key, observer) {
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:
//
@@ -5742,10 +5913,11 @@ function applyMixin(obj, mixins, partial) {
// * 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];
@@ -5799,7 +5971,7 @@ Ember.mixin = function(obj) {
});
// 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}}')
});
@@ -5818,6 +5990,12 @@ 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) {
@@ -6042,7 +6220,7 @@ 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.
@@ -6657,7 +6835,7 @@ define("container",
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) {
@@ -6708,79 +6886,6 @@ define("container",
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:
@@ -7139,7 +7244,8 @@ Ember.String = {
```
@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) {
@@ -8542,7 +8648,7 @@ Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.protot
@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 ;
@@ -9116,7 +9222,7 @@ var forEach = Ember.EnumerableUtils.forEach;
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);
@@ -9124,8 +9230,8 @@ var forEach = Ember.EnumerableUtils.forEach;
## 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
@@ -9152,7 +9258,7 @@ Ember.MutableEnumerable = Ember.Mixin.create(Ember.Enumerable, {
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
@@ -9179,10 +9285,10 @@ Ember.MutableEnumerable = Ember.Mixin.create(Ember.Enumerable, {
__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
@@ -9193,7 +9299,7 @@ Ember.MutableEnumerable = Ember.Mixin.create(Ember.Enumerable, {
/**
- 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
@@ -9351,8 +9457,8 @@ Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,
```
@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) ;
@@ -9431,8 +9537,8 @@ Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,
```
@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) ;
@@ -10049,6 +10155,15 @@ Ember.Observable = Ember.Mixin.create(/** @scope Ember.Observable.prototype */ {
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
@@ -10056,6 +10171,7 @@ var get = Ember.get, set = Ember.set;
Ember.TargetActionSupport = Ember.Mixin.create({
target: null,
action: null,
+ actionContext: null,
targetObject: Ember.computed(function() {
var target = get(this, 'target');
@@ -10069,21 +10185,86 @@ Ember.TargetActionSupport = Ember.Mixin.create({
}
}).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;
@@ -10393,6 +10574,9 @@ function makeCtor() {
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; }
@@ -10514,8 +10698,6 @@ CoreObject.PrototypeMixin = Mixin.create({
do important setup work, and you'll see strange behavior in your
application.
- ```
-
@method init
*/
init: function() {},
@@ -10626,12 +10808,12 @@ CoreObject.PrototypeMixin = Mixin.create({
this.isDestroying = true;
this._didCallDestroy = true;
- if (this.willDestroy) { this.willDestroy(); }
-
schedule('destroy', this, this._scheduledDestroy);
return this;
},
+ willDestroy: Ember.K,
+
/**
@private
@@ -10641,9 +10823,9 @@ CoreObject.PrototypeMixin = Mixin.create({
@method _scheduledDestroy
*/
_scheduledDestroy: function() {
+ if (this.willDestroy) { this.willDestroy(); }
destroy(this);
- set(this, 'isDestroyed', true);
-
+ this.isDestroyed = true;
if (this.didDestroy) { this.didDestroy(); }
},
@@ -10991,7 +11173,7 @@ function findNamespaces() {
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
@@ -11679,7 +11861,7 @@ Ember.EachProxy = Ember.Object.extend({
@method unknownProperty
@param keyName {String}
- @param value {anything}
+ @param value {*}
*/
unknownProperty: function(keyName, value) {
var ret;
@@ -11903,9 +12085,8 @@ if (ignore.length>0) {
@namespace Ember
@extends Ember.Mixin
@uses Ember.MutableArray
- @uses Ember.MutableEnumerable
+ @uses Ember.Observable
@uses Ember.Copyable
- @uses Ember.Freezable
*/
Ember.NativeArray = NativeArray;
@@ -12916,9 +13097,9 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin,
});
```
- @method
- @type String
- @default null
+ @method lookupItemController
+ @param {Object} object
+ @return {String}
*/
lookupItemController: function(object) {
return get(this, 'itemController');
@@ -12967,8 +13148,8 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin,
},
init: function() {
- this._super();
if (!this.get('content')) { Ember.defineProperty(this, 'content', undefined, Ember.A()); }
+ this._super();
this.set('_subControllers', Ember.A());
},
@@ -12996,10 +13177,11 @@ Ember.ArrayController = Ember.ArrayProxy.extend(Ember.ControllerMixin,
_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());
}
@@ -13276,7 +13458,7 @@ Ember.RenderBuffer = function(tagName) {
Ember._RenderBuffer = function(tagName) {
this.tagNames = [tagName || null];
- this.buffer = [];
+ this.buffer = "";
};
Ember._RenderBuffer.prototype =
@@ -13285,6 +13467,8 @@ Ember._RenderBuffer.prototype =
// The root view's element
_element: null,
+ _hasElement: true,
+
/**
@private
@@ -13400,7 +13584,7 @@ Ember._RenderBuffer.prototype =
@chainable
*/
push: function(string) {
- this.buffer.push(string);
+ this.buffer += string;
return this;
},
@@ -13533,7 +13717,7 @@ Ember._RenderBuffer.prototype =
var tagName = this.currentTagName();
if (!tagName) { return; }
- if (!this._element && this.buffer.length === 0) {
+ if (this._hasElement && !this._element && this.buffer.length === 0) {
this._element = this.generateElement();
return;
}
@@ -13546,27 +13730,27 @@ Ember._RenderBuffer.prototype =
style = this.elementStyle,
attr, prop;
- buffer.push('<' + tagName);
+ buffer += '<' + tagName;
if (id) {
- buffer.push(' id="' + this._escapeAttribute(id) + '"');
+ buffer += ' id="' + this._escapeAttribute(id) + '"';
this.elementId = null;
}
if (classes) {
- buffer.push(' class="' + this._escapeAttribute(classes.join(' ')) + '"');
+ buffer += ' class="' + this._escapeAttribute(classes.join(' ')) + '"';
this.classes = null;
}
if (style) {
- buffer.push(' style="');
+ buffer += ' style="';
for (prop in style) {
if (style.hasOwnProperty(prop)) {
- buffer.push(prop + ':' + this._escapeAttribute(style[prop]) + ';');
+ buffer += prop + ':' + this._escapeAttribute(style[prop]) + ';';
}
}
- buffer.push('"');
+ buffer += '"';
this.elementStyle = null;
}
@@ -13574,7 +13758,7 @@ Ember._RenderBuffer.prototype =
if (attrs) {
for (attr in attrs) {
if (attrs.hasOwnProperty(attr)) {
- buffer.push(' ' + attr + '="' + this._escapeAttribute(attrs[attr]) + '"');
+ buffer += ' ' + attr + '="' + this._escapeAttribute(attrs[attr]) + '"';
}
}
@@ -13587,9 +13771,9 @@ Ember._RenderBuffer.prototype =
var value = props[prop];
if (value || typeof(value) === 'number') {
if (value === true) {
- buffer.push(' ' + prop + '="' + prop + '"');
+ buffer += ' ' + prop + '="' + prop + '"';
} else {
- buffer.push(' ' + prop + '="' + this._escapeAttribute(props[prop]) + '"');
+ buffer += ' ' + prop + '="' + this._escapeAttribute(props[prop]) + '"';
}
}
}
@@ -13598,12 +13782,13 @@ Ember._RenderBuffer.prototype =
this.elementProperties = null;
}
- buffer.push('>');
+ buffer += '>';
+ this.buffer = buffer;
},
pushClosingTag: function() {
var tagName = this.tagNames.pop();
- if (tagName) { this.buffer.push('' + tagName + '>'); }
+ if (tagName) { this.buffer += '' + tagName + '>'; }
},
currentTagName: function() {
@@ -13687,17 +13872,20 @@ Ember._RenderBuffer.prototype =
@return {String} The generated HTML
*/
string: function() {
- if (this._element) {
+ if (this._hasElement && this._element) {
// Firefox versions < 11 do not have support for element.outerHTML.
- return this.element().outerHTML ||
- new XMLSerializer().serializeToString(this.element());
+ var thisElement = this.element(), outerHTML = thisElement.outerHTML;
+ if (typeof outerHTML === 'undefined'){
+ return Ember.$('
').append(thisElement).html();
+ }
+ return outerHTML;
} else {
return this.innerString();
}
},
innerString: function() {
- return this.buffer.join('');
+ return this.buffer;
},
_escapeAttribute: function(value) {
@@ -13785,7 +13973,7 @@ Ember.EventDispatcher = Ember.Object.extend(
setup: function(addedEvents) {
var event, events = {
touchstart : 'touchStart',
- touchmove : 'touchMove',
+ // touchmove : 'touchMove',
touchend : 'touchEnd',
touchcancel : 'touchCancel',
keydown : 'keyDown',
@@ -13796,7 +13984,7 @@ Ember.EventDispatcher = Ember.Object.extend(
contextmenu : 'contextMenu',
click : 'click',
dblclick : 'doubleClick',
- mousemove : 'mouseMove',
+ // mousemove : 'mouseMove',
focusin : 'focusIn',
focusout : 'focusOut',
mouseenter : 'mouseEnter',
@@ -14060,18 +14248,6 @@ Ember.CoreView = Ember.Object.extend(Ember.Evented, {
init: function() {
this._super();
-
- // Register the view for event handling. This hash is used by
- // Ember.EventDispatcher to dispatch incoming events.
- if (!this.isVirtual) {
- Ember.assert("Attempted to register a view with an id already in use: "+this.elementId, !Ember.View.views[this.elementId]);
- Ember.View.views[this.elementId] = this;
- }
-
- this.addBeforeObserver('elementId', function() {
- throw new Error("Changing a view's elementId after creation is not allowed");
- });
-
this.transitionTo('preRender');
},
@@ -14101,7 +14277,7 @@ Ember.CoreView = Ember.Object.extend(Ember.Evented, {
concreteView: Ember.computed(function() {
if (!this.isVirtual) { return this; }
else { return get(this, 'parentView'); }
- }).property('parentView').volatile(),
+ }).property('parentView'),
instrumentName: 'core_view',
@@ -14139,8 +14315,6 @@ Ember.CoreView = Ember.Object.extend(Ember.Evented, {
},
_renderToBuffer: function(parentBuffer, bufferOperation) {
- Ember.run.sync();
-
// If this is the top-most view, start a new buffer. Otherwise,
// create a new buffer relative to the original using the
// provided buffer operation (for example, `insertAfter` will
@@ -14186,9 +14360,11 @@ Ember.CoreView = Ember.Object.extend(Ember.Evented, {
return Ember.typeOf(this[name]) === 'function' || this._super(name);
},
- willDestroy: function() {
+ destroy: function() {
var parent = this._parentView;
+ if (!this._super()) { return; }
+
// destroy the element -- this will avoid each child view destroying
// the element over and over again...
if (!this.removedFromDOM) { this.destroyElement(); }
@@ -14198,10 +14374,9 @@ Ember.CoreView = Ember.Object.extend(Ember.Evented, {
// the DOM again.
if (parent) { parent.removeChild(this); }
- this.transitionTo('destroyed');
+ this.transitionTo('destroying', false);
- // next remove view from global hash
- if (!this.isVirtual) delete Ember.View.views[this.elementId];
+ return this;
},
clearRenderedChildren: Ember.K,
@@ -14211,6 +14386,68 @@ Ember.CoreView = Ember.Object.extend(Ember.Evented, {
destroyElement: Ember.K
});
+var ViewCollection = Ember._ViewCollection = function(initialViews) {
+ var views = this.views = initialViews || [];
+ this.length = views.length;
+};
+
+ViewCollection.prototype = {
+ length: 0,
+
+ trigger: function(eventName) {
+ var views = this.views, view;
+ for (var i = 0, l = views.length; i < l; i++) {
+ view = views[i];
+ if (view.trigger) { view.trigger(eventName); }
+ }
+ },
+
+ triggerRecursively: function(eventName) {
+ var views = this.views;
+ for (var i = 0, l = views.length; i < l; i++) {
+ views[i].triggerRecursively(eventName);
+ }
+ },
+
+ invokeRecursively: function(fn) {
+ var views = this.views, view;
+
+ for (var i = 0, l = views.length; i < l; i++) {
+ view = views[i];
+ fn(view);
+ }
+ },
+
+ transitionTo: function(state, children) {
+ var views = this.views;
+ for (var i = 0, l = views.length; i < l; i++) {
+ views[i].transitionTo(state, children);
+ }
+ },
+
+ push: function() {
+ this.length += arguments.length;
+ var views = this.views;
+ return views.push.apply(views, arguments);
+ },
+
+ objectAt: function(idx) {
+ return this.views[idx];
+ },
+
+ forEach: function(callback) {
+ var views = this.views;
+ return a_forEach(views, callback);
+ },
+
+ clear: function() {
+ this.length = 0;
+ this.views.length = 0;
+ }
+};
+
+var EMPTY_ARRAY = [];
+
/**
`Ember.View` is the class in Ember responsible for encapsulating templates of
HTML content, combining templates with data to render as sections of a page's
@@ -14838,14 +15075,6 @@ Ember.View = Ember.CoreView.extend(
return template || get(this, 'defaultTemplate');
}).property('templateName'),
- container: Ember.computed(function() {
- var parentView = get(this, '_parentView');
-
- if (parentView) { return get(parentView, 'container'); }
-
- return Ember.Container && Ember.Container.defaultContainer;
- }),
-
/**
The controller managing this view. If this property is set, it will be
made available for use by the template.
@@ -14883,14 +15112,9 @@ Ember.View = Ember.CoreView.extend(
templateForName: function(name, type) {
if (!name) { return; }
-
Ember.assert("templateNames are not allowed to contain periods: "+name, name.indexOf('.') === -1);
-
- var container = get(this, 'container');
-
- if (container) {
- return container.lookup('template:' + name);
- }
+ var container = this.container || (Ember.Container && Ember.Container.defaultContainer);
+ return container && container.lookup('template:' + name);
},
/**
@@ -14981,7 +15205,7 @@ Ember.View = Ember.CoreView.extend(
*/
childViews: childViewsProperty,
- _childViews: [],
+ _childViews: EMPTY_ARRAY,
// When it's a virtual view, we need to notify the parent that their
// childViews will change.
@@ -15279,7 +15503,7 @@ Ember.View = Ember.CoreView.extend(
@param {Ember.RenderBuffer} buffer
*/
_applyAttributeBindings: function(buffer, attributeBindings) {
- var attributeValue, elem, type;
+ var attributeValue, elem;
a_forEach(attributeBindings, function(binding) {
var split = binding.split(':'),
@@ -15370,7 +15594,7 @@ Ember.View = Ember.CoreView.extend(
while(--idx >= 0) {
view = childViews[idx];
- callback.call(this, view, idx);
+ callback(this, view, idx);
}
return this;
@@ -15384,9 +15608,9 @@ Ember.View = Ember.CoreView.extend(
var len = childViews.length,
view, idx;
- for(idx = 0; idx < len; idx++) {
+ for (idx = 0; idx < len; idx++) {
view = childViews[idx];
- callback.call(this, view);
+ callback(view);
}
return this;
@@ -15582,13 +15806,15 @@ Ember.View = Ember.CoreView.extend(
/**
@private
- Run this callback on the current view and recursively on child views.
+ Run this callback on the current view (unless includeSelf is false) and recursively on child views.
@method invokeRecursively
@param fn {Function}
+ @param includeSelf (optional, default true)
*/
- invokeRecursively: function(fn) {
- var childViews = [this], currentViews, view;
+ invokeRecursively: function(fn, includeSelf) {
+ var childViews = (includeSelf === false) ? this._childViews : [this];
+ var currentViews, view;
while (childViews.length) {
currentViews = childViews.slice();
@@ -15596,7 +15822,7 @@ Ember.View = Ember.CoreView.extend(
for (var i=0, l=currentViews.length; i=0; i--) {
@@ -15998,27 +16237,16 @@ Ember.View = Ember.CoreView.extend(
}
// remove from non-virtual parent view if viewName was specified
- if (this.viewName) {
- var nonVirtualParentView = get(this, 'parentView');
- if (nonVirtualParentView) {
- set(nonVirtualParentView, this.viewName, null);
- }
+ if (viewName && nonVirtualParentView) {
+ nonVirtualParentView.set(viewName, null);
}
- // remove from parent if found. Don't call removeFromParent,
- // as removeFromParent will try to remove the element from
- // the DOM again.
- if (parent) { parent.removeChild(this); }
-
- this.transitionTo('destroyed');
-
childLen = childViews.length;
for (i=childLen-1; i>=0; i--) {
childViews[i].destroy();
}
- // next remove view from global hash
- if (!this.isVirtual) delete Ember.View.views[get(this, 'elementId')];
+ return this;
},
/**
@@ -16039,6 +16267,7 @@ Ember.View = Ember.CoreView.extend(
if (Ember.CoreView.detect(view)) {
attrs = attrs || {};
attrs._parentView = this;
+ attrs.container = this.container;
attrs.templateData = attrs.templateData || get(this, 'templateData');
view = view.create(attrs);
@@ -16133,9 +16362,13 @@ Ember.View = Ember.CoreView.extend(
},
transitionTo: function(state, children) {
- this.currentState = this.states[state];
+ var priorState = this.currentState,
+ currentState = this.currentState = this.states[state];
this.state = state;
+ if (priorState && priorState.exit) { priorState.exit(this); }
+ if (currentState.enter) { currentState.enter(this); }
+
if (children !== false) {
this.forEachChildView(function(view) {
view.transitionTo(state);
@@ -16479,15 +16712,18 @@ Ember.merge(preRender, {
// created (createElement).
insertElement: function(view, fn) {
view.createElement();
- view.triggerRecursively('willInsertElement');
+ var viewCollection = view.viewHierarchyCollection();
+
+ viewCollection.trigger('willInsertElement');
// after createElement, the view will be in the hasElement state.
fn.call(view);
- view.transitionTo('inDOM');
- view.triggerRecursively('didInsertElement');
+ viewCollection.transitionTo('inDOM', false);
+ viewCollection.trigger('didInsertElement');
},
- renderToBufferIfNeeded: function(view) {
- return view.renderToBuffer();
+ renderToBufferIfNeeded: function(view, buffer) {
+ view.renderToBuffer(buffer);
+ return true;
},
empty: Ember.K,
@@ -16534,10 +16770,11 @@ Ember.merge(inBuffer, {
// view will render that view and append the resulting
// buffer into its buffer.
appendChild: function(view, childView, options) {
- var buffer = view.buffer;
+ var buffer = view.buffer, _childViews = view._childViews;
childView = view.createChildView(childView, options);
- view._childViews.push(childView);
+ if (!_childViews.length) { _childViews = view._childViews = _childViews.slice(); }
+ _childViews.push(childView);
childView.renderToBuffer(buffer);
@@ -16551,8 +16788,8 @@ Ember.merge(inBuffer, {
// state back into the preRender state.
destroyElement: function(view) {
view.clearBuffer();
- view._notifyWillDestroyElement();
- view.transitionTo('preRender');
+ var viewCollection = view._notifyWillDestroyElement();
+ viewCollection.transitionTo('preRender', false);
return view;
},
@@ -16561,8 +16798,8 @@ Ember.merge(inBuffer, {
Ember.assert("Emptying a view in the inBuffer state is not allowed and should not happen under normal circumstances. Most likely there is a bug in your application. This may be due to excessive property change notifications.");
},
- renderToBufferIfNeeded: function (view) {
- return view.buffer;
+ renderToBufferIfNeeded: function (view, buffer) {
+ return false;
},
// It should be impossible for a rendered view to be scheduled for
@@ -16681,6 +16918,23 @@ Ember.merge(hasElement, {
var inDOM = Ember.View.states.inDOM = Ember.create(hasElement);
Ember.merge(inDOM, {
+ enter: function(view) {
+ // Register the view for event handling. This hash is used by
+ // Ember.EventDispatcher to dispatch incoming events.
+ if (!view.isVirtual) {
+ Ember.assert("Attempted to register a view with an id already in use: "+view.elementId, !Ember.View.views[view.elementId]);
+ Ember.View.views[view.elementId] = view;
+ }
+
+ view.addBeforeObserver('elementId', function() {
+ throw new Error("Changing a view's elementId after creation is not allowed");
+ });
+ },
+
+ exit: function(view) {
+ if (!this.isVirtual) delete Ember.View.views[view.elementId];
+ },
+
insertElement: function(view, fn) {
throw "You can't insert an element into the DOM that has already been inserted";
}
@@ -16696,30 +16950,30 @@ Ember.merge(inDOM, {
@submodule ember-views
*/
-var destroyedError = "You can't call %@ on a destroyed view", fmt = Ember.String.fmt;
+var destroyingError = "You can't call %@ on a view being destroyed", fmt = Ember.String.fmt;
-var destroyed = Ember.View.states.destroyed = Ember.create(Ember.View.states._default);
+var destroying = Ember.View.states.destroying = Ember.create(Ember.View.states._default);
-Ember.merge(destroyed, {
+Ember.merge(destroying, {
appendChild: function() {
- throw fmt(destroyedError, ['appendChild']);
+ throw fmt(destroyingError, ['appendChild']);
},
rerender: function() {
- throw fmt(destroyedError, ['rerender']);
+ throw fmt(destroyingError, ['rerender']);
},
destroyElement: function() {
- throw fmt(destroyedError, ['destroyElement']);
+ throw fmt(destroyingError, ['destroyElement']);
},
empty: function() {
- throw fmt(destroyedError, ['empty']);
+ throw fmt(destroyingError, ['empty']);
},
setElement: function() {
- throw fmt(destroyedError, ["set('element', ...)"]);
+ throw fmt(destroyingError, ["set('element', ...)"]);
},
renderToBufferIfNeeded: function() {
- throw fmt(destroyedError, ["renderToBufferIfNeeded"]);
+ return false;
},
// Since element insertion is scheduled, don't do anything if
@@ -16738,7 +16992,7 @@ Ember.View.cloneStates = function(from) {
into._default = {};
into.preRender = Ember.create(into._default);
- into.destroyed = Ember.create(into._default);
+ into.destroying = Ember.create(into._default);
into.inBuffer = Ember.create(into._default);
into.hasElement = Ember.create(into._default);
into.inDOM = Ember.create(into.hasElement);
@@ -16765,6 +17019,7 @@ var states = Ember.View.cloneStates(Ember.View.states);
var get = Ember.get, set = Ember.set;
var forEach = Ember.EnumerableUtils.forEach;
+var ViewCollection = Ember._ViewCollection;
/**
A `ContainerView` is an `Ember.View` subclass that implements `Ember.MutableArray`
@@ -16969,6 +17224,7 @@ Ember.ContainerView = Ember.View.extend(Ember.MutableArray, {
var currentView = get(this, 'currentView');
if (currentView) {
+ if (!_childViews.length) { _childViews = this._childViews = this._childViews.slice(); }
_childViews.push(this.createChildView(currentView));
}
},
@@ -16983,6 +17239,7 @@ Ember.ContainerView = Ember.View.extend(Ember.MutableArray, {
this._childViews.splice(idx, removedCount) ;
} else {
var args = [idx, removedCount].concat(addedViews);
+ if (addedViews.length && !this._childViews.length) { this._childViews = this._childViews.slice(); }
this._childViews.splice.apply(this._childViews, args);
}
@@ -17127,26 +17384,48 @@ Ember.merge(states.hasElement, {
},
ensureChildrenAreInDOM: function(view) {
- var childViews = view._childViews, i, len, childView, previous, buffer;
+ var childViews = view._childViews, i, len, childView, previous, buffer, viewCollection = new ViewCollection();
+
for (i = 0, len = childViews.length; i < len; i++) {
childView = childViews[i];
- buffer = childView.renderToBufferIfNeeded();
- if (buffer) {
- childView.triggerRecursively('willInsertElement');
- if (previous) {
- previous.domManager.after(previous, buffer.string());
- } else {
- view.domManager.prepend(view, buffer.string());
- }
- childView.transitionTo('inDOM');
- childView.propertyDidChange('element');
- childView.triggerRecursively('didInsertElement');
+
+ if (!buffer) { buffer = Ember.RenderBuffer(); buffer._hasElement = false; }
+
+ if (childView.renderToBufferIfNeeded(buffer)) {
+ viewCollection.push(childView);
+ } else if (viewCollection.length) {
+ insertViewCollection(view, viewCollection, previous, buffer);
+ buffer = null;
+ previous = childView;
+ viewCollection.clear();
+ } else {
+ previous = childView;
}
- previous = childView;
+ }
+
+ if (viewCollection.length) {
+ insertViewCollection(view, viewCollection, previous, buffer);
}
}
});
+function insertViewCollection(view, viewCollection, previous, buffer) {
+ viewCollection.triggerRecursively('willInsertElement');
+
+ if (previous) {
+ previous.domManager.after(previous, buffer.string());
+ } else {
+ view.domManager.prepend(view, buffer.string());
+ }
+
+ viewCollection.forEach(function(v) {
+ v.transitionTo('inDOM');
+ v.propertyDidChange('element');
+ v.triggerRecursively('didInsertElement');
+ });
+}
+
+
})();
@@ -17387,15 +17666,17 @@ Ember.CollectionView = Ember.ContainerView.extend(
this.arrayDidChange(content, 0, null, len);
}, 'content'),
- willDestroy: function() {
+ destroy: function() {
+ if (!this._super()) { return; }
+
var content = get(this, 'content');
if (content) { content.removeArrayObserver(this); }
- this._super();
-
if (this._createdEmptyView) {
this._createdEmptyView.destroy();
}
+
+ return this;
},
arrayWillChange: function(content, start, removedCount) {
@@ -17417,11 +17698,13 @@ Ember.CollectionView = Ember.ContainerView.extend(
if (removingAll) {
this.currentState.empty(this);
+ this.invokeRecursively(function(view) {
+ view.removedFromDOM = true;
+ }, false);
}
for (idx = start + removedCount - 1; idx >= start; idx--) {
childView = childViews[idx];
- if (removingAll) { childView.removedFromDOM = true; }
childView.destroy();
}
},
@@ -17519,6 +17802,69 @@ Ember.CollectionView.CONTAINER_MAP = {
+(function() {
+/**
+`Ember.ViewTargetActionSupport` is a mixin that can be included in a
+view class to add a `triggerAction` method with semantics similar to
+the Handlebars `{{action}}` helper. It provides intelligent defaults
+for the action's target: the view's controller; and the context that is
+sent with the action: the view's context.
+
+Note: 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 custom View subclasses.
+
+For example:
+
+```javascript
+App.SaveButtonView = Ember.View.extend(Ember.ViewTargetActionSupport, {
+ action: 'save',
+ click: function(){
+ this.triggerAction(); // Sends the `save` action, along with the current context
+ // to the current controller
+ }
+});
+```
+
+The `action` can be provided as properties of an optional object argument
+to `triggerAction` as well.
+
+```javascript
+App.SaveButtonView = Ember.View.extend(Ember.ViewTargetActionSupport, {
+ click: function(){
+ this.triggerAction({
+ action: 'save'
+ }); // Sends the `save` action, along with the current context
+ // to the current controller
+ }
+});
+```
+
+@class ViewTargetActionSupport
+@namespace Ember
+@extends Ember.TargetActionSupport
+*/
+Ember.ViewTargetActionSupport = Ember.Mixin.create(Ember.TargetActionSupport, {
+ /**
+ @property target
+ */
+ target: Ember.computed.alias('controller'),
+ /**
+ @property actionContext
+ */
+ actionContext: Ember.computed.alias('context')
+});
+
+})();
+
+
+
+(function() {
+
+})();
+
+
+
(function() {
/*globals jQuery*/
/**
@@ -18030,6 +18376,17 @@ Ember.assert("Ember Handlebars requires Handlebars 1.0.0-rc.3 or greater. Includ
*/
Ember.Handlebars = objectCreate(Handlebars);
+Ember.Handlebars.helper = function(name, value) {
+ if (Ember.View.detect(value)) {
+ Ember.Handlebars.registerHelper(name, function(name, options) {
+ Ember.assert("You can only pass attributes as parameters to a application-defined helper", arguments.length < 3);
+ return Ember.Handlebars.helpers.view.call(this, value, options);
+ });
+ } else {
+ Ember.Handlebars.registerBoundHelper.apply(null, arguments);
+ }
+}
+
/**
@class helpers
@namespace Ember.Handlebars
@@ -18499,6 +18856,7 @@ Ember.Handlebars.registerBoundHelper = function(name, fn) {
Renders the unbound form of an otherwise bound helper function.
+ @method evaluateMultiPropertyBoundHelper
@param {Function} fn
@param {Object} context
@param {Array} normalizedProperties
@@ -18515,7 +18873,7 @@ function evaluateMultiPropertyBoundHelper(context, fn, normalizedProperties, opt
bindView = new Ember._SimpleHandlebarsView(null, null, !hash.unescaped, data);
bindView.normalizedValue = function() {
- var args = [], value, boundOption;
+ var args = [], boundOption;
// Copy over bound options.
for (boundOption in boundOptions) {
@@ -18560,6 +18918,7 @@ function evaluateMultiPropertyBoundHelper(context, fn, normalizedProperties, opt
Renders the unbound form of an otherwise bound helper function.
+ @method evaluateUnboundHelper
@param {Function} fn
@param {Object} context
@param {Array} normalizedProperties
@@ -18698,13 +19057,18 @@ var DOMManager = {
var buffer = view.renderToBuffer();
view.invokeRecursively(function(view) {
- view.propertyDidChange('element');
+ view.propertyWillChange('element');
});
-
view.triggerRecursively('willInsertElement');
+
morph.replaceWith(buffer.string());
view.transitionTo('inDOM');
+
+ view.invokeRecursively(function(view) {
+ view.propertyDidChange('element');
+ });
view.triggerRecursively('didInsertElement');
+
notifyMutationListeners();
});
},
@@ -18813,6 +19177,8 @@ SimpleHandlebarsView.prototype = {
this.morph = null;
},
+ propertyWillChange: Ember.K,
+
propertyDidChange: Ember.K,
normalizedValue: function() {
@@ -18863,7 +19229,7 @@ SimpleHandlebarsView.prototype = {
rerender: function() {
switch(this.state) {
case 'preRender':
- case 'destroyed':
+ case 'destroying':
break;
case 'inBuffer':
throw new Ember.Error("Something you did tried to replace an {{expression}} before it was inserted into the DOM.");
@@ -18894,7 +19260,7 @@ merge(states._default, {
merge(states.inDOM, {
rerenderIfNeeded: function(view) {
- if (get(view, 'normalizedValue') !== view._lastNormalizedValue) {
+ if (view.normalizedValue() !== view._lastNormalizedValue) {
view.rerender();
}
}
@@ -18998,7 +19364,7 @@ Ember._HandlebarsBoundView = Ember._MetamorphView.extend({
*/
pathRoot: null,
- normalizedValue: Ember.computed(function() {
+ normalizedValue: function() {
var path = get(this, 'path'),
pathRoot = get(this, 'pathRoot'),
valueNormalizer = get(this, 'valueNormalizerFunc'),
@@ -19016,7 +19382,7 @@ Ember._HandlebarsBoundView = Ember._MetamorphView.extend({
}
return valueNormalizer ? valueNormalizer(result) : result;
- }).property('path', 'pathRoot', 'valueNormalizerFunc').volatile(),
+ },
rerenderIfNeeded: function() {
this.currentState.rerenderIfNeeded(this);
@@ -19051,7 +19417,7 @@ Ember._HandlebarsBoundView = Ember._MetamorphView.extend({
var inverseTemplate = get(this, 'inverseTemplate'),
displayTemplate = get(this, 'displayTemplate');
- var result = get(this, 'normalizedValue');
+ var result = this.normalizedValue();
this._lastNormalizedValue = result;
// First, test the conditional to see if we should
@@ -19114,6 +19480,10 @@ var forEach = Ember.ArrayPolyfills.forEach;
var EmberHandlebars = Ember.Handlebars, helpers = EmberHandlebars.helpers;
+function exists(value){
+ return !Ember.isNone(value);
+}
+
// Binds a property into the DOM. This will create a hook in DOM that the
// KVO system will look for and update if the property changes.
function bind(property, options, preserveContext, shouldDisplay, valueNormalizer, childProperties) {
@@ -19292,9 +19662,7 @@ EmberHandlebars.registerHelper('bind', function(property, options) {
return simpleBind.call(context, property, options);
}
- return bind.call(context, property, options, false, function(result) {
- return !Ember.isNone(result);
- });
+ return bind.call(context, property, options, false, exists);
});
/**
@@ -19366,9 +19734,7 @@ EmberHandlebars.registerHelper('with', function(context, options) {
Ember.bind(options.data.keywords, keywordName, contextPath);
}
- return bind.call(this, path, options, true, function(result) {
- return !Ember.isNone(result);
- });
+ return bind.call(this, path, options, true, exists);
} else {
Ember.assert("You must pass exactly one argument to the with helper", arguments.length === 2);
Ember.assert("You must pass a block to the with helper", options.fn && options.fn !== Handlebars.VM.noop);
@@ -19520,7 +19886,7 @@ EmberHandlebars.registerHelper('unless', function(context, options) {
Results in the following rendered output:
```html
-
+
```
All three strategies - string return value, boolean return value, and
@@ -19859,11 +20225,8 @@ EmberHandlebars.ViewHelper = Ember.Object.create({
},
helper: function(thisContext, path, options) {
- var inverse = options.inverse,
- data = options.data,
- view = data.view,
+ var data = options.data,
fn = options.fn,
- hash = options.hash,
newView;
if ('string' === typeof path) {
@@ -19877,7 +20240,7 @@ EmberHandlebars.ViewHelper = Ember.Object.create({
var viewOptions = this.propertiesFromHTMLOptions(options, thisContext);
var currentView = data.view;
- viewOptions.templateData = options.data;
+ viewOptions.templateData = data;
var newViewProto = newView.proto ? newView.proto() : newView;
if (fn) {
@@ -20004,9 +20367,8 @@ EmberHandlebars.ViewHelper = Ember.Object.create({
{{/view}}
```
- The first argument can also be a relative path. Ember will search for the
- view class starting at the `Ember.View` of the template where `{{view}}` was
- used as the root object:
+ The first argument can also be a relative path accessible from the current
+ context.
```javascript
MyApp = Ember.Application.create({});
@@ -20014,7 +20376,7 @@ EmberHandlebars.ViewHelper = Ember.Object.create({
innerViewClass: Ember.View.extend({
classNames: ['a-custom-view-class-as-property']
}),
- template: Ember.Handlebars.compile('{{#view "innerViewClass"}} hi {{/view}}')
+ template: Ember.Handlebars.compile('{{#view "view.innerViewClass"}} hi {{/view}}')
});
MyApp.OuterView.create().appendTo('body');
@@ -20259,8 +20621,6 @@ Ember.Handlebars.registerHelper('collection', function(path, options) {
}
}
- var tagName = hash.tagName || collectionPrototype.tagName;
-
if (fn) {
itemHash.template = fn;
delete options.fn;
@@ -20282,8 +20642,6 @@ Ember.Handlebars.registerHelper('collection', function(path, options) {
itemHash._context = Ember.computed.alias('content');
}
- var viewString = view.toString();
-
var viewOptions = Ember.Handlebars.ViewHelper.propertiesFromHTMLOptions({ data: data, hash: itemHash }, this);
hash.itemViewClass = itemViewClass.extend(viewOptions);
@@ -20473,14 +20831,16 @@ Ember.Handlebars.EachView = Ember.CollectionView.extend(Ember._Metamorph, {
return view;
},
- willDestroy: function() {
+ destroy: function() {
+ if (!this._super()) { return; }
+
var arrayController = get(this, '_arrayController');
if (arrayController) {
arrayController.destroy();
}
- return this._super();
+ return this;
}
});
@@ -20797,6 +21157,7 @@ Ember.Handlebars.registerHelper('template', function(name, options) {
{{partial user_info}}
{{/with}}
+ ```
The `data-template-name` attribute of a partial template
is prefixed with an underscore.
@@ -21085,7 +21446,7 @@ var get = Ember.get, set = Ember.set;
By default `Ember.TextField` provides support for `type`, `value`, `size`,
`pattern`, `placeholder`, `disabled`, `maxlength` and `tabindex` attributes
- on a test field. If you need to support more attributes have a look at the
+ on a text field. If you need to support more attributes have a look at the
`attributeBindings` property in `Ember.View`'s HTML Attributes section.
To globally add support for additional attributes you can reopen
@@ -21159,6 +21520,20 @@ Ember.TextField = Ember.View.extend(Ember.TextSupport,
*/
action: null,
+ /**
+ The event that should send the action.
+
+ Options are:
+
+ * `enter`: the user pressed enter
+ * `keypress`: the user pressed a key
+
+ @property on
+ @type String
+ @default enter
+ */
+ onEvent: 'enter',
+
/**
Whether they `keyUp` event that triggers an `action` to be sent continues
propagating to other views.
@@ -21177,19 +21552,30 @@ Ember.TextField = Ember.View.extend(Ember.TextSupport,
bubbles: false,
insertNewline: function(event) {
- var controller = get(this, 'controller'),
- action = get(this, 'action');
+ sendAction('enter', this, event);
+ },
- if (action) {
- controller.send(action, get(this, 'value'), this);
-
- if (!get(this, 'bubbles')) {
- event.stopPropagation();
- }
- }
+ keyPress: function(event) {
+ sendAction('keyPress', this, event);
}
});
+function sendAction(eventName, view, event) {
+ var action = get(view, 'action'),
+ on = get(view, 'onEvent');
+
+ if (action && on === eventName) {
+ var controller = get(view, 'controller'),
+ value = get(view, 'value'),
+ bubbles = get(view, 'bubbles');
+
+ controller.send(action, value, view);
+
+ if (!bubbles) {
+ event.stopPropagation();
+ }
+ }
+}
})();
@@ -21407,6 +21793,55 @@ var set = Ember.set,
isArray = Ember.isArray,
precompileTemplate = Ember.Handlebars.compile;
+Ember.SelectOption = Ember.View.extend({
+ tagName: 'option',
+ attributeBindings: ['value', 'selected'],
+
+ defaultTemplate: function(context, options) {
+ options = { data: options.data, hash: {} };
+ Ember.Handlebars.helpers.bind.call(context, "view.label", options);
+ },
+
+ init: function() {
+ this.labelPathDidChange();
+ this.valuePathDidChange();
+
+ this._super();
+ },
+
+ selected: Ember.computed(function() {
+ var content = get(this, 'content'),
+ selection = get(this, 'parentView.selection');
+ if (get(this, 'parentView.multiple')) {
+ return selection && indexOf(selection, content.valueOf()) > -1;
+ } else {
+ // Primitives get passed through bindings as objects... since
+ // `new Number(4) !== 4`, we use `==` below
+ return content == selection;
+ }
+ }).property('content', 'parentView.selection'),
+
+ labelPathDidChange: Ember.observer(function() {
+ var labelPath = get(this, 'parentView.optionLabelPath');
+
+ if (!labelPath) { return; }
+
+ Ember.defineProperty(this, 'label', Ember.computed(function() {
+ return get(this, labelPath);
+ }).property(labelPath));
+ }, 'parentView.optionLabelPath'),
+
+ valuePathDidChange: Ember.observer(function() {
+ var valuePath = get(this, 'parentView.optionValuePath');
+
+ if (!valuePath) { return; }
+
+ Ember.defineProperty(this, 'value', Ember.computed(function() {
+ return get(this, valuePath);
+ }).property(valuePath));
+ }, 'parentView.optionValuePath')
+});
+
/**
The `Ember.Select` view class renders a
[select](https://developer.mozilla.org/en/HTML/Element/select) HTML element,
@@ -21583,7 +22018,7 @@ var set = Ember.set,
Interacting with the rendered element by selecting the first option
('Yehuda') will update the `selectedPerson` value of `App.controller`
to match the content object of the newly selected `