dist/ember.prod.js in ember-source-1.0.0.rc3.5 vs dist/ember.prod.js in ember-source-1.0.0.rc4
- old
+ new
@@ -10,16 +10,23 @@
requireModule = function(name) {
if (seen[name]) { return seen[name]; }
seen[name] = {};
- var mod = registry[name],
- deps = mod.deps,
- callback = mod.callback,
- reified = [],
- exports;
+ var mod, deps, callback, reified, exports;
+ mod = registry[name];
+
+ if (!mod) {
+ throw new Error("Module '" + name + "' not found.");
+ }
+
+ deps = mod.deps;
+ callback = mod.callback;
+ reified = [];
+ exports;
+
for (var i=0, l=deps.length; i<l; i++) {
if (deps[i] === 'exports') {
reified.push(exports = {});
} else {
reified.push(requireModule(deps[i]));
@@ -53,11 +60,11 @@
The core Runtime framework is based on the jQuery API with a number of
performance optimizations.
@class Ember
@static
- @version 1.0.0-rc.3
+ @version 1.0.0-rc4
*/
if ('undefined' === typeof Ember) {
// Create core object. Make it act like an instance of Ember.Namespace so that
// objects assigned to it are given a sane string representation.
@@ -80,14 +87,14 @@
/**
@property VERSION
@type String
- @default '1.0.0-rc.3'
+ @default '1.0.0-rc4'
@final
*/
-Ember.VERSION = '1.0.0-rc.3';
+Ember.VERSION = '1.0.0-rc4';
/**
Standard environmental variables. You can define these in a global `ENV`
variable before loading Ember to control various configuration
settings.
@@ -232,11 +239,11 @@
Ember.onerror = null;
/**
@private
- Wrap code block in a try/catch if {{#crossLink "Ember/onerror"}}{{/crossLink}} is set.
+ Wrap code block in a try/catch if `Ember.onerror` is set.
@method handleErrors
@for Ember
@param {Function} func
@param [context]
@@ -308,11 +315,11 @@
@for Ember
@param {Object} obj Value to test
@return {Boolean}
*/
Ember.isEmpty = function(obj) {
- return obj === null || obj === undefined || (obj.length === 0 && typeof obj !== 'function') || (typeof obj === 'object' && Ember.get(obj, 'length') === 0);
+ return Ember.isNone(obj) || (obj.length === 0 && typeof obj !== 'function') || (typeof obj === 'object' && Ember.get(obj, 'length') === 0);
};
Ember.empty = Ember.deprecateFunc("Ember.empty is deprecated. Please use Ember.isEmpty instead.", Ember.isEmpty) ;
})();
@@ -341,10 +348,17 @@
@method create
@for Ember
*/
Ember.create = Object.create;
+// IE8 has Object.create but it couldn't treat property descripters.
+if (Ember.create) {
+ if (Ember.create({a: 1}, {a: {value: 2}}).a !== 2) {
+ Ember.create = null;
+ }
+}
+
// STUB_OBJECT_CREATE allows us to override other libraries that stub
// Object.create different than we would prefer
if (!Ember.create || Ember.ENV.STUB_OBJECT_CREATE) {
var K = function() {};
@@ -661,11 +675,11 @@
// special cases where we don't want to add a key to object
if (obj === undefined) return "(undefined)";
if (obj === null) return "(null)";
- var cache, ret;
+ var ret;
var type = typeof obj;
// Don't allow prototype changes to String etc. to change the guidFor
switch(type) {
case 'number':
@@ -810,10 +824,11 @@
meta[property] = value;
return value;
};
/**
+ @deprecated
@private
In order to store defaults for a class, a prototype may need to create
a default meta object, which will be inherited by any objects instantiated
from the class's constructor.
@@ -842,10 +857,11 @@
@param {Boolean} writable whether or not to create a new meta
(or meta property) if one does not already exist or if it's
shared with its constructor
*/
Ember.metaPath = function metaPath(obj, path, writable) {
+
var meta = Ember.meta(obj, writable), keyName, value;
for (var i=0, l=path.length; i<l; i++) {
keyName = path[i];
value = meta[keyName];
@@ -1062,11 +1078,11 @@
unless that value is undefined, in which case it is the return value
of the tryable.
*/
if (needsFinallyFix) {
Ember.tryCatchFinally = function(tryable, catchable, finalizer, binding) {
- var result, finalResult, finalError, finalReturn;
+ var result, finalResult, finalError;
binding = binding || this;
try {
result = tryable.call(binding);
@@ -1172,10 +1188,11 @@
else ret = 'object';
}
return ret;
};
+
})();
(function() {
@@ -1418,375 +1435,10 @@
(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
- via `Ember.guidFor`, we can implement a performant Map with arbitrary
- keys. Because it is commonly used in low-level bookkeeping, Map is
- implemented as a pure JavaScript object for performance.
-
- This implementation follows the current iteration of the ES6 proposal for
- maps (http://wiki.ecmascript.org/doku.php?id=harmony:simple_maps_and_sets),
- with two exceptions. First, because we need our implementation to be pleasant
- on older browsers, we do not use the `delete` name (using `remove` instead).
- Second, as we do not have the luxury of in-VM iteration, we implement a
- forEach method for iteration.
-
- Map is mocked out to look like an Ember object, so you can do
- `Ember.Map.create()` for symmetry with other Ember classes.
-*/
-var guidFor = Ember.guidFor,
- indexOf = Ember.ArrayPolyfills.indexOf;
-
-var copy = function(obj) {
- var output = {};
-
- for (var prop in obj) {
- if (obj.hasOwnProperty(prop)) { output[prop] = obj[prop]; }
- }
-
- return output;
-};
-
-var copyMap = function(original, newObject) {
- var keys = original.keys.copy(),
- values = copy(original.values);
-
- newObject.keys = keys;
- newObject.values = values;
-
- return newObject;
-};
-
-/**
- This class is used internally by Ember and Ember Data.
- Please do not use it at this time. We plan to clean it up
- and add many tests soon.
-
- @class OrderedSet
- @namespace Ember
- @constructor
- @private
-*/
-var OrderedSet = Ember.OrderedSet = function() {
- this.clear();
-};
-
-/**
- @method create
- @static
- @return {Ember.OrderedSet}
-*/
-OrderedSet.create = function() {
- return new OrderedSet();
-};
-
-
-OrderedSet.prototype = {
- /**
- @method clear
- */
- clear: function() {
- this.presenceSet = {};
- this.list = [];
- },
-
- /**
- @method add
- @param obj
- */
- add: function(obj) {
- var guid = guidFor(obj),
- presenceSet = this.presenceSet,
- list = this.list;
-
- if (guid in presenceSet) { return; }
-
- presenceSet[guid] = true;
- list.push(obj);
- },
-
- /**
- @method remove
- @param obj
- */
- remove: function(obj) {
- var guid = guidFor(obj),
- presenceSet = this.presenceSet,
- list = this.list;
-
- delete presenceSet[guid];
-
- var index = indexOf.call(list, obj);
- if (index > -1) {
- list.splice(index, 1);
- }
- },
-
- /**
- @method isEmpty
- @return {Boolean}
- */
- isEmpty: function() {
- return this.list.length === 0;
- },
-
- /**
- @method has
- @param obj
- @return {Boolean}
- */
- has: function(obj) {
- var guid = guidFor(obj),
- presenceSet = this.presenceSet;
-
- return guid in presenceSet;
- },
-
- /**
- @method forEach
- @param {Function} fn
- @param self
- */
- forEach: function(fn, self) {
- // allow mutation during iteration
- var list = this.toArray();
-
- for (var i = 0, j = list.length; i < j; i++) {
- fn.call(self, list[i]);
- }
- },
-
- /**
- @method toArray
- @return {Array}
- */
- toArray: function() {
- return this.list.slice();
- },
-
- /**
- @method copy
- @return {Ember.OrderedSet}
- */
- copy: function() {
- var set = new OrderedSet();
-
- set.presenceSet = copy(this.presenceSet);
- set.list = this.toArray();
-
- return set;
- }
-};
-
-/**
- A Map stores values indexed by keys. Unlike JavaScript's
- default Objects, the keys of a Map can be any JavaScript
- object.
-
- Internally, a Map has two data structures:
-
- 1. `keys`: an OrderedSet of all of the existing keys
- 2. `values`: a JavaScript Object indexed by the `Ember.guidFor(key)`
-
- When a key/value pair is added for the first time, we
- add the key to the `keys` OrderedSet, and create or
- replace an entry in `values`. When an entry is deleted,
- we delete its entry in `keys` and `values`.
-
- @class Map
- @namespace Ember
- @private
- @constructor
-*/
-var Map = Ember.Map = function() {
- this.keys = Ember.OrderedSet.create();
- this.values = {};
-};
-
-/**
- @method create
- @static
-*/
-Map.create = function() {
- return new Map();
-};
-
-Map.prototype = {
- /**
- Retrieve the value associated with a given key.
-
- @method get
- @param {*} key
- @return {*} the value associated with the key, or `undefined`
- */
- get: function(key) {
- var values = this.values,
- guid = guidFor(key);
-
- return values[guid];
- },
-
- /**
- Adds a value to the map. If a value for the given key has already been
- provided, the new value will replace the old value.
-
- @method set
- @param {*} key
- @param {*} value
- */
- set: function(key, value) {
- var keys = this.keys,
- values = this.values,
- guid = guidFor(key);
-
- keys.add(key);
- values[guid] = value;
- },
-
- /**
- Removes a value from the map for an associated key.
-
- @method remove
- @param {*} key
- @return {Boolean} true if an item was removed, false otherwise
- */
- remove: function(key) {
- // don't use ES6 "delete" because it will be annoying
- // to use in browsers that are not ES6 friendly;
- var keys = this.keys,
- values = this.values,
- guid = guidFor(key);
-
- if (values.hasOwnProperty(guid)) {
- keys.remove(key);
- delete values[guid];
- return true;
- } else {
- return false;
- }
- },
-
- /**
- Check whether a key is present.
-
- @method has
- @param {*} key
- @return {Boolean} true if the item was present, false otherwise
- */
- has: function(key) {
- var values = this.values,
- guid = guidFor(key);
-
- return values.hasOwnProperty(guid);
- },
-
- /**
- Iterate over all the keys and values. Calls the function once
- for each key, passing in the key and value, in that order.
-
- The keys are guaranteed to be iterated over in insertion order.
-
- @method forEach
- @param {Function} callback
- @param {*} self if passed, the `this` value inside the
- callback. By default, `this` is the map.
- */
- forEach: function(callback, self) {
- var keys = this.keys,
- values = this.values;
-
- keys.forEach(function(key) {
- var guid = guidFor(key);
- callback.call(self, key, values[guid]);
- });
- },
-
- /**
- @method copy
- @return {Ember.Map}
- */
- copy: function() {
- return copyMap(this, new Map());
- }
-};
-
-/**
- @class MapWithDefault
- @namespace Ember
- @extends Ember.Map
- @private
- @constructor
- @param [options]
- @param {*} [options.defaultValue]
-*/
-var MapWithDefault = Ember.MapWithDefault = function(options) {
- Map.call(this);
- this.defaultValue = options.defaultValue;
-};
-
-/**
- @method create
- @static
- @param [options]
- @param {*} [options.defaultValue]
- @return {Ember.MapWithDefault|Ember.Map} If options are passed, returns
- `Ember.MapWithDefault` otherwise returns `Ember.Map`
-*/
-MapWithDefault.create = function(options) {
- if (options) {
- return new MapWithDefault(options);
- } else {
- return new Map();
- }
-};
-
-MapWithDefault.prototype = Ember.create(Map.prototype);
-
-/**
- Retrieve the value associated with a given key.
-
- @method get
- @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) {
- return Map.prototype.get.call(this, key);
- } else {
- var defaultValue = this.defaultValue(key);
- this.set(key, defaultValue);
- return defaultValue;
- }
-};
-
-/**
- @method copy
- @return {Ember.MapWithDefault}
-*/
-MapWithDefault.prototype.copy = function() {
- return copyMap(this, new MapWithDefault({
- defaultValue: this.defaultValue
- }));
-};
-
-})();
-
-
-
-(function() {
-/**
-@module ember-metal
-*/
-
var META_KEY = Ember.META_KEY, get;
var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
var IS_GLOBAL_PATH = /^([A-Z$]|([0-9][A-Z$])).*[\.\*]/;
@@ -1833,16 +1485,15 @@
if (!keyName && 'string'===typeof obj) {
keyName = obj;
obj = null;
}
- if (!obj || keyName.indexOf('.') !== -1) {
+ if (obj === null || keyName.indexOf('.') !== -1) {
return getPath(obj, keyName);
}
-
var meta = obj[META_KEY], desc = meta && meta.descs[keyName], ret;
if (desc) {
return desc.get(obj, keyName);
} else {
if (MANDATORY_SETTER && meta && meta.watching[keyName] > 0) {
@@ -1910,11 +1561,11 @@
tuple.length = 0;
}
parts = path.split(".");
len = parts.length;
- for (idx=0; root && idx<len; idx++) {
+ for (idx = 0; root != null && idx < len; idx++) {
root = get(root, parts[idx], true);
if (root && root.isDestroyed) { return undefined; }
}
return root;
};
@@ -1945,10 +1596,11 @@
};
Ember.get = get;
Ember.getPath = Ember.deprecateFunc('getPath is deprecated since get now supports paths', Ember.get);
+
})();
(function() {
@@ -1956,11 +1608,13 @@
@module ember-metal
*/
var o_create = Ember.create,
metaFor = Ember.meta,
- META_KEY = Ember.META_KEY;
+ META_KEY = Ember.META_KEY,
+ /* listener flags */
+ ONCE = 1, SUSPENDED = 2;
/*
The event system uses a series of nested hashes to store listeners on an
object. When a listener is registered, or when an event arrives, these
hashes are consulted to determine which target and action pair to invoke.
@@ -1969,11 +1623,11 @@
// Object's meta hash
{
listeners: { // variable name: `listenerSet`
"foo:changed": [ // variable name: `actions`
- [target, method, onceFlag, suspendedFlag]
+ [target, method, flags]
]
}
}
*/
@@ -2015,16 +1669,15 @@
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],
+ flags = actions[i][2],
actionIndex = indexOf(otherActions, target, method);
if (actionIndex === -1) {
- otherActions.push([target, method, once, suspended]);
+ otherActions.push([target, method, flags]);
}
}
}
function actionsDiff(obj, eventName, otherActions) {
@@ -2034,18 +1687,17 @@
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],
+ flags = actions[i][2],
actionIndex = indexOf(otherActions, target, method);
if (actionIndex !== -1) { continue; }
- otherActions.push([target, method, once, suspended]);
- diffActions.push([target, method, once, suspended]);
+ otherActions.push([target, method, flags]);
+ diffActions.push([target, method, flags]);
}
return diffActions;
}
@@ -2067,25 +1719,28 @@
method = target;
target = null;
}
var actions = actionsFor(obj, eventName),
- actionIndex = indexOf(actions, target, method);
+ actionIndex = indexOf(actions, target, method),
+ flags = 0;
+ if (once) flags |= ONCE;
+
if (actionIndex !== -1) { return; }
- actions.push([target, method, once, undefined]);
+ actions.push([target, method, flags]);
if ('function' === typeof obj.didAddListener) {
obj.didAddListener(eventName, target, method);
}
}
/**
Remove an event listener
- Arguments should match those passed to {{#crossLink "Ember/addListener"}}{{/crossLink}}
+ Arguments should match those passed to `Ember.addListener`.
@method removeListener
@for Ember
@param obj
@param {String} eventName
@@ -2098,11 +1753,11 @@
if (!method && 'function' === typeof target) {
method = target;
target = null;
}
- function _removeListener(target, method, once) {
+ function _removeListener(target, method) {
var actions = actionsFor(obj, eventName),
actionIndex = indexOf(actions, target, method);
// action doesn't exist, give up silently
if (actionIndex === -1) { return; }
@@ -2155,16 +1810,16 @@
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
+ action[2] |= SUSPENDED; // mark the action as suspended
actions[actionIndex] = action; // replace the shared object with our copy
}
function tryable() { return callback.call(target); }
- function finalizer() { if (action) { action[3] = undefined; } }
+ function finalizer() { if (action) { action[2] &= ~SUSPENDED; } }
return Ember.tryFinally(tryable, finalizer);
}
/**
@@ -2199,21 +1854,21 @@
actions = actionsFor(obj, eventName);
var actionIndex = indexOf(actions, target, method);
if (actionIndex !== -1) {
action = actions[actionIndex].slice();
- action[3] = true;
+ action[2] |= SUSPENDED;
actions[actionIndex] = action;
suspendedActions.push(action);
}
}
function tryable() { return callback.call(target); }
function finalizer() {
for (i = 0, l = suspendedActions.length; i < l; i++) {
- suspendedActions[i][3] = undefined;
+ suspendedActions[i][2] &= ~SUSPENDED;
}
}
return Ember.tryFinally(tryable, finalizer);
}
@@ -2259,17 +1914,15 @@
}
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); }
+ var action = actions[i];
+ if (!action) { continue; }
+ var target = action[0], method = action[1], flags = action[2];
+ if (flags & SUSPENDED) { continue; }
+ if (flags & ONCE) { removeListener(obj, eventName, target, method); }
if (!target) { target = obj; }
if ('string' === typeof method) { method = target[method]; }
if (params) {
method.apply(target, params);
} else {
@@ -2345,11 +1998,11 @@
{
sender: obj,
keyName: keyName,
eventName: eventName,
listeners: [
- [target, method, onceFlag, suspendedFlag]
+ [target, method, flags]
]
},
...
]
*/
@@ -2574,15 +2227,15 @@
obj1.set('foo', mayBlowUpWhenSet);
obj2.set('bar', baz);
});
```
- @method changePropertiess
+ @method changeProperties
@param {Function} callback
@param [binding]
*/
-var changeProperties = Ember.changeProperties = function(cb, binding){
+Ember.changeProperties = function(cb, binding){
beginPropertyChanges();
tryFinally(cb, endPropertyChanges, binding);
};
var notifyBeforeObservers = function(obj, keyName) {
@@ -2761,10 +2414,390 @@
(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
+ via `Ember.guidFor`, we can implement a performant Map with arbitrary
+ keys. Because it is commonly used in low-level bookkeeping, Map is
+ implemented as a pure JavaScript object for performance.
+
+ This implementation follows the current iteration of the ES6 proposal for
+ maps (http://wiki.ecmascript.org/doku.php?id=harmony:simple_maps_and_sets),
+ with two exceptions. First, because we need our implementation to be pleasant
+ on older browsers, we do not use the `delete` name (using `remove` instead).
+ Second, as we do not have the luxury of in-VM iteration, we implement a
+ forEach method for iteration.
+
+ Map is mocked out to look like an Ember object, so you can do
+ `Ember.Map.create()` for symmetry with other Ember classes.
+*/
+var get = Ember.get,
+ set = Ember.set,
+ guidFor = Ember.guidFor,
+ indexOf = Ember.ArrayPolyfills.indexOf;
+
+var copy = function(obj) {
+ var output = {};
+
+ for (var prop in obj) {
+ if (obj.hasOwnProperty(prop)) { output[prop] = obj[prop]; }
+ }
+
+ return output;
+};
+
+var copyMap = function(original, newObject) {
+ var keys = original.keys.copy(),
+ values = copy(original.values);
+
+ newObject.keys = keys;
+ newObject.values = values;
+ newObject.length = original.length;
+
+ return newObject;
+};
+
+/**
+ This class is used internally by Ember and Ember Data.
+ Please do not use it at this time. We plan to clean it up
+ and add many tests soon.
+
+ @class OrderedSet
+ @namespace Ember
+ @constructor
+ @private
+*/
+var OrderedSet = Ember.OrderedSet = function() {
+ this.clear();
+};
+
+/**
+ @method create
+ @static
+ @return {Ember.OrderedSet}
+*/
+OrderedSet.create = function() {
+ return new OrderedSet();
+};
+
+
+OrderedSet.prototype = {
+ /**
+ @method clear
+ */
+ clear: function() {
+ this.presenceSet = {};
+ this.list = [];
+ },
+
+ /**
+ @method add
+ @param obj
+ */
+ add: function(obj) {
+ var guid = guidFor(obj),
+ presenceSet = this.presenceSet,
+ list = this.list;
+
+ if (guid in presenceSet) { return; }
+
+ presenceSet[guid] = true;
+ list.push(obj);
+ },
+
+ /**
+ @method remove
+ @param obj
+ */
+ remove: function(obj) {
+ var guid = guidFor(obj),
+ presenceSet = this.presenceSet,
+ list = this.list;
+
+ delete presenceSet[guid];
+
+ var index = indexOf.call(list, obj);
+ if (index > -1) {
+ list.splice(index, 1);
+ }
+ },
+
+ /**
+ @method isEmpty
+ @return {Boolean}
+ */
+ isEmpty: function() {
+ return this.list.length === 0;
+ },
+
+ /**
+ @method has
+ @param obj
+ @return {Boolean}
+ */
+ has: function(obj) {
+ var guid = guidFor(obj),
+ presenceSet = this.presenceSet;
+
+ return guid in presenceSet;
+ },
+
+ /**
+ @method forEach
+ @param {Function} fn
+ @param self
+ */
+ forEach: function(fn, self) {
+ // allow mutation during iteration
+ var list = this.toArray();
+
+ for (var i = 0, j = list.length; i < j; i++) {
+ fn.call(self, list[i]);
+ }
+ },
+
+ /**
+ @method toArray
+ @return {Array}
+ */
+ toArray: function() {
+ return this.list.slice();
+ },
+
+ /**
+ @method copy
+ @return {Ember.OrderedSet}
+ */
+ copy: function() {
+ var set = new OrderedSet();
+
+ set.presenceSet = copy(this.presenceSet);
+ set.list = this.toArray();
+
+ return set;
+ }
+};
+
+/**
+ A Map stores values indexed by keys. Unlike JavaScript's
+ default Objects, the keys of a Map can be any JavaScript
+ object.
+
+ Internally, a Map has two data structures:
+
+ 1. `keys`: an OrderedSet of all of the existing keys
+ 2. `values`: a JavaScript Object indexed by the `Ember.guidFor(key)`
+
+ When a key/value pair is added for the first time, we
+ add the key to the `keys` OrderedSet, and create or
+ replace an entry in `values`. When an entry is deleted,
+ we delete its entry in `keys` and `values`.
+
+ @class Map
+ @namespace Ember
+ @private
+ @constructor
+*/
+var Map = Ember.Map = function() {
+ this.keys = Ember.OrderedSet.create();
+ this.values = {};
+};
+
+/**
+ @method create
+ @static
+*/
+Map.create = function() {
+ return new Map();
+};
+
+Map.prototype = {
+ /**
+ This property will change as the number of objects in the map changes.
+
+ @property length
+ @type number
+ @default 0
+ */
+ length: 0,
+
+
+ /**
+ Retrieve the value associated with a given key.
+
+ @method get
+ @param {*} key
+ @return {*} the value associated with the key, or `undefined`
+ */
+ get: function(key) {
+ var values = this.values,
+ guid = guidFor(key);
+
+ return values[guid];
+ },
+
+ /**
+ Adds a value to the map. If a value for the given key has already been
+ provided, the new value will replace the old value.
+
+ @method set
+ @param {*} key
+ @param {*} value
+ */
+ set: function(key, value) {
+ var keys = this.keys,
+ values = this.values,
+ guid = guidFor(key);
+
+ keys.add(key);
+ values[guid] = value;
+ set(this, 'length', keys.list.length);
+ },
+
+ /**
+ Removes a value from the map for an associated key.
+
+ @method remove
+ @param {*} key
+ @return {Boolean} true if an item was removed, false otherwise
+ */
+ remove: function(key) {
+ // don't use ES6 "delete" because it will be annoying
+ // to use in browsers that are not ES6 friendly;
+ var keys = this.keys,
+ values = this.values,
+ guid = guidFor(key);
+
+ if (values.hasOwnProperty(guid)) {
+ keys.remove(key);
+ delete values[guid];
+ set(this, 'length', keys.list.length);
+ return true;
+ } else {
+ return false;
+ }
+ },
+
+ /**
+ Check whether a key is present.
+
+ @method has
+ @param {*} key
+ @return {Boolean} true if the item was present, false otherwise
+ */
+ has: function(key) {
+ var values = this.values,
+ guid = guidFor(key);
+
+ return values.hasOwnProperty(guid);
+ },
+
+ /**
+ Iterate over all the keys and values. Calls the function once
+ for each key, passing in the key and value, in that order.
+
+ The keys are guaranteed to be iterated over in insertion order.
+
+ @method forEach
+ @param {Function} callback
+ @param {*} self if passed, the `this` value inside the
+ callback. By default, `this` is the map.
+ */
+ forEach: function(callback, self) {
+ var keys = this.keys,
+ values = this.values;
+
+ keys.forEach(function(key) {
+ var guid = guidFor(key);
+ callback.call(self, key, values[guid]);
+ });
+ },
+
+ /**
+ @method copy
+ @return {Ember.Map}
+ */
+ copy: function() {
+ return copyMap(this, new Map());
+ }
+};
+
+/**
+ @class MapWithDefault
+ @namespace Ember
+ @extends Ember.Map
+ @private
+ @constructor
+ @param [options]
+ @param {*} [options.defaultValue]
+*/
+var MapWithDefault = Ember.MapWithDefault = function(options) {
+ Map.call(this);
+ this.defaultValue = options.defaultValue;
+};
+
+/**
+ @method create
+ @static
+ @param [options]
+ @param {*} [options.defaultValue]
+ @return {Ember.MapWithDefault|Ember.Map} If options are passed, returns
+ `Ember.MapWithDefault` otherwise returns `Ember.Map`
+*/
+MapWithDefault.create = function(options) {
+ if (options) {
+ return new MapWithDefault(options);
+ } else {
+ return new Map();
+ }
+};
+
+MapWithDefault.prototype = Ember.create(Map.prototype);
+
+/**
+ Retrieve the value associated with a given key.
+
+ @method get
+ @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) {
+ return Map.prototype.get.call(this, key);
+ } else {
+ var defaultValue = this.defaultValue(key);
+ this.set(key, defaultValue);
+ return defaultValue;
+ }
+};
+
+/**
+ @method copy
+ @return {Ember.MapWithDefault}
+*/
+MapWithDefault.prototype.copy = function() {
+ return copyMap(this, new MapWithDefault({
+ defaultValue: this.defaultValue
+ }));
+};
+
+})();
+
+
+
+(function() {
+/**
+@module ember-metal
+*/
+
var META_KEY = Ember.META_KEY,
metaFor = Ember.meta,
objectDefineProperty = Ember.platform.defineProperty;
var MANDATORY_SETTER = Ember.ENV.MANDATORY_SETTER;
@@ -2782,11 +2815,11 @@
@class Descriptor
@namespace Ember
@private
@constructor
*/
-var Descriptor = Ember.Descriptor = function() {};
+Ember.Descriptor = function() {};
// ..........................................................
// DEFINING PROPERTIES API
//
@@ -3343,11 +3376,11 @@
watching[keyPath] = (watching[keyPath] || 0) + 1;
}
};
Ember.unwatchPath = function(obj, keyPath) {
- var m = metaFor(obj), watching = m.watching, desc;
+ var m = metaFor(obj), watching = m.watching;
if (watching[keyPath] === 1) {
watching[keyPath] = 0;
chainsFor(obj).remove(keyPath);
} else if (watching[keyPath] > 1) {
@@ -3604,32 +3637,19 @@
ComputedProperty.prototype = new Ember.Descriptor();
var ComputedPropertyPrototype = ComputedProperty.prototype;
/*
- Call on a computed property to explicitly change it's cacheable mode.
+ Properties are cacheable by default. Computed property will automatically
+ cache the return value of your function until one of the dependent keys changes.
- Please use `.volatile` over this method.
+ Call `volatile()` to set it into non-cached mode. When in this mode
+ the computed property will not automatically cache the return value.
- ```javascript
- MyApp.president = Ember.Object.create({
- fullName: function() {
- return this.get('firstName') + ' ' + this.get('lastName');
+ However, if a property is properly observable, there is no reason to disable
+ caching.
- // By default, Ember will return the value of this property
- // without re-executing this function.
- }.property('firstName', 'lastName')
-
- initials: function() {
- return this.get('firstName')[0] + this.get('lastName')[0];
-
- // This function will be executed every time this property
- // is requested.
- }.property('firstName', 'lastName').cacheable(false)
- });
- ```
-
@method cacheable
@param {Boolean} aFlag optional set to `false` to disable caching
@return {Ember.ComputedProperty} this
@chainable
*/
@@ -3888,11 +3908,10 @@
The function you pass will be used to both get and set property values.
The function should accept two parameters, key and value. If value is not
undefined you should set the value first. In either case return the
current value of the property.
-
@method computed
@for Ember
@param {Function} func The computed property function.
@return {Ember.ComputedProperty} property descriptor instance
*/
@@ -4130,11 +4149,11 @@
/**
@method computed.any
@for Ember
@param {String} dependentKey, [dependentKey...]
@return {Ember.ComputedProperty} computed property which returns
- the first trouthy value of given list of properties.
+ the first truthy value of given list of properties.
*/
registerComputedWithProperties('any', function(properties) {
for (var key in properties) {
if (properties.hasOwnProperty(key) && properties[key]) {
return properties[key];
@@ -4181,10 +4200,52 @@
}
});
};
/**
+ @method computed.oneWay
+ @for Ember
+ @param {String} dependentKey
+ @return {Ember.ComputedProperty} computed property which creates an
+ one way computed property to the original value for property.
+
+ Where `computed.alias` aliases `get` and `set`, and allows for bidirectional
+ data flow, `computed.oneWay` only provides an aliased `get`. The `set` will
+ not mutate the upstream property, rather causes the current property to
+ become the value set. This causes the downstream property to permentantly
+ diverge from the upstream property.
+
+ ```javascript
+ User = Ember.Object.extend({
+ firstName: null,
+ lastName: null,
+ nickName: Ember.computed.oneWay('firstName')
+ });
+
+ user = User.create({
+ firstName: 'Teddy',
+ lastName: 'Zeenny'
+ });
+
+ user.get('nickName');
+ # 'Teddy'
+
+ user.set('nickName', 'TeddyBear');
+ # 'TeddyBear'
+
+ user.get('firstName');
+ # 'Teddy'
+ ```
+*/
+Ember.computed.oneWay = function(dependentKey) {
+ return Ember.computed(dependentKey, function() {
+ return get(this, dependentKey);
+ });
+};
+
+
+/**
@method computed.defaultTo
@for Ember
@param {String} defaultPath
@return {Ember.ComputedProperty} computed property which acts like
a standard getter and setter, but defaults to the value from `defaultPath`.
@@ -4209,12 +4270,10 @@
*/
var AFTER_OBSERVERS = ':change';
var BEFORE_OBSERVERS = ':before';
-var guidFor = Ember.guidFor;
-
function changeEvent(keyName) {
return keyName+AFTER_OBSERVERS;
}
function beforeEvent(keyName) {
@@ -4307,188 +4366,512 @@
})();
(function() {
-// Ember.Logger
-// Ember.watch.flushPending
-// Ember.beginPropertyChanges, Ember.endPropertyChanges
-// Ember.guidFor, Ember.tryFinally
+define("backburner",
+ ["backburner/deferred_action_queues","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var DeferredActionQueues = __dependency1__.DeferredActionQueues;
-/**
-@module ember-metal
-*/
+ var slice = [].slice,
+ pop = [].pop,
+ debouncees = [],
+ timers = [],
+ autorun, laterTimer, laterTimerExpiresAt;
-// ..........................................................
-// HELPERS
-//
+ function Backburner(queueNames, options) {
+ this.queueNames = queueNames;
+ this.options = options || {};
+ if (!this.options.defaultQueue) {
+ this.options.defaultQueue = queueNames[0];
+ }
+ this.instanceStack = [];
+ }
-var slice = [].slice,
- forEach = Ember.ArrayPolyfills.forEach;
+ Backburner.prototype = {
+ queueNames: null,
+ options: null,
+ currentInstance: null,
+ instanceStack: null,
-// invokes passed params - normalizing so you can pass target/func,
-// target/string or just func
-function invoke(target, method, args, ignore) {
+ begin: function() {
+ var onBegin = this.options && this.options.onBegin,
+ previousInstance = this.currentInstance;
- if (method === undefined) {
- method = target;
- target = undefined;
- }
+ if (previousInstance) {
+ this.instanceStack.push(previousInstance);
+ }
- if ('string' === typeof method) { method = target[method]; }
- if (args && ignore > 0) {
- args = args.length > ignore ? slice.call(args, ignore) : null;
- }
+ this.currentInstance = new DeferredActionQueues(this.queueNames, this.options);
+ if (onBegin) {
+ onBegin(this.currentInstance, previousInstance);
+ }
+ },
- return Ember.handleErrors(function() {
- // IE8's Function.prototype.apply doesn't accept undefined/null arguments.
- return method.apply(target || this, args || []);
- }, this);
-}
+ end: function() {
+ var onEnd = this.options && this.options.onEnd,
+ currentInstance = this.currentInstance,
+ nextInstance = null;
+ try {
+ currentInstance.flush();
+ } finally {
+ this.currentInstance = null;
-// ..........................................................
-// RUNLOOP
-//
+ if (this.instanceStack.length) {
+ nextInstance = this.instanceStack.pop();
+ this.currentInstance = nextInstance;
+ }
-/**
-Ember RunLoop (Private)
+ if (onEnd) {
+ onEnd(currentInstance, nextInstance);
+ }
+ }
+ },
-@class RunLoop
-@namespace Ember
-@private
-@constructor
-*/
-var RunLoop = function(prev) {
- this._prev = prev || null;
- this.onceTimers = {};
-};
+ run: function(target, method /*, args */) {
+ var ret;
+ this.begin();
-RunLoop.prototype = {
- /**
- @method end
- */
- end: function() {
- this.flush();
- },
+ if (!method) {
+ method = target;
+ target = null;
+ }
- /**
- @method prev
- */
- prev: function() {
- return this._prev;
- },
+ if (typeof method === 'string') {
+ method = target[method];
+ }
- // ..........................................................
- // Delayed Actions
- //
+ // Prevent Safari double-finally.
+ var finallyAlreadyCalled = false;
+ try {
+ if (arguments.length > 2) {
+ ret = method.apply(target, slice.call(arguments, 2));
+ } else {
+ ret = method.call(target);
+ }
+ } finally {
+ if (!finallyAlreadyCalled) {
+ finallyAlreadyCalled = true;
+ this.end();
+ }
+ }
+ return ret;
+ },
- /**
- @method schedule
- @param {String} queueName
- @param target
- @param method
- */
- schedule: function(queueName, target, method) {
- var queues = this._queues, queue;
- if (!queues) { queues = this._queues = {}; }
- queue = queues[queueName];
- if (!queue) { queue = queues[queueName] = []; }
+ defer: function(queueName, target, method /* , args */) {
+ if (!method) {
+ method = target;
+ target = null;
+ }
- var args = arguments.length > 3 ? slice.call(arguments, 3) : null;
- queue.push({ target: target, method: method, args: args });
- return this;
- },
+ if (typeof method === 'string') {
+ method = target[method];
+ }
- /**
- @method flush
- @param {String} queueName
- */
- flush: function(queueName) {
- var queueNames, idx, len, queue, log;
+ var stack = new Error().stack,
+ args = arguments.length > 3 ? slice.call(arguments, 3) : undefined;
+ if (!this.currentInstance) { createAutorun(this); }
+ return this.currentInstance.schedule(queueName, target, method, args, false, stack);
+ },
- if (!this._queues) { return this; } // nothing to do
+ deferOnce: function(queueName, target, method /* , args */) {
+ if (!method) {
+ method = target;
+ target = null;
+ }
- function iter(item) {
- invoke(item.target, item.method, item.args);
+ if (typeof method === 'string') {
+ method = target[method];
+ }
+
+ var stack = new Error().stack,
+ args = arguments.length > 3 ? slice.call(arguments, 3) : undefined;
+ if (!this.currentInstance) { createAutorun(this); }
+ return this.currentInstance.schedule(queueName, target, method, args, true, stack);
+ },
+
+ setTimeout: function() {
+ var self = this,
+ wait = pop.call(arguments),
+ target = arguments[0],
+ method = arguments[1],
+ executeAt = (+new Date()) + wait;
+
+ if (!method) {
+ method = target;
+ target = null;
+ }
+
+ if (typeof method === 'string') {
+ method = target[method];
+ }
+
+ var fn, args;
+ if (arguments.length > 2) {
+ args = slice.call(arguments, 2);
+
+ fn = function() {
+ method.apply(target, args);
+ };
+ } else {
+ fn = function() {
+ method.call(target);
+ };
+ }
+
+ // find position to insert - TODO: binary search
+ var i, l;
+ for (i = 0, l = timers.length; i < l; i += 2) {
+ if (executeAt < timers[i]) { break; }
+ }
+
+ timers.splice(i, 0, executeAt, fn);
+
+ if (laterTimer && laterTimerExpiresAt < executeAt) { return fn; }
+
+ if (laterTimer) {
+ clearTimeout(laterTimer);
+ laterTimer = null;
+ }
+ laterTimer = setTimeout(function() {
+ executeTimers(self);
+ laterTimer = null;
+ laterTimerExpiresAt = null;
+ }, wait);
+ laterTimerExpiresAt = executeAt;
+
+ return fn;
+ },
+
+ debounce: function(target, method /* , args, wait */) {
+ var self = this,
+ args = arguments,
+ wait = pop.call(args),
+ debouncee;
+
+ for (var i = 0, l = debouncees.length; i < l; i++) {
+ debouncee = debouncees[i];
+ if (debouncee[0] === target && debouncee[1] === method) { return; } // do nothing
+ }
+
+ var timer = setTimeout(function() {
+ self.run.apply(self, args);
+
+ // remove debouncee
+ var index = -1;
+ for (var i = 0, l = debouncees.length; i < l; i++) {
+ debouncee = debouncees[i];
+ if (debouncee[0] === target && debouncee[1] === method) {
+ index = i;
+ break;
+ }
+ }
+
+ if (index > -1) { debouncees.splice(index, 1); }
+ }, wait);
+
+ debouncees.push([target, method, timer]);
+ },
+
+ cancelTimers: function() {
+ for (var i = 0, l = debouncees.length; i < l; i++) {
+ clearTimeout(debouncees[i][2]);
+ }
+ debouncees = [];
+
+ if (laterTimer) {
+ clearTimeout(laterTimer);
+ laterTimer = null;
+ }
+ timers = [];
+
+ if (autorun) {
+ clearTimeout(autorun);
+ autorun = null;
+ }
+ },
+
+ hasTimers: function() {
+ return !!timers.length || autorun;
+ },
+
+ cancel: function(timer) {
+ if (typeof timer === 'object' && timer.queue && timer.method) { // we're cancelling a deferOnce
+ return timer.queue.cancel(timer);
+ } else if (typeof timer === 'function') { // we're cancelling a setTimeout
+ for (var i = 0, l = timers.length; i < l; i += 2) {
+ if (timers[i + 1] === timer) {
+ timers.splice(i, 2); // remove the two elements
+ return true;
+ }
+ }
+ }
+ }
+ };
+
+ Backburner.prototype.schedule = Backburner.prototype.defer;
+ Backburner.prototype.scheduleOnce = Backburner.prototype.deferOnce;
+ Backburner.prototype.later = Backburner.prototype.setTimeout;
+
+ function createAutorun(backburner) {
+ backburner.begin();
+ autorun = setTimeout(function() {
+ backburner.end();
+ autorun = null;
+ });
}
- function tryable() {
- forEach.call(queue, iter);
+ function executeTimers(self) {
+ var now = +new Date(),
+ time, fns, i, l;
+
+ self.run(function() {
+ // TODO: binary search
+ for (i = 0, l = timers.length; i < l; i += 2) {
+ time = timers[i];
+ if (time > now) { break; }
+ }
+
+ fns = timers.splice(0, i);
+
+ for (i = 1, l = fns.length; i < l; i += 2) {
+ self.schedule(self.options.defaultQueue, null, fns[i]);
+ }
+ });
+
+ if (timers.length) {
+ laterTimer = setTimeout(function() {
+ executeTimers(self);
+ laterTimer = null;
+ laterTimerExpiresAt = null;
+ }, timers[0] - now);
+ laterTimerExpiresAt = timers[0];
+ }
}
- Ember.watch.flushPending(); // make sure all chained watchers are setup
- if (queueName) {
- while (this._queues && (queue = this._queues[queueName])) {
- this._queues[queueName] = null;
+ __exports__.Backburner = Backburner;
+ });
- // the sync phase is to allow property changes to propagate. don't
- // invoke observers until that is finished.
- if (queueName === 'sync') {
- log = Ember.LOG_BINDINGS;
- if (log) { Ember.Logger.log('Begin: Flush Sync Queue'); }
+define("backburner/deferred_action_queues",
+ ["backburner/queue","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Queue = __dependency1__.Queue;
- Ember.beginPropertyChanges();
+ function DeferredActionQueues(queueNames, options) {
+ var queues = this.queues = {};
+ this.queueNames = queueNames = queueNames || [];
- Ember.tryFinally(tryable, Ember.endPropertyChanges);
+ var queueName;
+ for (var i = 0, l = queueNames.length; i < l; i++) {
+ queueName = queueNames[i];
+ queues[queueName] = new Queue(this, queueName, options[queueName]);
+ }
+ }
- if (log) { Ember.Logger.log('End: Flush Sync Queue'); }
+ DeferredActionQueues.prototype = {
+ queueNames: null,
+ queues: null,
+ schedule: function(queueName, target, method, args, onceFlag, stack) {
+ var queues = this.queues,
+ queue = queues[queueName];
+
+ if (!queue) { throw new Error("You attempted to schedule an action in a queue (" + queueName + ") that doesn't exist"); }
+
+ if (onceFlag) {
+ return queue.pushUnique(target, method, args, stack);
} else {
- forEach.call(queue, iter);
+ return queue.push(target, method, args, stack);
}
- }
+ },
- } else {
- queueNames = Ember.run.queues;
- len = queueNames.length;
- idx = 0;
+ flush: function() {
+ var queues = this.queues,
+ queueNames = this.queueNames,
+ queueName, queue, queueItems, priorQueueNameIndex,
+ queueNameIndex = 0, numberOfQueues = queueNames.length;
- outerloop:
- while (idx < len) {
- queueName = queueNames[idx];
- queue = this._queues && this._queues[queueName];
- delete this._queues[queueName];
+ outerloop:
+ while (queueNameIndex < numberOfQueues) {
+ queueName = queueNames[queueNameIndex];
+ queue = queues[queueName];
+ queueItems = queue._queue.slice();
+ queue._queue = [];
- if (queue) {
- // the sync phase is to allow property changes to propagate. don't
- // invoke observers until that is finished.
- if (queueName === 'sync') {
- log = Ember.LOG_BINDINGS;
- if (log) { Ember.Logger.log('Begin: Flush Sync Queue'); }
+ var options = queue.options,
+ before = options && options.before,
+ after = options && options.after,
+ target, method, args, stack,
+ queueIndex = 0, numberOfQueueItems = queueItems.length;
- Ember.beginPropertyChanges();
+ if (numberOfQueueItems && before) { before(); }
+ while (queueIndex < numberOfQueueItems) {
+ target = queueItems[queueIndex];
+ method = queueItems[queueIndex+1];
+ args = queueItems[queueIndex+2];
+ stack = queueItems[queueIndex+3]; // Debugging assistance
- Ember.tryFinally(tryable, Ember.endPropertyChanges);
+ if (typeof method === 'string') { method = target[method]; }
- if (log) { Ember.Logger.log('End: Flush Sync Queue'); }
- } else {
- forEach.call(queue, iter);
+ // TODO: error handling
+ if (args && args.length > 0) {
+ method.apply(target, args);
+ } else {
+ method.call(target);
+ }
+
+ queueIndex += 4;
}
- }
+ if (numberOfQueueItems && after) { after(); }
- // Loop through prior queues
- for (var i = 0; i <= idx; i++) {
- if (this._queues && this._queues[queueNames[i]]) {
- // Start over at the first queue with contents
- idx = i;
+ if ((priorQueueNameIndex = indexOfPriorQueueWithActions(this, queueNameIndex)) !== -1) {
+ queueNameIndex = priorQueueNameIndex;
continue outerloop;
}
+
+ queueNameIndex++;
}
+ }
+ };
- idx++;
+ function indexOfPriorQueueWithActions(daq, currentQueueIndex) {
+ var queueName, queue;
+
+ for (var i = 0, l = currentQueueIndex; i <= l; i++) {
+ queueName = daq.queueNames[i];
+ queue = daq.queues[queueName];
+ if (queue._queue.length) { return i; }
}
+
+ return -1;
}
- return this;
- }
+ __exports__.DeferredActionQueues = DeferredActionQueues;
+ });
+define("backburner/queue",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
+ function Queue(daq, name, options) {
+ this.daq = daq;
+ this.name = name;
+ this.options = options;
+ this._queue = [];
+ }
+
+ Queue.prototype = {
+ daq: null,
+ name: null,
+ options: null,
+ _queue: null,
+
+ push: function(target, method, args, stack) {
+ var queue = this._queue;
+ queue.push(target, method, args, stack);
+ return {queue: this, target: target, method: method};
+ },
+
+ pushUnique: function(target, method, args, stack) {
+ var queue = this._queue, currentTarget, currentMethod, i, l;
+
+ for (i = 0, l = queue.length; i < l; i += 4) {
+ currentTarget = queue[i];
+ currentMethod = queue[i+1];
+
+ if (currentTarget === target && currentMethod === method) {
+ queue[i+2] = args; // replace args
+ queue[i+3] = stack; // replace stack
+ return {queue: this, target: target, method: method}; // TODO: test this code path
+ }
+ }
+
+ this._queue.push(target, method, args, stack);
+ return {queue: this, target: target, method: method};
+ },
+
+ // TODO: remove me, only being used for Ember.run.sync
+ flush: function() {
+ var queue = this._queue,
+ options = this.options,
+ before = options && options.before,
+ after = options && options.after,
+ target, method, args, stack, i, l = queue.length;
+
+ if (l && before) { before(); }
+ for (i = 0; i < l; i += 4) {
+ target = queue[i];
+ method = queue[i+1];
+ args = queue[i+2];
+ stack = queue[i+3]; // Debugging assistance
+
+ // TODO: error handling
+ if (args && args.length > 0) {
+ method.apply(target, args);
+ } else {
+ method.call(target);
+ }
+ }
+ if (l && after) { after(); }
+
+ // check if new items have been added
+ if (queue.length > l) {
+ this._queue = queue.slice(l);
+ this.flush();
+ } else {
+ this._queue.length = 0;
+ }
+ },
+
+ cancel: function(actionToCancel) {
+ var queue = this._queue, currentTarget, currentMethod, i, l;
+
+ for (i = 0, l = queue.length; i < l; i += 4) {
+ currentTarget = queue[i];
+ currentMethod = queue[i+1];
+
+ if (currentTarget === actionToCancel.target && currentMethod === actionToCancel.method) {
+ queue.splice(i, 4);
+ return true;
+ }
+ }
+ }
+ };
+
+ __exports__.Queue = Queue;
+ });
+
+})();
+
+
+
+(function() {
+var onBegin = function(current) {
+ Ember.run.currentRunLoop = current;
};
-Ember.RunLoop = RunLoop;
+var onEnd = function(current, next) {
+ Ember.run.currentRunLoop = next;
+};
+var Backburner = requireModule('backburner').Backburner,
+ backburner = new Backburner(['sync', 'actions', 'destroy'], {
+ sync: {
+ before: Ember.beginPropertyChanges,
+ after: Ember.endPropertyChanges
+ },
+ defaultQueue: 'actions',
+ onBegin: onBegin,
+ onEnd: onEnd
+ }),
+ slice = [].slice;
+
// ..........................................................
// Ember.run - this is ideally the only public API the dev sees
//
/**
@@ -4517,25 +4900,81 @@
then it will be looked up on the passed target.
@param {Object} [args*] Any additional arguments you wish to pass to the method.
@return {Object} return value from invoking the passed function.
*/
Ember.run = function(target, method) {
- var args = arguments;
- run.begin();
+ var ret;
- function tryable() {
- if (target || method) {
- return invoke(target, method, args, 2);
+ if (Ember.onerror) {
+ try {
+ ret = backburner.run.apply(backburner, arguments);
+ } catch (e) {
+ Ember.onerror(e);
}
+ } else {
+ ret = backburner.run.apply(backburner, arguments);
}
- return Ember.tryFinally(tryable, run.end);
+ return ret;
};
+/**
+
+ If no run-loop is present, it creates a new one. If a run loop is
+ present it will queue itself to run on the existing run-loops action
+ queue.
+
+ Please note: This is not for normal usage, and should be used sparingly.
+
+ If invoked when not within a run loop:
+
+ ```javascript
+ Ember.run.join(function(){
+ // creates a new run-loop
+ });
+ ```
+
+ Alternatively, if called within an existing run loop:
+
+ ```javascript
+ Ember.run(function(){
+ // creates a new run-loop
+ Ember.run.join(function(){
+ // joins with the existing run-loop, and queues for invocation on
+ // the existing run-loops action queue.
+ });
+ });
+ ```
+
+ @method join
+ @namespace Ember
+ @param {Object} [target] target of method to call
+ @param {Function|String} method Method to invoke.
+ May be a function or a string. If you pass a string
+ then it will be looked up on the passed target.
+ @param {Object} [args*] Any additional arguments you wish to pass to the method.
+ @return {Object} return value from invoking the passed function. Please note,
+ when called within an existing loop, no return value is possible.
+*/
+Ember.run.join = function(target, method) {
+ if (!Ember.run.currentRunLoop) {
+ return Ember.run.apply(Ember.run, arguments);
+ }
+
+ var args = slice.call(arguments);
+ args.unshift('actions');
+ Ember.run.schedule.apply(Ember.run, args);
+};
+
+Ember.run.backburner = backburner;
+
var run = Ember.run;
+Ember.run.currentRunLoop = null;
+Ember.run.queues = backburner.queueNames;
+
/**
Begins a new RunLoop. Any deferred actions invoked after the begin will
be buffered until you invoke a matching call to `Ember.run.end()`. This is
a lower-level way to use a RunLoop instead of using `Ember.run()`.
@@ -4547,11 +4986,11 @@
@method begin
@return {void}
*/
Ember.run.begin = function() {
- run.currentRunLoop = new RunLoop(run.currentRunLoop);
+ backburner.begin();
};
/**
Ends a RunLoop. This must be called sometime after you call
`Ember.run.begin()` to flush any deferred actions. This is a lower-level way
@@ -4565,16 +5004,11 @@
@method end
@return {void}
*/
Ember.run.end = function() {
-
-
- function tryable() { run.currentRunLoop.end(); }
- function finalizer() { run.currentRunLoop = run.currentRunLoop.prev(); }
-
- Ember.tryFinally(tryable, finalizer);
+ backburner.end();
};
/**
Array of named queues. This array determines the order in which queues
are flushed at the end of the RunLoop. You can define your own queues by
@@ -4583,11 +5017,10 @@
@property queues
@type Array
@default ['sync', 'actions', 'destroy']
*/
-Ember.run.queues = ['sync', 'actions', 'destroy'];
/**
Adds the passed target/method and any optional arguments to the named
queue to be executed at the end of the RunLoop. If you have not already
started a RunLoop when calling this method one will be started for you
@@ -4622,64 +5055,25 @@
invoked allowing you to change the target function.
@param {Object} [arguments*] Optional arguments to be passed to the queued method.
@return {void}
*/
Ember.run.schedule = function(queue, target, method) {
- var loop = run.autorun();
- loop.schedule.apply(loop, arguments);
+ checkAutoRun();
+ backburner.schedule.apply(backburner, arguments);
};
-var scheduledAutorun;
-function autorun() {
- scheduledAutorun = null;
- if (run.currentRunLoop) { run.end(); }
-}
-
// Used by global test teardown
Ember.run.hasScheduledTimers = function() {
- return !!(scheduledAutorun || scheduledLater);
+ return backburner.hasTimers();
};
// Used by global test teardown
Ember.run.cancelTimers = function () {
- if (scheduledAutorun) {
- clearTimeout(scheduledAutorun);
- scheduledAutorun = null;
- }
- if (scheduledLater) {
- clearTimeout(scheduledLater);
- scheduledLater = null;
- }
- timers = {};
+ backburner.cancelTimers();
};
/**
- Begins a new RunLoop if necessary and schedules a timer to flush the
- RunLoop at a later time. This method is used by parts of Ember to
- ensure the RunLoop always finishes. You normally do not need to call this
- method directly. Instead use `Ember.run()`
-
- @method autorun
- @example
- Ember.run.autorun();
- @return {Ember.RunLoop} the new current RunLoop
-*/
-Ember.run.autorun = function() {
- if (!run.currentRunLoop) {
-
-
- run.begin();
-
- if (!scheduledAutorun) {
- scheduledAutorun = setTimeout(autorun, 1);
- }
- }
-
- return run.currentRunLoop;
-};
-
-/**
Immediately flushes any events scheduled in the 'sync' queue. Bindings
use this queue so this method is a useful way to immediately force all
bindings in the application to sync.
You should call this method anytime you need any changed state to propagate
@@ -4692,46 +5086,13 @@
@method sync
@return {void}
*/
Ember.run.sync = function() {
- run.autorun();
- run.currentRunLoop.flush('sync');
+ backburner.currentInstance.queues.sync.flush();
};
-// ..........................................................
-// TIMERS
-//
-
-var timers = {}; // active timers...
-
-var scheduledLater, scheduledLaterExpires;
-function invokeLaterTimers() {
- scheduledLater = null;
- run(function() {
- var now = (+ new Date()), earliest = -1;
- for (var key in timers) {
- if (!timers.hasOwnProperty(key)) { continue; }
- var timer = timers[key];
- if (timer && timer.expires) {
- if (now >= timer.expires) {
- delete timers[key];
- invoke(timer.target, timer.method, timer.args, 2);
- } else {
- if (earliest < 0 || (timer.expires < earliest)) { earliest = timer.expires; }
- }
- }
- }
-
- // schedule next timeout to fire when the earliest timer expires
- if (earliest > 0) {
- scheduledLater = setTimeout(invokeLaterTimers, earliest - now);
- scheduledLaterExpires = earliest;
- }
- });
-}
-
/**
Invokes the passed target/method and optional arguments after a specified
period if time. The last parameter of this method must always be a number
of milliseconds.
@@ -4752,81 +5113,16 @@
If you pass a string it will be resolved on the
target at the time the method is invoked.
@param {Object} [args*] Optional arguments to pass to the timeout.
@param {Number} wait Number of milliseconds to wait.
@return {String} a string you can use to cancel the timer in
- {{#crossLink "Ember/run.cancel"}}{{/crossLink}} later.
+ `Ember.run.cancel` later.
*/
Ember.run.later = function(target, method) {
- var args, expires, timer, guid, wait;
-
- // setTimeout compatibility...
- if (arguments.length===2 && 'function' === typeof target) {
- wait = method;
- method = target;
- target = undefined;
- args = [target, method];
- } else {
- args = slice.call(arguments);
- wait = args.pop();
- }
-
- expires = (+ new Date()) + wait;
- timer = { target: target, method: method, expires: expires, args: args };
- guid = Ember.guidFor(timer);
- timers[guid] = timer;
-
- if(scheduledLater && expires < scheduledLaterExpires) {
- // Cancel later timer (then reschedule earlier timer below)
- clearTimeout(scheduledLater);
- scheduledLater = null;
- }
-
- if (!scheduledLater) {
- // Schedule later timers to be run.
- scheduledLater = setTimeout(invokeLaterTimers, wait);
- scheduledLaterExpires = expires;
- }
-
- return guid;
+ return backburner.later.apply(backburner, arguments);
};
-function invokeOnceTimer(guid, onceTimers) {
- if (onceTimers[this.tguid]) { delete onceTimers[this.tguid][this.mguid]; }
- if (timers[guid]) { invoke(this.target, this.method, this.args); }
- delete timers[guid];
-}
-
-function scheduleOnce(queue, target, method, args) {
- var tguid = Ember.guidFor(target),
- mguid = Ember.guidFor(method),
- onceTimers = run.autorun().onceTimers,
- guid = onceTimers[tguid] && onceTimers[tguid][mguid],
- timer;
-
- if (guid && timers[guid]) {
- timers[guid].args = args; // replace args
- } else {
- timer = {
- target: target,
- method: method,
- args: args,
- tguid: tguid,
- mguid: mguid
- };
-
- guid = Ember.guidFor(timer);
- timers[guid] = timer;
- if (!onceTimers[tguid]) { onceTimers[tguid] = {}; }
- onceTimers[tguid][mguid] = guid; // so it isn't scheduled more than once
-
- run.schedule(queue, timer, invokeOnceTimer, guid, onceTimers);
- }
-
- return guid;
-}
-
/**
Schedule a function to run one time during the current RunLoop. This is equivalent
to calling `scheduleOnce` with the "actions" queue.
@method once
@@ -4836,11 +5132,14 @@
target at the time the method is invoked.
@param {Object} [args*] Optional arguments to pass to the timeout.
@return {Object} timer
*/
Ember.run.once = function(target, method) {
- return scheduleOnce('actions', target, method, slice.call(arguments, 2));
+ checkAutoRun();
+ var args = slice.call(arguments);
+ args.unshift('actions');
+ return backburner.scheduleOnce.apply(backburner, args);
};
/**
Schedules a function to run one time in a given queue of the current RunLoop.
Calling this method with the same queue/target/method combination will have
@@ -4884,16 +5183,17 @@
target at the time the method is invoked.
@param {Object} [args*] Optional arguments to pass to the timeout.
@return {Object} timer
*/
Ember.run.scheduleOnce = function(queue, target, method) {
- return scheduleOnce(queue, target, method, slice.call(arguments, 3));
+ checkAutoRun();
+ return backburner.scheduleOnce.apply(backburner, arguments);
};
/**
- Schedules an item to run from within a separate run loop, after
- control has been returned to the system. This is equivalent to calling
+ Schedules an item to run from within a separate run loop, after
+ control has been returned to the system. This is equivalent to calling
`Ember.run.later` with a wait time of 1ms.
```javascript
Ember.run.next(myContext, function(){
// code to be executed in the next run loop, which will be scheduled after the current one
@@ -4901,11 +5201,11 @@
```
Multiple operations scheduled with `Ember.run.next` will coalesce
into the same later run loop, along with any other operations
scheduled by `Ember.run.later` that expire right around the same
- time that `Ember.run.next` operations will fire.
+ time that `Ember.run.next` operations will fire.
Note that there are often alternatives to using `Ember.run.next`.
For instance, if you'd like to schedule an operation to happen
after all DOM element operations have completed within the current
run loop, you can make use of the `afterRender` run loop queue (added
@@ -4927,17 +5227,17 @@
});
```
One benefit of the above approach compared to using `Ember.run.next` is
that you will be able to perform DOM/CSS operations before unprocessed
- elements are rendered to the screen, which may prevent flickering or
+ elements are rendered to the screen, which may prevent flickering or
other artifacts caused by delaying processing until after rendering.
- The other major benefit to the above approach is that `Ember.run.next`
- introduces an element of non-determinism, which can make things much
- harder to test, due to its reliance on `setTimeout`; it's much harder
- to guarantee the order of scheduled operations when they are scheduled
+ The other major benefit to the above approach is that `Ember.run.next`
+ introduces an element of non-determinism, which can make things much
+ harder to test, due to its reliance on `setTimeout`; it's much harder
+ to guarantee the order of scheduled operations when they are scheduled
outside of the current run loop, i.e. with `Ember.run.next`.
@method next
@param {Object} [target] target of method to invoke
@param {Function|String} method The method to invoke.
@@ -4946,12 +5246,12 @@
@param {Object} [args*] Optional arguments to pass to the timeout.
@return {Object} timer
*/
Ember.run.next = function() {
var args = slice.call(arguments);
- args.push(1); // 1 millisecond wait
- return run.later.apply(this, args);
+ args.push(1);
+ return backburner.later.apply(backburner, args);
};
/**
Cancels a scheduled item. Must be a value returned by `Ember.run.later()`,
`Ember.run.once()`, or `Ember.run.next()`.
@@ -4976,13 +5276,20 @@
@method cancel
@param {Object} timer Timer object to cancel
@return {void}
*/
Ember.run.cancel = function(timer) {
- delete timers[timer];
+ return backburner.cancel(timer);
};
+// Make sure it's not an autorun during testing
+function checkAutoRun() {
+ if (!Ember.run.currentRunLoop) {
+
+ }
+}
+
})();
(function() {
@@ -5263,22 +5570,22 @@
}
mixinProperties(Binding, {
/**
- See {{#crossLink "Ember.Binding/from"}}{{/crossLink}}
+ See `Ember.Binding.from`.
@method from
@static
*/
from: function() {
var C = this, binding = new C();
return binding.from.apply(binding, arguments);
},
/**
- See {{#crossLink "Ember.Binding/to"}}{{/crossLink}}
+ See `Ember.Binding.to`.
@method to
@static
*/
to: function() {
@@ -5291,11 +5598,11 @@
A one-way binding will relay changes on the `from` side object (supplied
as the `from` argument) the `to` side, but not the other way around.
This means that if you change the "to" side directly, the "from" side may have
a different value.
- See {{#crossLink "Binding/oneWay"}}{{/crossLink}}
+ See `Binding.oneWay`.
@method oneWay
@param {String} from from path.
@param {Boolean} [flag] (Optional) passing nothing here will make the
binding `oneWay`. You can instead pass `false` to disable `oneWay`, making the
@@ -5352,11 +5659,11 @@
One way bindings are almost twice as fast to setup and twice as fast to
execute because the binding only has to worry about changes to one side.
You should consider using one way bindings anytime you have an object that
may be created frequently and you do not intend to change a property; only
- to monitor it for changes. (such as in the example above).
+ to monitor it for changes (such as in the example above).
## Adding Bindings Manually
All of the examples above show you how to configure a custom binding, but the
result of these customizations will be a binding template, not a fully active
@@ -5653,18 +5960,10 @@
if (mixin._without) { a_forEach.call(mixin._without, removeKeys); }
}
}
}
-function writableReq(obj) {
- var m = Ember.meta(obj), req = m.required;
- if (!req || !m.hasOwnProperty('required')) {
- req = m.required = req ? o_create(req) : {};
- }
- return req;
-}
-
var IS_BINDING = Ember.IS_BINDING = /^.+Binding$/;
function detectBinding(obj, key, value, m) {
if (IS_BINDING.test(key)) {
var bindings = m.bindings;
@@ -6146,30 +6445,73 @@
*/
})();
(function() {
-define("rsvp",
- [],
- function() {
+define("rsvp/all",
+ ["rsvp/defer","exports"],
+ function(__dependency1__, __exports__) {
"use strict";
+ var defer = __dependency1__.defer;
+
+ function all(promises) {
+ var results = [], deferred = defer(), remaining = promises.length;
+
+ if (remaining === 0) {
+ deferred.resolve([]);
+ }
+
+ var resolver = function(index) {
+ return function(value) {
+ resolveAll(index, value);
+ };
+ };
+
+ var resolveAll = function(index, value) {
+ results[index] = value;
+ if (--remaining === 0) {
+ deferred.resolve(results);
+ }
+ };
+
+ var rejectAll = function(error) {
+ deferred.reject(error);
+ };
+
+ for (var i = 0; i < promises.length; i++) {
+ if (promises[i] && typeof promises[i].then === 'function') {
+ promises[i].then(resolver(i), rejectAll);
+ } else {
+ resolveAll(i, promises[i]);
+ }
+ }
+ return deferred.promise;
+ }
+
+ __exports__.all = all;
+ });
+
+define("rsvp/async",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
var browserGlobal = (typeof window !== 'undefined') ? window : {};
- var MutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
- var RSVP, async;
+ var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
+ var async;
if (typeof process !== 'undefined' &&
{}.toString.call(process) === '[object process]') {
async = function(callback, binding) {
process.nextTick(function() {
callback.call(binding);
});
};
- } else if (MutationObserver) {
+ } else if (BrowserMutationObserver) {
var queue = [];
- var observer = new MutationObserver(function() {
+ var observer = new BrowserMutationObserver(function() {
var toProcess = queue.slice();
queue = [];
toProcess.forEach(function(tuple) {
var callback = tuple[0], binding = tuple[1];
@@ -6196,10 +6538,51 @@
callback.call(binding);
}, 1);
};
}
+
+ __exports__.async = async;
+ });
+
+define("rsvp/config",
+ ["rsvp/async","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var async = __dependency1__.async;
+
+ var config = {};
+ config.async = async;
+
+ __exports__.config = config;
+ });
+
+define("rsvp/defer",
+ ["rsvp/promise","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__.Promise;
+
+ function defer() {
+ var deferred = {};
+
+ var promise = new Promise(function(resolve, reject) {
+ deferred.resolve = resolve;
+ deferred.reject = reject;
+ });
+
+ deferred.promise = promise;
+ return deferred;
+ }
+
+ __exports__.defer = defer;
+ });
+
+define("rsvp/events",
+ ["exports"],
+ function(__exports__) {
+ "use strict";
var Event = function(type, options) {
this.type = type;
for (var option in options) {
if (!options.hasOwnProperty(option)) { continue; }
@@ -6290,24 +6673,169 @@
}
}
}
};
- var Promise = function() {
+
+ __exports__.EventTarget = EventTarget;
+ });
+
+define("rsvp/hash",
+ ["rsvp/defer","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var defer = __dependency1__.defer;
+
+ function size(object) {
+ var size = 0;
+
+ for (var prop in object) {
+ size++;
+ }
+
+ return size;
+ }
+
+ function hash(promises) {
+ var results = {}, deferred = defer(), remaining = size(promises);
+
+ if (remaining === 0) {
+ deferred.resolve({});
+ }
+
+ var resolver = function(prop) {
+ return function(value) {
+ resolveAll(prop, value);
+ };
+ };
+
+ var resolveAll = function(prop, value) {
+ results[prop] = value;
+ if (--remaining === 0) {
+ deferred.resolve(results);
+ }
+ };
+
+ var rejectAll = function(error) {
+ deferred.reject(error);
+ };
+
+ for (var prop in promises) {
+ if (promises[prop] && typeof promises[prop].then === 'function') {
+ promises[prop].then(resolver(prop), rejectAll);
+ } else {
+ resolveAll(prop, promises[prop]);
+ }
+ }
+
+ return deferred.promise;
+ }
+
+ __exports__.hash = hash;
+ });
+
+define("rsvp/node",
+ ["rsvp/promise","rsvp/all","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__.Promise;
+ var all = __dependency2__.all;
+
+ function makeNodeCallbackFor(resolve, reject) {
+ return function (error, value) {
+ if (error) {
+ reject(error);
+ } else if (arguments.length > 2) {
+ resolve(Array.prototype.slice.call(arguments, 1));
+ } else {
+ resolve(value);
+ }
+ };
+ }
+
+ function denodeify(nodeFunc) {
+ return function() {
+ var nodeArgs = Array.prototype.slice.call(arguments), resolve, reject;
+
+ var promise = new Promise(function(nodeResolve, nodeReject) {
+ resolve = nodeResolve;
+ reject = nodeReject;
+ });
+
+ all(nodeArgs).then(function(nodeArgs) {
+ nodeArgs.push(makeNodeCallbackFor(resolve, reject));
+
+ try {
+ nodeFunc.apply(this, nodeArgs);
+ } catch(e) {
+ reject(e);
+ }
+ });
+
+ return promise;
+ };
+ }
+
+ __exports__.denodeify = denodeify;
+ });
+
+define("rsvp/promise",
+ ["rsvp/config","rsvp/events","exports"],
+ function(__dependency1__, __dependency2__, __exports__) {
+ "use strict";
+ var config = __dependency1__.config;
+ var EventTarget = __dependency2__.EventTarget;
+
+ function objectOrFunction(x) {
+ return isFunction(x) || (typeof x === "object" && x !== null);
+ }
+
+ function isFunction(x){
+ return typeof x === "function";
+ }
+
+ var Promise = function(resolver) {
+ var promise = this,
+ resolved = false;
+
+ if (typeof resolver !== 'function') {
+ throw new TypeError('You must pass a resolver function as the sole argument to the promise constructor');
+ }
+
+ if (!(promise instanceof Promise)) {
+ return new Promise(resolver);
+ }
+
+ var resolvePromise = function(value) {
+ if (resolved) { return; }
+ resolved = true;
+ resolve(promise, value);
+ };
+
+ var rejectPromise = function(value) {
+ if (resolved) { return; }
+ resolved = true;
+ reject(promise, value);
+ };
+
this.on('promise:resolved', function(event) {
this.trigger('success', { detail: event.detail });
}, this);
this.on('promise:failed', function(event) {
this.trigger('error', { detail: event.detail });
}, this);
+
+ try {
+ resolver(resolvePromise, rejectPromise);
+ } catch(e) {
+ rejectPromise(e);
+ }
};
- var noop = function() {};
-
var invokeCallback = function(type, promise, callback, event) {
- var hasCallback = typeof callback === 'function',
+ var hasCallback = isFunction(callback),
value, error, succeeded, failed;
if (hasCallback) {
try {
value = callback(event.detail);
@@ -6319,38 +6847,38 @@
} else {
value = event.detail;
succeeded = true;
}
- if (value && typeof value.then === 'function') {
- value.then(function(value) {
- promise.resolve(value);
- }, function(error) {
- promise.reject(error);
- });
+ if (handleThenable(promise, value)) {
+ return;
} else if (hasCallback && succeeded) {
- promise.resolve(value);
+ resolve(promise, value);
} else if (failed) {
- promise.reject(error);
- } else {
- promise[type](value);
+ reject(promise, error);
+ } else if (type === 'resolve') {
+ resolve(promise, value);
+ } else if (type === 'reject') {
+ reject(promise, value);
}
};
Promise.prototype = {
+ constructor: Promise,
+
then: function(done, fail) {
- var thenPromise = new Promise();
+ var thenPromise = new Promise(function() {});
- if (this.isResolved) {
- RSVP.async(function() {
- invokeCallback('resolve', thenPromise, done, { detail: this.resolvedValue });
+ if (this.isFulfilled) {
+ config.async(function() {
+ invokeCallback('resolve', thenPromise, done, { detail: this.fulfillmentValue });
}, this);
}
if (this.isRejected) {
- RSVP.async(function() {
- invokeCallback('reject', thenPromise, fail, { detail: this.rejectedValue });
+ config.async(function() {
+ invokeCallback('reject', thenPromise, fail, { detail: this.rejectedReason });
}, this);
}
this.on('promise:resolved', function(event) {
invokeCallback('resolve', thenPromise, done, event);
@@ -6359,79 +6887,166 @@
this.on('promise:failed', function(event) {
invokeCallback('reject', thenPromise, fail, event);
});
return thenPromise;
- },
+ }
+ };
- resolve: function(value) {
- resolve(this, value);
+ EventTarget.mixin(Promise.prototype);
- this.resolve = noop;
- this.reject = noop;
- },
+ function resolve(promise, value) {
+ if (promise === value) {
+ fulfill(promise, value);
+ } else if (!handleThenable(promise, value)) {
+ fulfill(promise, value);
+ }
+ }
- reject: function(value) {
- reject(this, value);
+ function handleThenable(promise, value) {
+ var then = null;
- this.resolve = noop;
- this.reject = noop;
+ if (objectOrFunction(value)) {
+ try {
+ then = value.then;
+ } catch(e) {
+ reject(promise, e);
+ return true;
+ }
+
+ if (isFunction(then)) {
+ try {
+ then.call(value, function(val) {
+ if (value !== val) {
+ resolve(promise, val);
+ } else {
+ fulfill(promise, val);
+ }
+ }, function(val) {
+ reject(promise, val);
+ });
+ } catch (e) {
+ reject(promise, e);
+ }
+ return true;
+ }
}
- };
- function resolve(promise, value) {
- RSVP.async(function() {
+ return false;
+ }
+
+ function fulfill(promise, value) {
+ config.async(function() {
promise.trigger('promise:resolved', { detail: value });
- promise.isResolved = true;
- promise.resolvedValue = value;
+ promise.isFulfilled = true;
+ promise.fulfillmentValue = value;
});
}
function reject(promise, value) {
- RSVP.async(function() {
+ config.async(function() {
promise.trigger('promise:failed', { detail: value });
promise.isRejected = true;
- promise.rejectedValue = value;
+ promise.rejectedReason = value;
});
}
- function all(promises) {
- var i, results = [];
- var allPromise = new Promise();
- var remaining = promises.length;
- if (remaining === 0) {
- allPromise.resolve([]);
- }
+ __exports__.Promise = Promise;
+ });
- var resolver = function(index) {
- return function(value) {
- resolve(index, value);
- };
- };
+define("rsvp/reject",
+ ["rsvp/promise","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__.Promise;
- var resolve = function(index, value) {
- results[index] = value;
- if (--remaining === 0) {
- allPromise.resolve(results);
+
+ function objectOrFunction(x) {
+ return typeof x === "function" || (typeof x === "object" && x !== null);
+ }
+
+
+ function reject(reason) {
+ return new Promise(function (resolve, reject) {
+ reject(reason);
+ });
+ }
+
+
+ __exports__.reject = reject;
+ });
+
+define("rsvp/resolve",
+ ["rsvp/promise","exports"],
+ function(__dependency1__, __exports__) {
+ "use strict";
+ var Promise = __dependency1__.Promise;
+
+
+ function objectOrFunction(x) {
+ return typeof x === "function" || (typeof x === "object" && x !== null);
+ }
+
+ function resolve(thenable){
+ var promise = new Promise(function(resolve, reject){
+ var then;
+
+ try {
+ if ( objectOrFunction(thenable) ) {
+ then = thenable.then;
+
+ if (typeof then === "function") {
+ then.call(thenable, resolve, reject);
+ } else {
+ resolve(thenable);
+ }
+
+ } else {
+ resolve(thenable);
+ }
+
+ } catch(error) {
+ reject(error);
}
- };
+ });
- var reject = function(error) {
- allPromise.reject(error);
- };
+ return promise;
+ }
- for (i = 0; i < remaining; i++) {
- promises[i].then(resolver(i), reject);
- }
- return allPromise;
+
+ __exports__.resolve = resolve;
+ });
+
+define("rsvp",
+ ["rsvp/events","rsvp/promise","rsvp/node","rsvp/all","rsvp/hash","rsvp/defer","rsvp/config","rsvp/resolve","rsvp/reject","exports"],
+ function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __dependency9__, __exports__) {
+ "use strict";
+ var EventTarget = __dependency1__.EventTarget;
+ var Promise = __dependency2__.Promise;
+ var denodeify = __dependency3__.denodeify;
+ var all = __dependency4__.all;
+ var hash = __dependency5__.hash;
+ var defer = __dependency6__.defer;
+ var config = __dependency7__.config;
+ var resolve = __dependency8__.resolve;
+ var reject = __dependency9__.reject;
+
+ function configure(name, value) {
+ config[name] = value;
}
- EventTarget.mixin(Promise.prototype);
- RSVP = { async: async, Promise: Promise, Event: Event, EventTarget: EventTarget, all: all, raiseOnUncaughtExceptions: true };
- return RSVP;
+ __exports__.Promise = Promise;
+ __exports__.EventTarget = EventTarget;
+ __exports__.all = all;
+ __exports__.hash = hash;
+ __exports__.defer = defer;
+ __exports__.denodeify = denodeify;
+ __exports__.configure = configure;
+ __exports__.resolve = resolve;
+ __exports__.reject = reject;
});
})();
(function() {
@@ -6604,14 +7219,10 @@
}
this.children = [];
eachDestroyable(this, function(item) {
- item.isDestroying = true;
- });
-
- eachDestroyable(this, function(item) {
item.destroy();
});
delete this.parent;
this.isDestroyed = true;
@@ -6977,14 +7588,19 @@
@param {Object} obj
@return {Array} Array containing keys of obj
*/
Ember.keys = Object.keys;
-if (!Ember.keys) {
+if (!Ember.keys || Ember.create.isSimulated) {
Ember.keys = function(obj) {
var ret = [];
for(var key in obj) {
+ // Prevents browsers that don't respect non-enumerability from
+ // copying internal Ember properties
+ if (key.substring(0,2) === '__') continue;
+ if (key === '_super') continue;
+
if (obj.hasOwnProperty(key)) { ret.push(key); }
}
return ret;
};
}
@@ -7002,11 +7618,11 @@
@namespace Ember
@extends Error
@constructor
*/
Ember.Error = function() {
- var tmp = Error.prototype.constructor.apply(this, arguments);
+ var tmp = Error.apply(this, arguments);
// Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work.
for (var idx = 0; idx < errorProps.length; idx++) {
this[errorProps[idx]] = tmp[errorProps[idx]];
}
@@ -7264,14 +7880,16 @@
},
/**
Returns the Capitalized form of a string
- 'innerHTML'.capitalize() // 'InnerHTML'
- 'action_name'.capitalize() // 'Action_name'
- 'css-class-name'.capitalize() // 'Css-class-name'
- 'my favorite items'.capitalize() // 'My favorite items'
+ ```javascript
+ 'innerHTML'.capitalize() // 'InnerHTML'
+ 'action_name'.capitalize() // 'Action_name'
+ 'css-class-name'.capitalize() // 'Css-class-name'
+ 'my favorite items'.capitalize() // 'My favorite items'
+ ```
@method capitalize
@param {String} str
@return {String}
*/
@@ -7304,91 +7922,91 @@
classify = Ember.String.classify;
if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
/**
- See {{#crossLink "Ember.String/fmt"}}{{/crossLink}}
+ See `Ember.String.fmt`.
@method fmt
@for String
*/
String.prototype.fmt = function() {
return fmt(this, arguments);
};
/**
- See {{#crossLink "Ember.String/w"}}{{/crossLink}}
+ See `Ember.String.w`.
@method w
@for String
*/
String.prototype.w = function() {
return w(this);
};
/**
- See {{#crossLink "Ember.String/loc"}}{{/crossLink}}
+ See `Ember.String.loc`.
@method loc
@for String
*/
String.prototype.loc = function() {
return loc(this, arguments);
};
/**
- See {{#crossLink "Ember.String/camelize"}}{{/crossLink}}
+ See `Ember.String.camelize`.
@method camelize
@for String
*/
String.prototype.camelize = function() {
return camelize(this);
};
/**
- See {{#crossLink "Ember.String/decamelize"}}{{/crossLink}}
+ See `Ember.String.decamelize`.
@method decamelize
@for String
*/
String.prototype.decamelize = function() {
return decamelize(this);
};
/**
- See {{#crossLink "Ember.String/dasherize"}}{{/crossLink}}
+ See `Ember.String.dasherize`.
@method dasherize
@for String
*/
String.prototype.dasherize = function() {
return dasherize(this);
};
/**
- See {{#crossLink "Ember.String/underscore"}}{{/crossLink}}
+ See `Ember.String.underscore`.
@method underscore
@for String
*/
String.prototype.underscore = function() {
return underscore(this);
};
/**
- See {{#crossLink "Ember.String/classify"}}{{/crossLink}}
+ See `Ember.String.classify`.
@method classify
@for String
*/
String.prototype.classify = function() {
return classify(this);
};
/**
- See {{#crossLink "Ember.String/capitalize"}}{{/crossLink}}
+ See `Ember.String.capitalize`.
@method capitalize
@for String
*/
String.prototype.capitalize = function() {
@@ -7460,12 +8078,11 @@
bindings that connect to a computed property. Changing a dependency
will not immediately trigger an update of the computed property, but
will instead clear the cache so that it is updated when the next `get`
is called on the property.
- See {{#crossLink "Ember.ComputedProperty"}}{{/crossLink}},
- {{#crossLink "Ember/computed"}}{{/crossLink}}
+ See `Ember.ComputedProperty`, `Ember.computed`.
@method property
@for Function
*/
Function.prototype.property = function() {
@@ -7488,11 +8105,11 @@
// Executes whenever the "value" property changes
}.observes('value')
});
```
- See {{#crossLink "Ember.Observable/observes"}}{{/crossLink}}
+ See `Ember.Observable.observes`.
@method observes
@for Function
*/
Function.prototype.observes = function() {
@@ -7515,11 +8132,11 @@
// Executes whenever the "value" property is about to change
}.observesBefore('value')
});
```
- See {{#crossLink "Ember.Observable/observesBefore"}}{{/crossLink}}
+ See `Ember.Observable.observesBefore`.
@method observesBefore
@for Function
*/
Function.prototype.observesBefore = function() {
@@ -8412,14 +9029,12 @@
// ..........................................................
// HELPERS
//
-var get = Ember.get, set = Ember.set, map = Ember.EnumerableUtils.map, cacheFor = Ember.cacheFor;
+var get = Ember.get, set = Ember.set, isNone = Ember.isNone, map = Ember.EnumerableUtils.map, cacheFor = Ember.cacheFor;
-function none(obj) { return obj===null || obj===undefined; }
-
// ..........................................................
// ARRAY
//
/**
This module implements Observer-friendly Array-like behavior. This mixin is
@@ -8437,11 +9052,11 @@
flexibility of using both true JavaScript arrays and "virtual" arrays such
as controllers and collections.
You can use the methods defined in this module to access and modify array
contents in a KVO-friendly way. You can also be notified whenever the
- membership if an array changes by changing the syntax of the property to
+ membership of an array changes by changing the syntax of the property to
`.observes('*myProperty.[]')`.
To support `Ember.Array` in your own class, you must override two
primitives to use it: `replace()` and `objectAt()`.
@@ -8454,13 +9069,10 @@
@uses Ember.Enumerable
@since Ember 0.9.0
*/
Ember.Array = Ember.Mixin.create(Ember.Enumerable, /** @scope Ember.Array.prototype */ {
- // compatibility
- isSCArray: true,
-
/**
Your array must support the `length` property. Your replace methods should
set this property whenever it changes.
@property {Number} length
@@ -8564,12 +9176,12 @@
@return {Array} New array with specified slice
*/
slice: function(beginIndex, endIndex) {
var ret = Ember.A([]);
var length = get(this, 'length') ;
- if (none(beginIndex)) beginIndex = 0 ;
- if (none(endIndex) || (endIndex > length)) endIndex = length ;
+ if (isNone(beginIndex)) beginIndex = 0 ;
+ if (isNone(endIndex) || (endIndex > length)) endIndex = length ;
if (beginIndex < 0) beginIndex = length + beginIndex;
if (endIndex < 0) endIndex = length + endIndex;
while(beginIndex < endIndex) {
@@ -8725,11 +9337,11 @@
@method arrayContentWillChange
@param {Number} startIdx The starting index in the array that will change.
@param {Number} removeAmt The number of items that will be removed. If you
pass `null` assumes 0
- @param {Number} addAmt The number of items that will be added If you
+ @param {Number} addAmt The number of items that will be added. If you
pass `null` assumes 0.
@return {Ember.Array} receiver
*/
arrayContentWillChange: function(startIdx, removeAmt, addAmt) {
@@ -8759,10 +9371,24 @@
this.enumerableContentWillChange(removing, addAmt);
return this;
},
+ /**
+ If you are implementing an object that supports `Ember.Array`, call this
+ method just after the array content changes to notify any observers and
+ invalidate any related properties. Pass the starting index of the change
+ as well as a delta of the amounts to change.
+
+ @method arrayContentDidChange
+ @param {Number} startIdx The starting index in the array that did change.
+ @param {Number} removeAmt The number of items that were removed. If you
+ pass `null` assumes 0
+ @param {Number} addAmt The number of items that were added. If you
+ pass `null` assumes 0.
+ @return {Ember.Array} receiver
+ */
arrayContentDidChange: function(startIdx, removeAmt, addAmt) {
// if no args are passed assume everything changes
if (startIdx===undefined) {
startIdx = 0;
@@ -8899,12 +9525,11 @@
@class Copyable
@namespace Ember
@extends Ember.Mixin
@since Ember 0.9
*/
-Ember.Copyable = Ember.Mixin.create(
-/** @scope Ember.Copyable.prototype */ {
+Ember.Copyable = Ember.Mixin.create(/** @scope Ember.Copyable.prototype */ {
/**
Override to return a copy of the receiver. Default implementation raises
an exception.
@@ -9005,12 +9630,11 @@
@class Freezable
@namespace Ember
@extends Ember.Mixin
@since Ember 0.9
*/
-Ember.Freezable = Ember.Mixin.create(
-/** @scope Ember.Freezable.prototype */ {
+Ember.Freezable = Ember.Mixin.create(/** @scope Ember.Freezable.prototype */ {
/**
Set to `true` when the object is frozen. Use this property to detect
whether your object is frozen or not.
@@ -9187,12 +9811,11 @@
@namespace Ember
@extends Ember.Mixin
@uses Ember.Array
@uses Ember.MutableEnumerable
*/
-Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,
- /** @scope Ember.MutableArray.prototype */ {
+Ember.MutableArray = Ember.Mixin.create(Ember.Array, Ember.MutableEnumerable,/** @scope Ember.MutableArray.prototype */ {
/**
__Required.__ You must implement this method to apply this mixin.
This is one of the primitives you must implement to support `Ember.Array`.
@@ -9459,11 +10082,10 @@
return this ;
}
});
-
})();
(function() {
@@ -9507,11 +10129,11 @@
You typically observe property changes simply by adding the `observes`
call to the end of your method declarations in classes that you write.
For example:
```javascript
- Ember.Object.create({
+ Ember.Object.extend({
valueObserver: function() {
// Executes whenever the "value" property changes
}.observes('value')
});
```
@@ -9526,12 +10148,12 @@
```javascript
object.addObserver('propertyKey', targetObject, targetAction)
```
- This will call the `targetAction` method on the `targetObject` to be called
- whenever the value of the `propertyKey` changes.
+ This will call the `targetAction` method on the `targetObject` whenever
+ the value of the `propertyKey` changes.
Note that if `propertyKey` is a computed property, the observer will be
called when any of the property dependencies are changed, even if the
resulting value of the computed property is unchanged. This is necessary
because computed properties are not computed until `get` is called.
@@ -9787,12 +10409,12 @@
/**
Adds an observer on a property.
This is the core method used to register an observer for a property.
- Once you call this method, anytime the key's value is set, your observer
- will be notified. Note that the observers are triggered anytime the
+ Once you call this method, any time the key's value is set, your observer
+ will be notified. Note that the observers are triggered any time the
value is set, regardless of whether it has actually changed. Your
observer should be prepared to handle that.
You can also pass an optional context parameter to this method. The
context will be passed to your observer method whenever it is triggered.
@@ -9913,15 +10535,15 @@
team.incrementProperty('score', 2);
```
@method incrementProperty
@param {String} keyName The name of the property to increment
- @param {Object} increment The amount to increment by. Defaults to 1
- @return {Object} The new property value
+ @param {Number} increment The amount to increment by. Defaults to 1
+ @return {Number} The new property value
*/
incrementProperty: function(keyName, increment) {
- if (!increment) { increment = 1; }
+ if (Ember.isNone(increment)) { increment = 1; }
set(this, keyName, (get(this, keyName) || 0)+increment);
return get(this, keyName);
},
/**
@@ -9932,25 +10554,25 @@
orc.decrementProperty('health', 5);
```
@method decrementProperty
@param {String} keyName The name of the property to decrement
- @param {Object} increment The amount to decrement by. Defaults to 1
- @return {Object} The new property value
+ @param {Number} decrement The amount to decrement by. Defaults to 1
+ @return {Number} The new property value
*/
- decrementProperty: function(keyName, increment) {
- if (!increment) { increment = 1; }
- set(this, keyName, (get(this, keyName) || 0)-increment);
+ decrementProperty: function(keyName, decrement) {
+ if (Ember.isNone(decrement)) { decrement = 1; }
+ set(this, keyName, (get(this, keyName) || 0)-decrement);
return get(this, keyName);
},
/**
Set the value of a boolean property to the opposite of it's
current value.
```javascript
- starship.toggleProperty('warpDriveEnaged');
+ starship.toggleProperty('warpDriveEngaged');
```
@method toggleProperty
@param {String} keyName The name of the property to toggle
@return {Object} The new property value
@@ -9978,11 +10600,10 @@
observersForKey: function(keyName) {
return Ember.observersFor(this, keyName);
}
});
-
})();
(function() {
@@ -10273,13 +10894,13 @@
(function() {
var RSVP = requireModule("rsvp");
-RSVP.async = function(callback, binding) {
+RSVP.configure('async', function(callback, binding) {
Ember.run.schedule('actions', binding, callback);
-};
+});
/**
@module ember
@submodule ember-runtime
*/
@@ -10297,35 +10918,57 @@
@method then
@param {Function} doneCallback a callback function to be called when done
@param {Function} failCallback a callback function to be called when failed
*/
- then: function(doneCallback, failCallback) {
- var promise = get(this, 'promise');
- return promise.then.apply(promise, arguments);
+ then: function(resolve, reject) {
+ var deferred, promise, entity;
+
+ entity = this;
+ deferred = get(this, '_deferred');
+ promise = deferred.promise;
+
+ function fulfillmentHandler(fulfillment) {
+ if (fulfillment === promise) {
+ return resolve(entity);
+ } else {
+ return resolve(fulfillment);
+ }
+ }
+
+ return promise.then(resolve && fulfillmentHandler, reject);
},
/**
Resolve a Deferred object and call any `doneCallbacks` with the given args.
@method resolve
*/
resolve: function(value) {
- get(this, 'promise').resolve(value);
+ var deferred, promise;
+
+ deferred = get(this, '_deferred');
+ promise = deferred.promise;
+
+ if (value === this){
+ deferred.resolve(promise);
+ } else {
+ deferred.resolve(value);
+ }
},
/**
Reject a Deferred object and call any `failCallbacks` with the given args.
@method reject
*/
reject: function(value) {
- get(this, 'promise').reject(value);
+ get(this, '_deferred').reject(value);
},
- promise: Ember.computed(function() {
- return new RSVP.Promise();
+ _deferred: Ember.computed(function() {
+ return RSVP.defer();
})
});
})();
@@ -10632,25 +11275,27 @@
If you try to set a property on a destroyed object, an exception will be
raised.
Note that destruction is scheduled for the end of the run loop and does not
- happen immediately.
+ happen immediately. It will set an isDestroying flag immediately.
@method destroy
@return {Ember.Object} receiver
*/
destroy: function() {
- if (this._didCallDestroy) { return; }
-
+ if (this.isDestroying) { return; }
this.isDestroying = true;
- this._didCallDestroy = true;
+ schedule('actions', this, this.willDestroy);
schedule('destroy', this, this._scheduledDestroy);
return this;
},
+ /**
+ Override to implement teardown.
+ */
willDestroy: Ember.K,
/**
@private
@@ -10658,14 +11303,13 @@
scheduled for execution by the `destroy` method.
@method _scheduledDestroy
*/
_scheduledDestroy: function() {
- if (this.willDestroy) { this.willDestroy(); }
+ if (this.isDestroyed) { return; }
destroy(this);
this.isDestroyed = true;
- if (this.didDestroy) { this.didDestroy(); }
},
bind: function(to, from) {
if (!(from instanceof Ember.Binding)) { from = Ember.Binding.from(from); }
from.to(to).connect(this);
@@ -11151,12 +11795,11 @@
@class ArrayProxy
@namespace Ember
@extends Ember.Object
@uses Ember.MutableArray
*/
-Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray,
-/** @scope Ember.ArrayProxy.prototype */ {
+Ember.ArrayProxy = Ember.Object.extend(Ember.MutableArray,/** @scope Ember.ArrayProxy.prototype */ {
/**
The content array. Must be an object that implements `Ember.Array` and/or
`Ember.MutableArray.`
@@ -11426,11 +12069,10 @@
this._teardownArrangedContent();
this._teardownContent();
}
});
-
})();
(function() {
@@ -11526,12 +12168,11 @@
@class ObjectProxy
@namespace Ember
@extends Ember.Object
*/
-Ember.ObjectProxy = Ember.Object.extend(
-/** @scope Ember.ObjectProxy.prototype */ {
+Ember.ObjectProxy = Ember.Object.extend(/** @scope Ember.ObjectProxy.prototype */ {
/**
The object whose properties will be forwarded.
@property content
@type Ember.Object
@@ -11965,11 +12606,11 @@
/**
@module ember
@submodule ember-runtime
*/
-var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, none = Ember.isNone, fmt = Ember.String.fmt;
+var get = Ember.get, set = Ember.set, guidFor = Ember.guidFor, isNone = Ember.isNone, fmt = Ember.String.fmt;
/**
An unordered collection of objects.
A Set works a bit like an array except that its items are not ordered. You
@@ -12323,11 +12964,11 @@
}),
// implements Ember.MutableEnumerable
addObject: function(obj) {
if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
- if (none(obj)) return this; // nothing to do
+ if (isNone(obj)) return this; // nothing to do
var guid = guidFor(obj),
idx = this[guid],
len = get(this, 'length'),
added ;
@@ -12351,11 +12992,11 @@
},
// implements Ember.MutableEnumerable
removeObject: function(obj) {
if (get(this, 'isFrozen')) throw new Error(Ember.FROZEN_ERROR);
- if (none(obj)) return this; // nothing to do
+ if (isNone(obj)) return this; // nothing to do
var guid = guidFor(obj),
idx = this[guid],
len = get(this, 'length'),
isFirst = idx === 0,
@@ -12426,21 +13067,23 @@
Deferred.reopenClass({
promise: function(callback, binding) {
var deferred = Deferred.create();
callback.call(binding, deferred);
- return get(deferred, 'promise');
+ return deferred;
}
});
Ember.Deferred = Deferred;
})();
(function() {
+var forEach = Ember.ArrayPolyfills.forEach;
+
/**
@module ember
@submodule ember-runtime
*/
@@ -12469,16 +13112,14 @@
@for Ember
@param name {String} name of hook
@param object {Object} object to pass to callbacks
*/
Ember.runLoadHooks = function(name, object) {
- var hooks;
-
loaded[name] = object;
- if (hooks = loadHooks[name]) {
- loadHooks[name].forEach(function(callback) {
+ if (loadHooks[name]) {
+ forEach.call(loadHooks[name], function(callback) {
callback(object);
});
}
};
@@ -12548,10 +13189,12 @@
*/
target: null,
container: null,
+ parentController: null,
+
store: null,
model: Ember.computed.alias('content'),
send: function(actionName) {
@@ -12889,10 +13532,14 @@
}
}
});
```
+ The itemController instances will have a `parentController` property set to
+ either the the `parentController` property of the `ArrayController`
+ or to the `ArrayController` instance itself.
+
@class ArrayController
@namespace Ember
@extends Ember.ArrayProxy
@uses Ember.SortableMixin
@uses Ember.ControllerMixin
@@ -12999,10 +13646,11 @@
if (!subController) {
throw new Error('Could not resolve itemController: "' + controllerClass + '"');
}
subController.set('target', this);
+ subController.set('parentController', get(this, 'parentController') || this);
subController.set('content', object);
return subController;
},
@@ -13112,11 +13760,11 @@
/**
@module ember
@submodule ember-views
*/
-/*** BEGIN METAMORPH HELPERS ***/
+/* BEGIN METAMORPH HELPERS */
// Internet Explorer prior to 9 does not allow setting innerHTML if the first element
// is a "zero-scope" element. This problem can be worked around by making
// the first node an invisible text node. We, like Modernizr, use ­
@@ -13185,11 +13833,11 @@
shyElement.nodeValue = shyElement.nodeValue.slice(1);
}
}
};
-/*** END METAMORPH HELPERS */
+/* END METAMORPH HELPERS */
var innerHTMLTags = {};
var canSetInnerHTML = function(tagName) {
if (innerHTMLTags[tagName] !== undefined) {
@@ -13274,49 +13922,10 @@
toDOM: function() {
return this.list.join(" ");
}
};
-var BAD_TAG_NAME_TEST_REGEXP = /[^a-zA-Z\-]/;
-var BAD_TAG_NAME_REPLACE_REGEXP = /[^a-zA-Z\-]/g;
-
-function stripTagName(tagName) {
- if (!tagName) {
- return tagName;
- }
-
- if (!BAD_TAG_NAME_TEST_REGEXP.test(tagName)) {
- return tagName;
- }
-
- return tagName.replace(BAD_TAG_NAME_REPLACE_REGEXP, '');
-}
-
-var BAD_CHARS_REGEXP = /&(?!\w+;)|[<>"'`]/g;
-var POSSIBLE_CHARS_REGEXP = /[&<>"'`]/;
-
-function escapeAttribute(value) {
- // Stolen shamelessly from Handlebars
-
- var escape = {
- "<": "<",
- ">": ">",
- '"': """,
- "'": "'",
- "`": "`"
- };
-
- var escapeChar = function(chr) {
- return escape[chr] || "&";
- };
-
- var string = value.toString();
-
- if(!POSSIBLE_CHARS_REGEXP.test(string)) { return string; }
- return string.replace(BAD_CHARS_REGEXP, escapeChar);
-}
-
/**
`Ember.RenderBuffer` gathers information regarding the a view and generates the
final representation. `Ember.RenderBuffer` will generate HTML which can be pushed
to the DOM.
@@ -13600,27 +14209,27 @@
attrs = this.elementAttributes,
props = this.elementProperties,
style = this.elementStyle,
attr, prop;
- buffer += '<' + stripTagName(tagName);
+ buffer += '<' + tagName;
if (id) {
- buffer += ' id="' + escapeAttribute(id) + '"';
+ buffer += ' id="' + this._escapeAttribute(id) + '"';
this.elementId = null;
}
if (classes) {
- buffer += ' class="' + escapeAttribute(classes.join(' ')) + '"';
+ buffer += ' class="' + this._escapeAttribute(classes.join(' ')) + '"';
this.classes = null;
}
if (style) {
buffer += ' style="';
for (prop in style) {
if (style.hasOwnProperty(prop)) {
- buffer += prop + ':' + escapeAttribute(style[prop]) + ';';
+ buffer += prop + ':' + this._escapeAttribute(style[prop]) + ';';
}
}
buffer += '"';
@@ -13628,11 +14237,11 @@
}
if (attrs) {
for (attr in attrs) {
if (attrs.hasOwnProperty(attr)) {
- buffer += ' ' + attr + '="' + escapeAttribute(attrs[attr]) + '"';
+ buffer += ' ' + attr + '="' + this._escapeAttribute(attrs[attr]) + '"';
}
}
this.elementAttributes = null;
}
@@ -13643,11 +14252,11 @@
var value = props[prop];
if (value || typeof(value) === 'number') {
if (value === true) {
buffer += ' ' + prop + '="' + prop + '"';
} else {
- buffer += ' ' + prop + '="' + escapeAttribute(props[prop]) + '"';
+ buffer += ' ' + prop + '="' + this._escapeAttribute(props[prop]) + '"';
}
}
}
}
@@ -13658,11 +14267,11 @@
this.buffer = buffer;
},
pushClosingTag: function() {
var tagName = this.tagNames.pop();
- if (tagName) { this.buffer += '</' + stripTagName(tagName) + '>'; }
+ if (tagName) { this.buffer += '</' + tagName + '>'; }
},
currentTagName: function() {
return this.tagNames[this.tagNames.length-1];
},
@@ -13756,11 +14365,36 @@
}
},
innerString: function() {
return this.buffer;
+ },
+
+ _escapeAttribute: function(value) {
+ // Stolen shamelessly from Handlebars
+
+ var escape = {
+ "<": "<",
+ ">": ">",
+ '"': """,
+ "'": "'",
+ "`": "`"
+ };
+
+ var badChars = /&(?!\w+;)|[<>"'`]/g;
+ var possible = /[&<>"'`]/;
+
+ var escapeChar = function(chr) {
+ return escape[chr] || "&";
+ };
+
+ var string = value.toString();
+
+ if(!possible.test(string)) { return string; }
+ return string.replace(badChars, escapeChar);
}
+
};
})();
@@ -13782,12 +14416,11 @@
@class EventDispatcher
@namespace Ember
@private
@extends Ember.Object
*/
-Ember.EventDispatcher = Ember.Object.extend(
-/** @scope Ember.EventDispatcher.prototype */{
+Ember.EventDispatcher = Ember.Object.extend(/** @scope Ember.EventDispatcher.prototype */{
/**
@private
The root DOM element to which event listeners should be attached. Event
@@ -13815,11 +14448,11 @@
dispatcher's `root` property.
@method setup
@param addedEvents {Hash}
*/
- setup: function(addedEvents) {
+ setup: function(addedEvents, rootElement) {
var event, events = {
touchstart : 'touchStart',
touchmove : 'touchMove',
touchend : 'touchEnd',
touchcancel : 'touchCancel',
@@ -13848,15 +14481,20 @@
dragend : 'dragEnd'
};
Ember.$.extend(events, addedEvents || {});
- var rootElement = Ember.$(get(this, 'rootElement'));
+ if (!Ember.isNone(rootElement)) {
+ set(this, 'rootElement', rootElement);
+ }
+ rootElement = Ember.$(get(this, 'rootElement'));
+
+
rootElement.addClass('ember-application');
for (event in events) {
if (events.hasOwnProperty(event)) {
@@ -13888,11 +14526,11 @@
@param {String} eventName the name of the method to call on the view
*/
setupHandler: function(rootElement, event, eventName) {
var self = this;
- rootElement.delegate('.ember-view', event + '.ember', function(evt, triggeringManager) {
+ rootElement.on(event + '.ember', '.ember-view', function(evt, triggeringManager) {
return Ember.handleErrors(function() {
var view = Ember.View.views[this.id],
result = true, manager = null;
manager = self._findNearestEventManager(view,eventName);
@@ -13907,11 +14545,11 @@
return result;
}, this);
});
- rootElement.delegate('[data-ember-action]', event + '.ember', function(evt) {
+ rootElement.on(event + '.ember', '[data-ember-action]', function(evt) {
return Ember.handleErrors(function() {
var actionId = Ember.$(evt.currentTarget).attr('data-ember-action'),
action = Ember.Handlebars.ActionHelper.registeredActions[actionId];
// We have to check for action here since in some cases, jQuery will trigger
@@ -13959,11 +14597,11 @@
});
},
destroy: function() {
var rootElement = get(this, 'rootElement');
- Ember.$(rootElement).undelegate('.ember').removeClass('ember-application');
+ Ember.$(rootElement).off('.ember', '**').removeClass('ember-application');
return this._super();
}
});
})();
@@ -14761,11 +15399,11 @@
OuterView = Ember.View.extend({
template: Ember.Handlebars.compile("outer {{#view InnerView}}inner{{/view}} outer"),
eventManager: Ember.Object.create({
mouseEnter: function(event, view){
// view might be instance of either
- // OutsideView or InnerView depending on
+ // OuterView or InnerView depending on
// where on the page the user interaction occured
}
})
});
@@ -14953,10 +15591,12 @@
}).property('layoutName'),
templateForName: function(name, type) {
if (!name) { return; }
+
+ // the defaultContainer is deprecated
var container = this.container || (Ember.Container && Ember.Container.defaultContainer);
return container && container.lookup('template:' + name);
},
/**
@@ -16420,11 +17060,11 @@
var parts = path.split('.');
return Ember.String.dasherize(parts[parts.length-1]);
// If the value is not false, undefined, or null, return the current
// value of the property.
- } else if (val !== false && val !== undefined && val !== null) {
+ } else if (val !== false && val != null) {
return val;
// Nothing to display. Return null so that the old class is removed
// but no new class is added.
} else {
@@ -17068,11 +17708,13 @@
}
},
replace: function(idx, removedCount, addedViews) {
var addedCount = addedViews ? get(addedViews, 'length') : 0;
+ var self = this;
+
this.arrayContentWillChange(idx, removedCount, addedCount);
this.childViewsWillChange(this._childViews, idx, removedCount);
if (addedCount === 0) {
this._childViews.splice(idx, removedCount) ;
@@ -17108,11 +17750,11 @@
this.forEachChildView(function(view) {
view.renderToBuffer(buffer);
});
},
- instrumentName: 'render.container',
+ instrumentName: 'container',
/**
@private
When a child view is removed, destroy its element so that
@@ -17188,10 +17830,11 @@
}, 'currentView'),
_currentViewDidChange: Ember.observer(function() {
var currentView = get(this, 'currentView');
if (currentView) {
+
this.pushObject(currentView);
}
}, 'currentView'),
_ensureChildrenAreInDOM: function () {
@@ -17277,11 +17920,11 @@
var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
/**
`Ember.CollectionView` is an `Ember.View` descendent responsible for managing
- a collection (an array or array-like object) by maintaing a child view object
+ a collection (an array or array-like object) by maintaining a child view object
and associated DOM representation for each item in the array and ensuring
that child views and their associated rendered HTML are updated when items in
the array are added, removed, or replaced.
## Setting content
@@ -17428,12 +18071,11 @@
@class CollectionView
@namespace Ember
@extends Ember.ContainerView
@since Ember 0.9
*/
-Ember.CollectionView = Ember.ContainerView.extend(
-/** @scope Ember.CollectionView.prototype */ {
+Ember.CollectionView = Ember.ContainerView.extend(/** @scope Ember.CollectionView.prototype */ {
/**
A list of items to be displayed by the `Ember.CollectionView`.
@property content
@@ -17555,13 +18197,14 @@
`Ember.CollectionView`, ensuring that the view reflects the model.
This array observer is added in `contentDidChange`.
@method arrayDidChange
- @param {Array} addedObjects the objects that were added to the content
- @param {Array} removedObjects the objects that were removed from the content
- @param {Number} changeIndex the index at which the changes occurred
+ @param {Array} content the managed collection of objects
+ @param {Number} start the index at which the changes occurred
+ @param {Number} removed number of object removed from content
+ @param {Number} added number of object added to content
*/
arrayDidChange: function(content, start, removed, added) {
var itemViewClass = get(this, 'itemViewClass'),
addedViews = [], view, item, idx, len;
@@ -18195,10 +18838,11 @@
if(!Handlebars && typeof require === 'function') {
Handlebars = require('handlebars');
}
+
/**
Prepares the Handlebars templating library for use inside Ember's view
system.
The `Ember.Handlebars` object is the standard Handlebars library, extended to
@@ -18211,14 +18855,29 @@
@class Handlebars
@namespace Ember
*/
Ember.Handlebars = objectCreate(Handlebars);
+function makeBindings(options) {
+ var hash = options.hash,
+ hashType = options.hashTypes;
+
+ for (var prop in hash) {
+ if (hashType[prop] === 'ID') {
+ hash[prop + 'Binding'] = hash[prop];
+ hashType[prop + 'Binding'] = 'STRING';
+ delete hash[prop];
+ delete hashType[prop];
+ }
+ }
+}
+
Ember.Handlebars.helper = function(name, value) {
if (Ember.View.detect(value)) {
- Ember.Handlebars.registerHelper(name, function(name, options) {
+ Ember.Handlebars.registerHelper(name, function(options) {
+ makeBindings(options);
return Ember.Handlebars.helpers.view.call(this, value, options);
});
} else {
Ember.Handlebars.registerBoundHelper.apply(null, arguments);
}
@@ -18590,29 +19249,33 @@
var values = arguments[arguments.length - 1];
return values.join('||');
});
```
- Which allows for template syntax such as {{concatenate prop1 prop2}} or
- {{concatenate prop1 prop2 prop3}}. If any of the properties change,
+ Which allows for template syntax such as `{{concatenate prop1 prop2}}` or
+ `{{concatenate prop1 prop2 prop3}}`. If any of the properties change,
the helpr will re-render. Note that dependency keys cannot be
using in conjunction with multi-property helpers, since it is ambiguous
which property the dependent keys would belong to.
## Use with unbound helper
- The {{unbound}} helper can be used with bound helper invocations
+ The `{{unbound}}` helper can be used with bound helper invocations
to render them in their unbound form, e.g.
```handlebars
{{unbound capitalize name}}
```
In this example, if the name property changes, the helper
will not re-render.
+ ## Use with blocks not supported
+ Bound helpers do not support use with Handlebars blocks or
+ the addition of child views of any kind.
+
@method registerBoundHelper
@for Ember.Handlebars
@param {String} name
@param {Function} function
@param {String} dependentKeys*
@@ -18631,10 +19294,11 @@
currentContext = (options.contexts && options.contexts[0]) || this,
normalized,
pathRoot, path,
loc, hashOption;
+
// Detect bound options (e.g. countBinding="otherCount")
hash.boundOptions = {};
for (hashOption in hash) {
if (!hash.hasOwnProperty(hashOption)) { continue; }
@@ -18790,11 +19454,10 @@
var t = Handlebars.template(spec);
t.isTop = true;
return t;
};
-
})();
(function() {
@@ -18810,11 +19473,11 @@
var htmlSafe = Ember.String.htmlSafe;
if (Ember.EXTEND_PROTOTYPES === true || Ember.EXTEND_PROTOTYPES.String) {
/**
- See {{#crossLink "Ember.String/htmlSafe"}}{{/crossLink}}
+ See `Ember.String.htmlSafe`.
@method htmlSafe
@for String
*/
String.prototype.htmlSafe = function() {
@@ -18926,11 +19589,11 @@
*/
Ember._Metamorph = Ember.Mixin.create({
isVirtual: true,
tagName: '',
- instrumentName: 'render.metamorph',
+ instrumentName: 'metamorph',
init: function() {
this._super();
this.morph = Metamorph();
@@ -19115,11 +19778,11 @@
@namespace Ember
@extends Ember._MetamorphView
@private
*/
Ember._HandlebarsBoundView = Ember._MetamorphView.extend({
- instrumentName: 'render.boundHandlebars',
+ instrumentName: 'boundHandlebars',
states: states,
/**
The function used to determine if the `displayTemplate` or
`inverseTemplate` should be rendered. This should be a function that takes
@@ -19749,11 +20412,11 @@
// the bound property changes.
var dataId = ++Ember.uuid;
// Handle classes differently, as we can bind multiple classes
var classBindings = attrs['class'];
- if (classBindings !== null && classBindings !== undefined) {
+ if (classBindings != null) {
var classResults = EmberHandlebars.bindClasses(this, classBindings, view, dataId, options);
ret.push('class="' + Handlebars.Utils.escapeExpression(classResults.join(' ')) + '"');
delete attrs['class'];
}
@@ -20597,10 +21260,11 @@
var controller = Ember.ArrayController.create();
set(controller, 'itemController', itemController);
set(controller, 'container', get(this, 'controller.container'));
set(controller, '_eachView', this);
set(controller, 'target', get(this, 'controller'));
+ set(controller, 'parentController', get(this, 'controller'));
this.disableContentObservers(function() {
set(this, 'content', controller);
binding = new Ember.Binding('content', '_eachView.dataSource').oneWay();
binding.connect(controller);
@@ -20870,10 +21534,13 @@
{{#each person in developers itemController="developer"}}
{{person.name}} {{#if person.isAvailableForHire}}Hire me!{{/if}}
{{/each}}
```
+ Each itemController will receive a reference to the current controller as
+ a `parentController` property.
+
@method each
@for Ember.Handlebars.helpers
@param [name] {String} name for item (used with `in`)
@param [path] {String} path
@param [options] {Object} Handlebars key/value pairs of options
@@ -20891,10 +21558,15 @@
if (path === '') { path = "this"; }
options.hash.keyword = keywordName;
}
+ if (arguments.length === 1) {
+ options = path;
+ path = 'this';
+ }
+
options.hash.dataSourceBinding = path;
// Set up emptyView as a metamorph with no tag
//options.hash.emptyViewClass = Ember._MetamorphView;
if (options.data.insideGroup && !options.hash.groupedRows && !options.hash.itemViewClass) {
@@ -21008,11 +21680,11 @@
nameParts[nameParts.length - 1] = "_" + lastPart;
var view = options.data.view,
underscoredName = nameParts.join("/"),
template = view.templateForName(underscoredName),
- deprecatedTemplate = view.templateForName(name);
+ deprecatedTemplate = !template && view.templateForName(name);
template = template || deprecatedTemplate;
@@ -21132,11 +21804,11 @@
```
You can add a `label` tag yourself in the template where the `Ember.Checkbox`
is being used.
- ```html
+ ```handlebars
<label>
{{view Ember.Checkbox classNames="applicaton-specific-checkbox"}}
Some Title
</label>
```
@@ -21352,11 +22024,11 @@
Options are:
* `enter`: the user pressed enter
* `keypress`: the user pressed a key
- @property on
+ @property onEvent
@type String
@default enter
*/
onEvent: 'enter',
@@ -21919,38 +22591,42 @@
/** @scope Ember.Select.prototype */ {
tagName: 'select',
classNames: ['ember-select'],
defaultTemplate: Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) {
-this.compilerInfo = [2,'>= 1.0.0-rc.3'];
+this.compilerInfo = [3,'>= 1.0.0-rc.4'];
helpers = helpers || Ember.Handlebars.helpers; data = data || {};
- var buffer = '', stack1, hashTypes, escapeExpression=this.escapeExpression, self=this;
+ var buffer = '', stack1, hashTypes, hashContexts, escapeExpression=this.escapeExpression, self=this;
function program1(depth0,data) {
- var buffer = '', hashTypes;
+ var buffer = '', hashTypes, hashContexts;
data.buffer.push("<option value=\"\">");
hashTypes = {};
- data.buffer.push(escapeExpression(helpers._triageMustache.call(depth0, "view.prompt", {hash:{},contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data})));
+ hashContexts = {};
+ data.buffer.push(escapeExpression(helpers._triageMustache.call(depth0, "view.prompt", {hash:{},contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data})));
data.buffer.push("</option>");
return buffer;
}
function program3(depth0,data) {
- var hashTypes;
+ var hashContexts, hashTypes;
+ hashContexts = {'contentBinding': depth0};
hashTypes = {'contentBinding': "STRING"};
data.buffer.push(escapeExpression(helpers.view.call(depth0, "view.optionView", {hash:{
'contentBinding': ("this")
- },contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data})));
+ },contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data})));
}
hashTypes = {};
- stack1 = helpers['if'].call(depth0, "view.prompt", {hash:{},inverse:self.noop,fn:self.program(1, program1, data),contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data});
+ hashContexts = {};
+ stack1 = helpers['if'].call(depth0, "view.prompt", {hash:{},inverse:self.noop,fn:self.program(1, program1, data),contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data});
if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
hashTypes = {};
- stack1 = helpers.each.call(depth0, "view.content", {hash:{},inverse:self.noop,fn:self.program(3, program3, data),contexts:[depth0],types:["ID"],hashTypes:hashTypes,data:data});
+ hashContexts = {};
+ stack1 = helpers.each.call(depth0, "view.content", {hash:{},inverse:self.noop,fn:self.program(3, program3, data),contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data});
if(stack1 || stack1 === 0) { data.buffer.push(stack1); }
return buffer;
}),
attributeBindings: ['multiple', 'disabled', 'tabindex', 'name'],
@@ -22084,13 +22760,13 @@
valuePath = get(this, 'optionValuePath').replace(/^content\.?/, ''),
selectedValue = (valuePath ? get(this, 'selection.' + valuePath) : get(this, 'selection')),
selection;
if (value !== selectedValue) {
- selection = content.find(function(obj) {
+ selection = content ? content.find(function(obj) {
return value === (valuePath ? get(obj, valuePath) : obj);
- });
+ }) : null;
this.set('selection', selection);
}
}, 'value'),
@@ -22108,11 +22784,11 @@
_changeSingle: function() {
var selectedIndex = this.$()[0].selectedIndex,
content = get(this, 'content'),
prompt = get(this, 'prompt');
- if (!get(content, 'length')) { return; }
+ if (!content || !get(content, 'length')) { return; }
if (prompt && selectedIndex === 0) { set(this, 'selection', null); return; }
if (prompt) { selectedIndex -= 1; }
set(this, 'selection', content.objectAt(selectedIndex));
},
@@ -22189,10 +22865,19 @@
delete hash[prop];
}
}
}
+/**
+ * `{{input}}` inserts a new instance of either Ember.TextField or
+ * Ember.Checkbox, depending on the `type` option passed in. If no `type`
+ * is supplied it defaults to Ember.TextField.
+ *
+ * @method input
+ * @for Ember.Handlebars.helpers
+ * @param {Hash} options
+ */
Ember.Handlebars.registerHelper('input', function(options) {
var hash = options.hash,
types = options.hashTypes,
@@ -22211,10 +22896,18 @@
hash.onEvent = onEvent || 'enter';
return Ember.Handlebars.helpers.view.call(this, Ember.TextField, options);
}
});
+/**
+ * `{{textarea}}` inserts a new instance of Ember.TextArea into the template
+ * passing its options to `Ember.TextArea`'s `create` method.
+ *
+ * @method textarea
+ * @for Ember.Handlebars.helpers
+ * @param {Hash} options
+ */
Ember.Handlebars.registerHelper('textarea', function(options) {
var hash = options.hash,
types = options.hashTypes;
@@ -22684,18 +23377,18 @@
return output;
},
recognize: function(path) {
- var states = [ this.rootState ], i, l;
+ var states = [ this.rootState ],
+ pathLen, i, l;
// DEBUG GROUP path
- var pathLen = path.length;
-
if (path.charAt(0) !== "/") { path = "/" + path; }
+ pathLen = path.length;
if (pathLen > 1 && path.charAt(pathLen - 1) === "/") {
path = path.substr(0, pathLen - 1);
}
for (i=0, l=path.length; i<l; i++) {
@@ -22876,10 +23569,25 @@
hasRoute: function(route) {
return this.recognizer.hasRoute(route);
},
/**
+ Clears the current and target route handlers and triggers exit
+ on each of them starting at the leaf and traversing up through
+ its ancestors.
+ */
+ reset: function() {
+ eachHandler(this.currentHandlerInfos, function(handler) {
+ if (handler.exit) {
+ handler.exit();
+ }
+ });
+ this.currentHandlerInfos = null;
+ this.targetHandlerInfos = null;
+ },
+
+ /**
The entry point for handling a change to the URL (usually
via the back and forward button).
Returns an Array of handlers and the parameters associated
with those parameters.
@@ -23067,15 +23775,17 @@
},
isActive: function(handlerName) {
var contexts = [].slice.call(arguments, 1);
- var currentHandlerInfos = this.currentHandlerInfos,
- found = false, object, handlerInfo;
+ var targetHandlerInfos = this.targetHandlerInfos,
+ found = false, names, object, handlerInfo, handlerObj;
- for (var i=currentHandlerInfos.length-1; i>=0; i--) {
- handlerInfo = currentHandlerInfos[i];
+ if (!targetHandlerInfos) { return; }
+
+ for (var i=targetHandlerInfos.length-1; i>=0; i--) {
+ handlerInfo = targetHandlerInfos[i];
if (handlerInfo.name === handlerName) { found = true; }
if (found) {
if (contexts.length === 0) { break; }
@@ -23162,11 +23872,14 @@
`setup` method.
*/
function failure(router, error) {
loaded(router);
var handler = router.getHandler('failure');
- if (handler && handler.setup) { handler.setup(error); }
+ if (handler) {
+ if (handler.enter) { handler.enter(); }
+ if (handler.setup) { handler.setup(error); }
+ }
}
/**
@private
*/
@@ -23289,32 +24002,40 @@
*/
function setupContexts(router, handlerInfos) {
var partition =
partitionHandlers(router.currentHandlerInfos || [], handlerInfos);
- router.currentHandlerInfos = handlerInfos;
+ router.targetHandlerInfos = handlerInfos;
eachHandler(partition.exited, function(handler, context) {
delete handler.context;
if (handler.exit) { handler.exit(); }
});
- eachHandler(partition.updatedContext, function(handler, context) {
+ var currentHandlerInfos = partition.unchanged.slice();
+ router.currentHandlerInfos = currentHandlerInfos;
+
+ eachHandler(partition.updatedContext, function(handler, context, handlerInfo) {
setContext(handler, context);
if (handler.setup) { handler.setup(context); }
+ currentHandlerInfos.push(handlerInfo);
});
var aborted = false;
- eachHandler(partition.entered, function(handler, context) {
+ eachHandler(partition.entered, function(handler, context, handlerInfo) {
if (aborted) { return; }
if (handler.enter) { handler.enter(); }
setContext(handler, context);
if (handler.setup) {
if (false === handler.setup(context)) {
aborted = true;
}
}
+
+ if (!aborted) {
+ currentHandlerInfos.push(handlerInfo);
+ }
});
if (!aborted && router.didTransition) {
router.didTransition(handlerInfos);
}
@@ -23333,11 +24054,11 @@
for (var i=0, l=handlerInfos.length; i<l; i++) {
var handlerInfo = handlerInfos[i],
handler = handlerInfo.handler,
context = handlerInfo.context;
- callback(handler, context);
+ callback(handler, context, handlerInfo);
}
}
/**
@private
@@ -23359,19 +24080,20 @@
* exited: the handler was active in the old URL, but is
no longer active.
* entered: the handler was not active in the old URL, but
is now active.
- The PartitionedHandlers structure has three fields:
+ The PartitionedHandlers structure has four fields:
* `updatedContext`: a list of `HandlerInfo` objects that
represent handlers that remain active but have a changed
context
* `entered`: a list of `HandlerInfo` objects that represent
handlers that are newly active
* `exited`: a list of `HandlerInfo` objects that are no
longer active.
+ * `unchanged`: a list of `HanderInfo` objects that remain active.
@param {Array[HandlerInfo]} oldHandlers a list of the handler
information for the previous URL (or `[]` if this is the
first handled transition)
@param {Array[HandlerInfo]} newHandlers a list of the handler
@@ -23381,11 +24103,12 @@
*/
function partitionHandlers(oldHandlers, newHandlers) {
var handlers = {
updatedContext: [],
exited: [],
- entered: []
+ entered: [],
+ unchanged: []
};
var handlerChanged, contextChanged, i, l;
for (i=0, l=newHandlers.length; i<l; i++) {
@@ -23399,10 +24122,12 @@
handlers.entered.push(newHandler);
if (oldHandler) { handlers.exited.unshift(oldHandler); }
} else if (contextChanged || oldHandler.context !== newHandler.context) {
contextChanged = true;
handlers.updatedContext.push(newHandler);
+ } else {
+ handlers.unchanged.push(oldHandler);
}
}
for (i=newHandlers.length, l=oldHandlers.length; i<l; i++) {
handlers.exited.unshift(oldHandlers[i]);
@@ -23418,21 +24143,28 @@
if (!currentHandlerInfos) {
throw new Error("Could not trigger event '" + name + "'. There are no active handlers");
}
+ var eventWasHandled = false;
+
for (var i=currentHandlerInfos.length-1; i>=0; i--) {
var handlerInfo = currentHandlerInfos[i],
handler = handlerInfo.handler;
if (handler.events && handler.events[name]) {
- handler.events[name].apply(handler, args);
- return;
+ if (handler.events[name].apply(handler, args) === true) {
+ eventWasHandled = true;
+ } else {
+ return;
+ }
}
}
- throw new Error("Nothing handled the event '" + name + "'.");
+ if (!eventWasHandled) {
+ throw new Error("Nothing handled the event '" + name + "'.");
+ }
}
function setContext(handler, context) {
handler.context = context;
if (handler.contextDidChange) { handler.contextDidChange(); }
@@ -23530,10 +24262,12 @@
})();
(function() {
+var get = Ember.get;
+
/**
@module ember
@submodule ember-routing
*/
@@ -23546,11 +24280,11 @@
The type of generated controller depends on the context.
You can customize your generated controllers by defining
`App.ObjectController` and `App.ArrayController`
*/
Ember.generateController = function(container, controllerName, context) {
- var controller, DefaultController, fullName;
+ var controller, DefaultController, fullName, instance;
if (context && Ember.isArray(context)) {
DefaultController = container.resolve('controller:array');
controller = DefaultController.extend({
content: context
@@ -23567,14 +24301,20 @@
controller.toString = function() {
return "(generated " + controllerName + " controller)";
};
-
fullName = 'controller:' + controllerName;
container.register(fullName, controller);
- return container.lookup(fullName);
+
+ instance = container.lookup(fullName);
+
+ if (get(instance, 'namespace.LOG_ACTIVE_GENERATION')) {
+ Ember.Logger.info("generated -> " + fullName, { fullName: fullName });
+ }
+
+ return instance;
};
})();
@@ -23585,10 +24325,11 @@
@submodule ember-routing
*/
var Router = requireModule("router");
var get = Ember.get, set = Ember.set;
+var defineProperty = Ember.defineProperty;
var DefaultView = Ember._MetamorphView;
function setupLocation(router) {
var location = get(router, 'location'),
rootURL = get(router, 'rootURL'),
@@ -23648,10 +24389,13 @@
didTransition: function(infos) {
var appController = this.container.lookup('controller:application'),
path = routePath(infos);
+ if (!('currentPath' in appController)) {
+ defineProperty(appController, 'currentPath');
+ }
set(appController, 'currentPath', path);
this.notifyPropertyChange('url');
if (get(this, 'namespace').LOG_TRANSITIONS) {
Ember.Logger.log("Transitioned into '" + path + "'");
@@ -23661,10 +24405,26 @@
handleURL: function(url) {
this.router.handleURL(url);
this.notifyPropertyChange('url');
},
+ /**
+ Transition to another route via the `routeTo` event which
+ will by default be handled by ApplicationRoute.
+
+ @method routeTo
+ @param {TransitionEvent} transitionEvent
+ */
+ routeTo: function(transitionEvent) {
+ var handlerInfos = this.router.currentHandlerInfos;
+ if (handlerInfos) {
+ transitionEvent.sourceRoute = handlerInfos[handlerInfos.length - 1].handler;
+ }
+
+ this.send('routeTo', transitionEvent);
+ },
+
transitionTo: function(name) {
var args = [].slice.call(arguments);
doTransition(this, 'transitionTo', args);
},
@@ -23689,10 +24449,22 @@
hasRoute: function(route) {
return this.router.hasRoute(route);
},
+ /**
+ @private
+
+ Resets the state of the router by clearing the current route
+ handlers and deactivating them.
+
+ @method reset
+ */
+ reset: function() {
+ this.router.reset();
+ },
+
_lookupActiveView: function(templateName) {
var active = this._activeViews[templateName];
return active && active[0];
},
@@ -23739,12 +24511,22 @@
if (name === 'loading') { return {}; }
if (name === 'failure') { return router.constructor.defaultFailureHandler; }
container.register(routeName, DefaultRoute.extend());
handler = container.lookup(routeName);
+
+ if (get(router, 'namespace.LOG_ACTIVE_GENERATION')) {
+ Ember.Logger.info("generated -> " + routeName, { fullName: routeName });
+ }
}
+ if (name === 'application') {
+ // Inject default `routeTo` handler.
+ handler.events = handler.events || {};
+ handler.events.routeTo = handler.events.routeTo || Ember.TransitionEvent.defaultHandler;
+ }
+
handler.routeName = name;
return handler;
};
}
@@ -23861,19 +24643,20 @@
enter: function() {
this.activate();
},
/**
- The collection of functions keyed by name available on this route as
+ The collection of functions, keyed by name, available on this route as
action targets.
These functions will be invoked when a matching `{{action}}` is triggered
from within a template and the application's current route is this route.
- Events can also be invoked from other parts of your application via `Route#send`.
+ Events can also be invoked from other parts of your application via `Route#send`
+ or `Controller#send`.
- The context of event will be the this route.
+ The context of the event will be this route.
@see {Ember.Route#send}
@see {Handlebars.helpers.action}
@property events
@@ -23897,10 +24680,21 @@
@method activate
*/
activate: Ember.K,
/**
+ Transition to another route via the `routeTo` event which
+ will by default be handled by ApplicationRoute.
+
+ @method routeTo
+ @param {TransitionEvent} transitionEvent
+ */
+ routeTo: function(transitionEvent) {
+ this.router.routeTo(transitionEvent);
+ },
+
+ /**
Transition into another route. Optionally supply a model for the
route in question. The model will be serialized into the URL
using the `serialize` hook.
@method transitionTo
@@ -24012,14 +24806,13 @@
return false;
}
var controller = this.controllerFor(this.routeName, context);
- if (controller) {
- this.controller = controller;
- set(controller, 'model', context);
- }
+ // Assign the route's controller so that it can more easily be
+ // referenced in event handlers
+ this.controller = controller;
if (this.setupControllers) {
this.setupControllers(controller, context);
} else {
@@ -24171,34 +24964,45 @@
A hook you can use to setup the controller for the current route.
This method is called with the controller for the current route and the
model supplied by the `model` hook.
- ```js
- App.Router.map(function() {
- this.resource('post', {path: '/posts/:post_id'});
- });
- ```
-
- For the `post` route, the controller is `App.PostController`.
-
By default, the `setupController` hook sets the `content` property of
the controller to the `model`.
- If no explicit controller is defined, the route will automatically create
- an appropriate controller for the model:
+ This means that your template will get a proxy for the model as its
+ context, and you can act as though the model itself was the context.
+ The provided controller will be one resolved based on the name
+ of this route.
+
+ If no explicit controller is defined, Ember will automatically create
+ an appropriate controller for the model.
+
* if the model is an `Ember.Array` (including record arrays from Ember
Data), the controller is an `Ember.ArrayController`.
* otherwise, the controller is an `Ember.ObjectController`.
- This means that your template will get a proxy for the model as its
- context, and you can act as though the model itself was the context.
+ As an example, consider the router:
+ ```js
+ App.Router.map(function() {
+ this.resource('post', {path: '/posts/:post_id'});
+ });
+ ```
+
+ For the `post` route, a controller named `App.PostController` would
+ be used if it is defined. If it is not defined, an `Ember.ObjectController`
+ instance would be used.
+
@method setupController
*/
- setupController: Ember.K,
+ setupController: function(controller, context){
+ if (controller && (context !== undefined)) {
+ set(controller, 'model', context);
+ }
+ },
/**
Returns the controller for a particular route.
```js
@@ -24328,11 +25132,16 @@
var container = this.container,
view = container.lookup('view:' + name),
template = container.lookup('template:' + name);
- if (!view && !template) { return; }
+ if (!view && !template) {
+ if (get(this.router, 'namespace.LOG_VIEW_LOOKUPS')) {
+ Ember.Logger.info("Could not find \"" + name + "\" template or view. Nothing will be rendered", { fullName: 'template:' + name });
+ }
+ return;
+ }
options = normalizeOptions(this, name, template, options);
view = setupView(view, container, options);
if (options.outlet === 'main') { this.lastRenderedTemplate = name; }
@@ -24344,11 +25153,11 @@
teardownView(this);
}
});
function parentRoute(route) {
- var handlerInfos = route.router.router.currentHandlerInfos;
+ var handlerInfos = route.router.router.targetHandlerInfos;
var parent, current;
for (var i=0, l=handlerInfos.length; i<l; i++) {
current = handlerInfos[i].handler;
@@ -24374,10 +25183,11 @@
options = options || {};
options.into = options.into ? options.into.replace(/\//g, '.') : parentTemplate(route);
options.outlet = options.outlet || 'main';
options.name = name;
options.template = template;
+ options.LOG_VIEW_LOOKUPS = get(route.router, 'namespace.LOG_VIEW_LOOKUPS');
var controller = options.controller, namedController;
if (options.controller) {
@@ -24396,14 +25206,22 @@
return options;
}
function setupView(view, container, options) {
- var defaultView = options.into ? 'view:default' : 'view:toplevel';
+ if (view) {
+ if (options.LOG_VIEW_LOOKUPS) {
+ Ember.Logger.info("Rendering " + options.name + " with " + view, { fullName: 'view:' + options.name });
+ }
+ } else {
+ var defaultView = options.into ? 'view:default' : 'view:toplevel';
+ view = container.lookup(defaultView);
+ if (options.LOG_VIEW_LOOKUPS) {
+ Ember.Logger.info("Rendering " + options.name + " with default view " + view, { fullName: 'view:' + options.name });
+ }
+ }
- view = view || container.lookup(defaultView);
-
if (!get(view, 'templateName')) {
set(view, 'template', options.template);
set(view, '_debugTemplateName', options.name);
}
@@ -24449,16 +25267,75 @@
})();
(function() {
+/**
+@module ember
+@submodule ember-routing
+*/
+
+/*
+ A TransitionEvent is passed as the argument for `transitionTo`
+ events and contains information about an attempted transition
+ that can be modified or decorated by leafier `transitionTo` event
+ handlers before the actual transition is committed by ApplicationRoute.
+
+ @class TransitionEvent
+ @namespace Ember
+ @extends Ember.Deferred
+ */
+Ember.TransitionEvent = Ember.Object.extend({
+
+ /*
+ The Ember.Route method used to perform the transition. Presently,
+ the only valid values are 'transitionTo' and 'replaceWith'.
+ */
+ transitionMethod: 'transitionTo',
+ destinationRouteName: null,
+ sourceRoute: null,
+ contexts: null,
+
+ init: function() {
+ this._super();
+ this.contexts = this.contexts || [];
+ },
+
+ /*
+ Convenience method that returns an array that can be used for
+ legacy `transitionTo` and `replaceWith`.
+ */
+ transitionToArgs: function() {
+ return [this.destinationRouteName].concat(this.contexts);
+ }
+});
+
+
+Ember.TransitionEvent.reopenClass({
+ /*
+ This is the default transition event handler that will be injected
+ into ApplicationRoute. The context, like all route event handlers in
+ the events hash, will be an `Ember.Route`.
+ */
+ defaultHandler: function(transitionEvent) {
+ var router = this.router;
+ router[transitionEvent.transitionMethod].apply(router, transitionEvent.transitionToArgs());
+ }
+});
+
})();
(function() {
+
+})();
+
+
+
+(function() {
Ember.onLoad('Ember.Handlebars', function() {
var handlebarsResolve = Ember.Handlebars.resolveParams,
map = Ember.ArrayPolyfills.map,
get = Ember.get;
@@ -24520,10 +25397,91 @@
var ret = [ routeName ];
return ret.concat(resolvedPaths(linkView.parameters));
}
/**
+ @class LinkView
+ @namespace Ember
+ @extends Ember.View
+ **/
+ var LinkView = Ember.LinkView = Ember.View.extend({
+ tagName: 'a',
+ namedRoute: null,
+ currentWhen: null,
+ title: null,
+ activeClass: 'active',
+ disabledClass: 'disabled',
+ _isDisabled: false,
+ replace: false,
+ attributeBindings: ['href', 'title'],
+ classNameBindings: ['active', 'disabled'],
+
+ // 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'),
+
+ disabled: Ember.computed(function(key, value) {
+ if (value !== undefined) { this.set('_isDisabled', value); }
+
+ return value ? this.get('disabledClass') : false;
+ }),
+
+ active: Ember.computed(function() {
+ var router = this.get('router'),
+ params = resolvedPaths(this.parameters),
+ currentWithIndex = this.currentWhen + '.index',
+ isActive = router.isActive.apply(router, [this.currentWhen].concat(params)) ||
+ router.isActive.apply(router, [currentWithIndex].concat(params));
+
+ if (isActive) { return get(this, 'activeClass'); }
+ }).property('namedRoute', 'router.url'),
+
+ router: Ember.computed(function() {
+ return this.get('controller').container.lookup('router:main');
+ }),
+
+ click: function(event) {
+ if (!isSimpleClick(event)) { return true; }
+
+ event.preventDefault();
+ if (this.bubbles === false) { event.stopPropagation(); }
+
+ if (get(this, '_isDisabled')) { return false; }
+
+ var router = this.get('router');
+
+ if (Ember.ENV.ENABLE_ROUTE_TO) {
+
+ var routeArgs = args(this, router);
+
+ router.routeTo(Ember.TransitionEvent.create({
+ transitionMethod: this.get('replace') ? 'replaceWith' : 'transitionTo',
+ destinationRouteName: routeArgs[0],
+ contexts: routeArgs.slice(1)
+ }));
+ } else {
+ if (this.get('replace')) {
+ router.replaceWith.apply(router, args(this, router));
+ } else {
+ router.transitionTo.apply(router, args(this, router));
+ }
+ }
+ },
+
+ href: Ember.computed(function() {
+ if (this.get('tagName') !== 'a') { return false; }
+
+ var router = this.get('router');
+ return router.generate.apply(router, args(this, router));
+ })
+ });
+
+ LinkView.toString = function() { return "LinkView"; };
+
+ /**
The `{{linkTo}}` helper renders a link to the supplied
`routeName` passing an optionally supplied model to the
route as its `model` context of the route. The block
for `{{linkTo}}` becomes the innerHTML of the rendered
element:
@@ -24538,16 +25496,16 @@
<a href="/hamster-photos">
Great Hamster Photos
</a>
```
- ## Supplying a tagName
- By default `{{linkTo}} renders an `<a>` element. This can
+ ### Supplying a tagName
+ By default `{{linkTo}}` renders an `<a>` element. This can
be overridden for a single use of `{{linkTo}}` by supplying
a `tagName` option:
- ```
+ ```handlebars
{{#linkTo photoGallery tagName="li"}}
Great Hamster Photos
{{/linkTo}}
```
@@ -24558,25 +25516,25 @@
```
To override this option for your entire application, see
"Overriding Application-wide Defaults".
- ## Handling `href`
+ ### Handling `href`
`{{linkTo}}` will use your application's Router to
fill the element's `href` property with a url that
matches the path to the supplied `routeName` for your
routers's configured `Location` scheme, which defaults
to Ember.HashLocation.
- ## Handling current route
+ ### Handling current route
`{{linkTo}}` will apply a CSS class name of 'active'
when the application's current route matches
the supplied routeName. For example, if the application's
current route is 'photoGallery.recent' the following
use of `{{linkTo}}`:
- ```
+ ```handlebars
{{#linkTo photoGallery.recent}}
Great Hamster Photos from the last week
{{/linkTo}}
```
@@ -24590,11 +25548,11 @@
The CSS class name used for active classes can be customized
for a single use of `{{linkTo}}` by passing an `activeClass`
option:
- ```
+ ```handlebars
{{#linkTo photoGallery.recent activeClass="current-url"}}
Great Hamster Photos from the last week
{{/linkTo}}
```
@@ -24605,11 +25563,11 @@
```
To override this option for your entire application, see
"Overriding Application-wide Defaults".
- ## Supplying a model
+ ### Supplying a model
An optional model argument can be used for routes whose
paths contain dynamic segments. This argument will become
the model context of the linked route:
```javascript
@@ -24628,11 +25586,11 @@
<a href="/hamster-photos/42">
Tomster
</a>
```
- ## Supplying multiple models
+ ### Supplying multiple models
For deep-linking to route paths that contain multiple
dynamic segments, multiple model arguments can be used.
As the router transitions through the route path, each
supplied model argument will become the context for the
route with the dynamic segments:
@@ -24656,11 +25614,11 @@
<a href="/hamster-photos/42/comment/718">
A+++ would snuggle again.
</a>
```
- ## Overriding Application-wide Defaults
+ ### Overriding Application-wide Defaults
``{{linkTo}}`` creates an instance of Ember.LinkView
for rendering. To override options for your entire
application, reopen Ember.LinkView and supply the
desired values:
@@ -24668,71 +25626,11 @@
Ember.LinkView.reopen({
activeClass: "is-active",
tagName: 'li'
})
```
-
- @class LinkView
- @namespace Ember
- @extends Ember.View
- **/
- var LinkView = Ember.LinkView = Ember.View.extend({
- tagName: 'a',
- namedRoute: null,
- currentWhen: null,
- title: null,
- activeClass: 'active',
- replace: false,
- attributeBindings: ['href', 'title'],
- classNameBindings: 'active',
- // 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'),
-
- active: Ember.computed(function() {
- var router = this.get('router'),
- params = resolvedPaths(this.parameters),
- currentWithIndex = this.currentWhen + '.index',
- isActive = router.isActive.apply(router, [this.currentWhen].concat(params)) ||
- router.isActive.apply(router, [currentWithIndex].concat(params));
-
- if (isActive) { return get(this, 'activeClass'); }
- }).property('namedRoute', 'router.url'),
-
- router: Ember.computed(function() {
- return this.get('controller').container.lookup('router:main');
- }),
-
- click: function(event) {
- if (!isSimpleClick(event)) { return true; }
-
- event.preventDefault();
- if (this.bubbles === false) { event.stopPropagation(); }
-
- var router = this.get('router');
-
- if (this.get('replace')) {
- router.replaceWith.apply(router, args(this, router));
- } else {
- router.transitionTo.apply(router, args(this, router));
- }
- },
-
- href: Ember.computed(function() {
- if (this.get('tagName') !== 'a') { return false; }
-
- var router = this.get('router');
- return router.generate.apply(router, args(this, router));
- })
- });
-
- LinkView.toString = function() { return "LinkView"; };
-
- /**
@method linkTo
@for Ember.Handlebars.helpers
@param {String} routeName
@param {Object} [context]*
@return {String} HTML string
@@ -24743,10 +25641,11 @@
var hash = options.hash;
hash.namedRoute = name;
hash.currentWhen = hash.currentWhen || name;
+ hash.disabledBinding = hash.disabledWhen;
hash.parameters = {
context: this,
options: options,
params: params
@@ -24818,17 +25717,31 @@
this.render('posts', { outlet: 'posts' });
}
});
```
+ You can specify the view class that the outlet uses to contain and manage the
+ templates rendered into it.
+
+ ``` handlebars
+ {{outlet viewClass=App.SectionContainer}}
+ ```
+
+ ``` javascript
+ App.SectionContainer = Ember.ContainerView.extend({
+ tagName: 'section',
+ classNames: ['special']
+ });
+ ```
+
@method outlet
@for Ember.Handlebars.helpers
@param {String} property the property on the controller
that holds the view for this outlet
*/
Handlebars.registerHelper('outlet', function(property, options) {
- var outletSource;
+ var outletSource, outletContainerClass;
if (property && property.data && property.data.isRenderData) {
options = property;
property = 'main';
}
@@ -24836,14 +25749,16 @@
outletSource = options.data.view;
while (!(outletSource.get('template.isTop'))){
outletSource = outletSource.get('_parentView');
}
+ outletContainerClass = options.hash.viewClass || Handlebars.OutletView;
+
options.data.view.set('outletSource', outletSource);
options.hash.currentViewBinding = '_view.outletSource._outlets.' + property;
- return Handlebars.helpers.view.call(this, Handlebars.OutletView, options);
+ return Handlebars.helpers.view.call(this, outletContainerClass, options);
});
});
})();
@@ -24857,19 +25772,27 @@
var get = Ember.get, set = Ember.set;
Ember.onLoad('Ember.Handlebars', function(Handlebars) {
/**
- Renders the named template in the current context using the singleton
- instance of the same-named controller.
+ Renders the named template in the current context with the same-named
+ controller.
- If a view class with the same name exists, uses the view class.
+ If a view class with the same name exists, the view class will be used.
- If a `model` is specified, it becomes the model for that controller.
+ The optional second argument is a property path that will be bound
+ to the `model` property of the controller.
+ If a `model` property path is specified, then a new instance of the
+ controller will be created.
+
+ If no `model` property path is provided, then the helper will use the
+ singleton instance of the controller. A given controller may only be used
+ one time in your app in this manner.
+
The default target for `{{action}}`s in the rendered template is the
- named controller.
+ controller.
@method render
@for Ember.Handlebars.helpers
@param {String} name
@param {Object?} contextString
@@ -24944,10 +25867,11 @@
isSimpleClick = Ember.ViewUtils.isSimpleClick;
var EmberHandlebars = Ember.Handlebars,
handlebarsGet = EmberHandlebars.get,
SafeString = EmberHandlebars.SafeString,
+ forEach = Ember.ArrayPolyfills.forEach,
get = Ember.get,
a_slice = Array.prototype.slice;
function args(options, actionName) {
var ret = [];
@@ -24970,11 +25894,11 @@
return isSimpleClick(event);
}
var allowed = true;
- keys.forEach(function(key) {
+ forEach.call(keys, function(key) {
if (event[key + "Key"] && allowedKeys.indexOf(key) === -1) {
allowed = false;
}
});
@@ -25056,11 +25980,11 @@
aView = AView.create();
aView.appendTo('body');
```
- Will results in the following rendered HTML
+ Will result in the following rendered HTML
```html
<div class="ember-view">
<div data-ember-action="1">
click me
@@ -25418,10 +26342,19 @@
set(this, '_outlets', {});
this._super();
},
connectOutlet: function(outletName, view) {
+ if (this._pendingDisconnections) {
+ delete this._pendingDisconnections[outletName];
+ }
+
+ if (this._hasEquivalentView(outletName, view)) {
+ view.destroy();
+ return;
+ }
+
var outlets = get(this, '_outlets'),
container = get(this, 'container'),
router = container && container.lookup('router:main'),
renderedName = get(view, 'renderedName');
@@ -25430,14 +26363,34 @@
if (router && renderedName) {
router._connectActiveView(renderedName, view);
}
},
+ _hasEquivalentView: function(outletName, view) {
+ var existingView = get(this, '_outlets.'+outletName);
+ return existingView &&
+ existingView.prototype === view.prototype &&
+ existingView.get('template') === view.get('template') &&
+ existingView.get('context') === view.get('context');
+ },
+
disconnectOutlet: function(outletName) {
+ if (!this._pendingDisconnections) {
+ this._pendingDisconnections = {};
+ }
+ this._pendingDisconnections[outletName] = true;
+ Ember.run.once(this, '_finishDisconnections');
+ },
+
+ _finishDisconnections: function() {
var outlets = get(this, '_outlets');
+ var pendingDisconnections = this._pendingDisconnections;
+ this._pendingDisconnections = null;
- set(outlets, outletName, null);
+ for (var outletName in pendingDisconnections) {
+ set(outlets, outletName, null);
+ }
}
});
})();
@@ -25619,11 +26572,11 @@
*/
onUpdateURL: function(callback) {
var self = this;
var guid = Ember.guidFor(this);
- Ember.$(window).bind('hashchange.ember-location-'+guid, function() {
+ Ember.$(window).on('hashchange.ember-location-'+guid, function() {
Ember.run(function() {
var path = location.hash.substr(1);
if (get(self, 'lastSetURL') === path) { return; }
set(self, 'lastSetURL', null);
@@ -25806,11 +26759,11 @@
*/
onUpdateURL: function(callback) {
var guid = Ember.guidFor(this),
self = this;
- Ember.$(window).bind('popstate.ember-location-'+guid, function(e) {
+ Ember.$(window).on('popstate.ember-location-'+guid, function(e) {
// Ignore initial page load popstate event in Chrome
if(!popstateFired) {
popstateFired = true;
if (self.getURL() === self._previousURL) { return; }
}
@@ -26193,10 +27146,29 @@
@submodule ember-application
*/
var get = Ember.get, set = Ember.set;
+function DeprecatedContainer(container) {
+ this._container = container;
+}
+
+DeprecatedContainer.deprecate = function(method) {
+ return function() {
+ var container = this._container;
+
+ return container[method].apply(container, arguments);
+ };
+};
+
+DeprecatedContainer.prototype = {
+ _container: null,
+ lookup: DeprecatedContainer.deprecate('lookup'),
+ resolve: DeprecatedContainer.deprecate('resolve'),
+ register: DeprecatedContainer.deprecate('register')
+};
+
/**
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.
@@ -26320,21 +27292,10 @@
method. Once routing can begin, call the `advanceReadiness()` method.
If there is any setup required before routing begins, you can implement a
`ready()` method on your app that will be invoked immediately before routing
begins.
-
- To begin routing, you must have at a minimum a top-level controller and view.
- You define these as `App.ApplicationController` and `App.ApplicationView`,
- respectively. Your application will not work if you do not define these two
- mandatory classes. For example:
-
- ```javascript
- App.ApplicationView = Ember.View.extend({
- templateName: 'application'
- });
- App.ApplicationController = Ember.Controller.extend();
```
@class Application
@namespace Ember
@extends Ember.Namespace
@@ -26452,14 +27413,14 @@
to configure.
This allows application developers to do:
```javascript
- App = Ember.Application.create();
+ var App = Ember.Application.create();
- App.Router.map(function(match) {
- match("/").to("index");
+ App.Router.map(function() {
+ this.resource('posts');
});
```
@method defaultRouter
@return {Ember.Router} the default router
@@ -26630,25 +27591,97 @@
this.advanceReadiness();
return this;
},
+ /**
+ Reset the application. This is typically used only in tests. It cleans up
+ the application in the following order:
+
+ 1. Deactivate existing routes
+ 2. Destroy all objects in the container
+ 3. Create a new application container
+ 4. Re-route to the existing url
+
+ Typical Example:
+
+ ```javascript
+
+ var App;
+
+ Ember.run(function(){
+ App = Ember.Application.create();
+ });
+
+ module("acceptance test", {
+ setup: function() {
+ App.reset();
+ }
+ });
+
+ test("first test", function(){
+ // App is freshly reset
+ });
+
+ test("first test", function(){
+ // App is again freshly reset
+ });
+ ```
+
+ Advanced Example:
+
+ Occasionally you may want to prevent the app from initializing during
+ setup. This could enable extra configuration, or enable asserting prior
+ to the app becoming ready.
+
+ ```javascript
+
+ var App;
+
+ Ember.run(function(){
+ App = Ember.Application.create();
+ });
+
+ module("acceptance test", {
+ setup: function() {
+ Ember.run(function() {
+ App.reset();
+ App.deferReadiness();
+ });
+ }
+ });
+
+ test("first test", function(){
+ ok(true, 'something before app is initialized');
+
+ Ember.run(function(){
+ App.advanceReadiness();
+ });
+ ok(true, 'something after app is initialized');
+ });
+ ```
+
+ @method reset
+ **/
reset: function() {
+ this._readinessDeferrals = 1;
- Ember.run(this, function(){
- Ember.run(get(this,'__container__'), 'destroy');
+ function handleReset() {
+ var router = this.__container__.lookup('router:main');
+ router.reset();
+ Ember.run(this.__container__, 'destroy');
+
this.buildContainer();
- this._readinessDeferrals = 1;
- this.$(this.rootElement).removeClass('ember-application');
-
Ember.run.schedule('actions', this, function(){
this._initialize();
this.startRouting();
});
- });
+ }
+
+ Ember.run.join(this, handleReset);
},
/**
@private
@method runInitializers
@@ -26698,36 +27731,21 @@
`customEvents`.
@method setupEventDispatcher
*/
setupEventDispatcher: function() {
- var eventDispatcher = this.createEventDispatcher(),
- customEvents = get(this, 'customEvents');
+ var customEvents = get(this, 'customEvents'),
+ rootElement = get(this, 'rootElement'),
+ dispatcher = this.__container__.lookup('event_dispatcher:main');
- eventDispatcher.setup(customEvents);
+ set(this, 'eventDispatcher', dispatcher);
+ dispatcher.setup(customEvents, rootElement);
},
/**
@private
- Create an event dispatcher for the application's `rootElement`.
-
- @method createEventDispatcher
- */
- createEventDispatcher: function() {
- var rootElement = get(this, 'rootElement'),
- eventDispatcher = Ember.EventDispatcher.create({
- rootElement: rootElement
- });
-
- set(this, 'eventDispatcher', eventDispatcher);
- return eventDispatcher;
- },
-
- /**
- @private
-
If the application has a router, use it to route to the current URL, and
trigger a new call to `route` whenever the URL changes.
@method startRouting
@property router {Ember.Router}
@@ -26761,14 +27779,11 @@
resolver: null,
willDestroy: function() {
Ember.BOOTED = false;
- var eventDispatcher = get(this, 'eventDispatcher');
- if (eventDispatcher) { eventDispatcher.destroy(); }
-
- get(this, '__container__').destroy();
+ this.__container__.destroy();
},
initializer: function(options) {
this.constructor.initializer(options);
}
@@ -26812,12 +27827,13 @@
container for.
@return {Ember.Container} the built container
*/
buildContainer: function(namespace) {
var container = new Ember.Container();
- Ember.Container.defaultContainer = Ember.Container.defaultContainer || container;
+ Ember.Container.defaultContainer = new DeprecatedContainer(container);
+
container.set = Ember.set;
container.normalize = normalize;
container.resolver = resolverFor(namespace);
container.optionsForType('view', { singleton: false });
container.optionsForType('template', { instantiate: false });
@@ -26825,17 +27841,18 @@
container.register('controller:basic', Ember.Controller, { instantiate: false });
container.register('controller:object', Ember.ObjectController, { instantiate: false });
container.register('controller:array', Ember.ArrayController, { instantiate: false });
container.register('route:basic', Ember.Route, { instantiate: false });
+ container.register('event_dispatcher:main', Ember.EventDispatcher);
container.injection('router:main', 'namespace', 'application:main');
- container.typeInjection('controller', 'target', 'router:main');
- container.typeInjection('controller', 'namespace', 'application:main');
+ container.injection('controller', 'target', 'router:main');
+ container.injection('controller', 'namespace', 'application:main');
- container.typeInjection('route', 'router', 'router:main');
+ container.injection('route', 'router', 'router:main');
return container;
}
});
@@ -26946,10 +27963,35 @@
return satisfied;
}
Ember.ControllerMixin.reopen({
concatenatedProperties: ['needs'],
+
+ /**
+ An array of other controller objects available inside
+ instances of this controller via the `controllers`
+ property:
+
+ For example, when you define a controller:
+
+ ```javascript
+ App.CommentsController = Ember.ArrayController.extend({
+ needs: ['post']
+ });
+ ```
+
+ The application's single instance of these other
+ controllers are accessible by name through the
+ `controllers` property:
+
+ ```javascript
+ this.get('controllers.post'); // instance of App.PostController
+ ```
+
+ @property {Array} needs
+ @default []
+ */
needs: [],
init: function() {
this._super.apply(this, arguments);
@@ -28252,109 +29294,427 @@
*/
})();
(function() {
-/*globals EMBER_APP_BEING_TESTED */
+var slice = [].slice,
+ helpers = {},
+ originalMethods = {},
+ injectHelpersCallbacks = [];
-var Promise = Ember.RSVP.Promise,
- pendingAjaxRequests = 0,
- originalFind;
+/**
+ @class Test
+ @namespace Ember
+*/
+Ember.Test = {
-function visit(url) {
- var promise = new Promise();
- Ember.run(EMBER_APP_BEING_TESTED, EMBER_APP_BEING_TESTED.handleURL, url);
- wait(promise, promise.resolve);
- return promise;
-}
+ /**
+ @public
-function click(selector) {
- var promise = new Promise();
- Ember.run(function() {
- Ember.$(selector).click();
- });
- wait(promise, promise.resolve);
- return promise;
-}
+ `registerHelper` is used to register a
+ test helper that will be injected when
+ `App.injectTestHelpers` is called.
-function fillIn(selector, text) {
- var promise = new Promise();
- var $el = find(selector);
- Ember.run(function() {
- $el.val(text);
- });
+ The helper method will always be called
+ with the current Application as the first
+ parameter.
- wait(promise, promise.resolve);
- return promise;
-}
+ For example:
+ ```javascript
+ Ember.Test.registerHelper('boot', function(app)) {
+ Ember.run(app, app.deferReadiness);
+ }
+ ```
-function find(selector) {
- return Ember.$('.ember-application').find(selector);
-}
+ This helper can later be called without arguments
+ because it will be called with `app` as the
+ first parameter.
-function wait(target, method) {
- if (!method) {
- method = target;
- target = null;
- }
- stop();
- var watcher = setInterval(function() {
- var routerIsLoading = EMBER_APP_BEING_TESTED.__container__.lookup('router:main').router.isLoading;
- if (routerIsLoading) { return; }
- if (pendingAjaxRequests) { return; }
- if (Ember.run.hasScheduledTimers() || Ember.run.currentRunLoop) { return; }
- clearInterval(watcher);
- start();
- Ember.run(target, method);
- }, 200);
+ ```javascript
+ App = Ember.Application.create();
+ App.injectTestHelpers();
+ boot();
+ ```
+
+ Whenever you register a helper that
+ performs async operations,
+ make sure you `return wait();` at the
+ end of the helper.
+
+ If an async helper also needs to return a value,
+ pass it to the `wait` helper as a first argument:
+ `return wait(val);`
+
+ @method registerHelper
+ @param name {String}
+ @param helperMethod {Function}
+ */
+ registerHelper: function(name, helperMethod) {
+ helpers[name] = helperMethod;
+ },
+ /**
+ @public
+ @method unregisterHelper
+ @param name {String}
+ */
+ unregisterHelper: function(name) {
+ delete helpers[name];
+ if (originalMethods[name]) {
+ window[name] = originalMethods[name];
+ }
+ delete originalMethods[name];
+ },
+
+ /**
+ @public
+
+ Used to register callbacks to be fired
+ whenever `App.injectTestHelpers` is called
+
+ The callback will receive the current application
+ as an argument.
+
+ @method unregisterHelper
+ @param name {String}
+ */
+ onInjectHelpers: function(callback) {
+ injectHelpersCallbacks.push(callback);
+ },
+
+ /**
+ @public
+
+ This returns a thenable tailored
+ for testing. It catches failed
+ `onSuccess` callbacks and invokes
+ the `Ember.Test.adapter.exception`
+ callback in the last chained then.
+
+ This method should be returned
+ by async helpers such as `wait`.
+
+ @method promise
+ @param resolver {Function}
+ */
+ promise: function(resolver) {
+ var promise = new Ember.RSVP.Promise(resolver);
+ var thenable = {
+ chained: false
+ };
+ thenable.then = function(onSuccess, onFailure) {
+ var self = this, thenPromise, nextPromise;
+ thenable.chained = true;
+ thenPromise = promise.then(onSuccess, onFailure);
+ // this is to ensure all downstream fulfillment
+ // handlers are wrapped in the error handling
+ nextPromise = Ember.Test.promise(function(resolve) {
+ resolve(thenPromise);
+ });
+ thenPromise.then(null, function(reason) {
+ // ensure this is the last promise in the chain
+ // if not, ignore and the exception will propagate
+ // this prevents the same error from being fired multiple times
+ if (!nextPromise.chained) {
+ Ember.Test.adapter.exception(reason);
+ }
+ });
+ return nextPromise;
+ };
+ return thenable;
+ },
+
+ /**
+ @public
+
+ Used to allow ember-testing
+ to communicate with a specific
+ testing framework.
+
+ You can manually set it before calling
+ `App.setupForTesting()`.
+
+ Example:
+ 'Ember.Test.adapter = MyCustomAdapter.create()'
+
+ If you do not set it, ember-testing
+ will default to `Ember.Test.QUnitAdapter`.
+ */
+ adapter: null
+};
+
+function curry(app, fn) {
+ return function() {
+ var args = slice.call(arguments);
+ args.unshift(app);
+ return fn.apply(app, args);
+ };
}
Ember.Application.reopen({
+ testHelpers: {},
+
setupForTesting: function() {
this.deferReadiness();
this.Router.reopen({
location: 'none'
});
- window.EMBER_APP_BEING_TESTED = this;
+ // if adapter is not manually set
+ // default to QUnit
+ if (!Ember.Test.adapter) {
+ Ember.Test.adapter = Ember.Test.QUnitAdapter.create();
+ }
},
injectTestHelpers: function() {
- Ember.$(document).ajaxStart(function() {
- pendingAjaxRequests++;
- });
+ this.testHelpers = {};
+ for (var name in helpers) {
+ originalMethods[name] = window[name];
+ this.testHelpers[name] = window[name] = curry(this, helpers[name]);
+ }
- Ember.$(document).ajaxStop(function() {
- pendingAjaxRequests--;
- });
-
- window.visit = visit;
- window.click = click;
- window.fillIn = fillIn;
- originalFind = window.find;
- window.find = find;
+ for(var i = 0, l = injectHelpersCallbacks.length; i < l; i++) {
+ injectHelpersCallbacks[i](this);
+ }
},
removeTestHelpers: function() {
- window.visit = null;
- window.click = null;
- window.fillIn = null;
- window.find = originalFind;
+ for (var name in helpers) {
+ window[name] = originalMethods[name];
+ delete this.testHelpers[name];
+ delete originalMethods[name];
+ }
}
});
+
})();
(function() {
+var Test = Ember.Test;
+/**
+ @class Adapter
+ @namespace Ember.Test
+*/
+Test.Adapter = Ember.Object.extend({
+ /**
+ @public
+
+ This callback will be called
+ whenever an async operation
+ is about to start.
+
+ Override this to call your
+ framework's methods
+ that handle async operations
+
+ @method asyncStart
+ */
+ asyncStart: Ember.K,
+
+ /**
+ @public
+
+ This callback will be called
+ whenever an async operation
+ has completed.
+
+ @method asyncEnd
+ */
+ asyncEnd: Ember.K,
+
+ /**
+ @public
+
+ Override this method with your
+ testing framework's false assertion
+ This function is called whenever
+ an exception occurs causing the testing
+ promise to fail.
+
+ QUnit example:
+
+ ```javascript
+ exception: function(error) {
+ ok(false, error);
+ }
+ ```
+
+ @method exception
+ @param reason {String}
+ */
+ exception: function(error) {
+ setTimeout(function() {
+ throw error;
+ });
+ }
+});
+
+/**
+ @class QUnitAdapter
+ @namespace Ember.Test
+*/
+Test.QUnitAdapter = Test.Adapter.extend({
+ asyncStart: function() {
+ stop();
+ },
+ asyncEnd: function() {
+ start();
+ },
+ exception: function(error) {
+ ok(false, error);
+ }
+});
+
})();
+
+(function() {
+var get = Ember.get,
+ helper = Ember.Test.registerHelper,
+ pendingAjaxRequests = 0,
+ countAsync = 0;
+
+
+Ember.Test.onInjectHelpers(function() {
+ Ember.$(document).ajaxStart(function() {
+ pendingAjaxRequests++;
+ });
+
+ Ember.$(document).ajaxStop(function() {
+ pendingAjaxRequests--;
+ });
+});
+
+
+function visit(app, url) {
+ Ember.run(app, app.handleURL, url);
+ app.__container__.lookup('router:main').location.setURL(url);
+ return wait(app);
+}
+
+function click(app, selector, context) {
+ var $el = find(app, selector, context);
+ Ember.run(function() {
+ $el.click();
+ });
+ return wait(app);
+}
+
+function fillIn(app, selector, context, text) {
+ var $el;
+ if (typeof text === 'undefined') {
+ text = context;
+ context = null;
+ }
+ $el = find(app, selector, context);
+ Ember.run(function() {
+ $el.val(text).change();
+ });
+ return wait(app);
+}
+
+function find(app, selector, context) {
+ var $el;
+ context = context || get(app, 'rootElement');
+ $el = app.$(selector, context);
+ if ($el.length === 0) {
+ throw("Element " + selector + " not found.");
+ }
+ return $el;
+}
+
+function wait(app, value) {
+ var promise, obj = {}, helperName;
+
+ promise = Ember.Test.promise(function(resolve) {
+ if (++countAsync === 1) {
+ Ember.Test.adapter.asyncStart();
+ }
+ var watcher = setInterval(function() {
+ var routerIsLoading = app.__container__.lookup('router:main').router.isLoading;
+ if (routerIsLoading) { return; }
+ if (pendingAjaxRequests) { return; }
+ if (Ember.run.hasScheduledTimers() || Ember.run.currentRunLoop) { return; }
+ clearInterval(watcher);
+ if (--countAsync === 0) {
+ Ember.Test.adapter.asyncEnd();
+ }
+ Ember.run(function() {
+ resolve(value);
+ });
+ }, 10);
+ });
+
+ return buildChainObject(app, promise);
+}
+
+/**
+ Builds an object that contains
+ all helper methods. This object will be
+ returned by helpers and then-promises.
+
+ This allows us to chain helpers:
+
+ ```javascript
+ visit('posts/new')
+ .click('.add-btn')
+ .fillIn('.title', 'Post')
+ .click('.submit')
+ .then(function() {
+ equal('.post-title', 'Post');
+ })
+ .visit('comments')
+ .then(function() {
+ equal(find('.comments'),length, 0);
+ });
+ ```
+*/
+function buildChainObject(app, promise) {
+ var helperName, obj = {};
+ for(helperName in app.testHelpers) {
+ obj[helperName] = chain(app, promise, app.testHelpers[helperName]);
+ }
+ obj.then = function(fn) {
+ var thenPromise = promise.then(fn);
+ return buildChainObject(app, thenPromise);
+ };
+ return obj;
+}
+
+function chain(app, promise, fn) {
+ return function() {
+ var args = arguments, chainedPromise;
+ chainedPromise = promise.then(function() {
+ return fn.apply(null, args);
+ });
+ return buildChainObject(app, chainedPromise);
+ };
+}
+
+// expose these methods as test helpers
+helper('visit', visit);
+helper('click', click);
+helper('fillIn', fillIn);
+helper('find', find);
+helper('wait', wait);
+
})();
+
+(function() {
+
+})();
+
+
+})();
+
+
if (typeof location !== 'undefined' && (location.hostname === 'localhost' || location.hostname === '127.0.0.1')) {
- console.warn("You are running a production build of Ember on localhost and won't receive detailed error messages. "+
+ Ember.Logger.warn("You are running a production build of Ember on localhost and won't receive detailed error messages. "+
"If you want full error messages please use the non-minified build provided on the Ember website.");
}