dist/ember.js in ember-source-1.0.0.rc6.4 vs dist/ember.js in ember-source-1.0.0.rc7
- old
+ new
@@ -1,7 +1,7 @@
-// Version: v1.0.0-rc.6-221-g9d051c2
-// Last commit: 9d051c2 (2013-07-28 23:13:59 -0700)
+// Version: v1.0.0-rc.7-2-ga4bfa51
+// Last commit: a4bfa51 (2013-08-14 00:39:16 -0500)
(function() {
/*global __fail__*/
@@ -154,12 +154,12 @@
};
};
})();
-// Version: v1.0.0-rc.6-221-g9d051c2
-// Last commit: 9d051c2 (2013-07-28 23:13:59 -0700)
+// Version: v1.0.0-rc.7-2-ga4bfa51
+// Last commit: a4bfa51 (2013-08-14 00:39:16 -0500)
(function() {
var define, requireModule;
@@ -222,11 +222,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.6
+ @version 1.0.0-rc.7
*/
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.
@@ -249,14 +249,14 @@
/**
@property VERSION
@type String
- @default '1.0.0-rc.6.1'
+ @default '1.0.0-rc.7.1'
@final
*/
-Ember.VERSION = '1.0.0-rc.6.1';
+Ember.VERSION = '1.0.0-rc.7.1';
/**
Standard environmental variables. You can define these in a global `ENV`
variable before loading Ember to control various configuration
settings.
@@ -6194,17 +6194,17 @@
} else {
return mixin; // apply anonymous mixin properties
}
}
-function concatenatedProperties(props, values, base) {
+function concatenatedMixinProperties(concatProp, props, values, base) {
var concats;
// reset before adding each new mixin to pickup concats from previous
- concats = values.concatenatedProperties || base.concatenatedProperties;
- if (props.concatenatedProperties) {
- concats = concats ? concats.concat(props.concatenatedProperties) : props.concatenatedProperties;
+ concats = values[concatProp] || base[concatProp];
+ if (props[concatProp]) {
+ concats = concats ? concats.concat(props[concatProp]) : props[concatProp];
}
return concats;
}
@@ -6267,11 +6267,32 @@
} else {
return Ember.makeArray(value);
}
}
-function addNormalizedProperty(base, key, value, meta, descs, values, concats) {
+function applyMergedProperties(obj, key, value, values) {
+ var baseValue = values[key] || obj[key];
+
+ if (!baseValue) { return value; }
+
+ var newBase = Ember.merge({}, baseValue);
+ for (var prop in value) {
+ if (!value.hasOwnProperty(prop)) { continue; }
+
+ var propValue = value[prop];
+ if (isMethod(propValue)) {
+ // TODO: support for Computed Properties, etc?
+ newBase[prop] = giveMethodSuper(obj, prop, propValue, baseValue, {});
+ } else {
+ newBase[prop] = propValue;
+ }
+ }
+
+ return newBase;
+}
+
+function addNormalizedProperty(base, key, value, meta, descs, values, concats, mergings) {
if (value instanceof Ember.Descriptor) {
if (value === REQUIRED && descs[key]) { return CONTINUE; }
// Wrap descriptor function to implement
// _super() if needed
@@ -6283,21 +6304,25 @@
values[key] = undefined;
} else {
// impl super if needed...
if (isMethod(value)) {
value = giveMethodSuper(base, key, value, values, descs);
- } else if ((concats && a_indexOf.call(concats, key) >= 0) || key === 'concatenatedProperties') {
+ } else if ((concats && a_indexOf.call(concats, key) >= 0) ||
+ key === 'concatenatedProperties' ||
+ key === 'mergedProperties') {
value = applyConcatenatedProperties(base, key, value, values);
+ } else if ((mergings && a_indexOf.call(mergings, key) >= 0)) {
+ value = applyMergedProperties(base, key, value, values);
}
descs[key] = undefined;
values[key] = value;
}
}
function mergeMixins(mixins, m, descs, values, base, keys) {
- var mixin, props, key, concats, meta;
+ var mixin, props, key, concats, mergings, meta;
function removeKeys(keyName) {
delete descs[keyName];
delete values[keyName];
}
@@ -6309,16 +6334,17 @@
props = mixinProperties(m, mixin);
if (props === CONTINUE) { continue; }
if (props) {
meta = Ember.meta(base);
- concats = concatenatedProperties(props, values, base);
+ concats = concatenatedMixinProperties('concatenatedProperties', props, values, base);
+ mergings = concatenatedMixinProperties('mergedProperties', props, values, base);
for (key in props) {
if (!props.hasOwnProperty(key)) { continue; }
keys.push(key);
- addNormalizedProperty(base, key, props[key], meta, descs, values, concats);
+ addNormalizedProperty(base, key, props[key], meta, descs, values, concats, mergings);
}
// manually copy toString() because some JS engines do not enumerate it
if (props.hasOwnProperty('toString')) { base.toString = props.toString; }
} else if (mixin.mixins) {
@@ -6413,10 +6439,11 @@
key, value, desc, keys = [];
// Go through all mixins and hashes passed in, and:
//
// * Handle concatenated properties
+ // * Handle merged properties
// * Set up _super wrapping if necessary
// * Set up computed property descriptors
// * Copying `toString` in broken browsers
mergeMixins(mixins, mixinsMeta(obj), descs, values, obj, keys);
@@ -6752,30 +6779,36 @@
return Ember.observer.apply(this, arguments);
};
/**
- When observers fire, they are called with the arguments `obj`, `keyName`
- and `value`. In a typical observer, value is the new, post-change value.
+ When observers fire, they are called with the arguments `obj`, `keyName`.
- A `beforeObserver` fires before a property changes. The `value` argument contains
- the pre-change value.
+ Note, `@each.property` observer is called per each add or replace of an element
+ and it's not called with a specific enumeration item.
+ A `beforeObserver` fires before a property changes.
+
A `beforeObserver` is an alternative form of `.observesBefore()`.
```javascript
App.PersonView = Ember.View.extend({
- valueWillChange: function (obj, keyName, value) {
- this.changingFrom = value;
+ friends: [{ name: 'Tom' }, { name: 'Stefan' }, { name: 'Kris' }],
+ valueWillChange: function (obj, keyName) {
+ this.changingFrom = obj.get(keyName);
}.observesBefore('content.value'),
- valueDidChange: function(obj, keyName, value) {
+ valueDidChange: function(obj, keyName) {
// only run if updating a value already in the DOM
if (this.get('state') === 'inDOM') {
- var color = value > this.changingFrom ? 'green' : 'red';
+ var color = obj.get(keyName) > this.changingFrom ? 'green' : 'red';
// logic
}
- }.observes('content.value')
+ }.observes('content.value'),
+ friendsDidChange: function(obj, keyName) {
+ // some logic
+ // obj.get(keyName) returns friends array
+ }.observes('friends.@each.name')
});
```
@method beforeObserver
@for Ember
@@ -8451,10 +8484,12 @@
(function() {
/**
Expose RSVP implementation
+
+ Documentation can be found here: https://github.com/tildeio/rsvp.js/blob/master/README.md
@class RSVP
@namespace Ember
@constructor
*/
@@ -11339,11 +11374,12 @@
@param {Number} increment The amount to increment by. Defaults to 1
@return {Number} The new property value
*/
incrementProperty: function(keyName, increment) {
if (Ember.isNone(increment)) { increment = 1; }
- set(this, keyName, (get(this, keyName) || 0)+increment);
+ Ember.assert("Must pass a numeric value to incrementProperty", (!isNaN(parseFloat(increment)) && isFinite(increment)));
+ set(this, keyName, (get(this, keyName) || 0) + increment);
return get(this, keyName);
},
/**
Set the value of a property to the current value minus some amount.
@@ -11358,11 +11394,12 @@
@param {Number} decrement The amount to decrement by. Defaults to 1
@return {Number} The new property value
*/
decrementProperty: function(keyName, decrement) {
if (Ember.isNone(decrement)) { decrement = 1; }
- set(this, keyName, (get(this, keyName) || 0)-decrement);
+ Ember.assert("Must pass a numeric value to decrementProperty", (!isNaN(parseFloat(decrement)) && isFinite(decrement)));
+ 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
@@ -11950,12 +11987,10 @@
reopen: function() {
applyMixin(this, arguments, true);
return this;
},
- isInstance: true,
-
/**
An overridable method called when objects are instantiated. By default,
does nothing unless it is overridden during class definition.
Example:
@@ -12094,10 +12129,12 @@
return this;
},
/**
Override to implement teardown.
+
+ @method willDestroy
*/
willDestroy: Ember.K,
/**
@private
@@ -14548,14 +14585,11 @@
@module ember
@submodule ember-runtime
*/
/**
- `Ember.ObjectController` is part of Ember's Controller layer. A single shared
- instance of each `Ember.ObjectController` subclass in your application's
- namespace will be created at application initialization and be stored on your
- application's `Ember.Router` instance.
+ `Ember.ObjectController` is part of Ember's Controller layer.
`Ember.ObjectController` derives its functionality from its superclass
`Ember.ObjectProxy` and the `Ember.ControllerMixin` mixin.
@class ObjectController
@@ -15478,11 +15512,13 @@
_dispatchEvent: function(object, evt, eventName, view) {
var result = true;
var handler = object[eventName];
if (Ember.typeOf(handler) === 'function') {
- result = handler.call(object, evt, view);
+ result = Ember.run(function() {
+ return handler.call(object, evt, view);
+ });
// Do not preventDefault in eventManagers.
evt.stopPropagation();
}
else {
result = this._bubbleEvent(view, evt, eventName);
@@ -15662,10 +15698,14 @@
} else {
return parent;
}
}).property('_parentView'),
+ _viewForYield: Ember.computed(function(){
+ return this;
+ }).property(),
+
state: null,
_parentView: null,
// return the current view, not including virtual views
@@ -15976,11 +16016,11 @@
and a different class name if it evaluates to false, you can pass a binding
like this:
```javascript
// Applies 'enabled' class when isEnabled is true and 'disabled' when isEnabled is false
- Ember.View.create({
+ Ember.View.extend({
classNameBindings: ['isEnabled:enabled:disabled']
isEnabled: true
});
```
@@ -15999,11 +16039,11 @@
This syntax offers the convenience to add a class if a property is `false`:
```javascript
// Applies no class when isEnabled is true and class 'disabled' when isEnabled is false
- Ember.View.create({
+ Ember.View.extend({
classNameBindings: ['isEnabled::disabled']
isEnabled: true
});
```
@@ -16293,11 +16333,11 @@
mouseEnter: function(event) {
// will never trigger.
},
eventManager: Ember.Object.create({
mouseEnter: function(event, view) {
- // takes presedence over AView#mouseEnter
+ // takes precedence over AView#mouseEnter
}
})
});
```
@@ -16536,10 +16576,13 @@
}
}).volatile(),
/**
The parent context for this template.
+
+ @method parentContext
+ @return {Ember.View}
*/
parentContext: function() {
var parentView = get(this, '_parentView');
return parentView && get(parentView, '_context');
},
@@ -17046,10 +17089,11 @@
*/
appendTo: function(target) {
// Schedule the DOM element to be created and appended to the given
// element after bindings have synchronized.
this._insertElementLater(function() {
+ Ember.assert("You tried to append to (" + target + ") but that isn't in the DOM", Ember.$(target).length > 0);
Ember.assert("You cannot append to an existing Ember.View. Consider using Ember.ContainerView instead.", !Ember.$(target).is('.ember-view') && !Ember.$(target).parents().is('.ember-view'));
this.$().appendTo(target);
});
return this;
@@ -17067,10 +17111,11 @@
@method replaceIn
@param {String|DOMElement|jQuery} A selector, element, HTML string, or jQuery object
@return {Ember.View} received
*/
replaceIn: function(target) {
+ Ember.assert("You tried to replace in (" + target + ") but that isn't in the DOM", Ember.$(target).length > 0);
Ember.assert("You cannot replace an existing Ember.View. Consider using Ember.ContainerView instead.", !Ember.$(target).is('.ember-view') && !Ember.$(target).parents().is('.ember-view'));
this._insertElementLater(function() {
Ember.$(target).empty();
this.$().appendTo(target);
@@ -17457,33 +17502,33 @@
is a string value, the value of that string will be applied as a class
name.
```javascript
// Applies the 'high' class to the view element
- Ember.View.create({
+ Ember.View.extend({
classNameBindings: ['priority']
priority: 'high'
});
```
If the value of the property is a Boolean, the name of that property is
added as a dasherized class name.
```javascript
// Applies the 'is-urgent' class to the view element
- Ember.View.create({
+ Ember.View.extend({
classNameBindings: ['isUrgent']
isUrgent: true
});
```
If you would prefer to use a custom value instead of the dasherized
property name, you can pass a binding like this:
```javascript
// Applies the 'urgent' class to the view element
- Ember.View.create({
+ Ember.View.extend({
classNameBindings: ['isUrgent:urgent']
isUrgent: true
});
```
@@ -17500,22 +17545,22 @@
a string value, the value of that string will be applied as the attribute.
```javascript
// Applies the type attribute to the element
// with the value "button", like <div type="button">
- Ember.View.create({
+ Ember.View.extend({
attributeBindings: ['type'],
type: 'button'
});
```
If the value of the property is a Boolean, the name of that property is
added as an attribute.
```javascript
// Renders something like <div enabled="enabled">
- Ember.View.create({
+ Ember.View.extend({
attributeBindings: ['enabled'],
enabled: true
});
```
@@ -18744,12 +18789,15 @@
},
initializeViews: function(views, parentView, templateData) {
forEach(views, function(view) {
set(view, '_parentView', parentView);
- set(view, 'container', parentView && parentView.container);
+ if (!view.container && parentView) {
+ set(view, 'container', parentView.container);
+ }
+
if (!get(view, 'templateData')) {
set(view, 'templateData', templateData);
}
});
},
@@ -19305,47 +19353,83 @@
Ember.Component = Ember.View.extend(Ember.TargetActionSupport, {
init: function() {
this._super();
set(this, 'context', this);
set(this, 'controller', this);
- set(this, 'templateData', {keywords: {}});
},
+ // during render, isolate keywords
+ cloneKeywords: function() {
+ return {
+ view: this,
+ controller: this
+ };
+ },
+
targetObject: Ember.computed(function(key) {
var parentView = get(this, '_parentView');
return parentView ? get(parentView, 'controller') : null;
}).property('_parentView'),
+ _viewForYield: Ember.computed(function(){
+ return get(this, '_parentView') || this;
+ }).property('_parentView'),
+
/**
- Sends an action to component's controller. A component inherits its
- controller from the context in which it is used.
+ Sends an action to component's controller. A component inherits its
+ controller from the context in which it is used.
- By default, calling `sendAction()` will send an action with the name
- of the component's `action` property.
+ By default, calling `sendAction()` will send an action with the name
+ of the component's `action` property.
- For example, if the component had a property `action` with the value
- `"addItem"`, calling `sendAction()` would send the `addItem` action
- to the component's controller.
+ For example, if the component had a property `action` with the value
+ `"addItem"`, calling `sendAction()` would send the `addItem` action
+ to the component's controller.
- If you provide an argument to `sendAction()`, that key will be used to look
- up the action name.
+ If you provide the `action` argument to `sendAction()`, that key will
+ be used to look up the action name.
- For example, if the component had a property `playing` with the value
- `didStartPlaying`, calling `sendAction('playing')` would send the
- `didStartPlaying` action to the component's controller.
+ For example, if the component had a property `playing` with the value
+ `didStartPlaying`, calling `sendAction('playing')` would send the
+ `didStartPlaying` action to the component's controller.
- Whether or not you are using the default action or a named action, if
- the action name is not defined on the component, calling `sendAction()`
- does not have any effect.
+ Whether or not you are using the default action or a named action, if
+ the action name is not defined on the component, calling `sendAction()`
+ does not have any effect.
- For example, if you call `sendAction()` on a component that does not have
- an `action` property defined, no action will be sent to the controller,
- nor will an exception be raised.
+ For example, if you call `sendAction()` on a component that does not have
+ an `action` property defined, no action will be sent to the controller,
+ nor will an exception be raised.
- @param [action] {String} the action to trigger
+ You can send a context object with the action by supplying the `context`
+ argument. The context will be supplied as the first argument in the
+ target's action method. Example:
+
+ ```javascript
+ App.MyTree = Ember.Component.extend({
+ click: function() {
+ this.sendAction('didClickTreeNode', this.get('node'));
+ }
+ });
+
+ App.CategoriesController = Ember.Controller.extend({
+ didClickCategory: function(category) {
+ //Do something with the node/category that was clicked
+ }
+ });
+ ```
+
+ ```handlebars
+ {{! categories.hbs}}
+ {{my-tree didClickTreeNode='didClickCategory'}}
+ ```
+
+ @method sendAction
+ @param [action] {String} the action to trigger
+ @param [context] {*} a context to send with the action
*/
- sendAction: function(action) {
+ sendAction: function(action, context) {
var actionName;
// Send the default action
if (action === undefined) {
actionName = get(this, 'action');
@@ -19353,16 +19437,16 @@
} else {
actionName = get(this, action);
Ember.assert("The " + action + " action was triggered on the component " + this.toString() + ", but the action name (" + actionName + ") was not a string.", isNone(actionName) || typeof actionName === 'string');
}
-
// If no action name for that action could be found, just abort.
if (actionName === undefined) { return; }
this.triggerAction({
- action: actionName
+ action: actionName,
+ actionContext: context
});
}
});
})();
@@ -19986,11 +20070,11 @@
For more examples of bound helpers, see documentation for
`Ember.Handlebars.registerBoundHelper`.
## Custom view helper example
- Assuming a view subclass named `App.CalenderView` were defined, a helper
+ Assuming a view subclass named `App.CalendarView` were defined, a helper
for rendering instances of this view could be registered as follows:
```javascript
Ember.Handlebars.helper('calendar', App.CalendarView):
```
@@ -20179,11 +20263,14 @@
var ast = Handlebars.parse(string);
var options = { data: true, stringParams: true };
var environment = new Ember.Handlebars.Compiler().compile(ast, options);
var templateSpec = new Ember.Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true);
- return Ember.Handlebars.template(templateSpec);
+ var template = Ember.Handlebars.template(templateSpec);
+ template.isMethod = false; //Make sure we don't wrap templates with ._super
+
+ return template;
};
}
})();
@@ -22412,11 +22499,11 @@
*/
var handlebarsGet = Ember.Handlebars.get, normalizePath = Ember.Handlebars.normalizePath;
/**
- `log` allows you to output the value of a value in the current rendering
+ `log` allows you to output the value of a variable in the current rendering
context.
```handlebars
{{log myVariable}}
```
@@ -22972,28 +23059,29 @@
@for Ember.Handlebars.helpers
@param {Hash} options
@return {String} HTML string
*/
Ember.Handlebars.registerHelper('yield', function(options) {
- var currentView = options.data.view, view = currentView, template;
+ var currentView = options.data.view, view = currentView;
while (view && !get(view, 'layout')) {
view = get(view, 'parentView');
}
Ember.assert("You called yield in a template that was not a layout", !!view);
- template = get(view, 'template');
+ var template = get(view, 'template'),
+ contextView = get(view, '_viewForYield'),
+ keywords = contextView.cloneKeywords();
- var keywords = view._parentView.cloneKeywords();
-
currentView.appendChild(Ember.View, {
- isVirtual: true,
- tagName: '',
- template: template,
- context: get(view._parentView, 'context'),
- controller: get(view._parentView, 'controller'),
+ isVirtual: true,
+ isYield: true,
+ tagName: '',
+ template: template,
+ context: get(contextView, 'context'),
+ controller: get(contextView, 'controller'),
templateData: {keywords: keywords}
});
});
})();
@@ -24352,25 +24440,20 @@
There's no harm to running this twice, since we remove the templates
from the DOM after processing.
*/
Ember.onLoad('Ember.Application', function(Application) {
- if (Application.initializer) {
- Application.initializer({
- name: 'domTemplates',
- initialize: bootstrap
- });
+ Application.initializer({
+ name: 'domTemplates',
+ initialize: bootstrap
+ });
- Application.initializer({
- name: 'registerComponents',
- after: 'domTemplates',
- initialize: registerComponents
- });
- } else {
- // for ember-old-router
- Ember.onLoad('application', bootstrap);
- }
+ Application.initializer({
+ name: 'registerComponents',
+ after: 'domTemplates',
+ initialize: registerComponents
+ });
});
})();
@@ -24933,16 +25016,16 @@
/**
@private
A Transition is a thennable (a promise-like object) that represents
an attempt to transition to another route. It can be aborted, either
- explicitly via `abort` or by attempting another transition while a
+ explicitly via `abort` or by attempting another transition while a
previous one is still underway. An aborted transition can also
- be `retry()`d later.
+ be `retry()`d later.
*/
- function Transition(router, promise) {
+ function Transition(router, promise) {
this.router = router;
this.promise = promise;
this.data = {};
this.resolvedModels = {};
this.providedModels = {};
@@ -24962,13 +25045,13 @@
/**
The Transition's internal promise. Calling `.then` on this property
is that same as calling `.then` on the Transition object itself, but
this property is exposed for when you want to pass around a
- Transition's promise, but not the Transition object itself, since
+ Transition's promise, but not the Transition object itself, since
Transition object can be externally `abort`ed, while the promise
- cannot.
+ cannot.
*/
promise: null,
/**
Custom state can be stored on a Transition's `data` object.
@@ -24978,38 +25061,38 @@
transition.
*/
data: null,
/**
- A standard promise hook that resolves if the transition
+ A standard promise hook that resolves if the transition
succeeds and rejects if it fails/redirects/aborts.
Forwards to the internal `promise` property which you can
use in situations where you want to pass around a thennable,
- but not the Transition itself.
+ but not the Transition itself.
@param {Function} success
@param {Function} failure
*/
then: function(success, failure) {
return this.promise.then(success, failure);
},
/**
Aborts the Transition. Note you can also implicitly abort a transition
- by initiating another transition while a previous one is underway.
+ by initiating another transition while a previous one is underway.
*/
abort: function() {
if (this.isAborted) { return this; }
log(this.router, this.sequence, this.targetName + ": transition was aborted");
this.isAborted = true;
this.router.activeTransition = null;
- return this;
+ return this;
},
/**
- Retries a previously-aborted transition (making sure to abort the
+ Retries a previously-aborted transition (making sure to abort the
transition if it's still active). Returns a new transition that
represents the new attempt to transition.
*/
retry: function() {
this.abort();
@@ -25019,11 +25102,11 @@
return newTransition;
},
/**
- Sets the URL-changing method to be employed at the end of a
+ Sets the URL-changing method to be employed at the end of a
successful transition. By default, a new Transition will just
use `updateURL`, but passing 'replace' to this method will
cause the URL to update using 'replaceWith' instead. Omitting
a parameter will disable the URL change, allowing for transitions
that don't update the URL at completion (this is also used for
@@ -25052,16 +25135,16 @@
/**
Promise reject reasons passed to promise rejection
handlers for failed transitions.
*/
Router.UnrecognizedURLError = function(message) {
- this.message = (message || "UnrecognizedURLError");
+ this.message = (message || "UnrecognizedURLError");
this.name = "UnrecognizedURLError";
};
Router.TransitionAborted = function(message) {
- this.message = (message || "TransitionAborted");
+ this.message = (message || "TransitionAborted");
this.name = "TransitionAborted";
};
function errorTransition(router, reason) {
return new Transition(router, RSVP.reject(reason));
@@ -25224,12 +25307,12 @@
object = contexts.pop();
if (isParam(object)) {
var recogHandler = recogHandlers[i], name = recogHandler.names[0];
if (object.toString() !== this.currentParams[name]) { return false; }
- } else if (handlerInfo.context !== object) {
- return false;
+ } else if (handlerInfo.context !== object) {
+ return false;
}
}
}
}
@@ -25256,24 +25339,24 @@
a shared pivot parent route and other data necessary to perform
a transition.
*/
function getMatchPoint(router, handlers, objects, inputParams) {
- var matchPoint = handlers.length,
+ var matchPoint = handlers.length,
providedModels = {}, i,
currentHandlerInfos = router.currentHandlerInfos || [],
params = {},
oldParams = router.currentParams || {},
activeTransition = router.activeTransition,
handlerParams = {},
obj;
objects = slice.call(objects);
merge(params, inputParams);
-
+
for (i = handlers.length - 1; i >= 0; i--) {
- var handlerObj = handlers[i],
+ var handlerObj = handlers[i],
handlerName = handlerObj.handler,
oldHandlerInfo = currentHandlerInfos[i],
hasChanged = false;
// Check if handler names have changed.
@@ -25307,11 +25390,11 @@
for (var j = 0, len = names.length; j < len; ++j) {
var name = names[j];
handlerParams[handlerName][name] = params[name] = params[name] || oldParams[name];
}
}
- }
+ }
if (hasChanged) { matchPoint = i; }
}
if (objects.length > 0) {
@@ -25333,17 +25416,17 @@
} else {
return object;
}
} else if (activeTransition) {
// Use model from previous transition attempt, preferably the resolved one.
- return (paramName && activeTransition.providedModels[handlerName]) ||
- activeTransition.resolvedModels[handlerName];
- }
+ return activeTransition.resolvedModels[handlerName] ||
+ (paramName && activeTransition.providedModels[handlerName]);
+ }
}
function isParam(object) {
- return object && (typeof object === "string" || object instanceof String || !isNaN(object));
+ return (typeof object === "string" || object instanceof String || !isNaN(object));
}
/**
@private
@@ -25480,14 +25563,10 @@
});
eachHandler(partition.entered, function(handlerInfo) {
handlerEnteredOrUpdated(transition, currentHandlerInfos, handlerInfo, true);
});
-
- if (router.didTransition) {
- router.didTransition(handlerInfos);
- }
}
/**
@private
@@ -25505,11 +25584,11 @@
setContext(handler, context);
if (handler.setup) { handler.setup(context); }
checkAbort(transition);
} catch(e) {
- if (!(e instanceof Router.TransitionAborted)) {
+ if (!(e instanceof Router.TransitionAborted)) {
// Trigger the `error` event starting from this failed handler.
trigger(currentHandlerInfos.concat(handlerInfo), true, ['error', e, transition]);
}
// Propagate the error so that the transition promise will reject.
@@ -25652,18 +25731,19 @@
*/
function performTransition(router, recogHandlers, providedModelsArray, params, data) {
var matchPointResults = getMatchPoint(router, recogHandlers, providedModelsArray, params),
targetName = recogHandlers[recogHandlers.length - 1].handler,
- wasTransitioning = false;
+ wasTransitioning = false,
+ currentHandlerInfos = router.currentHandlerInfos;
// Check if there's already a transition underway.
- if (router.activeTransition) {
+ if (router.activeTransition) {
if (transitionsIdentical(router.activeTransition, targetName, providedModelsArray)) {
return router.activeTransition;
}
- router.activeTransition.abort();
+ router.activeTransition.abort();
wasTransitioning = true;
}
var deferred = RSVP.defer(),
transition = new Transition(router, deferred.promise);
@@ -25678,11 +25758,11 @@
var handlerInfos = generateHandlerInfos(router, recogHandlers);
// Fire 'willTransition' event on current handlers, but don't fire it
// if a transition was already underway.
if (!wasTransitioning) {
- trigger(router.currentHandlerInfos, true, ['willTransition', transition]);
+ trigger(currentHandlerInfos, true, ['willTransition', transition]);
}
log(router, transition.sequence, "Beginning validation for transition to " + transition.targetName);
validateEntry(transition, handlerInfos, 0, matchPointResults.matchPoint, matchPointResults.handlerParams)
.then(transitionSuccess, transitionFailure);
@@ -25691,12 +25771,24 @@
function transitionSuccess() {
checkAbort(transition);
try {
- finalizeTransition(transition, handlerInfos);
+ log(router, transition.sequence, "Validation succeeded, finalizing transition;");
+ // Don't overwrite contexts / update URL if this was a noop transition.
+ if (!currentHandlerInfos || !currentHandlerInfos.length ||
+ currentHandlerInfos.length !== matchPointResults.matchPoint) {
+ finalizeTransition(transition, handlerInfos);
+ }
+
+ if (router.didTransition) {
+ router.didTransition(handlerInfos);
+ }
+
+ log(router, transition.sequence, "TRANSITION COMPLETE.");
+
// Resolve with the final handler.
deferred.resolve(handlerInfos[handlerInfos.length - 1].handler);
} catch(e) {
deferred.reject(e);
}
@@ -25715,12 +25807,12 @@
/**
@private
Accepts handlers in Recognizer format, either returned from
- recognize() or handlersFor(), and returns unified
- `HandlerInfo`s.
+ recognize() or handlersFor(), and returns unified
+ `HandlerInfo`s.
*/
function generateHandlerInfos(router, recogHandlers) {
var handlerInfos = [];
for (var i = 0, len = recogHandlers.length; i < len; ++i) {
var handlerObj = recogHandlers[i],
@@ -25761,12 +25853,10 @@
var router = transition.router,
seq = transition.sequence,
handlerName = handlerInfos[handlerInfos.length - 1].name;
- log(router, seq, "Validation succeeded, finalizing transition;");
-
// Collect params for URL.
var objects = [], providedModels = transition.providedModelsArray.slice();
for (var i = handlerInfos.length - 1; i>=0; --i) {
var handlerInfo = handlerInfos[i];
if (handlerInfo.isDynamic) {
@@ -25780,11 +25870,11 @@
transition.providedModelsArray = [];
transition.providedContexts = {};
router.currentParams = params;
var urlMethod = transition.urlMethod;
- if (urlMethod) {
+ if (urlMethod) {
var url = router.recognizer.generate(handlerName, params);
if (urlMethod === 'replace') {
router.replaceURL(url);
} else {
@@ -25792,11 +25882,10 @@
router.updateURL(url);
}
}
setupContexts(transition, handlerInfos);
- log(router, seq, "TRANSITION COMPLETE.");
}
/**
@private
@@ -25821,11 +25910,13 @@
if (index < matchPoint) {
log(router, seq, handlerName + ": using context from already-active handler");
// We're before the match point, so don't run any hooks,
// just use the already resolved context from the handler.
- transition.resolvedModels[handlerInfo.name] = handlerInfo.handler.context;
+ transition.resolvedModels[handlerInfo.name] =
+ transition.providedModels[handlerInfo.name] ||
+ handlerInfo.handler.context;
return proceed();
}
return RSVP.resolve().then(handleAbort)
.then(beforeModel)
@@ -25856,16 +25947,16 @@
// otherwise, we're here because of a different error
transition.abort();
log(router, seq, handlerName + ": handling error: " + reason);
- // An error was thrown / promise rejected, so fire an
+ // An error was thrown / promise rejected, so fire an
// `error` event from this handler info up to root.
trigger(handlerInfos.slice(0, index + 1), true, ['error', reason, transition]);
- if (handler.error) {
- handler.error(reason, transition);
+ if (handler.error) {
+ handler.error(reason, transition);
}
// Propagate the original error.
return RSVP.reject(reason);
}
@@ -25911,11 +26002,11 @@
@private
Throws a TransitionAborted if the provided transition has been aborted.
*/
function checkAbort(transition) {
- if (transition.isAborted) {
+ if (transition.isAborted) {
log(transition.router, transition.sequence, "detected abort.");
throw new Router.TransitionAborted();
}
}
@@ -25941,11 +26032,11 @@
return handler.model && handler.model(handlerParams || {}, transition);
}
/**
- @private
+ @private
*/
function log(router, sequence, msg) {
if (!router.log) { return; }
@@ -26000,11 +26091,11 @@
}
// Use custom serialize if it exists.
if (handler.serialize) {
return handler.serialize(model, names);
- }
+ }
if (names.length !== 1) { return; }
var name = names[0];
@@ -26178,26 +26269,10 @@
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'),
- options = {};
-
- if (typeof rootURL === 'string') {
- options.rootURL = rootURL;
- }
-
- if ('string' === typeof location) {
- options.implementation = location;
- location = set(router, 'location', Ember.Location.create(options));
-
- }
-}
-
/**
The `Ember.Router` class manages the application state and URLs. Refer to
the [routing guide](http://emberjs.com/guides/routing/) for documentation.
@class Router
@@ -26208,11 +26283,11 @@
location: 'hash',
init: function() {
this.router = this.constructor.router || this.constructor.map(Ember.K);
this._activeViews = {};
- setupLocation(this);
+ this._setupLocation();
},
url: Ember.computed(function() {
return get(this, 'location').getURL();
}),
@@ -26223,11 +26298,11 @@
var router = this.router,
location = get(this, 'location'),
container = this.container,
self = this;
- setupRouter(this, router, location);
+ this._setupRouter(router, location);
container.register('view:default', DefaultView);
container.register('view:toplevel', Ember.View.extend());
location.onUpdateURL(function(url) {
@@ -26237,11 +26312,11 @@
this.handleURL(location.getURL());
},
didTransition: function(infos) {
var appController = this.container.lookup('controller:application'),
- path = routePath(infos);
+ path = Ember.Router._routePath(infos);
if (!('currentPath' in appController)) {
defineProperty(appController, 'currentPath');
}
set(appController, 'currentPath', path);
@@ -26251,21 +26326,19 @@
Ember.Logger.log("Transitioned into '" + path + "'");
}
},
handleURL: function(url) {
- scheduleLoadingStateEntry(this);
-
- return this.router.handleURL(url).then(transitionCompleted);
+ return this._doTransition('handleURL', [url]);
},
transitionTo: function() {
- return doTransition(this, 'transitionTo', arguments);
+ return this._doTransition('transitionTo', arguments);
},
replaceWith: function() {
- return doTransition(this, 'replaceWith', arguments);
+ return this._doTransition('replaceWith', arguments);
},
generate: function() {
var url = this.router.generate.apply(this.router, arguments);
return this.location.formatURL(url);
@@ -26312,159 +26385,159 @@
delete this._activeViews[templateName];
};
this._activeViews[templateName] = [view, disconnect];
view.one('willDestroyElement', this, disconnect);
- }
-});
+ },
-function getHandlerFunction(router) {
- var seen = {}, container = router.container,
- DefaultRoute = container.resolve('route:basic');
+ _setupLocation: function() {
+ var location = get(this, 'location'),
+ rootURL = get(this, 'rootURL'),
+ options = {};
- return function(name) {
- var routeName = 'route:' + name,
- handler = container.lookup(routeName);
-
- if (seen[name]) { return handler; }
-
- seen[name] = true;
-
- if (!handler) {
- if (name === 'loading') { return {}; }
-
- container.register(routeName, DefaultRoute.extend());
- handler = container.lookup(routeName);
-
- if (get(router, 'namespace.LOG_ACTIVE_GENERATION')) {
- Ember.Logger.info("generated -> " + routeName, { fullName: routeName });
- }
+ if (typeof rootURL === 'string') {
+ options.rootURL = rootURL;
}
- if (name === 'application') {
- // Inject default `error` handler.
- handler.events = handler.events || {};
- handler.events.error = handler.events.error || defaultErrorHandler;
+ if ('string' === typeof location) {
+ options.implementation = location;
+ location = set(this, 'location', Ember.Location.create(options));
}
+ },
- handler.routeName = name;
- return handler;
- };
-}
+ _getHandlerFunction: function() {
+ var seen = {}, container = this.container,
+ DefaultRoute = container.resolve('route:basic'),
+ self = this;
-function defaultErrorHandler(error, transition) {
- Ember.Logger.error('Error while loading route:', error);
+ return function(name) {
+ var routeName = 'route:' + name,
+ handler = container.lookup(routeName);
- // Using setTimeout allows us to escape from the Promise's try/catch block
- setTimeout(function() { throw error; });
-}
+ if (seen[name]) { return handler; }
+ seen[name] = true;
-function routePath(handlerInfos) {
- var path = [];
+ if (!handler) {
+ if (name === 'loading') { return {}; }
- for (var i=1, l=handlerInfos.length; i<l; i++) {
- var name = handlerInfos[i].name,
- nameParts = name.split(".");
+ container.register(routeName, DefaultRoute.extend());
+ handler = container.lookup(routeName);
- path.push(nameParts[nameParts.length - 1]);
- }
+ if (get(self, 'namespace.LOG_ACTIVE_GENERATION')) {
+ Ember.Logger.info("generated -> " + routeName, { fullName: routeName });
+ }
+ }
- return path.join(".");
-}
+ if (name === 'application') {
+ // Inject default `error` handler.
+ handler.events = handler.events || {};
+ handler.events.error = handler.events.error || Ember.Router._defaultErrorHandler;
+ }
-function setupRouter(emberRouter, router, location) {
- var lastURL;
+ handler.routeName = name;
+ return handler;
+ };
+ },
- router.getHandler = getHandlerFunction(emberRouter);
+ _setupRouter: function(router, location) {
+ var lastURL, emberRouter = this;
- var doUpdateURL = function() {
- location.setURL(lastURL);
- };
+ router.getHandler = this._getHandlerFunction();
- router.updateURL = function(path) {
- lastURL = path;
- Ember.run.once(doUpdateURL);
- };
-
- if (location.replaceURL) {
- var doReplaceURL = function() {
- location.replaceURL(lastURL);
+ var doUpdateURL = function() {
+ location.setURL(lastURL);
};
- router.replaceURL = function(path) {
+ router.updateURL = function(path) {
lastURL = path;
- Ember.run.once(doReplaceURL);
+ Ember.run.once(doUpdateURL);
};
- }
- router.didTransition = function(infos) {
- emberRouter.didTransition(infos);
- };
-}
+ if (location.replaceURL) {
+ var doReplaceURL = function() {
+ location.replaceURL(lastURL);
+ };
-function doTransition(router, method, args) {
- // Normalize blank route to root URL.
- args = [].slice.call(args);
- args[0] = args[0] || '/';
+ router.replaceURL = function(path) {
+ lastURL = path;
+ Ember.run.once(doReplaceURL);
+ };
+ }
- var passedName = args[0], name;
+ router.didTransition = function(infos) {
+ emberRouter.didTransition(infos);
+ };
+ },
- if (passedName.charAt(0) === '/') {
- name = passedName;
- } else {
- if (!router.router.hasRoute(passedName)) {
- name = args[0] = passedName + '.index';
- } else {
+ _doTransition: function(method, args) {
+ // Normalize blank route to root URL.
+ args = [].slice.call(args);
+ args[0] = args[0] || '/';
+
+ var passedName = args[0], name, self = this;
+
+ if (passedName.charAt(0) === '/') {
name = passedName;
+ } else {
+ if (!this.router.hasRoute(passedName)) {
+ name = args[0] = passedName + '.index';
+ } else {
+ name = passedName;
+ }
+
+ Ember.assert("The route " + passedName + " was not found", this.router.hasRoute(name));
}
- Ember.assert("The route " + passedName + " was not found", router.router.hasRoute(name));
- }
+ var transitionPromise = this.router[method].apply(this.router, args);
- scheduleLoadingStateEntry(router);
+ // Don't schedule loading state entry if user has already aborted the transition.
+ if (this.router.activeTransition) {
+ this._scheduleLoadingStateEntry();
+ }
- var transitionPromise = router.router[method].apply(router.router, args);
- transitionPromise.then(transitionCompleted);
+ transitionPromise.then(function(route) {
+ self._transitionCompleted(route);
+ });
- // We want to return the configurable promise object
- // so that callers of this function can use `.method()` on it,
- // which obviously doesn't exist for normal RSVP promises.
- return transitionPromise;
-}
+ // We want to return the configurable promise object
+ // so that callers of this function can use `.method()` on it,
+ // which obviously doesn't exist for normal RSVP promises.
+ return transitionPromise;
+ },
-function scheduleLoadingStateEntry(router) {
- if (router._loadingStateActive) { return; }
- router._shouldEnterLoadingState = true;
- Ember.run.scheduleOnce('routerTransitions', null, enterLoadingState, router);
-}
+ _scheduleLoadingStateEntry: function() {
+ if (this._loadingStateActive) { return; }
+ this._shouldEnterLoadingState = true;
+ Ember.run.scheduleOnce('routerTransitions', this, this._enterLoadingState);
+ },
-function enterLoadingState(router) {
- if (router._loadingStateActive || !router._shouldEnterLoadingState) { return; }
+ _enterLoadingState: function() {
+ if (this._loadingStateActive || !this._shouldEnterLoadingState) { return; }
- var loadingRoute = router.router.getHandler('loading');
- if (loadingRoute) {
- if (loadingRoute.enter) { loadingRoute.enter(); }
- if (loadingRoute.setup) { loadingRoute.setup(); }
- router._loadingStateActive = true;
- }
-}
+ var loadingRoute = this.router.getHandler('loading');
+ if (loadingRoute) {
+ if (loadingRoute.enter) { loadingRoute.enter(); }
+ if (loadingRoute.setup) { loadingRoute.setup(); }
+ this._loadingStateActive = true;
+ }
+ },
-function exitLoadingState(router) {
- router._shouldEnterLoadingState = false;
- if (!router._loadingStateActive) { return; }
+ _exitLoadingState: function () {
+ this._shouldEnterLoadingState = false;
+ if (!this._loadingStateActive) { return; }
- var loadingRoute = router.router.getHandler('loading');
- if (loadingRoute && loadingRoute.exit) { loadingRoute.exit(); }
- router._loadingStateActive = false;
-}
+ var loadingRoute = this.router.getHandler('loading');
+ if (loadingRoute && loadingRoute.exit) { loadingRoute.exit(); }
+ this._loadingStateActive = false;
+ },
-function transitionCompleted(route) {
- var router = route.router;
- router.notifyPropertyChange('url');
- exitLoadingState(router);
-}
+ _transitionCompleted: function(route) {
+ this.notifyPropertyChange('url');
+ this._exitLoadingState();
+ }
+});
Ember.Router.reopenClass({
map: function(callback) {
var router = this.router;
if (!router) {
@@ -26486,10 +26559,30 @@
});
router.callbacks.push(callback);
router.map(dsl.generate());
return router;
+ },
+
+ _defaultErrorHandler: function(error, transition) {
+ Ember.Logger.error('Error while loading route:', error);
+
+ // Using setTimeout allows us to escape from the Promise's try/catch block
+ setTimeout(function() { throw error; });
+ },
+
+ _routePath: function(handlerInfos) {
+ var path = [];
+
+ for (var i=1, l=handlerInfos.length; i<l; i++) {
+ var name = handlerInfos[i].name,
+ nameParts = name.split(".");
+
+ path.push(nameParts[nameParts.length - 1]);
+ }
+
+ return path.join(".");
}
});
})();
@@ -26542,14 +26635,47 @@
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`
- or `Controller#send`.
+ or `Controller#send`.
- The context of the event will be this route.
+ The `events` hash will inherit event handlers from
+ the `events` hash defined on extended Route parent classes
+ or mixins rather than just replace the entire hash, e.g.:
+ ```js
+ App.CanDisplayBanner = Ember.Mixin.create({
+ events: {
+ displayBanner: function(msg) {
+ // ...
+ }
+ }
+ });
+
+ App.WelcomeRoute = Ember.Route.extend(App.CanDisplayBanner, {
+ events: {
+ playMusic: function() {
+ // ...
+ }
+ }
+ });
+
+ // `WelcomeRoute`, when active, will be able to respond
+ // to both events, since the events hash is merged rather
+ // then replaced when extending mixins / parent classes.
+ this.send('displayBanner');
+ this.send('playMusic');
+ ```
+
+ It is also possible to call `this._super()` from within an
+ event handler if it overrides a handle defined on a parent
+ class or mixin.
+
+ Within a route's event handler, the value of the `this` context
+ is the Route object.
+
## Bubbling
By default, an event will stop bubbling once a handler defined
on the `events` hash handles it. To continue bubbling the event,
you must return `true` from the handler.
@@ -26653,10 +26779,12 @@
@type Hash
@default null
*/
events: null,
+ mergedProperties: ['events'],
+
/**
This hook is executed when the router completely exits this route. It is
not executed when the model for the route changes.
@method deactivate
@@ -26853,10 +26981,12 @@
Refer to documentation for `beforeModel` for a description
of transition-pausing semantics when a promise is returned
from this hook.
@method afterModel
+ @param {Object} resolvedModel the value returned from `model`,
+ or its resolved value if it was a promise
@param {Transition} transition
@return {Promise} if the value returned from this hook is
a promise, the transition will pause until the transition
resolves. Otherwise, non-promise return values are not
utilized in any way.
@@ -26908,12 +27038,17 @@
if a promise returned from `model` fails, the error will be
handled by the `error` hook on `Ember.Route`.
@method model
@param {Object} params the parameters extracted from the URL
+ @param {Transition} transition
+ @return {Object|Promise} the model for this route. If
+ a promise is returned, the transition will pause until
+ the promise resolves, and the resolved value of the promise
+ will be used as the model for this route.
*/
- model: function(params, resolvedParentModels) {
+ model: function(params, transition) {
var match, name, sawParams, value;
for (var prop in params) {
if (match = prop.match(/^(.*)_id$/)) {
name = match[1];
@@ -27043,11 +27178,11 @@
controller = container.lookup('controller:' + name);
// NOTE: We're specifically checking that skipAssert is true, because according
// to the old API the second parameter was model. We do not want people who
// passed a model to skip the assertion.
- Ember.assert("The controller "+name+" could not be found. Make sure the controller has been generated first. This will happen the first time the associated route is entered.", controller || _skipAssert === true);
+ Ember.assert("The controller for route '"+name+"'' could not be found. Make sure that this route exists and has already been entered at least once. If you must intialize the controller without entering a route, use `generateController`.", controller || _skipAssert === true);
return controller;
},
/**
@@ -27063,12 +27198,10 @@
generateController: function(name, model) {
var container = this.router.container;
model = model || this.modelFor(name);
- Ember.assert("You are trying to look up a controller that you did not define, and for which Ember does not know the model.\n\nThis is not a controller for a route, so you must explicitly define the controller ("+this.router.namespace.toString() + "." + Ember.String.capitalize(Ember.String.camelize(name))+"Controller) or pass a model as the second parameter to `controllerFor`, so that Ember knows which type of controller to create for you.", model || this.container.lookup('route:' + name));
-
return Ember.generateController(container, name, model);
},
/**
Returns the current model for a given route.
@@ -27466,10 +27599,18 @@
@default null
**/
title: null,
/**
+ Sets the `rel` attribute of the `LinkView`'s HTML element.
+
+ @property rel
+ @default null
+ **/
+ rel: null,
+
+ /**
The CSS class to apply to `LinkView`'s element when its `active`
property is `true`.
@property activeClass
@type String
@@ -27505,11 +27646,11 @@
@property replace
@type Boolean
@default false
**/
replace: false,
- attributeBindings: ['href', 'title'],
+ attributeBindings: ['href', 'title', 'rel'],
classNameBindings: ['active', 'loading', 'disabled'],
/**
By default the `{{linkTo}}` helper responds to the `click` event. You
can override this globally by setting this property to your custom
@@ -27554,19 +27695,11 @@
for(i=0; i < length; i++) {
paths.pushObject(createPath(params[i]));
}
var observer = function(object, path) {
- var notify = true, i;
- for(i=0; i < paths.length; i++) {
- if (!get(this, paths[i])) {
- notify = false;
- }
- }
- if (notify) {
- this.notifyPropertyChange('routeArgs');
- }
+ this.notifyPropertyChange('routeArgs');
};
for(i=0; i < length; i++) {
this.registerObserver(this, paths[i], this, observer);
}
@@ -27653,11 +27786,11 @@
if (this.bubbles === false) { event.stopPropagation(); }
if (get(this, '_isDisabled')) { return false; }
if (get(this, 'loading')) {
- Ember.Logger.warn("This linkTo's parameters are either not yet loaded or point to an invalid route.");
+ Ember.Logger.warn("This linkTo is in an inactive loading state because at least one of its parameters' presently has a null/undefined value, or the provided route name is invalid.");
return false;
}
var router = get(this, 'router'),
routeArgs = get(this, 'routeArgs');
@@ -28009,11 +28142,11 @@
options = property;
property = 'main';
}
outletSource = options.data.view;
- while (!(outletSource.get('template.isTop'))) {
+ while (!outletSource.get('template.isTop') || outletSource.isYield) {
outletSource = outletSource.get('_parentView');
}
outletContainerClass = options.hash.viewClass || Handlebars.OutletView;
@@ -28062,11 +28195,12 @@
@param {Object?} contextString
@param {Hash} options
*/
Ember.Handlebars.registerHelper('render', function(name, contextString, options) {
Ember.assert("You must pass a template to render", arguments.length >= 2);
- var container, router, controller, view, context, lookupOptions;
+ var contextProvided = arguments.length === 3,
+ container, router, controller, view, context, lookupOptions;
if (arguments.length === 2) {
options = contextString;
contextString = undefined;
}
@@ -28078,11 +28212,11 @@
name = name.replace(/\//g, '.');
container = options.data.keywords.controller.container;
router = container.lookup('router:main');
- Ember.assert("You can only use the {{render}} helper once without a model object as its second argument, as in {{render \"post\" post}}.", context || !router || !router._lookupActiveView(name));
+ Ember.assert("You can only use the {{render}} helper once without a model object as its second argument, as in {{render \"post\" post}}.", contextProvided || !router || !router._lookupActiveView(name));
view = container.lookup('view:' + name) || container.lookup('view:default');
var controllerName = options.hash.controller;
@@ -28093,11 +28227,11 @@
} else {
controller = container.lookup('controller:' + name, lookupOptions) ||
Ember.generateController(container, name, context);
}
- if (controller && context) {
+ if (controller && contextProvided) {
controller.set('model', context);
}
var root = options.contexts[1];
@@ -29827,19 +29961,21 @@
to use the router for this purpose.
@method deferReadiness
*/
deferReadiness: function() {
+ Ember.assert("You must call deferReadiness on an instance of Ember.Application", this instanceof Ember.Application);
Ember.assert("You cannot defer readiness since the `ready()` hook has already been called.", this._readinessDeferrals > 0);
this._readinessDeferrals++;
},
/**
@method advanceReadiness
@see {Ember.Application#deferReadiness}
*/
advanceReadiness: function() {
+ Ember.assert("You must call advanceReadiness on an instance of Ember.Application", this instanceof Ember.Application);
this._readinessDeferrals--;
if (this._readinessDeferrals === 0) {
Ember.run.once(this, this.didBecomeReady);
}
@@ -30176,10 +30312,11 @@
container.set = Ember.set;
container.normalize = normalize;
container.resolver = resolverFor(namespace);
container.describe = container.resolver.describe;
+ container.optionsForType('component', { singleton: false });
container.optionsForType('view', { singleton: false });
container.optionsForType('template', { instantiate: false });
container.register('application:main', namespace, { instantiate: false });
container.register('controller:basic', Ember.Controller, { instantiate: false });
@@ -31644,9 +31781,447 @@
Ember States
@module ember
@submodule ember-states
@requires ember-runtime
+*/
+
+})();
+
+(function() {
+/**
+@module ember
+@submodule ember-extension-support
+*/
+/**
+ The `DataAdapter` helps a data persistence library
+ interface with tools that debug Ember such
+ as the Chrome Ember Extension.
+
+ This class will be extended by a persistence library
+ which will override some of the methods with
+ library-specific code.
+
+ The methods likely to be overriden are
+ `getFilters`, `detect`, `columnsForType`,
+ `getRecords`, `getRecordColumnValues`,
+ `getRecordKeywords`, `getRecordFilterValues`,
+ `getRecordColor`, `observeRecord`
+
+ The adapter will need to be registered
+ in the application's container as `dataAdapter:main`
+
+ Example:
+ ```javascript
+ Application.initializer({
+ name: "dataAdapter",
+
+ initialize: function(container, application) {
+ application.register('dataAdapter:main', DS.DataAdapter);
+ }
+ });
+ ```
+
+ @class DataAdapter
+ @namespace Ember
+ @extends Ember.Object
+*/
+Ember.DataAdapter = Ember.Object.extend({
+ init: function() {
+ this._super();
+ this.releaseMethods = Ember.A();
+ },
+
+ /**
+ The container of the application being debugged.
+ This property will be injected
+ on creation.
+ */
+ container: null,
+
+ /**
+ @private
+
+ Number of attributes to send
+ as columns. (Enough to make the record
+ identifiable).
+ */
+ attributeLimit: 3,
+
+ /**
+ @private
+
+ Stores all methods that clear observers.
+ These methods will be called on destruction.
+ */
+ releaseMethods: Ember.A(),
+
+ /**
+ @public
+
+ Specifies how records can be filtered.
+ Records returned will need to have a `filterValues`
+ property with a key for every name in the returned array.
+
+ @method getFilters
+ @return {Array} List of objects defining filters.
+ The object should have a `name` and `desc` property.
+ */
+ getFilters: function() {
+ return Ember.A();
+ },
+
+ /**
+ @public
+
+ Fetch the model types and observe them for changes.
+
+ @method watchModelTypes
+
+ @param {Function} typesAdded Callback to call to add types.
+ Takes an array of objects containing wrapped types (returned from `wrapModelType`).
+
+ @param {Function} typesUpdated Callback to call when a type has changed.
+ Takes an array of objects containing wrapped types.
+
+ @return {Function} Method to call to remove all observers
+ */
+ watchModelTypes: function(typesAdded, typesUpdated) {
+ var modelTypes = this.getModelTypes(),
+ self = this, typesToSend, releaseMethods = Ember.A();
+
+ typesToSend = modelTypes.map(function(type) {
+ var wrapped = self.wrapModelType(type);
+ releaseMethods.push(self.observeModelType(type, typesUpdated));
+ return wrapped;
+ });
+
+ typesAdded(typesToSend);
+
+ var release = function() {
+ releaseMethods.forEach(function(fn) { fn(); });
+ self.releaseMethods.removeObject(release);
+ };
+ this.releaseMethods.pushObject(release);
+ return release;
+ },
+
+ /**
+ @public
+
+ Fetch the records of a given type and observe them for changes.
+
+ @method watchRecords
+
+ @param {Function} recordsAdded Callback to call to add records.
+ Takes an array of objects containing wrapped records.
+ The object should have the following properties:
+ columnValues: {Object} key and value of a table cell
+ object: {Object} the actual record object
+
+ @param {Function} recordsUpdated Callback to call when a record has changed.
+ Takes an array of objects containing wrapped records.
+
+ @param {Function} recordsRemoved Callback to call when a record has removed.
+ Takes the following parameters:
+ index: the array index where the records were removed
+ count: the number of records removed
+
+ @return {Function} Method to call to remove all observers
+ */
+ watchRecords: function(type, recordsAdded, recordsUpdated, recordsRemoved) {
+ var self = this, releaseMethods = Ember.A(), records = this.getRecords(type), release;
+
+ var recordUpdated = function(updatedRecord) {
+ recordsUpdated([updatedRecord]);
+ };
+
+ var recordsToSend = records.map(function(record) {
+ releaseMethods.push(self.observeRecord(record, recordUpdated));
+ return self.wrapRecord(record);
+ });
+
+
+ var contentDidChange = function(array, idx, removedCount, addedCount) {
+ for (var i = idx; i < idx + addedCount; i++) {
+ var record = array.objectAt(i);
+ var wrapped = self.wrapRecord(record);
+ releaseMethods.push(self.observeRecord(record, recordUpdated));
+ recordsAdded([wrapped]);
+ }
+
+ if (removedCount) {
+ recordsRemoved(idx, removedCount);
+ }
+ };
+
+ var observer = { didChange: contentDidChange, willChange: Ember.K };
+ records.addArrayObserver(self, observer);
+
+ release = function() {
+ releaseMethods.forEach(function(fn) { fn(); });
+ records.removeArrayObserver(self, observer);
+ self.releaseMethods.removeObject(release);
+ };
+
+ recordsAdded(recordsToSend);
+
+ this.releaseMethods.pushObject(release);
+ return release;
+ },
+
+ /**
+ @private
+
+ Clear all observers before destruction
+ */
+ willDestroy: function() {
+ this._super();
+ this.releaseMethods.forEach(function(fn) {
+ fn();
+ });
+ },
+
+ /**
+ @private
+
+ Detect whether a class is a model.
+
+ Test that against the model class
+ of your persistence library
+
+ @method detect
+ @param {Class} klass The class to test
+ @return boolean Whether the class is a model class or not
+ */
+ detect: function(klass) {
+ return false;
+ },
+
+ /**
+ @private
+
+ Get the columns for a given model type.
+
+ @method columnsForType
+ @param {Class} type The model type
+ @return {Array} An array of columns of the following format:
+ name: {String} name of the column
+ desc: {String} Humanized description (what would show in a table column name)
+ */
+ columnsForType: function(type) {
+ return Ember.A();
+ },
+
+ /**
+ @private
+
+ Adds observers to a model type class.
+
+ @method observeModelType
+ @param {Class} type The model type class
+ @param {Function} typesUpdated Called when a type is modified.
+ @return {Function} The function to call to remove observers
+ */
+
+ observeModelType: function(type, typesUpdated) {
+ var self = this, records = this.getRecords(type);
+
+ var onChange = function() {
+ typesUpdated([self.wrapModelType(type)]);
+ };
+ var observer = {
+ didChange: function() {
+ Ember.run.scheduleOnce('actions', this, onChange);
+ },
+ willChange: Ember.K
+ };
+
+ records.addArrayObserver(this, observer);
+
+ var release = function() {
+ records.removeArrayObserver(self, observer);
+ };
+
+ return release;
+ },
+
+
+ /**
+ @private
+
+ Wraps a given model type and observes changes to it.
+
+ @method wrapModelType
+ @param {Class} type A model class
+ @param {Function} typesUpdated callback to call when the type changes
+ @return {Object} contains the wrapped type and the function to remove observers
+ Format:
+ type: {Object} the wrapped type
+ The wrapped type has the following format:
+ name: {String} name of the type
+ count: {Integer} number of records available
+ columns: {Columns} array of columns to describe the record
+ object: {Class} the actual Model type class
+ release: {Function} The function to remove observers
+ */
+ wrapModelType: function(type, typesUpdated) {
+ var release, records = this.getRecords(type),
+ typeToSend, self = this;
+
+ typeToSend = {
+ name: type.toString(),
+ count: Ember.get(records, 'length'),
+ columns: this.columnsForType(type),
+ object: type
+ };
+
+
+ return typeToSend;
+ },
+
+
+ /**
+ @private
+
+ Fetches all models defined in the application.
+ TODO: Use the resolver instead of looping over namespaces.
+
+ @method getModelTypes
+ @return {Array} Array of model types
+ */
+ getModelTypes: function() {
+ var namespaces = Ember.A(Ember.Namespace.NAMESPACES), types = Ember.A(), self = this;
+
+ namespaces.forEach(function(namespace) {
+ for (var key in namespace) {
+ if (!namespace.hasOwnProperty(key)) { continue; }
+ var klass = namespace[key];
+ if (self.detect(klass)) {
+ types.push(klass);
+ }
+ }
+ });
+ return types;
+ },
+
+ /**
+ @private
+
+ Fetches all loaded records for a given type.
+
+ @method getRecords
+ @return {Array} array of records.
+ This array will be observed for changes,
+ so it should update when new records are added/removed.
+ */
+ getRecords: function(type) {
+ return Ember.A();
+ },
+
+ /**
+ @private
+
+ Wraps a record and observers changes to it
+
+ @method wrapRecord
+ @param {Object} record The record instance
+ @return {Object} the wrapped record. Format:
+ columnValues: {Array}
+ searchKeywords: {Array}
+ */
+ wrapRecord: function(record) {
+ var recordToSend = { object: record }, columnValues = {}, self = this;
+
+ recordToSend.columnValues = this.getRecordColumnValues(record);
+ recordToSend.searchKeywords = this.getRecordKeywords(record);
+ recordToSend.filterValues = this.getRecordFilterValues(record);
+ recordToSend.color = this.getRecordColor(record);
+
+ return recordToSend;
+ },
+
+ /**
+ @private
+
+ Gets the values for each column.
+
+ @method getRecordColumnValues
+ @return {Object} Keys should match column names defined
+ by the model type.
+ */
+ getRecordColumnValues: function(record) {
+ return {};
+ },
+
+ /**
+ @private
+
+ Returns keywords to match when searching records.
+
+ @method getRecordKeywords
+ @return {Array} Relevant keywords for search.
+ */
+ getRecordKeywords: function(record) {
+ return Ember.A();
+ },
+
+ /**
+ @private
+
+ Returns the values of filters defined by `getFilters`.
+
+ @method getRecordFilterValues
+ @param {Object} record The record instance
+ @return {Object} The filter values
+ */
+ getRecordFilterValues: function(record) {
+ return {};
+ },
+
+ /**
+ @private
+
+ Each record can have a color that represents its state.
+
+ @method getRecordColor
+ @param {Object} record The record instance
+ @return {String} The record's color
+ Possible options: black, red, blue, green
+ */
+ getRecordColor: function(record) {
+ return null;
+ },
+
+ /**
+ @private
+
+ Observes all relevant properties and re-sends the wrapped record
+ when a change occurs.
+
+ @method observerRecord
+ @param {Object} record The record instance
+ @param {Function} recordUpdated The callback to call when a record is updated.
+ @return {Function} The function to call to remove all observers.
+ */
+ observeRecord: function(record, recordUpdated) {
+ return function(){};
+ }
+
+});
+
+
+})();
+
+
+
+(function() {
+/**
+Ember Extension Support
+
+@module ember
+@submodule ember-extension-support
+@requires ember-application
*/
})();
(function() {