dist/ember.js in ember-source-1.0.0.rc2.0 vs dist/ember.js in ember-source-1.0.0.rc2.1
- old
+ new
@@ -1,7 +1,7 @@
-// Version: v1.0.0-rc.1-240-gd3a6cc4
-// Last commit: d3a6cc4 (2013-03-25 04:06:07 -0400)
+// Version: v1.0.0-rc.1-354-g4e75d2b
+// Last commit: 4e75d2b (2013-04-09 16:28:08 -0700)
(function() {
/*global __fail__*/
@@ -149,12 +149,12 @@
};
};
})();
-// Version: v1.0.0-rc.1-275-g9104bf1
-// Last commit: 9104bf1 (2013-03-29 13:59:19 -0700)
+// Version: v1.0.0-rc.1-354-g4e75d2b
+// Last commit: 4e75d2b (2013-04-09 16:28:08 -0700)
(function() {
var define, requireModule;
@@ -633,15 +633,168 @@
})();
(function() {
+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);
+ },
+
+ forEach: function(obj, callback, thisArg) {
+ return obj.forEach ? obj.forEach.call(obj, callback, thisArg) : Array.prototype.forEach.call(obj, callback, thisArg);
+ },
+
+ indexOf: function(obj, element, index) {
+ return obj.indexOf ? obj.indexOf.call(obj, element, index) : Array.prototype.indexOf.call(obj, element, index);
+ },
+
+ indexesOf: function(obj, elements) {
+ return elements === undefined ? [] : utils.map(elements, function(item) {
+ return utils.indexOf(obj, item);
+ });
+ },
+
+ addObject: function(array, item) {
+ var index = utils.indexOf(array, item);
+ if (index === -1) { array.push(item); }
+ },
+
+ removeObject: function(array, item) {
+ var index = utils.indexOf(array, item);
+ if (index !== -1) { array.splice(index, 1); }
+ },
+
+ replace: function(array, idx, amt, objects) {
+ if (array.replace) {
+ return array.replace(idx, amt, objects);
+ } else {
+ var args = Array.prototype.concat.apply([idx, amt], objects);
+ return array.splice.apply(array, args);
+ }
+ },
+
+ intersection: function(array1, array2) {
+ var intersection = [];
+
+ array1.forEach(function(element) {
+ if (array2.indexOf(element) >= 0) {
+ intersection.push(element);
+ }
+ });
+
+ return intersection;
+ }
+};
+
+})();
+
+
+
+(function() {
+/*jshint newcap:false*/
/**
@module ember-metal
*/
+// NOTE: There is a bug in jshint that doesn't recognize `Object()` without `new`
+// as being ok unless both `newcap:false` and not `use strict`.
+// https://github.com/jshint/jshint/issues/392
+// Testing this is not ideal, but we want to use native functions
+// if available, but not to use versions created by libraries like Prototype
+var isNativeFunc = function(func) {
+ // This should probably work in all browsers likely to have ES5 array methods
+ return func && Function.prototype.toString.call(func).indexOf('[native code]') > -1;
+};
+
+// From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/map
+var arrayMap = isNativeFunc(Array.prototype.map) ? Array.prototype.map : function(fun /*, thisp */) {
+ //"use strict";
+
+ if (this === void 0 || this === null) {
+ throw new TypeError();
+ }
+
+ var t = Object(this);
+ var len = t.length >>> 0;
+ if (typeof fun !== "function") {
+ throw new TypeError();
+ }
+
+ var res = new Array(len);
+ var thisp = arguments[1];
+ for (var i = 0; i < len; i++) {
+ if (i in t) {
+ res[i] = fun.call(thisp, t[i], i, t);
+ }
+ }
+
+ return res;
+};
+
+// From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/foreach
+var arrayForEach = isNativeFunc(Array.prototype.forEach) ? Array.prototype.forEach : function(fun /*, thisp */) {
+ //"use strict";
+
+ if (this === void 0 || this === null) {
+ throw new TypeError();
+ }
+
+ var t = Object(this);
+ var len = t.length >>> 0;
+ if (typeof fun !== "function") {
+ throw new TypeError();
+ }
+
+ var thisp = arguments[1];
+ for (var i = 0; i < len; i++) {
+ if (i in t) {
+ fun.call(thisp, t[i], i, t);
+ }
+ }
+};
+
+var arrayIndexOf = isNativeFunc(Array.prototype.indexOf) ? Array.prototype.indexOf : function (obj, fromIndex) {
+ if (fromIndex === null || fromIndex === undefined) { fromIndex = 0; }
+ else if (fromIndex < 0) { fromIndex = Math.max(0, this.length + fromIndex); }
+ for (var i = fromIndex, j = this.length; i < j; i++) {
+ if (this[i] === obj) { return i; }
+ }
+ return -1;
+};
+
+Ember.ArrayPolyfills = {
+ map: arrayMap,
+ forEach: arrayForEach,
+ indexOf: arrayIndexOf
+};
+
+if (Ember.SHIM_ES5) {
+ if (!Array.prototype.map) {
+ Array.prototype.map = arrayMap;
+ }
+
+ if (!Array.prototype.forEach) {
+ Array.prototype.forEach = arrayForEach;
+ }
+
+ if (!Array.prototype.indexOf) {
+ Array.prototype.indexOf = arrayIndexOf;
+ }
+}
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+
var o_defineProperty = Ember.platform.defineProperty,
o_create = Ember.create,
// Used for guid generation...
GUID_KEY = '__ember'+ (+ new Date()),
uuid = 0,
@@ -1034,11 +1187,11 @@
@method tryInvoke
@for Ember
@param {Object} obj The object to check for the method
@param {String} methodName The method name to check for
@param {Array} [args] The arguments to pass to the method
- @return {anything} the return value of the invoked method or undefined if it cannot be invoked
+ @return {*} the return value of the invoked method or undefined if it cannot be invoked
*/
Ember.tryInvoke = function(obj, methodName, args) {
if (canInvoke(obj, methodName)) {
return obj[methodName].apply(obj, args || []);
}
@@ -1065,11 +1218,11 @@
@method tryFinally
@for Ember
@param {Function} tryable The function to run the try callback
@param {Function} finalizer The function to run the finally callback
@param [binding]
- @return {anything} The return value is the that of the finalizer,
+ @return {*} The return value is the that of the finalizer,
unless that valueis undefined, in which case it is the return value
of the tryable
*/
if (needsFinallyFix) {
@@ -1116,11 +1269,11 @@
@for Ember
@param {Function} tryable The function to run the try callback
@param {Function} catchable The function to run the catchable callback
@param {Function} finalizer The function to run the finally callback
@param [binding]
- @return {anything} The return value is the that of the finalizer,
+ @return {*} The return value is the that of the finalizer,
unless that value is undefined, in which case it is the return value
of the tryable.
*/
if (needsFinallyFix) {
Ember.tryCatchFinally = function(tryable, catchable, finalizer, binding) {
@@ -1160,10 +1313,82 @@
return (finalResult === undefined) ? result : finalResult;
};
}
+// ........................................
+// TYPING & ARRAY MESSAGING
+//
+
+var TYPE_MAP = {};
+var t = "Boolean Number String Function Array Date RegExp Object".split(" ");
+Ember.ArrayPolyfills.forEach.call(t, function(name) {
+ TYPE_MAP[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+var toString = Object.prototype.toString;
+
+/**
+ Returns a consistent type for the passed item.
+
+ Use this instead of the built-in `typeof` to get the type of an item.
+ It will return the same result across all browsers and includes a bit
+ more detail. Here is what will be returned:
+
+ | Return Value | Meaning |
+ |---------------|------------------------------------------------------|
+ | 'string' | String primitive |
+ | 'number' | Number primitive |
+ | 'boolean' | Boolean primitive |
+ | 'null' | Null value |
+ | 'undefined' | Undefined value |
+ | 'function' | A function |
+ | 'array' | An instance of Array |
+ | 'class' | An Ember class (created using Ember.Object.extend()) |
+ | 'instance' | An Ember object instance |
+ | 'error' | An instance of the Error object |
+ | 'object' | A JavaScript object not inheriting from Ember.Object |
+
+ Examples:
+
+ ```javascript
+ Ember.typeOf(); // 'undefined'
+ Ember.typeOf(null); // 'null'
+ Ember.typeOf(undefined); // 'undefined'
+ Ember.typeOf('michael'); // 'string'
+ Ember.typeOf(101); // 'number'
+ Ember.typeOf(true); // 'boolean'
+ Ember.typeOf(Ember.makeArray); // 'function'
+ Ember.typeOf([1,2,90]); // 'array'
+ Ember.typeOf(Ember.Object.extend()); // 'class'
+ Ember.typeOf(Ember.Object.create()); // 'instance'
+ Ember.typeOf(new Error('teamocil')); // 'error'
+
+ // "normal" JavaScript object
+ Ember.typeOf({a: 'b'}); // 'object'
+ ```
+
+ @method typeOf
+ @for Ember
+ @param {Object} item the item to check
+ @return {String} the type
+*/
+Ember.typeOf = function(item) {
+ var ret;
+
+ ret = (item === null || item === undefined) ? String(item) : TYPE_MAP[toString.call(item)] || 'object';
+
+ if (ret === 'function') {
+ if (Ember.Object && Ember.Object.detect(item)) ret = 'class';
+ } else if (ret === 'object') {
+ if (item instanceof Error) ret = 'error';
+ else if (Ember.Object && item instanceof Ember.Object) ret = 'instance';
+ else ret = 'object';
+ }
+
+ return ret;
+};
})();
(function() {
@@ -1340,167 +1565,14 @@
})();
(function() {
-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);
- },
-
- forEach: function(obj, callback, thisArg) {
- return obj.forEach ? obj.forEach.call(obj, callback, thisArg) : Array.prototype.forEach.call(obj, callback, thisArg);
- },
-
- indexOf: function(obj, element, index) {
- return obj.indexOf ? obj.indexOf.call(obj, element, index) : Array.prototype.indexOf.call(obj, element, index);
- },
-
- indexesOf: function(obj, elements) {
- return elements === undefined ? [] : utils.map(elements, function(item) {
- return utils.indexOf(obj, item);
- });
- },
-
- addObject: function(array, item) {
- var index = utils.indexOf(array, item);
- if (index === -1) { array.push(item); }
- },
-
- removeObject: function(array, item) {
- var index = utils.indexOf(array, item);
- if (index !== -1) { array.splice(index, 1); }
- },
-
- replace: function(array, idx, amt, objects) {
- if (array.replace) {
- return array.replace(idx, amt, objects);
- } else {
- var args = Array.prototype.concat.apply([idx, amt], objects);
- return array.splice.apply(array, args);
- }
- },
-
- intersection: function(array1, array2) {
- var intersection = [];
-
- array1.forEach(function(element) {
- if (array2.indexOf(element) >= 0) {
- intersection.push(element);
- }
- });
-
- return intersection;
- }
-};
-
-})();
-
-
-
-(function() {
-/*jshint newcap:false*/
/**
@module ember-metal
*/
-// NOTE: There is a bug in jshint that doesn't recognize `Object()` without `new`
-// as being ok unless both `newcap:false` and not `use strict`.
-// https://github.com/jshint/jshint/issues/392
-
-// Testing this is not ideal, but we want to use native functions
-// if available, but not to use versions created by libraries like Prototype
-var isNativeFunc = function(func) {
- // This should probably work in all browsers likely to have ES5 array methods
- return func && Function.prototype.toString.call(func).indexOf('[native code]') > -1;
-};
-
-// From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/map
-var arrayMap = isNativeFunc(Array.prototype.map) ? Array.prototype.map : function(fun /*, thisp */) {
- //"use strict";
-
- if (this === void 0 || this === null) {
- throw new TypeError();
- }
-
- var t = Object(this);
- var len = t.length >>> 0;
- if (typeof fun !== "function") {
- throw new TypeError();
- }
-
- var res = new Array(len);
- var thisp = arguments[1];
- for (var i = 0; i < len; i++) {
- if (i in t) {
- res[i] = fun.call(thisp, t[i], i, t);
- }
- }
-
- return res;
-};
-
-// From: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/array/foreach
-var arrayForEach = isNativeFunc(Array.prototype.forEach) ? Array.prototype.forEach : function(fun /*, thisp */) {
- //"use strict";
-
- if (this === void 0 || this === null) {
- throw new TypeError();
- }
-
- var t = Object(this);
- var len = t.length >>> 0;
- if (typeof fun !== "function") {
- throw new TypeError();
- }
-
- var thisp = arguments[1];
- for (var i = 0; i < len; i++) {
- if (i in t) {
- fun.call(thisp, t[i], i, t);
- }
- }
-};
-
-var arrayIndexOf = isNativeFunc(Array.prototype.indexOf) ? Array.prototype.indexOf : function (obj, fromIndex) {
- if (fromIndex === null || fromIndex === undefined) { fromIndex = 0; }
- else if (fromIndex < 0) { fromIndex = Math.max(0, this.length + fromIndex); }
- for (var i = fromIndex, j = this.length; i < j; i++) {
- if (this[i] === obj) { return i; }
- }
- return -1;
-};
-
-Ember.ArrayPolyfills = {
- map: arrayMap,
- forEach: arrayForEach,
- indexOf: arrayIndexOf
-};
-
-if (Ember.SHIM_ES5) {
- if (!Array.prototype.map) {
- Array.prototype.map = arrayMap;
- }
-
- if (!Array.prototype.forEach) {
- Array.prototype.forEach = arrayForEach;
- }
-
- if (!Array.prototype.indexOf) {
- Array.prototype.indexOf = arrayIndexOf;
- }
-}
-
-})();
-
-
-
-(function() {
-/**
-@module ember-metal
-*/
-
/*
JavaScript (before ES6) does not have a Map implementation. Objects,
which are often used as dictionaries, may only have Strings as keys.
Because Ember has a way to get a unique identifier for every object
@@ -1698,12 +1770,12 @@
Map.prototype = {
/**
Retrieve the value associated with a given key.
@method get
- @param {anything} key
- @return {anything} the value associated with the key, or `undefined`
+ @param {*} key
+ @return {*} the value associated with the key, or `undefined`
*/
get: function(key) {
var values = this.values,
guid = guidFor(key);
@@ -1713,12 +1785,12 @@
/**
Adds a value to the map. If a value for the given key has already been
provided, the new value will replace the old value.
@method set
- @param {anything} key
- @param {anything} value
+ @param {*} key
+ @param {*} value
*/
set: function(key, value) {
var keys = this.keys,
values = this.values,
guid = guidFor(key);
@@ -1729,11 +1801,11 @@
/**
Removes a value from the map for an associated key.
@method remove
- @param {anything} key
+ @param {*} key
@return {Boolean} true if an item was removed, false otherwise
*/
remove: function(key) {
// don't use ES6 "delete" because it will be annoying
// to use in browsers that are not ES6 friendly;
@@ -1754,11 +1826,11 @@
/**
Check whether a key is present.
@method has
- @param {anything} key
+ @param {*} key
@return {Boolean} true if the item was present, false otherwise
*/
has: function(key) {
var values = this.values,
guid = guidFor(key);
@@ -1772,11 +1844,11 @@
The keys are guaranteed to be iterated over in insertion order.
@method forEach
@param {Function} callback
- @param {anything} self if passed, the `this` value inside the
+ @param {*} self if passed, the `this` value inside the
callback. By default, `this` is the map.
*/
forEach: function(callback, self) {
var keys = this.keys,
values = this.values;
@@ -1801,22 +1873,22 @@
@namespace Ember
@extends Ember.Map
@private
@constructor
@param [options]
- @param {anything} [options.defaultValue]
+ @param {*} [options.defaultValue]
*/
var MapWithDefault = Ember.MapWithDefault = function(options) {
Map.call(this);
this.defaultValue = options.defaultValue;
};
/**
@method create
@static
@param [options]
- @param {anything} [options.defaultValue]
+ @param {*} [options.defaultValue]
@return {Ember.MapWithDefault|Ember.Map} If options are passed, returns
`Ember.MapWithDefault` otherwise returns `Ember.Map`
*/
MapWithDefault.create = function(options) {
if (options) {
@@ -1830,12 +1902,12 @@
/**
Retrieve the value associated with a given key.
@method get
- @param {anything} key
- @return {anything} the value associated with the key, or the default value
+ @param {*} key
+ @return {*} the value associated with the key, or the default value
*/
MapWithDefault.prototype.get = function(key) {
var hasValue = this.has(key);
if (hasValue) {
@@ -1864,15 +1936,14 @@
(function() {
/**
@module ember-metal
*/
-var META_KEY = Ember.META_KEY, get, set;
+var META_KEY = Ember.META_KEY, get;
var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
-var IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/;
var IS_GLOBAL_PATH = /^([A-Z$]|([0-9][A-Z$])).*[\.\*]/;
var HAS_THIS = /^this[\.\*]/;
var FIRST_KEY = /^([^\.\*]+)/;
// ..........................................................
@@ -1941,11 +2012,776 @@
return ret;
}
};
+// Currently used only by Ember Data tests
+if (Ember.config.overrideAccessors) {
+ Ember.get = get;
+ Ember.config.overrideAccessors();
+ get = Ember.get;
+}
+
+function firstKey(path) {
+ return path.match(FIRST_KEY)[0];
+}
+
+// assumes path is already normalized
+function normalizeTuple(target, path) {
+ var hasThis = HAS_THIS.test(path),
+ isGlobal = !hasThis && IS_GLOBAL_PATH.test(path),
+ key;
+
+ if (!target || isGlobal) target = Ember.lookup;
+ if (hasThis) path = path.slice(5);
+
+ if (target === Ember.lookup) {
+ key = firstKey(path);
+ target = get(target, key);
+ path = path.slice(key.length+1);
+ }
+
+ // must return some kind of path to be valid else other things will break.
+ if (!path || path.length===0) throw new Error('Invalid Path');
+
+ return [ target, path ];
+}
+
+var getPath = Ember._getPath = function(root, path) {
+ var hasThis, parts, tuple, idx, len;
+
+ // If there is no root and path is a key name, return that
+ // property from the global object.
+ // E.g. get('Ember') -> Ember
+ if (root === null && path.indexOf('.') === -1) { return get(Ember.lookup, path); }
+
+ // detect complicated paths and normalize them
+ hasThis = HAS_THIS.test(path);
+
+ if (!root || hasThis) {
+ tuple = normalizeTuple(root, path);
+ root = tuple[0];
+ path = tuple[1];
+ tuple.length = 0;
+ }
+
+ parts = path.split(".");
+ len = parts.length;
+ for (idx=0; root && idx<len; idx++) {
+ root = get(root, parts[idx], true);
+ if (root && root.isDestroyed) { return undefined; }
+ }
+ return root;
+};
+
/**
+ @private
+
+ Normalizes a target/path pair to reflect that actual target/path that should
+ be observed, etc. This takes into account passing in global property
+ paths (i.e. a path beginning with a captial letter not defined on the
+ target) and * separators.
+
+ @method normalizeTuple
+ @for Ember
+ @param {Object} target The current target. May be `null`.
+ @param {String} path A path on the target or a global property path.
+ @return {Array} a temporary array with the normalized target/path pair.
+*/
+Ember.normalizeTuple = function(target, path) {
+ return normalizeTuple(target, path);
+};
+
+Ember.getWithDefault = function(root, key, defaultValue) {
+ var value = get(root, key);
+
+ if (value === undefined) { return defaultValue; }
+ return value;
+};
+
+
+Ember.get = get;
+Ember.getPath = Ember.deprecateFunc('getPath is deprecated since get now supports paths', Ember.get);
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
+var o_create = Ember.create,
+ metaFor = Ember.meta,
+ META_KEY = Ember.META_KEY;
+
+/*
+ The event system uses a series of nested hashes to store listeners on an
+ object. When a listener is registered, or when an event arrives, these
+ hashes are consulted to determine which target and action pair to invoke.
+
+ The hashes are stored in the object's meta hash, and look like this:
+
+ // Object's meta hash
+ {
+ listeners: { // variable name: `listenerSet`
+ "foo:changed": [ // variable name: `actions`
+ [target, method, onceFlag, suspendedFlag]
+ ]
+ }
+ }
+
+*/
+
+function indexOf(array, target, method) {
+ var index = -1;
+ for (var i = 0, l = array.length; i < l; i++) {
+ if (target === array[i][0] && method === array[i][1]) { index = i; break; }
+ }
+ return index;
+}
+
+function actionsFor(obj, eventName) {
+ var meta = metaFor(obj, true),
+ actions;
+
+ if (!meta.listeners) { meta.listeners = {}; }
+
+ if (!meta.hasOwnProperty('listeners')) {
+ // setup inherited copy of the listeners object
+ meta.listeners = o_create(meta.listeners);
+ }
+
+ actions = meta.listeners[eventName];
+
+ // if there are actions, but the eventName doesn't exist in our listeners, then copy them from the prototype
+ if (actions && !meta.listeners.hasOwnProperty(eventName)) {
+ actions = meta.listeners[eventName] = meta.listeners[eventName].slice();
+ } else if (!actions) {
+ actions = meta.listeners[eventName] = [];
+ }
+
+ return actions;
+}
+
+function actionsUnion(obj, eventName, otherActions) {
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName];
+
+ if (!actions) { return; }
+ for (var i = actions.length - 1; i >= 0; i--) {
+ var target = actions[i][0],
+ method = actions[i][1],
+ once = actions[i][2],
+ suspended = actions[i][3],
+ actionIndex = indexOf(otherActions, target, method);
+
+ if (actionIndex === -1) {
+ otherActions.push([target, method, once, suspended]);
+ }
+ }
+}
+
+function actionsDiff(obj, eventName, otherActions) {
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName],
+ diffActions = [];
+
+ if (!actions) { return; }
+ for (var i = actions.length - 1; i >= 0; i--) {
+ var target = actions[i][0],
+ method = actions[i][1],
+ once = actions[i][2],
+ suspended = actions[i][3],
+ actionIndex = indexOf(otherActions, target, method);
+
+ if (actionIndex !== -1) { continue; }
+
+ otherActions.push([target, method, once, suspended]);
+ diffActions.push([target, method, once, suspended]);
+ }
+
+ return diffActions;
+}
+
+/**
+ Add an event listener
+
+ @method addListener
+ @for Ember
+ @param obj
+ @param {String} eventName
+ @param {Object|Function} targetOrMethod A target object or a function
+ @param {Function|String} method A function or the name of a function to be called on `target`
+ @param {Boolean} once A flag whether a function should only be called once
+*/
+function addListener(obj, eventName, target, method, once) {
+ Ember.assert("You must pass at least an object and event name to Ember.addListener", !!obj && !!eventName);
+
+ if (!method && 'function' === typeof target) {
+ method = target;
+ target = null;
+ }
+
+ var actions = actionsFor(obj, eventName),
+ actionIndex = indexOf(actions, target, method);
+
+ if (actionIndex !== -1) { return; }
+
+ actions.push([target, method, once, undefined]);
+
+ if ('function' === typeof obj.didAddListener) {
+ obj.didAddListener(eventName, target, method);
+ }
+}
+
+/**
+ Remove an event listener
+
+ Arguments should match those passed to {{#crossLink "Ember/addListener"}}{{/crossLink}}
+
+ @method removeListener
+ @for Ember
+ @param obj
+ @param {String} eventName
+ @param {Object|Function} targetOrMethod A target object or a function
+ @param {Function|String} method A function or the name of a function to be called on `target`
+*/
+function removeListener(obj, eventName, target, method) {
+ Ember.assert("You must pass at least an object and event name to Ember.removeListener", !!obj && !!eventName);
+
+ if (!method && 'function' === typeof target) {
+ method = target;
+ target = null;
+ }
+
+ function _removeListener(target, method, once) {
+ var actions = actionsFor(obj, eventName),
+ actionIndex = indexOf(actions, target, method);
+
+ // action doesn't exist, give up silently
+ if (actionIndex === -1) { return; }
+
+ actions.splice(actionIndex, 1);
+
+ if ('function' === typeof obj.didRemoveListener) {
+ obj.didRemoveListener(eventName, target, method);
+ }
+ }
+
+ if (method) {
+ _removeListener(target, method);
+ } else {
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName];
+
+ if (!actions) { return; }
+ for (var i = actions.length - 1; i >= 0; i--) {
+ _removeListener(actions[i][0], actions[i][1]);
+ }
+ }
+}
+
+/**
+ @private
+
+ Suspend listener during callback.
+
+ This should only be used by the target of the event listener
+ when it is taking an action that would cause the event, e.g.
+ an object might suspend its property change listener while it is
+ setting that property.
+
+ @method suspendListener
+ @for Ember
+ @param obj
+ @param {String} eventName
+ @param {Object|Function} targetOrMethod A target object or a function
+ @param {Function|String} method A function or the name of a function to be called on `target`
+ @param {Function} callback
+*/
+function suspendListener(obj, eventName, target, method, callback) {
+ if (!method && 'function' === typeof target) {
+ method = target;
+ target = null;
+ }
+
+ var actions = actionsFor(obj, eventName),
+ actionIndex = indexOf(actions, target, method),
+ action;
+
+ if (actionIndex !== -1) {
+ action = actions[actionIndex].slice(); // copy it, otherwise we're modifying a shared object
+ action[3] = true; // mark the action as suspended
+ actions[actionIndex] = action; // replace the shared object with our copy
+ }
+
+ function tryable() { return callback.call(target); }
+ function finalizer() { if (action) { action[3] = undefined; } }
+
+ return Ember.tryFinally(tryable, finalizer);
+}
+
+/**
+ @private
+
+ Suspend listener during callback.
+
+ This should only be used by the target of the event listener
+ when it is taking an action that would cause the event, e.g.
+ an object might suspend its property change listener while it is
+ setting that property.
+
+ @method suspendListener
+ @for Ember
+ @param obj
+ @param {Array} eventName Array of event names
+ @param {Object|Function} targetOrMethod A target object or a function
+ @param {Function|String} method A function or the name of a function to be called on `target`
+ @param {Function} callback
+*/
+function suspendListeners(obj, eventNames, target, method, callback) {
+ if (!method && 'function' === typeof target) {
+ method = target;
+ target = null;
+ }
+
+ var suspendedActions = [],
+ eventName, actions, action, i, l;
+
+ for (i=0, l=eventNames.length; i<l; i++) {
+ eventName = eventNames[i];
+ actions = actionsFor(obj, eventName);
+ var actionIndex = indexOf(actions, target, method);
+
+ if (actionIndex !== -1) {
+ action = actions[actionIndex].slice();
+ action[3] = true;
+ actions[actionIndex] = action;
+ suspendedActions.push(action);
+ }
+ }
+
+ function tryable() { return callback.call(target); }
+
+ function finalizer() {
+ for (i = 0, l = suspendedActions.length; i < l; i++) {
+ suspendedActions[i][3] = undefined;
+ }
+ }
+
+ return Ember.tryFinally(tryable, finalizer);
+}
+
+/**
+ @private
+
+ Return a list of currently watched events
+
+ @method watchedEvents
+ @for Ember
+ @param obj
+*/
+function watchedEvents(obj) {
+ var listeners = obj[META_KEY].listeners, ret = [];
+
+ if (listeners) {
+ for(var eventName in listeners) {
+ if (listeners[eventName]) { ret.push(eventName); }
+ }
+ }
+ return ret;
+}
+
+/**
+ @method sendEvent
+ @for Ember
+ @param obj
+ @param {String} eventName
+ @param {Array} params
+ @param {Array} actions
+ @return true
+*/
+function sendEvent(obj, eventName, params, actions) {
+ // first give object a chance to handle it
+ if (obj !== Ember && 'function' === typeof obj.sendEvent) {
+ obj.sendEvent(eventName, params);
+ }
+
+ if (!actions) {
+ var meta = obj[META_KEY];
+ actions = meta && meta.listeners && meta.listeners[eventName];
+ }
+
+ if (!actions) { return; }
+
+ for (var i = actions.length - 1; i >= 0; i--) { // looping in reverse for once listeners
+ if (!actions[i] || actions[i][3] === true) { continue; }
+
+ var target = actions[i][0],
+ method = actions[i][1],
+ once = actions[i][2];
+
+ if (once) { removeListener(obj, eventName, target, method); }
+ if (!target) { target = obj; }
+ if ('string' === typeof method) { method = target[method]; }
+ if (params) {
+ method.apply(target, params);
+ } else {
+ method.call(target);
+ }
+ }
+ return true;
+}
+
+/**
+ @private
+ @method hasListeners
+ @for Ember
+ @param obj
+ @param {String} eventName
+*/
+function hasListeners(obj, eventName) {
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName];
+
+ return !!(actions && actions.length);
+}
+
+/**
+ @private
+ @method listenersFor
+ @for Ember
+ @param obj
+ @param {String} eventName
+*/
+function listenersFor(obj, eventName) {
+ var ret = [];
+ var meta = obj[META_KEY],
+ actions = meta && meta.listeners && meta.listeners[eventName];
+
+ if (!actions) { return ret; }
+
+ for (var i = 0, l = actions.length; i < l; i++) {
+ var target = actions[i][0],
+ method = actions[i][1];
+ ret.push([target, method]);
+ }
+
+ return ret;
+}
+
+Ember.addListener = addListener;
+Ember.removeListener = removeListener;
+Ember._suspendListener = suspendListener;
+Ember._suspendListeners = suspendListeners;
+Ember.sendEvent = sendEvent;
+Ember.hasListeners = hasListeners;
+Ember.watchedEvents = watchedEvents;
+Ember.listenersFor = listenersFor;
+Ember.listenersDiff = actionsDiff;
+Ember.listenersUnion = actionsUnion;
+
+})();
+
+
+
+(function() {
+var guidFor = Ember.guidFor,
+ sendEvent = Ember.sendEvent;
+
+/*
+ this.observerSet = {
+ [senderGuid]: { // variable name: `keySet`
+ [keyName]: listIndex
+ }
+ },
+ this.observers = [
+ {
+ sender: obj,
+ keyName: keyName,
+ eventName: eventName,
+ listeners: [
+ [target, method, onceFlag, suspendedFlag]
+ ]
+ },
+ ...
+ ]
+*/
+var ObserverSet = Ember._ObserverSet = function() {
+ this.clear();
+};
+
+ObserverSet.prototype.add = function(sender, keyName, eventName) {
+ var observerSet = this.observerSet,
+ observers = this.observers,
+ senderGuid = guidFor(sender),
+ keySet = observerSet[senderGuid],
+ index;
+
+ if (!keySet) {
+ observerSet[senderGuid] = keySet = {};
+ }
+ index = keySet[keyName];
+ if (index === undefined) {
+ index = observers.push({
+ sender: sender,
+ keyName: keyName,
+ eventName: eventName,
+ listeners: []
+ }) - 1;
+ keySet[keyName] = index;
+ }
+ return observers[index].listeners;
+};
+
+ObserverSet.prototype.flush = function() {
+ var observers = this.observers, i, len, observer, sender;
+ this.clear();
+ for (i=0, len=observers.length; i < len; ++i) {
+ observer = observers[i];
+ sender = observer.sender;
+ if (sender.isDestroying || sender.isDestroyed) { continue; }
+ sendEvent(sender, observer.eventName, [sender, observer.keyName], observer.listeners);
+ }
+};
+
+ObserverSet.prototype.clear = function() {
+ this.observerSet = {};
+ this.observers = [];
+};
+})();
+
+
+
+(function() {
+var metaFor = Ember.meta,
+ guidFor = Ember.guidFor,
+ tryFinally = Ember.tryFinally,
+ sendEvent = Ember.sendEvent,
+ listenersUnion = Ember.listenersUnion,
+ listenersDiff = Ember.listenersDiff,
+ ObserverSet = Ember._ObserverSet,
+ beforeObserverSet = new ObserverSet(),
+ observerSet = new ObserverSet(),
+ deferred = 0;
+
+// ..........................................................
+// PROPERTY CHANGES
+//
+
+/**
+ This function is called just before an object property is about to change.
+ It will notify any before observers and prepare caches among other things.
+
+ Normally you will not need to call this method directly but if for some
+ reason you can't directly watch a property you can invoke this method
+ manually along with `Ember.propertyDidChange()` which you should call just
+ after the property value changes.
+
+ @method propertyWillChange
+ @for Ember
+ @param {Object} obj The object with the property that will change
+ @param {String} keyName The property key (or path) that will change.
+ @return {void}
+*/
+var propertyWillChange = Ember.propertyWillChange = function(obj, keyName) {
+ var m = metaFor(obj, false),
+ watching = m.watching[keyName] > 0 || keyName === 'length',
+ proto = m.proto,
+ desc = m.descs[keyName];
+
+ if (!watching) { return; }
+ if (proto === obj) { return; }
+ if (desc && desc.willChange) { desc.willChange(obj, keyName); }
+ dependentKeysWillChange(obj, keyName, m);
+ chainsWillChange(obj, keyName, m);
+ notifyBeforeObservers(obj, keyName);
+};
+
+/**
+ This function is called just after an object property has changed.
+ It will notify any observers and clear caches among other things.
+
+ Normally you will not need to call this method directly but if for some
+ reason you can't directly watch a property you can invoke this method
+ manually along with `Ember.propertyWilLChange()` which you should call just
+ before the property value changes.
+
+ @method propertyDidChange
+ @for Ember
+ @param {Object} obj The object with the property that will change
+ @param {String} keyName The property key (or path) that will change.
+ @return {void}
+*/
+var propertyDidChange = Ember.propertyDidChange = function(obj, keyName) {
+ var m = metaFor(obj, false),
+ watching = m.watching[keyName] > 0 || keyName === 'length',
+ proto = m.proto,
+ desc = m.descs[keyName];
+
+ if (proto === obj) { return; }
+
+ // shouldn't this mean that we're watching this key?
+ if (desc && desc.didChange) { desc.didChange(obj, keyName); }
+ if (!watching && keyName !== 'length') { return; }
+
+ dependentKeysDidChange(obj, keyName, m);
+ chainsDidChange(obj, keyName, m);
+ notifyObservers(obj, keyName);
+};
+
+var WILL_SEEN, DID_SEEN;
+
+// called whenever a property is about to change to clear the cache of any dependent keys (and notify those properties of changes, etc...)
+function dependentKeysWillChange(obj, depKey, meta) {
+ if (obj.isDestroying) { return; }
+
+ var seen = WILL_SEEN, top = !seen;
+ if (top) { seen = WILL_SEEN = {}; }
+ iterDeps(propertyWillChange, obj, depKey, seen, meta);
+ if (top) { WILL_SEEN = null; }
+}
+
+// called whenever a property has just changed to update dependent keys
+function dependentKeysDidChange(obj, depKey, meta) {
+ if (obj.isDestroying) { return; }
+
+ var seen = DID_SEEN, top = !seen;
+ if (top) { seen = DID_SEEN = {}; }
+ iterDeps(propertyDidChange, obj, depKey, seen, meta);
+ if (top) { DID_SEEN = null; }
+}
+
+function iterDeps(method, obj, depKey, seen, meta) {
+ var guid = guidFor(obj);
+ if (!seen[guid]) seen[guid] = {};
+ if (seen[guid][depKey]) return;
+ seen[guid][depKey] = true;
+
+ var deps = meta.deps;
+ deps = deps && deps[depKey];
+ if (deps) {
+ for(var key in deps) {
+ var desc = meta.descs[key];
+ if (desc && desc._suspended === obj) continue;
+ method(obj, key);
+ }
+ }
+}
+
+var chainsWillChange = function(obj, keyName, m, arg) {
+ if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
+
+ var nodes = m.chainWatchers;
+
+ nodes = nodes[keyName];
+ if (!nodes) { return; }
+
+ for(var i = 0, l = nodes.length; i < l; i++) {
+ nodes[i].willChange(arg);
+ }
+};
+
+var chainsDidChange = function(obj, keyName, m, arg) {
+ if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
+
+ var nodes = m.chainWatchers;
+
+ nodes = nodes[keyName];
+ if (!nodes) { return; }
+
+ // looping in reverse because the chainWatchers array can be modified inside didChange
+ for (var i = nodes.length - 1; i >= 0; i--) {
+ nodes[i].didChange(arg);
+ }
+};
+
+Ember.overrideChains = function(obj, keyName, m) {
+ chainsDidChange(obj, keyName, m, true);
+};
+
+/**
+ @method beginPropertyChanges
+ @chainable
+*/
+var beginPropertyChanges = Ember.beginPropertyChanges = function() {
+ deferred++;
+};
+
+/**
+ @method endPropertyChanges
+*/
+var endPropertyChanges = Ember.endPropertyChanges = function() {
+ deferred--;
+ if (deferred<=0) {
+ beforeObserverSet.clear();
+ observerSet.flush();
+ }
+};
+
+/**
+ Make a series of property changes together in an
+ exception-safe way.
+
+ ```javascript
+ Ember.changeProperties(function() {
+ obj1.set('foo', mayBlowUpWhenSet);
+ obj2.set('bar', baz);
+ });
+ ```
+
+ @method changePropertiess
+ @param {Function} callback
+ @param [binding]
+*/
+var changeProperties = Ember.changeProperties = function(cb, binding){
+ beginPropertyChanges();
+ tryFinally(cb, endPropertyChanges, binding);
+};
+
+var notifyBeforeObservers = function(obj, keyName) {
+ if (obj.isDestroying) { return; }
+
+ var eventName = keyName + ':before', listeners, diff;
+ if (deferred) {
+ listeners = beforeObserverSet.add(obj, keyName, eventName);
+ diff = listenersDiff(obj, eventName, listeners);
+ sendEvent(obj, eventName, [obj, keyName], diff);
+ } else {
+ sendEvent(obj, eventName, [obj, keyName]);
+ }
+};
+
+var notifyObservers = function(obj, keyName) {
+ if (obj.isDestroying) { return; }
+
+ var eventName = keyName + ':change', listeners;
+ if (deferred) {
+ listeners = observerSet.add(obj, keyName, eventName);
+ listenersUnion(obj, eventName, listeners);
+ } else {
+ sendEvent(obj, eventName, [obj, keyName]);
+ }
+};
+})();
+
+
+
+(function() {
+// META_KEY
+// _getPath
+// propertyWillChange, propertyDidChange
+
+var META_KEY = Ember.META_KEY,
+ MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
+ IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/,
+ propertyWillChange = Ember.propertyWillChange,
+ propertyDidChange = Ember.propertyDidChange,
+ getPath = Ember._getPath;
+
+/**
Sets the value of a property on an object, respecting computed properties
and notifying observers and other listeners of the change. If the
property is not defined but the object implements the `unknownProperty`
method then that will be invoked as well.
@@ -1964,11 +2800,11 @@
@param {Object} obj The object to modify.
@param {String} keyName The property key to set
@param {Object} value The value to set
@return {Object} the passed value.
*/
-set = function set(obj, keyName, value, tolerant) {
+var set = function set(obj, keyName, value, tolerant) {
if (typeof obj === 'string') {
Ember.assert("Path '" + obj + "' must be global if no obj is given.", IS_GLOBAL.test(obj));
value = keyName;
keyName = obj;
obj = null;
@@ -2020,69 +2856,15 @@
return value;
};
// Currently used only by Ember Data tests
if (Ember.config.overrideAccessors) {
- Ember.get = get;
Ember.set = set;
Ember.config.overrideAccessors();
- get = Ember.get;
set = Ember.set;
}
-function firstKey(path) {
- return path.match(FIRST_KEY)[0];
-}
-
-// assumes path is already normalized
-function normalizeTuple(target, path) {
- var hasThis = HAS_THIS.test(path),
- isGlobal = !hasThis && IS_GLOBAL_PATH.test(path),
- key;
-
- if (!target || isGlobal) target = Ember.lookup;
- if (hasThis) path = path.slice(5);
-
- if (target === Ember.lookup) {
- key = firstKey(path);
- target = get(target, key);
- path = path.slice(key.length+1);
- }
-
- // must return some kind of path to be valid else other things will break.
- if (!path || path.length===0) throw new Error('Invalid Path');
-
- return [ target, path ];
-}
-
-function getPath(root, path) {
- var hasThis, parts, tuple, idx, len;
-
- // If there is no root and path is a key name, return that
- // property from the global object.
- // E.g. get('Ember') -> Ember
- if (root === null && path.indexOf('.') === -1) { return get(Ember.lookup, path); }
-
- // detect complicated paths and normalize them
- hasThis = HAS_THIS.test(path);
-
- if (!root || hasThis) {
- tuple = normalizeTuple(root, path);
- root = tuple[0];
- path = tuple[1];
- tuple.length = 0;
- }
-
- parts = path.split(".");
- len = parts.length;
- for (idx=0; root && idx<len; idx++) {
- root = get(root, parts[idx], true);
- if (root && root.isDestroyed) { return undefined; }
- }
- return root;
-}
-
function setPath(root, path, value, tolerant) {
var keyName;
// get the last part of the path
keyName = path.slice(path.lastIndexOf('.') + 1);
@@ -2106,39 +2888,10 @@
}
return set(root, keyName, value);
}
-/**
- @private
-
- Normalizes a target/path pair to reflect that actual target/path that should
- be observed, etc. This takes into account passing in global property
- paths (i.e. a path beginning with a captial letter not defined on the
- target) and * separators.
-
- @method normalizeTuple
- @for Ember
- @param {Object} target The current target. May be `null`.
- @param {String} path A path on the target or a global property path.
- @return {Array} a temporary array with the normalized target/path pair.
-*/
-Ember.normalizeTuple = function(target, path) {
- return normalizeTuple(target, path);
-};
-
-Ember.getWithDefault = function(root, key, defaultValue) {
- var value = get(root, key);
-
- if (value === undefined) { return defaultValue; }
- return value;
-};
-
-
-Ember.get = get;
-Ember.getPath = Ember.deprecateFunc('getPath is deprecated since get now supports paths', Ember.get);
-
Ember.set = set;
Ember.setPath = Ember.deprecateFunc('setPath is deprecated since set now supports paths', Ember.set);
/**
Error-tolerant form of `Ember.set`. Will not blow up if any part of the
@@ -2156,25 +2909,10 @@
Ember.trySet = function(root, path, value) {
return set(root, path, value, true);
};
Ember.trySetPath = Ember.deprecateFunc('trySetPath has been renamed to trySet', Ember.trySet);
-/**
- Returns true if the provided path is global (e.g., `MyApp.fooController.bar`)
- instead of local (`foo.bar.baz`).
-
- @method isGlobalPath
- @for Ember
- @private
- @param {String} path
- @return Boolean
-*/
-Ember.isGlobalPath = function(path) {
- return IS_GLOBAL.test(path);
-};
-
-
})();
(function() {
@@ -2261,11 +2999,11 @@
@param {Object} obj the object to define this property on. This may be a prototype.
@param {String} keyName the name of the property
@param {Ember.Descriptor} [desc] an instance of `Ember.Descriptor` (typically a
computed property) or an ES5 descriptor.
You must provide this or `data` but not both.
- @param {anything} [data] something other than a descriptor, that will
+ @param {*} [data] something other than a descriptor, that will
become the explicit value of this property.
*/
Ember.defineProperty = function(obj, keyName, desc, data, meta) {
var descs, existingDesc, watching, value;
@@ -2332,344 +3070,135 @@
})();
(function() {
-// Ember.tryFinally
-/**
-@module ember-metal
-*/
+var changeProperties = Ember.changeProperties,
+ set = Ember.set;
-var AFTER_OBSERVERS = ':change';
-var BEFORE_OBSERVERS = ':before';
-
-var guidFor = Ember.guidFor;
-
-var deferred = 0;
-
-/*
- this.observerSet = {
- [senderGuid]: { // variable name: `keySet`
- [keyName]: listIndex
- }
- },
- this.observers = [
- {
- sender: obj,
- keyName: keyName,
- eventName: eventName,
- listeners: [
- [target, method, onceFlag, suspendedFlag]
- ]
- },
- ...
- ]
-*/
-function ObserverSet() {
- this.clear();
-}
-
-ObserverSet.prototype.add = function(sender, keyName, eventName) {
- var observerSet = this.observerSet,
- observers = this.observers,
- senderGuid = Ember.guidFor(sender),
- keySet = observerSet[senderGuid],
- index;
-
- if (!keySet) {
- observerSet[senderGuid] = keySet = {};
- }
- index = keySet[keyName];
- if (index === undefined) {
- index = observers.push({
- sender: sender,
- keyName: keyName,
- eventName: eventName,
- listeners: []
- }) - 1;
- keySet[keyName] = index;
- }
- return observers[index].listeners;
-};
-
-ObserverSet.prototype.flush = function() {
- var observers = this.observers, i, len, observer, sender;
- this.clear();
- for (i=0, len=observers.length; i < len; ++i) {
- observer = observers[i];
- sender = observer.sender;
- if (sender.isDestroying || sender.isDestroyed) { continue; }
- Ember.sendEvent(sender, observer.eventName, [sender, observer.keyName], observer.listeners);
- }
-};
-
-ObserverSet.prototype.clear = function() {
- this.observerSet = {};
- this.observers = [];
-};
-
-var beforeObserverSet = new ObserverSet(), observerSet = new ObserverSet();
-
/**
- @method beginPropertyChanges
- @chainable
-*/
-Ember.beginPropertyChanges = function() {
- deferred++;
-};
-
-/**
- @method endPropertyChanges
-*/
-Ember.endPropertyChanges = function() {
- deferred--;
- if (deferred<=0) {
- beforeObserverSet.clear();
- observerSet.flush();
- }
-};
-
-/**
- Make a series of property changes together in an
- exception-safe way.
-
- ```javascript
- Ember.changeProperties(function() {
- obj1.set('foo', mayBlowUpWhenSet);
- obj2.set('bar', baz);
- });
- ```
-
- @method changeProperties
- @param {Function} callback
- @param [binding]
-*/
-Ember.changeProperties = function(cb, binding){
- Ember.beginPropertyChanges();
- Ember.tryFinally(cb, Ember.endPropertyChanges, binding);
-};
-
-/**
Set a list of properties on an object. These properties are set inside
a single `beginPropertyChanges` and `endPropertyChanges` batch, so
observers will be buffered.
@method setProperties
@param target
@param {Hash} properties
@return target
*/
Ember.setProperties = function(self, hash) {
- Ember.changeProperties(function(){
+ changeProperties(function(){
for(var prop in hash) {
- if (hash.hasOwnProperty(prop)) Ember.set(self, prop, hash[prop]);
+ if (hash.hasOwnProperty(prop)) { set(self, prop, hash[prop]); }
}
});
return self;
};
+})();
-function changeEvent(keyName) {
- return keyName+AFTER_OBSERVERS;
-}
-function beforeEvent(keyName) {
- return keyName+BEFORE_OBSERVERS;
-}
+(function() {
+var metaFor = Ember.meta, // utils.js
+ typeOf = Ember.typeOf, // utils.js
+ MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
+ o_defineProperty = Ember.platform.defineProperty;
-/**
- @method addObserver
- @param obj
- @param {String} path
- @param {Object|Function} targetOrMethod
- @param {Function|String} [method]
-*/
-Ember.addObserver = function(obj, path, target, method) {
- Ember.addListener(obj, changeEvent(path), target, method);
- Ember.watch(obj, path);
- return this;
-};
+Ember.watchKey = function(obj, keyName) {
+ // can't watch length on Array - it is special...
+ if (keyName === 'length' && typeOf(obj) === 'array') { return; }
-Ember.observersFor = function(obj, path) {
- return Ember.listenersFor(obj, changeEvent(path));
-};
+ var m = metaFor(obj), watching = m.watching, desc;
-/**
- @method removeObserver
- @param obj
- @param {String} path
- @param {Object|Function} targetOrMethod
- @param {Function|String} [method]
-*/
-Ember.removeObserver = function(obj, path, target, method) {
- Ember.unwatch(obj, path);
- Ember.removeListener(obj, changeEvent(path), target, method);
- return this;
-};
+ // activate watching first time
+ if (!watching[keyName]) {
+ watching[keyName] = 1;
+ desc = m.descs[keyName];
+ if (desc && desc.willWatch) { desc.willWatch(obj, keyName); }
-/**
- @method addBeforeObserver
- @param obj
- @param {String} path
- @param {Object|Function} targetOrMethod
- @param {Function|String} [method]
-*/
-Ember.addBeforeObserver = function(obj, path, target, method) {
- Ember.addListener(obj, beforeEvent(path), target, method);
- Ember.watch(obj, path);
- return this;
-};
+ if ('function' === typeof obj.willWatchProperty) {
+ obj.willWatchProperty(keyName);
+ }
-// Suspend observer during callback.
-//
-// This should only be used by the target of the observer
-// while it is setting the observed path.
-Ember._suspendBeforeObserver = function(obj, path, target, method, callback) {
- return Ember._suspendListener(obj, beforeEvent(path), target, method, callback);
+ if (MANDATORY_SETTER && keyName in obj) {
+ m.values[keyName] = obj[keyName];
+ o_defineProperty(obj, keyName, {
+ configurable: true,
+ enumerable: true,
+ set: Ember.MANDATORY_SETTER_FUNCTION,
+ get: Ember.DEFAULT_GETTER_FUNCTION(keyName)
+ });
+ }
+ } else {
+ watching[keyName] = (watching[keyName] || 0) + 1;
+ }
};
-Ember._suspendObserver = function(obj, path, target, method, callback) {
- return Ember._suspendListener(obj, changeEvent(path), target, method, callback);
-};
-var map = Ember.ArrayPolyfills.map;
+Ember.unwatchKey = function(obj, keyName) {
+ var m = metaFor(obj), watching = m.watching, desc;
-Ember._suspendBeforeObservers = function(obj, paths, target, method, callback) {
- var events = map.call(paths, beforeEvent);
- return Ember._suspendListeners(obj, events, target, method, callback);
-};
+ if (watching[keyName] === 1) {
+ watching[keyName] = 0;
+ desc = m.descs[keyName];
-Ember._suspendObservers = function(obj, paths, target, method, callback) {
- var events = map.call(paths, changeEvent);
- return Ember._suspendListeners(obj, events, target, method, callback);
-};
+ if (desc && desc.didUnwatch) { desc.didUnwatch(obj, keyName); }
-Ember.beforeObserversFor = function(obj, path) {
- return Ember.listenersFor(obj, beforeEvent(path));
-};
+ if ('function' === typeof obj.didUnwatchProperty) {
+ obj.didUnwatchProperty(keyName);
+ }
-/**
- @method removeBeforeObserver
- @param obj
- @param {String} path
- @param {Object|Function} targetOrMethod
- @param {Function|String} [method]
-*/
-Ember.removeBeforeObserver = function(obj, path, target, method) {
- Ember.unwatch(obj, path);
- Ember.removeListener(obj, beforeEvent(path), target, method);
- return this;
-};
-
-Ember.notifyBeforeObservers = function(obj, keyName) {
- if (obj.isDestroying) { return; }
-
- var eventName = beforeEvent(keyName), listeners, listenersDiff;
- if (deferred) {
- listeners = beforeObserverSet.add(obj, keyName, eventName);
- listenersDiff = Ember.listenersDiff(obj, eventName, listeners);
- Ember.sendEvent(obj, eventName, [obj, keyName], listenersDiff);
- } else {
- Ember.sendEvent(obj, eventName, [obj, keyName]);
+ if (MANDATORY_SETTER && keyName in obj) {
+ o_defineProperty(obj, keyName, {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: m.values[keyName]
+ });
+ delete m.values[keyName];
+ }
+ } else if (watching[keyName] > 1) {
+ watching[keyName]--;
}
};
-
-Ember.notifyObservers = function(obj, keyName) {
- if (obj.isDestroying) { return; }
-
- var eventName = changeEvent(keyName), listeners;
- if (deferred) {
- listeners = observerSet.add(obj, keyName, eventName);
- Ember.listenersUnion(obj, eventName, listeners);
- } else {
- Ember.sendEvent(obj, eventName, [obj, keyName]);
- }
-};
-
})();
(function() {
-/**
-@module ember-metal
-*/
-
-var guidFor = Ember.guidFor, // utils.js
- metaFor = Ember.meta, // utils.js
- get = Ember.get, // accessors.js
- set = Ember.set, // accessors.js
- normalizeTuple = Ember.normalizeTuple, // accessors.js
- GUID_KEY = Ember.GUID_KEY, // utils.js
- META_KEY = Ember.META_KEY, // utils.js
- // circular reference observer depends on Ember.watch
- // we should move change events to this file or its own property_events.js
+var metaFor = Ember.meta, // utils.js
+ get = Ember.get, // property_get.js
+ normalizeTuple = Ember.normalizeTuple, // property_get.js
forEach = Ember.ArrayPolyfills.forEach, // array.js
- FIRST_KEY = /^([^\.\*]+)/,
- IS_PATH = /[\.\*]/;
+ warn = Ember.warn,
+ watchKey = Ember.watchKey,
+ unwatchKey = Ember.unwatchKey,
+ propertyWillChange = Ember.propertyWillChange,
+ propertyDidChange = Ember.propertyDidChange,
+ FIRST_KEY = /^([^\.\*]+)/;
-var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER,
-o_defineProperty = Ember.platform.defineProperty;
-
function firstKey(path) {
return path.match(FIRST_KEY)[0];
}
-// returns true if the passed path is just a keyName
-function isKeyName(path) {
- return path==='*' || !IS_PATH.test(path);
-}
+var pendingQueue = [];
-// ..........................................................
-// DEPENDENT KEYS
-//
+// attempts to add the pendingQueue chains again. If some of them end up
+// back in the queue and reschedule is true, schedules a timeout to try
+// again.
+Ember.flushPendingChains = function() {
+ if (pendingQueue.length === 0) { return; } // nothing to do
-function iterDeps(method, obj, depKey, seen, meta) {
+ var queue = pendingQueue;
+ pendingQueue = [];
- var guid = guidFor(obj);
- if (!seen[guid]) seen[guid] = {};
- if (seen[guid][depKey]) return;
- seen[guid][depKey] = true;
+ forEach.call(queue, function(q) { q[0].add(q[1]); });
- var deps = meta.deps;
- deps = deps && deps[depKey];
- if (deps) {
- for(var key in deps) {
- var desc = meta.descs[key];
- if (desc && desc._suspended === obj) continue;
- method(obj, key);
- }
- }
-}
+ warn('Watching an undefined global, Ember expects watched globals to be setup by the time the run loop is flushed, check for typos', pendingQueue.length === 0);
+};
-var WILL_SEEN, DID_SEEN;
-
-// called whenever a property is about to change to clear the cache of any dependent keys (and notify those properties of changes, etc...)
-function dependentKeysWillChange(obj, depKey, meta) {
- if (obj.isDestroying) { return; }
-
- var seen = WILL_SEEN, top = !seen;
- if (top) { seen = WILL_SEEN = {}; }
- iterDeps(propertyWillChange, obj, depKey, seen, meta);
- if (top) { WILL_SEEN = null; }
-}
-
-// called whenever a property has just changed to update dependent keys
-function dependentKeysDidChange(obj, depKey, meta) {
- if (obj.isDestroying) { return; }
-
- var seen = DID_SEEN, top = !seen;
- if (top) { seen = DID_SEEN = {}; }
- iterDeps(propertyDidChange, obj, depKey, seen, meta);
- if (top) { DID_SEEN = null; }
-}
-
-// ..........................................................
-// CHAIN
-//
-
function addChainWatcher(obj, keyName, node) {
if (!obj || ('object' !== typeof obj)) { return; } // nothing to do
var m = metaFor(obj), nodes = m.chainWatchers;
@@ -2677,14 +3206,14 @@
nodes = m.chainWatchers = {};
}
if (!nodes[keyName]) { nodes[keyName] = []; }
nodes[keyName].push(node);
- Ember.watch(obj, keyName);
+ watchKey(obj, keyName);
}
-function removeChainWatcher(obj, keyName, node) {
+var removeChainWatcher = Ember.removeChainWatcher = function(obj, keyName, node) {
if (!obj || 'object' !== typeof obj) { return; } // nothing to do
var m = metaFor(obj, false);
if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
@@ -2694,37 +3223,21 @@
nodes = nodes[keyName];
for (var i = 0, l = nodes.length; i < l; i++) {
if (nodes[i] === node) { nodes.splice(i, 1); }
}
}
- Ember.unwatch(obj, keyName);
-}
+ unwatchKey(obj, keyName);
+};
-var pendingQueue = [];
-
-// attempts to add the pendingQueue chains again. If some of them end up
-// back in the queue and reschedule is true, schedules a timeout to try
-// again.
-function flushPendingChains() {
- if (pendingQueue.length === 0) { return; } // nothing to do
-
- var queue = pendingQueue;
- pendingQueue = [];
-
- forEach.call(queue, function(q) { q[0].add(q[1]); });
-
- Ember.warn('Watching an undefined global, Ember expects watched globals to be setup by the time the run loop is flushed, check for typos', pendingQueue.length === 0);
-}
-
function isProto(pvalue) {
return metaFor(pvalue, false).proto === pvalue;
}
// A ChainNode watches a single key on an object. If you provide a starting
// value for the key then the node won't actually watch it. For a root node
// pass null for parent and key and object for value.
-var ChainNode = function(parent, key, value) {
+var ChainNode = Ember._ChainNode = function(parent, key, value) {
var obj;
this._parent = parent;
this._key = key;
// _watching is true when calling get(this._parent, this._key) will
@@ -2893,24 +3406,24 @@
if (this._key) { path = this._key + '.' + path; }
if (this._parent) {
this._parent.chainWillChange(this, path, depth+1);
} else {
- if (depth > 1) { Ember.propertyWillChange(this.value(), path); }
+ if (depth > 1) { propertyWillChange(this.value(), path); }
path = 'this.' + path;
- if (this._paths[path] > 0) { Ember.propertyWillChange(this.value(), path); }
+ if (this._paths[path] > 0) { propertyWillChange(this.value(), path); }
}
};
ChainNodePrototype.chainDidChange = function(chain, path, depth) {
if (this._key) { path = this._key + '.' + path; }
if (this._parent) {
this._parent.chainDidChange(this, path, depth+1);
} else {
- if (depth > 1) { Ember.propertyDidChange(this.value(), path); }
+ if (depth > 1) { propertyDidChange(this.value(), path); }
path = 'this.' + path;
- if (this._paths[path] > 0) { Ember.propertyDidChange(this.value(), path); }
+ if (this._paths[path] > 0) { propertyDidChange(this.value(), path); }
}
};
ChainNodePrototype.didChange = function(suppressEvent) {
// invalidate my own value first.
@@ -2942,10 +3455,28 @@
// and finally tell parent about my path changing...
if (this._parent) { this._parent.chainDidChange(this, this._key, 1); }
};
+Ember.finishChains = function(obj) {
+ var m = metaFor(obj, false), chains = m.chains;
+ if (chains) {
+ if (chains.value() !== obj) {
+ m.chains = chains = chains.copy(obj);
+ }
+ chains.didChange(true);
+ }
+};
+})();
+
+
+
+(function() {
+var metaFor = Ember.meta, // utils.js
+ typeOf = Ember.typeOf, // utils.js
+ ChainNode = Ember._ChainNode; // chains.js
+
// get the chains for the current object. If the current object has
// chains inherited from the proto they will be cloned and reconfigured for
// the current object.
function chainsFor(obj) {
var m = metaFor(obj), ret = m.chains;
@@ -2955,45 +3486,60 @@
ret = m.chains = ret.copy(obj);
}
return ret;
}
-Ember.overrideChains = function(obj, keyName, m) {
- chainsDidChange(obj, keyName, m, true);
-};
+Ember.watchPath = function(obj, keyPath) {
+ // can't watch length on Array - it is special...
+ if (keyPath === 'length' && typeOf(obj) === 'array') { return; }
-function chainsWillChange(obj, keyName, m, arg) {
- if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
+ var m = metaFor(obj), watching = m.watching;
- var nodes = m.chainWatchers;
+ if (!watching[keyPath]) { // activate watching first time
+ watching[keyPath] = 1;
+ chainsFor(obj).add(keyPath);
+ } else {
+ watching[keyPath] = (watching[keyPath] || 0) + 1;
+ }
+};
- nodes = nodes[keyName];
- if (!nodes) { return; }
+Ember.unwatchPath = function(obj, keyPath) {
+ var m = metaFor(obj), watching = m.watching, desc;
- for(var i = 0, l = nodes.length; i < l; i++) {
- nodes[i].willChange(arg);
+ if (watching[keyPath] === 1) {
+ watching[keyPath] = 0;
+ chainsFor(obj).remove(keyPath);
+ } else if (watching[keyPath] > 1) {
+ watching[keyPath]--;
}
-}
+};
+})();
-function chainsDidChange(obj, keyName, m, arg) {
- if (!m.hasOwnProperty('chainWatchers')) { return; } // nothing to do
- var nodes = m.chainWatchers;
- nodes = nodes[keyName];
- if (!nodes) { return; }
+(function() {
+/**
+@module ember-metal
+*/
- // looping in reverse because the chainWatchers array can be modified inside didChange
- for (var i = nodes.length - 1; i >= 0; i--) {
- nodes[i].didChange(arg);
- }
+var metaFor = Ember.meta, // utils.js
+ GUID_KEY = Ember.GUID_KEY, // utils.js
+ META_KEY = Ember.META_KEY, // utils.js
+ removeChainWatcher = Ember.removeChainWatcher,
+ watchKey = Ember.watchKey, // watch_key.js
+ unwatchKey = Ember.unwatchKey,
+ watchPath = Ember.watchPath, // watch_path.js
+ unwatchPath = Ember.unwatchPath,
+ typeOf = Ember.typeOf, // utils.js
+ generateGuid = Ember.generateGuid,
+ IS_PATH = /[\.\*]/;
+
+// returns true if the passed path is just a keyName
+function isKeyName(path) {
+ return path==='*' || !IS_PATH.test(path);
}
-// ..........................................................
-// WATCH
-//
-
/**
@private
Starts watching a property on an object. Whenever the property changes,
invokes `Ember.propertyWillChange` and `Ember.propertyDidChange`. This is the
@@ -3004,88 +3550,37 @@
@method watch
@for Ember
@param obj
@param {String} keyName
*/
-Ember.watch = function(obj, keyName) {
+Ember.watch = function(obj, keyPath) {
// can't watch length on Array - it is special...
- if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; }
+ if (keyPath === 'length' && typeOf(obj) === 'array') { return; }
- var m = metaFor(obj), watching = m.watching, desc;
-
- // activate watching first time
- if (!watching[keyName]) {
- watching[keyName] = 1;
- if (isKeyName(keyName)) {
- desc = m.descs[keyName];
- if (desc && desc.willWatch) { desc.willWatch(obj, keyName); }
-
- if ('function' === typeof obj.willWatchProperty) {
- obj.willWatchProperty(keyName);
- }
-
- if (MANDATORY_SETTER && keyName in obj) {
- m.values[keyName] = obj[keyName];
- o_defineProperty(obj, keyName, {
- configurable: true,
- enumerable: true,
- set: Ember.MANDATORY_SETTER_FUNCTION,
- get: Ember.DEFAULT_GETTER_FUNCTION(keyName)
- });
- }
- } else {
- chainsFor(obj).add(keyName);
- }
-
- } else {
- watching[keyName] = (watching[keyName] || 0) + 1;
+ if (isKeyName(keyPath)) {
+ watchKey(obj, keyPath);
+ } else {
+ watchPath(obj, keyPath);
}
- return this;
};
Ember.isWatching = function isWatching(obj, key) {
var meta = obj[META_KEY];
return (meta && meta.watching[key]) > 0;
};
-Ember.watch.flushPending = flushPendingChains;
+Ember.watch.flushPending = Ember.flushPendingChains;
-Ember.unwatch = function(obj, keyName) {
+Ember.unwatch = function(obj, keyPath) {
// can't watch length on Array - it is special...
- if (keyName === 'length' && Ember.typeOf(obj) === 'array') { return this; }
+ if (keyPath === 'length' && typeOf(obj) === 'array') { return; }
- var m = metaFor(obj), watching = m.watching, desc;
-
- if (watching[keyName] === 1) {
- watching[keyName] = 0;
-
- if (isKeyName(keyName)) {
- desc = m.descs[keyName];
- if (desc && desc.didUnwatch) { desc.didUnwatch(obj, keyName); }
-
- if ('function' === typeof obj.didUnwatchProperty) {
- obj.didUnwatchProperty(keyName);
- }
-
- if (MANDATORY_SETTER && keyName in obj) {
- o_defineProperty(obj, keyName, {
- configurable: true,
- enumerable: true,
- writable: true,
- value: m.values[keyName]
- });
- delete m.values[keyName];
- }
- } else {
- chainsFor(obj).remove(keyName);
- }
-
- } else if (watching[keyName]>1) {
- watching[keyName]--;
+ if (isKeyName(keyPath)) {
+ unwatchKey(obj, keyPath);
+ } else {
+ unwatchPath(obj, keyPath);
}
-
- return this;
};
/**
@private
@@ -3100,100 +3595,19 @@
Ember.rewatch = function(obj) {
var m = metaFor(obj, false), chains = m.chains;
// make sure the object has its own guid.
if (GUID_KEY in obj && !obj.hasOwnProperty(GUID_KEY)) {
- Ember.generateGuid(obj, 'ember');
+ generateGuid(obj, 'ember');
}
// make sure any chained watchers update.
if (chains && chains.value() !== obj) {
m.chains = chains.copy(obj);
}
-
- return this;
};
-Ember.finishChains = function(obj) {
- var m = metaFor(obj, false), chains = m.chains;
- if (chains) {
- if (chains.value() !== obj) {
- m.chains = chains = chains.copy(obj);
- }
- chains.didChange(true);
- }
-};
-
-// ..........................................................
-// PROPERTY CHANGES
-//
-
-/**
- This function is called just before an object property is about to change.
- It will notify any before observers and prepare caches among other things.
-
- Normally you will not need to call this method directly but if for some
- reason you can't directly watch a property you can invoke this method
- manually along with `Ember.propertyDidChange()` which you should call just
- after the property value changes.
-
- @method propertyWillChange
- @for Ember
- @param {Object} obj The object with the property that will change
- @param {String} keyName The property key (or path) that will change.
- @return {void}
-*/
-function propertyWillChange(obj, keyName) {
- var m = metaFor(obj, false),
- watching = m.watching[keyName] > 0 || keyName === 'length',
- proto = m.proto,
- desc = m.descs[keyName];
-
- if (!watching) { return; }
- if (proto === obj) { return; }
- if (desc && desc.willChange) { desc.willChange(obj, keyName); }
- dependentKeysWillChange(obj, keyName, m);
- chainsWillChange(obj, keyName, m);
- Ember.notifyBeforeObservers(obj, keyName);
-}
-
-Ember.propertyWillChange = propertyWillChange;
-
-/**
- This function is called just after an object property has changed.
- It will notify any observers and clear caches among other things.
-
- Normally you will not need to call this method directly but if for some
- reason you can't directly watch a property you can invoke this method
- manually along with `Ember.propertyWilLChange()` which you should call just
- before the property value changes.
-
- @method propertyDidChange
- @for Ember
- @param {Object} obj The object with the property that will change
- @param {String} keyName The property key (or path) that will change.
- @return {void}
-*/
-function propertyDidChange(obj, keyName) {
- var m = metaFor(obj, false),
- watching = m.watching[keyName] > 0 || keyName === 'length',
- proto = m.proto,
- desc = m.descs[keyName];
-
- if (proto === obj) { return; }
-
- // shouldn't this mean that we're watching this key?
- if (desc && desc.didChange) { desc.didChange(obj, keyName); }
- if (!watching && keyName !== 'length') { return; }
-
- dependentKeysDidChange(obj, keyName, m);
- chainsDidChange(obj, keyName, m);
- Ember.notifyObservers(obj, keyName);
-}
-
-Ember.propertyDidChange = propertyDidChange;
-
var NODE_STACK = [];
/**
Tears down the meta on an object so that it can be garbage collected.
Multiple calls will have no effect.
@@ -3667,11 +4081,11 @@
@method cacheFor
@for Ember
@param {Object} obj the object whose property you want to check
@param {String} key the name of the property whose cached value you want
to return
- @return {any} the cached value
+ @return {*} the cached value
*/
Ember.cacheFor = function cacheFor(obj, key) {
var cache = metaFor(obj, false).cache;
if (cache && key in cache) {
@@ -3943,385 +4357,111 @@
})();
(function() {
+// Ember.tryFinally
/**
@module ember-metal
*/
-var o_create = Ember.create,
- metaFor = Ember.meta,
- META_KEY = Ember.META_KEY;
+var AFTER_OBSERVERS = ':change';
+var BEFORE_OBSERVERS = ':before';
-/*
- The event system uses a series of nested hashes to store listeners on an
- object. When a listener is registered, or when an event arrives, these
- hashes are consulted to determine which target and action pair to invoke.
+var guidFor = Ember.guidFor;
- The hashes are stored in the object's meta hash, and look like this:
-
- // Object's meta hash
- {
- listeners: { // variable name: `listenerSet`
- "foo:changed": [ // variable name: `actions`
- [target, method, onceFlag, suspendedFlag]
- ]
- }
- }
-
-*/
-
-function indexOf(array, target, method) {
- var index = -1;
- for (var i = 0, l = array.length; i < l; i++) {
- if (target === array[i][0] && method === array[i][1]) { index = i; break; }
- }
- return index;
+function changeEvent(keyName) {
+ return keyName+AFTER_OBSERVERS;
}
-function actionsFor(obj, eventName) {
- var meta = metaFor(obj, true),
- actions;
-
- if (!meta.listeners) { meta.listeners = {}; }
-
- if (!meta.hasOwnProperty('listeners')) {
- // setup inherited copy of the listeners object
- meta.listeners = o_create(meta.listeners);
- }
-
- actions = meta.listeners[eventName];
-
- // if there are actions, but the eventName doesn't exist in our listeners, then copy them from the prototype
- if (actions && !meta.listeners.hasOwnProperty(eventName)) {
- actions = meta.listeners[eventName] = meta.listeners[eventName].slice();
- } else if (!actions) {
- actions = meta.listeners[eventName] = [];
- }
-
- return actions;
+function beforeEvent(keyName) {
+ return keyName+BEFORE_OBSERVERS;
}
-function actionsUnion(obj, eventName, otherActions) {
- var meta = obj[META_KEY],
- actions = meta && meta.listeners && meta.listeners[eventName];
-
- if (!actions) { return; }
- for (var i = actions.length - 1; i >= 0; i--) {
- var target = actions[i][0],
- method = actions[i][1],
- once = actions[i][2],
- suspended = actions[i][3],
- actionIndex = indexOf(otherActions, target, method);
-
- if (actionIndex === -1) {
- otherActions.push([target, method, once, suspended]);
- }
- }
-}
-
-function actionsDiff(obj, eventName, otherActions) {
- var meta = obj[META_KEY],
- actions = meta && meta.listeners && meta.listeners[eventName],
- diffActions = [];
-
- if (!actions) { return; }
- for (var i = actions.length - 1; i >= 0; i--) {
- var target = actions[i][0],
- method = actions[i][1],
- once = actions[i][2],
- suspended = actions[i][3],
- actionIndex = indexOf(otherActions, target, method);
-
- if (actionIndex !== -1) { continue; }
-
- otherActions.push([target, method, once, suspended]);
- diffActions.push([target, method, once, suspended]);
- }
-
- return diffActions;
-}
-
/**
- Add an event listener
-
- @method addListener
- @for Ember
+ @method addObserver
@param obj
- @param {String} eventName
- @param {Object|Function} targetOrMethod A target object or a function
- @param {Function|String} method A function or the name of a function to be called on `target`
- @param {Boolean} once A flag whether a function should only be called once
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
*/
-function addListener(obj, eventName, target, method, once) {
- Ember.assert("You must pass at least an object and event name to Ember.addListener", !!obj && !!eventName);
+Ember.addObserver = function(obj, path, target, method) {
+ Ember.addListener(obj, changeEvent(path), target, method);
+ Ember.watch(obj, path);
+ return this;
+};
- if (!method && 'function' === typeof target) {
- method = target;
- target = null;
- }
+Ember.observersFor = function(obj, path) {
+ return Ember.listenersFor(obj, changeEvent(path));
+};
- var actions = actionsFor(obj, eventName),
- actionIndex = indexOf(actions, target, method);
-
- if (actionIndex !== -1) { return; }
-
- actions.push([target, method, once, undefined]);
-
- if ('function' === typeof obj.didAddListener) {
- obj.didAddListener(eventName, target, method);
- }
-}
-
/**
- Remove an event listener
-
- Arguments should match those passed to {{#crossLink "Ember/addListener"}}{{/crossLink}}
-
- @method removeListener
- @for Ember
+ @method removeObserver
@param obj
- @param {String} eventName
- @param {Object|Function} targetOrMethod A target object or a function
- @param {Function|String} method A function or the name of a function to be called on `target`
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
*/
-function removeListener(obj, eventName, target, method) {
- Ember.assert("You must pass at least an object and event name to Ember.removeListener", !!obj && !!eventName);
+Ember.removeObserver = function(obj, path, target, method) {
+ Ember.unwatch(obj, path);
+ Ember.removeListener(obj, changeEvent(path), target, method);
+ return this;
+};
- if (!method && 'function' === typeof target) {
- method = target;
- target = null;
- }
-
- function _removeListener(target, method, once) {
- var actions = actionsFor(obj, eventName),
- actionIndex = indexOf(actions, target, method);
-
- // action doesn't exist, give up silently
- if (actionIndex === -1) { return; }
-
- actions.splice(actionIndex, 1);
-
- if ('function' === typeof obj.didRemoveListener) {
- obj.didRemoveListener(eventName, target, method);
- }
- }
-
- if (method) {
- _removeListener(target, method);
- } else {
- var meta = obj[META_KEY],
- actions = meta && meta.listeners && meta.listeners[eventName];
-
- if (!actions) { return; }
- for (var i = actions.length - 1; i >= 0; i--) {
- _removeListener(actions[i][0], actions[i][1]);
- }
- }
-}
-
/**
- @private
-
- Suspend listener during callback.
-
- This should only be used by the target of the event listener
- when it is taking an action that would cause the event, e.g.
- an object might suspend its property change listener while it is
- setting that property.
-
- @method suspendListener
- @for Ember
+ @method addBeforeObserver
@param obj
- @param {String} eventName
- @param {Object|Function} targetOrMethod A target object or a function
- @param {Function|String} method A function or the name of a function to be called on `target`
- @param {Function} callback
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
*/
-function suspendListener(obj, eventName, target, method, callback) {
- if (!method && 'function' === typeof target) {
- method = target;
- target = null;
- }
+Ember.addBeforeObserver = function(obj, path, target, method) {
+ Ember.addListener(obj, beforeEvent(path), target, method);
+ Ember.watch(obj, path);
+ return this;
+};
- var actions = actionsFor(obj, eventName),
- actionIndex = indexOf(actions, target, method),
- action;
+// Suspend observer during callback.
+//
+// This should only be used by the target of the observer
+// while it is setting the observed path.
+Ember._suspendBeforeObserver = function(obj, path, target, method, callback) {
+ return Ember._suspendListener(obj, beforeEvent(path), target, method, callback);
+};
- if (actionIndex !== -1) {
- action = actions[actionIndex].slice(); // copy it, otherwise we're modifying a shared object
- action[3] = true; // mark the action as suspended
- actions[actionIndex] = action; // replace the shared object with our copy
- }
+Ember._suspendObserver = function(obj, path, target, method, callback) {
+ return Ember._suspendListener(obj, changeEvent(path), target, method, callback);
+};
- function tryable() { return callback.call(target); }
- function finalizer() { if (action) { action[3] = undefined; } }
+var map = Ember.ArrayPolyfills.map;
- return Ember.tryFinally(tryable, finalizer);
-}
+Ember._suspendBeforeObservers = function(obj, paths, target, method, callback) {
+ var events = map.call(paths, beforeEvent);
+ return Ember._suspendListeners(obj, events, target, method, callback);
+};
-/**
- @private
+Ember._suspendObservers = function(obj, paths, target, method, callback) {
+ var events = map.call(paths, changeEvent);
+ return Ember._suspendListeners(obj, events, target, method, callback);
+};
- Suspend listener during callback.
+Ember.beforeObserversFor = function(obj, path) {
+ return Ember.listenersFor(obj, beforeEvent(path));
+};
- This should only be used by the target of the event listener
- when it is taking an action that would cause the event, e.g.
- an object might suspend its property change listener while it is
- setting that property.
-
- @method suspendListener
- @for Ember
- @param obj
- @param {Array} eventName Array of event names
- @param {Object|Function} targetOrMethod A target object or a function
- @param {Function|String} method A function or the name of a function to be called on `target`
- @param {Function} callback
-*/
-function suspendListeners(obj, eventNames, target, method, callback) {
- if (!method && 'function' === typeof target) {
- method = target;
- target = null;
- }
-
- var suspendedActions = [],
- eventName, actions, action, i, l;
-
- for (i=0, l=eventNames.length; i<l; i++) {
- eventName = eventNames[i];
- actions = actionsFor(obj, eventName);
- var actionIndex = indexOf(actions, target, method);
-
- if (actionIndex !== -1) {
- action = actions[actionIndex].slice();
- action[3] = true;
- actions[actionIndex] = action;
- suspendedActions.push(action);
- }
- }
-
- function tryable() { return callback.call(target); }
-
- function finalizer() {
- for (i = 0, l = suspendedActions.length; i < l; i++) {
- suspendedActions[i][3] = undefined;
- }
- }
-
- return Ember.tryFinally(tryable, finalizer);
-}
-
/**
- @private
-
- Return a list of currently watched events
-
- @method watchedEvents
- @for Ember
+ @method removeBeforeObserver
@param obj
+ @param {String} path
+ @param {Object|Function} targetOrMethod
+ @param {Function|String} [method]
*/
-function watchedEvents(obj) {
- var listeners = obj[META_KEY].listeners, ret = [];
-
- if (listeners) {
- for(var eventName in listeners) {
- if (listeners[eventName]) { ret.push(eventName); }
- }
- }
- return ret;
-}
-
-/**
- @method sendEvent
- @for Ember
- @param obj
- @param {String} eventName
- @param {Array} params
- @param {Array} actions
- @return true
-*/
-function sendEvent(obj, eventName, params, actions) {
- // first give object a chance to handle it
- if (obj !== Ember && 'function' === typeof obj.sendEvent) {
- obj.sendEvent(eventName, params);
- }
-
- if (!actions) {
- var meta = obj[META_KEY];
- actions = meta && meta.listeners && meta.listeners[eventName];
- }
-
- if (!actions) { return; }
-
- for (var i = actions.length - 1; i >= 0; i--) { // looping in reverse for once listeners
- if (!actions[i] || actions[i][3] === true) { continue; }
-
- var target = actions[i][0],
- method = actions[i][1],
- once = actions[i][2];
-
- if (once) { removeListener(obj, eventName, target, method); }
- if (!target) { target = obj; }
- if ('string' === typeof method) { method = target[method]; }
- if (params) {
- method.apply(target, params);
- } else {
- method.call(target);
- }
- }
- return true;
-}
-
-/**
- @private
- @method hasListeners
- @for Ember
- @param obj
- @param {String} eventName
-*/
-function hasListeners(obj, eventName) {
- var meta = obj[META_KEY],
- actions = meta && meta.listeners && meta.listeners[eventName];
-
- return !!(actions && actions.length);
-}
-
-/**
- @private
- @method listenersFor
- @for Ember
- @param obj
- @param {String} eventName
-*/
-function listenersFor(obj, eventName) {
- var ret = [];
- var meta = obj[META_KEY],
- actions = meta && meta.listeners && meta.listeners[eventName];
-
- if (!actions) { return ret; }
-
- for (var i = 0, l = actions.length; i < l; i++) {
- var target = actions[i][0],
- method = actions[i][1];
- ret.push([target, method]);
- }
-
- return ret;
-}
-
-Ember.addListener = addListener;
-Ember.removeListener = removeListener;
-Ember._suspendListener = suspendListener;
-Ember._suspendListeners = suspendListeners;
-Ember.sendEvent = sendEvent;
-Ember.hasListeners = hasListeners;
-Ember.watchedEvents = watchedEvents;
-Ember.listenersFor = listenersFor;
-Ember.listenersDiff = actionsDiff;
-Ember.listenersUnion = actionsUnion;
-
+Ember.removeBeforeObserver = function(obj, path, target, method) {
+ Ember.unwatch(obj, path);
+ Ember.removeListener(obj, beforeEvent(path), target, method);
+ return this;
+};
})();
(function() {
@@ -4989,12 +5129,13 @@
(function() {
// Ember.Logger
-// get, set, trySet
-// guidFor, isArray, meta
+// get
+// set
+// guidFor, meta
// addObserver, removeObserver
// Ember.run.schedule
/**
@module ember-metal
*/
@@ -5016,13 +5157,26 @@
Ember.LOG_BINDINGS = false || !!Ember.ENV.LOG_BINDINGS;
var get = Ember.get,
set = Ember.set,
guidFor = Ember.guidFor,
- isGlobalPath = Ember.isGlobalPath;
+ IS_GLOBAL = /^([A-Z$]|([0-9][A-Z$]))/;
+/**
+ Returns true if the provided path is global (e.g., `MyApp.fooController.bar`)
+ instead of local (`foo.bar.baz`).
+ @method isGlobalPath
+ @for Ember
+ @private
+ @param {String} path
+ @return Boolean
+*/
+var isGlobalPath = Ember.isGlobalPath = function(path) {
+ return IS_GLOBAL.test(path);
+};
+
function getWithGlobals(obj, path) {
return get(isGlobalPath(path) ? Ember.lookup : obj, path);
}
// ..........................................................
@@ -5609,11 +5763,11 @@
descs[key] = undefined;
values[key] = value;
}
}
-function mergeMixins(mixins, m, descs, values, base) {
+function mergeMixins(mixins, m, descs, values, base, keys) {
var mixin, props, key, concats, meta;
function removeKeys(keyName) {
delete descs[keyName];
delete values[keyName];
@@ -5630,17 +5784,18 @@
meta = Ember.meta(base);
concats = concatenatedProperties(props, values, base);
for (key in props) {
if (!props.hasOwnProperty(key)) { continue; }
+ keys.push(key);
addNormalizedProperty(base, key, props[key], meta, descs, values, concats);
}
// manually copy toString() because some JS engines do not enumerate it
if (props.hasOwnProperty('toString')) { base.toString = props.toString; }
} else if (mixin.mixins) {
- mergeMixins(mixin.mixins, m, descs, values, base);
+ mergeMixins(mixin.mixins, m, descs, values, base, keys);
if (mixin._without) { a_forEach.call(mixin._without, removeKeys); }
}
}
}
@@ -5732,22 +5887,23 @@
updateObservers(obj, key, observer, '__ember_observes__', 'addObserver');
}
function applyMixin(obj, mixins, partial) {
var descs = {}, values = {}, m = Ember.meta(obj),
- key, value, desc;
+ key, value, desc, keys = [];
// Go through all mixins and hashes passed in, and:
//
// * Handle concatenated properties
// * Set up _super wrapping if necessary
// * Set up computed property descriptors
// * Copying `toString` in broken browsers
- mergeMixins(mixins, mixinsMeta(obj), descs, values, obj);
+ mergeMixins(mixins, mixinsMeta(obj), descs, values, obj, keys);
- for(key in values) {
- if (key === 'contructor' || !values.hasOwnProperty(key)) { continue; }
+ for(var i = 0, l = keys.length; i < l; i++) {
+ key = keys[i];
+ if (key === 'constructor' || !values.hasOwnProperty(key)) { continue; }
desc = descs[key];
value = values[key];
if (desc === REQUIRED) { continue; }
@@ -5797,11 +5953,11 @@
},
isEditing: false
});
// Mix mixins into classes by passing them as the first arguments to
- // .extend or .create.
+ // .extend.
App.CommentView = Ember.View.extend(App.Editable, {
template: Ember.Handlebars.compile('{{#if isEditing}}...{{else}}...{{/if}}')
});
commentView = App.CommentView.create();
@@ -5816,10 +5972,16 @@
*/
Ember.Mixin = function() { return initMixin(this, arguments); };
Mixin = Ember.Mixin;
+Mixin.prototype = {
+ properties: null,
+ mixins: null,
+ ownerConstructor: null
+};
+
Mixin._apply = applyMixin;
Mixin.applyPartial = function(obj) {
var args = a_slice.call(arguments, 1);
return applyMixin(obj, args, true);
@@ -6706,84 +6868,11 @@
@submodule ember-runtime
*/
var indexOf = Ember.EnumerableUtils.indexOf;
-// ........................................
-// TYPING & ARRAY MESSAGING
-//
-
-var TYPE_MAP = {};
-var t = "Boolean Number String Function Array Date RegExp Object".split(" ");
-Ember.ArrayPolyfills.forEach.call(t, function(name) {
- TYPE_MAP[ "[object " + name + "]" ] = name.toLowerCase();
-});
-
-var toString = Object.prototype.toString;
-
/**
- Returns a consistent type for the passed item.
-
- Use this instead of the built-in `typeof` to get the type of an item.
- It will return the same result across all browsers and includes a bit
- more detail. Here is what will be returned:
-
- | Return Value | Meaning |
- |---------------|------------------------------------------------------|
- | 'string' | String primitive |
- | 'number' | Number primitive |
- | 'boolean' | Boolean primitive |
- | 'null' | Null value |
- | 'undefined' | Undefined value |
- | 'function' | A function |
- | 'array' | An instance of Array |
- | 'class' | An Ember class (created using Ember.Object.extend()) |
- | 'instance' | An Ember object instance |
- | 'error' | An instance of the Error object |
- | 'object' | A JavaScript object not inheriting from Ember.Object |
-
- Examples:
-
- ```javascript
- Ember.typeOf(); // 'undefined'
- Ember.typeOf(null); // 'null'
- Ember.typeOf(undefined); // 'undefined'
- Ember.typeOf('michael'); // 'string'
- Ember.typeOf(101); // 'number'
- Ember.typeOf(true); // 'boolean'
- Ember.typeOf(Ember.makeArray); // 'function'
- Ember.typeOf([1,2,90]); // 'array'
- Ember.typeOf(Ember.Object.extend()); // 'class'
- Ember.typeOf(Ember.Object.create()); // 'instance'
- Ember.typeOf(new Error('teamocil')); // 'error'
-
- // "normal" JavaScript object
- Ember.typeOf({a: 'b'}); // 'object'
- ```
-
- @method typeOf
- @for Ember
- @param {Object} item the item to check
- @return {String} the type
-*/
-Ember.typeOf = function(item) {
- var ret;
-
- ret = (item === null || item === undefined) ? String(item) : TYPE_MAP[toString.call(item)] || 'object';
-
- if (ret === 'function') {
- if (Ember.Object && Ember.Object.detect(item)) ret = 'class';
- } else if (ret === 'object') {
- if (item instanceof Error) ret = 'error';
- else if (Ember.Object && item instanceof Ember.Object) ret = 'instance';
- else ret = 'object';
- }
-
- return ret;
-};
-
-/**
This will compare two javascript values of possibly different types.
It will tell you which one is greater than the other by returning:
- -1 if the first is smaller than the second,
- 0 if both are equal,
@@ -8540,11 +8629,11 @@
arr.objectAt(5); // undefined
```
@method objectAt
@param {Number} idx The index of the item to return.
- @return {any} item at index or undefined
+ @return {*} item at index or undefined
*/
objectAt: function(idx) {
if ((idx < 0) || (idx>=get(this, 'length'))) return undefined ;
return get(this, idx);
},
@@ -9349,12 +9438,12 @@
colors.pushObject("black"); // ["red", "green", "blue", "black"]
colors.pushObject(["yellow", "orange"]); // ["red", "green", "blue", "black", ["yellow", "orange"]]
```
@method pushObject
- @param {anything} obj object to push
- @return {any} the same obj passed as param
+ @param {*} obj object to push
+ @return {*} the same obj passed as param
*/
pushObject: function(obj) {
this.insertAt(get(this, 'length'), obj) ;
return obj ;
},
@@ -9429,12 +9518,12 @@
colors.unshiftObject("yellow"); // ["yellow", "red", "green", "blue"]
colors.unshiftObject(["black", "white"]); // [["black", "white"], "yellow", "red", "green", "blue"]
```
@method unshiftObject
- @param {anything} obj object to unshift
- @return {any} the same obj passed as param
+ @param {*} obj object to unshift
+ @return {*} the same obj passed as param
*/
unshiftObject: function(obj) {
this.insertAt(0, obj) ;
return obj ;
},
@@ -10391,10 +10480,13 @@
var concatenatedProperties = this.concatenatedProperties;
for (var i = 0, l = props.length; i < l; i++) {
var properties = props[i];
+
+ Ember.assert("Ember.Object.create no longer supports mixing in other definitions, use createWithMixins instead.", !(properties instanceof Ember.Mixin));
+
for (var keyName in properties) {
if (!properties.hasOwnProperty(keyName)) { continue; }
var value = properties[keyName],
IS_BINDING = Ember.IS_BINDING;
@@ -10512,12 +10604,10 @@
`Ember.ArrayController`, be sure to call `this._super()` in your
`init` declaration! If you don't, Ember may not have an opportunity to
do important setup work, and you'll see strange behavior in your
application.
- ```
-
@method init
*/
init: function() {},
/**
@@ -10624,28 +10714,28 @@
if (this._didCallDestroy) { return; }
this.isDestroying = true;
this._didCallDestroy = true;
- if (this.willDestroy) { this.willDestroy(); }
-
schedule('destroy', this, this._scheduledDestroy);
return this;
},
+ willDestroy: Ember.K,
+
/**
@private
Invoked by the run loop to actually destroy the object. This is
scheduled for execution by the `destroy` method.
@method _scheduledDestroy
*/
_scheduledDestroy: function() {
+ if (this.willDestroy) { this.willDestroy(); }
destroy(this);
- set(this, 'isDestroyed', true);
-
+ this.isDestroyed = true;
if (this.didDestroy) { this.didDestroy(); }
},
bind: function(to, from) {
if (!(from instanceof Ember.Binding)) { from = Ember.Binding.from(from); }
@@ -11677,11 +11767,11 @@
You can directly access mapped properties by simply requesting them.
The `unknownProperty` handler will generate an EachArray of each item.
@method unknownProperty
@param keyName {String}
- @param value {anything}
+ @param value {*}
*/
unknownProperty: function(keyName, value) {
var ret;
ret = new EachArray(this._content, keyName, this);
Ember.defineProperty(this, keyName, null, ret);
@@ -12965,12 +13055,12 @@
// calling `objectAt`
this._super(idx, removedCnt, addedCnt);
},
init: function() {
- this._super();
if (!this.get('content')) { Ember.defineProperty(this, 'content', undefined, Ember.A()); }
+ this._super();
this.set('_subControllers', Ember.A());
},
controllerAt: function(idx, object, controllerClass) {
var container = get(this, 'container'),
@@ -13274,19 +13364,21 @@
return new Ember._RenderBuffer(tagName);
};
Ember._RenderBuffer = function(tagName) {
this.tagNames = [tagName || null];
- this.buffer = [];
+ this.buffer = "";
};
Ember._RenderBuffer.prototype =
/** @scope Ember.RenderBuffer.prototype */ {
// The root view's element
_element: null,
+ _hasElement: true,
+
/**
@private
An internal set used to de-dupe class names when `addClass()` is
used. After each call to `addClass()`, the `classes` property
@@ -13398,11 +13490,11 @@
@method push
@param {String} string HTML to push into the buffer
@chainable
*/
push: function(string) {
- this.buffer.push(string);
+ this.buffer += string;
return this;
},
/**
Adds a class to the buffer, which will be rendered to the class attribute.
@@ -13531,11 +13623,11 @@
pushOpeningTag: function() {
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;
}
var buffer = this.buffer,
@@ -13544,39 +13636,39 @@
attrs = this.elementAttributes,
props = this.elementProperties,
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;
}
if (attrs) {
for (attr in attrs) {
if (attrs.hasOwnProperty(attr)) {
- buffer.push(' ' + attr + '="' + this._escapeAttribute(attrs[attr]) + '"');
+ buffer += ' ' + attr + '="' + this._escapeAttribute(attrs[attr]) + '"';
}
}
this.elementAttributes = null;
}
@@ -13585,27 +13677,28 @@
for (prop in props) {
if (props.hasOwnProperty(prop)) {
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]) + '"';
}
}
}
}
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() {
return this.tagNames[this.tagNames.length-1];
},
@@ -13685,21 +13778,21 @@
@method string
@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());
} else {
return this.innerString();
}
},
innerString: function() {
- return this.buffer.join('');
+ return this.buffer;
},
_escapeAttribute: function(value) {
// Stolen shamelessly from Handlebars
@@ -14058,22 +14151,10 @@
states: states,
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');
},
/**
If the view is currently inserted into the DOM of a parent view, this
@@ -14099,11 +14180,11 @@
// return the current view, not including virtual views
concreteView: Ember.computed(function() {
if (!this.isVirtual) { return this; }
else { return get(this, 'parentView'); }
- }).property('parentView').volatile(),
+ }).property('parentView'),
instrumentName: 'core_view',
instrumentDetails: function(hash) {
hash.object = this.toString();
@@ -14137,12 +14218,10 @@
return this._renderToBuffer(parentBuffer, bufferOperation);
}, this);
},
_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
// insert a new buffer after the "parent buffer").
var tagName = this.tagName;
@@ -14184,35 +14263,89 @@
has: function(name) {
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(); }
// 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');
+ this.transitionTo('destroying', false);
- // next remove view from global hash
- if (!this.isVirtual) delete Ember.View.views[this.elementId];
+ return this;
},
clearRenderedChildren: Ember.K,
triggerRecursively: Ember.K,
invokeRecursively: Ember.K,
transitionTo: Ember.K,
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);
+ }
+ },
+
+ 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() {
+ var views = this.views;
+ return views.forEach.apply(views, arguments);
+ },
+
+ 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
DOM, and registering and responding to user-initiated events.
@@ -14836,18 +14969,10 @@
Ember.assert("You specified the templateName " + templateName + " for " + this + ", but it did not exist.", !templateName || template);
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.
@property controller
@@ -14881,18 +15006,13 @@
return layout || get(this, 'defaultLayout');
}).property('layoutName'),
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);
},
/**
The object from which templates should access properties.
@@ -14979,11 +15099,11 @@
@type Array
@default []
*/
childViews: childViewsProperty,
- _childViews: [],
+ _childViews: EMPTY_ARRAY,
// When it's a virtual view, we need to notify the parent that their
// childViews will change.
_childViewsWillChange: Ember.beforeObserver(function() {
if (this.isVirtual) {
@@ -15580,17 +15700,19 @@
willClearRender: Ember.K,
/**
@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();
childViews = [];
@@ -15619,10 +15741,23 @@
}
}
}
},
+ viewHierarchyCollection: function() {
+ var currentView, viewCollection = new ViewCollection([this]);
+
+ for (var i = 0; i < viewCollection.length; i++) {
+ currentView = viewCollection.objectAt(i);
+ if (currentView._childViews) {
+ viewCollection.push.apply(viewCollection, currentView._childViews);
+ }
+ }
+
+ return viewCollection;
+ },
+
/**
Destroys any existing element along with the element for any child views
as well. If the view does not currently have a element, then this method
will do nothing.
@@ -15663,12 +15798,14 @@
`willClearRender` event recursively.
@method _notifyWillDestroyElement
*/
_notifyWillDestroyElement: function() {
- this.triggerRecursively('willClearRender');
- this.triggerRecursively('willDestroyElement');
+ var viewCollection = this.viewHierarchyCollection();
+ viewCollection.trigger('willClearRender');
+ viewCollection.trigger('willDestroyElement');
+ return viewCollection;
},
_elementWillChange: Ember.beforeObserver(function() {
this.forEachChildView(function(view) {
Ember.propertyWillChange(view, 'element');
@@ -15710,12 +15847,12 @@
this.lengthAfterRender = this._childViews.length;
return buffer;
},
- renderToBufferIfNeeded: function () {
- return this.currentState.renderToBufferIfNeeded(this, this);
+ renderToBufferIfNeeded: function (buffer) {
+ return this.currentState.renderToBufferIfNeeded(this, buffer);
},
beforeRender: function(buffer) {
this.applyAttributesToBuffer(buffer);
buffer.pushOpeningTag();
@@ -15839,11 +15976,11 @@
@property classNameBindings
@type Array
@default []
*/
- classNameBindings: [],
+ classNameBindings: EMPTY_ARRAY,
/**
A list of properties of the view to apply as attributes. If the property is
a string value, the value of that string will be applied as the attribute.
@@ -15867,11 +16004,11 @@
});
```
@property attributeBindings
*/
- attributeBindings: [],
+ attributeBindings: EMPTY_ARRAY,
// .......................................................
// CORE DISPLAY METHODS
//
@@ -15977,50 +16114,38 @@
You must call `destroy` on a view to destroy the view (and all of its
child views). This will remove the view from any parent node, then make
sure that the DOM element managed by the view can be released by the
memory manager.
- @method willDestroy
+ @method destroy
*/
- willDestroy: function() {
- // calling this._super() will nuke computed properties and observers,
- // so collect any information we need before calling super.
+ destroy: function() {
var childViews = this._childViews,
parent = this._parentView,
+ // get parentView before calling super because it'll be destroyed
+ nonVirtualParentView = get(this, 'parentView'),
+ viewName = this.viewName,
childLen, i;
- // destroy the element -- this will avoid each child view destroying
- // the element over and over again...
- if (!this.removedFromDOM) { this.destroyElement(); }
+ if (!this._super()) { return; }
childLen = childViews.length;
for (i=childLen-1; i>=0; i--) {
childViews[i].removedFromDOM = true;
}
// 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[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;
},
/**
Instantiates a view to be added to the childViews array during view
initialization. You generally will not call this method directly unless
@@ -16037,10 +16162,11 @@
if (view.isView && view._parentView === this) { return view; }
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);
// don't set the property on a virtual view, as they are invisible to
@@ -16131,13 +16257,17 @@
view.buffer = null;
});
},
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);
});
}
@@ -16477,19 +16607,22 @@
Ember.merge(preRender, {
// a view leaves the preRender state once its element has been
// 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,
setElement: function(view, value) {
@@ -16532,14 +16665,15 @@
// when a view is rendered in a buffer, appending a child
// 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);
view.propertyDidChange('childViews');
@@ -16549,22 +16683,22 @@
// when a view is rendered in a buffer, destroying the
// element will simply destroy the buffer and put the
// 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;
},
empty: function() {
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
// insertion.
insertElement: function() {
@@ -16679,10 +16813,27 @@
});
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";
}
});
@@ -16694,34 +16845,34 @@
/**
@module ember
@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
// the view has been destroyed between scheduling and execution
insertElement: Ember.K
@@ -16736,11 +16887,11 @@
Ember.View.cloneStates = function(from) {
var into = {};
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);
for (var stateName in from) {
@@ -16763,10 +16914,11 @@
@submodule ember-views
*/
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`
allowing programatic management of its child views.
@@ -16967,10 +17119,11 @@
_childViews[idx] = view;
}, this);
var currentView = get(this, 'currentView');
if (currentView) {
+ if (!_childViews.length) { _childViews = this._childViews = this._childViews.slice(); }
_childViews.push(this.createChildView(currentView));
}
},
replace: function(idx, removedCount, addedViews) {
@@ -16981,10 +17134,11 @@
if (addedCount === 0) {
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);
}
this.arrayContentDidChange(idx, removedCount, addedCount);
this.childViewsDidChange(this._childViews, idx, removedCount, addedCount);
@@ -17125,27 +17279,44 @@
childViewsDidChange: function(view, views, start, added) {
Ember.run.scheduleOnce('render', view, '_ensureChildrenAreInDOM');
},
ensureChildrenAreInDOM: function(view) {
- var childViews = view._childViews, i, len, childView, previous, buffer;
+ var childViews = view._childViews, i, len, childView, previous, buffer, viewCollection = new ViewCollection();
+
+ function insertViewCollection() {
+ viewCollection.triggerRecursively('willInsertElement');
+ if (previous) {
+ previous.domManager.after(previous, buffer.string());
+ } else {
+ view.domManager.prepend(view, buffer.string());
+ }
+ buffer = null;
+ viewCollection.forEach(function(v) {
+ v.transitionTo('inDOM');
+ v.propertyDidChange('element');
+ v.triggerRecursively('didInsertElement');
+ });
+ }
+
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();
+ previous = childView;
+ viewCollection.clear();
+ } else {
+ previous = childView;
}
- previous = childView;
}
+
+ if (viewCollection.length) { insertViewCollection(); }
}
});
})();
@@ -17385,19 +17556,21 @@
var len = content ? get(content, 'length') : 0;
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) {
// If the contents were empty before and this template collection has an
// empty view remove it now.
@@ -17415,15 +17588,17 @@
var removingAll = removedCount === len;
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();
}
},
/**
@@ -18028,10 +18203,21 @@
@class Handlebars
@namespace Ember
*/
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
*/
Ember.Handlebars.helpers = objectCreate(Handlebars.helpers);
@@ -18861,11 +19047,11 @@
},
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.");
case 'hasElement':
case 'inDOM':
@@ -18892,11 +19078,11 @@
rerenderIfNeeded: Ember.K
});
merge(states.inDOM, {
rerenderIfNeeded: function(view) {
- if (get(view, 'normalizedValue') !== view._lastNormalizedValue) {
+ if (view.normalizedValue() !== view._lastNormalizedValue) {
view.rerender();
}
}
});
@@ -18996,11 +19182,11 @@
@property pathRoot
@type Object
*/
pathRoot: null,
- normalizedValue: Ember.computed(function() {
+ normalizedValue: function() {
var path = get(this, 'path'),
pathRoot = get(this, 'pathRoot'),
valueNormalizer = get(this, 'valueNormalizerFunc'),
result, templateData;
@@ -19014,11 +19200,11 @@
templateData = get(this, 'templateData');
result = handlebarsGet(pathRoot, path, { data: templateData });
}
return valueNormalizer ? valueNormalizer(result) : result;
- }).property('path', 'pathRoot', 'valueNormalizerFunc').volatile(),
+ },
rerenderIfNeeded: function() {
this.currentState.rerenderIfNeeded(this);
},
@@ -19049,11 +19235,11 @@
context = get(this, 'previousContext');
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
// render the template or not.
if (shouldDisplay(result)) {
@@ -19112,10 +19298,14 @@
var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath;
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) {
var data = options.data,
fn = options.fn,
@@ -19290,13 +19480,11 @@
if (!options.fn) {
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);
});
/**
@private
@@ -19364,13 +19552,11 @@
// if the path is '' ("this"), just bind directly to the current context
var contextPath = path ? contextKey + '.' + path : contextKey;
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);
return helpers.bind.call(options.contexts[0], context, options);
}
@@ -20471,18 +20657,20 @@
}
return view;
},
- willDestroy: function() {
+ destroy: function() {
+ if (!this._super()) { return; }
+
var arrayController = get(this, '_arrayController');
if (arrayController) {
arrayController.destroy();
}
- return this._super();
+ return this;
}
});
var GroupedEach = Ember.Handlebars.GroupedEach = function(context, path, options) {
var self = this,
@@ -20795,10 +20983,11 @@
<script type="text/x-handlebars" data-template-name="header_bar">
{{#with currentUser}}
{{partial user_info}}
{{/with}}
</script>
+ ```
The `data-template-name` attribute of a partial template
is prefixed with an underscore.
```html
@@ -21083,11 +21272,11 @@
## HTML Attributes
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
`Ember.TextField` or `Ember.TextSupport`.
@@ -21158,10 +21347,24 @@
@default null
*/
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.
By default, when the user presses the return key on their keyboard and
the text field has an `action` set, the action will be sent to the view's
@@ -21175,23 +21378,34 @@
@default false
*/
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();
+ }
+ }
+}
})();
(function() {
@@ -21931,11 +22145,11 @@
} else {
// Primitives get passed through bindings as objects... since
// `new Number(4) !== 4`, we use `==` below
return content == selection;
}
- }).property('content', 'parentView.selection').volatile(),
+ }).property('content', 'parentView.selection'),
labelPathDidChange: Ember.observer(function() {
var labelPath = get(this, 'parentView.optionLabelPath');
if (!labelPath) { return; }
@@ -21959,11 +22173,50 @@
})();
(function() {
+function normalizeHash(hash, hashTypes) {
+ for (var prop in hash) {
+ if (hashTypes[prop] === 'ID') {
+ hash[prop + 'Binding'] = hash[prop];
+ delete hash[prop];
+ }
+ }
+}
+Ember.Handlebars.registerHelper('input', function(options) {
+ Ember.assert('You can only pass attributes to the `input` helper, not arguments', arguments.length < 2);
+
+ var hash = options.hash,
+ types = options.hashTypes,
+ inputType = hash.type,
+ onEvent = hash.on;
+
+ delete hash.type;
+ delete hash.on;
+
+ normalizeHash(hash, types);
+
+ if (inputType === 'checkbox') {
+ return Ember.Handlebars.helpers.view.call(this, Ember.Checkbox, options);
+ } else {
+ hash.type = inputType;
+ hash.onEvent = onEvent || 'enter';
+ return Ember.Handlebars.helpers.view.call(this, Ember.TextField, options);
+ }
+});
+
+Ember.Handlebars.registerHelper('textarea', function(options) {
+ Ember.assert('You can only pass attributes to the `input` helper, not arguments', arguments.length < 2);
+
+ var hash = options.hash,
+ types = options.hashTypes;
+
+ normalizeHash(hash, types);
+ return Ember.Handlebars.helpers.view.call(this, Ember.TextArea, options);
+});
})();
(function() {
@@ -23685,11 +23938,15 @@
*/
setup: function(context) {
this.redirected = false;
this._checkingRedirect = true;
- this.redirect(context);
+ if (context === undefined) {
+ this.redirect();
+ } else {
+ this.redirect(context);
+ }
this._checkingRedirect = false;
if (this.redirected) { return false; }
var controller = this.controllerFor(this.routeName, context);
@@ -24101,10 +24358,14 @@
var parentView = route.router._lookupActiveView(options.into);
route.teardownView = teardownOutlet(parentView, options.outlet);
parentView.connectOutlet(options.outlet, view);
} else {
var rootElement = get(route, 'router.namespace.rootElement');
+ // tear down view if one is already rendered
+ if (route.teardownView) {
+ route.teardownView();
+ }
route.router._connectActiveView(options.name, view);
route.teardownView = teardownTopLevel(view);
view.appendTo(rootElement);
}
}
@@ -24230,11 +24491,11 @@
// Even though this isn't a virtual view, we want to treat it as if it is
// so that you can access the parent with {{view.prop}}
concreteView: Ember.computed(function() {
return get(this, 'parentView');
- }).property('parentView').volatile(),
+ }).property('parentView'),
active: Ember.computed(function() {
var router = this.get('router'),
params = resolvedPaths(this.parameters),
currentWithIndex = this.currentWhen + '.index',
@@ -25318,11 +25579,11 @@
Ember.$(window).bind('popstate.ember-location-'+guid, function(e) {
// Ignore initial page load popstate event in Chrome
if(!popstateFired) {
popstateFired = true;
- if (self.getURL() === self._initialUrl) { return; }
+ if (self.formatURL(self.getURL()) === self._initialUrl) { return; }
}
callback(self.getURL());
});
},
@@ -25700,14 +25961,11 @@
/**
@module ember
@submodule ember-application
*/
-var get = Ember.get, set = Ember.set,
- classify = Ember.String.classify,
- capitalize = Ember.String.capitalize,
- decamelize = Ember.String.decamelize;
+var get = Ember.get, set = Ember.set;
/**
An instance of `Ember.Application` is the starting point for every Ember
application. It helps to instantiate, initialize and coordinate the many
objects that make up your app.
@@ -26357,11 +26615,11 @@
This allows the application to register default injections in the container
that could be overridden by the normal naming convention.
@param {Ember.Namespace} namespace the namespace to look for classes
- @return {any} the resolved value for a given lookup
+ @return {*} the resolved value for a given lookup
*/
function resolverFor(namespace) {
var resolverClass = namespace.get('resolver') || Ember.DefaultResolver;
var resolver = resolverClass.create({
namespace: namespace
@@ -27739,11 +27997,11 @@
})();
})();
-// Version: v1.0.0-rc.1-275-g9104bf1
-// Last commit: 9104bf1 (2013-03-29 13:59:19 -0700)
+// Version: v1.0.0-rc.1-354-g4e75d2b
+// Last commit: 4e75d2b (2013-04-09 16:28:08 -0700)
(function() {
/**
Ember