Update ember and ember-data

This commit is contained in:
Piotr Sarnacki 2012-07-30 03:39:26 +02:00
parent 3eb8e17daa
commit cd26fc103e
3 changed files with 1124 additions and 518 deletions

View File

@ -248,7 +248,7 @@ DS.ManyArrayStateManager = Ember.StateManager.extend({
(function() { (function() {
var get = Ember.get, set = Ember.set, set = Ember.set; var get = Ember.get, set = Ember.set;
DS.ManyArray = DS.RecordArray.extend({ DS.ManyArray = DS.RecordArray.extend({
init: function() { init: function() {
@ -3743,31 +3743,35 @@ DS.FixtureAdapter = DS.Adapter.extend({
find: function(store, type, id) { find: function(store, type, id) {
var fixtures = this.fixturesForType(type); var fixtures = this.fixturesForType(type);
Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
if (fixtures) { if (fixtures) {
fixtures = fixtures.findProperty('id', id); fixtures = fixtures.findProperty('id', id);
} }
Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures); if (fixtures) {
this.simulateRemoteCall(function() { this.simulateRemoteCall(function() {
store.load(type, fixtures); store.load(type, fixtures);
}, store, type); }, store, type);
}
}, },
findMany: function(store, type, ids) { findMany: function(store, type, ids) {
var fixtures = this.fixturesForType(type); var fixtures = this.fixturesForType(type);
Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
if (fixtures) { if (fixtures) {
fixtures = fixtures.filter(function(item) { fixtures = fixtures.filter(function(item) {
return ids.indexOf(item.id) !== -1; return ids.indexOf(item.id) !== -1;
}); });
} }
Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures); if (fixtures) {
this.simulateRemoteCall(function() { this.simulateRemoteCall(function() {
store.loadMany(type, fixtures); store.loadMany(type, fixtures);
}, store, type); }, store, type);
}
}, },
findAll: function(store, type) { findAll: function(store, type) {
@ -3783,13 +3787,15 @@ DS.FixtureAdapter = DS.Adapter.extend({
findQuery: function(store, type, query, array) { findQuery: function(store, type, query, array) {
var fixtures = this.fixturesForType(type); var fixtures = this.fixturesForType(type);
fixtures = this.queryFixtures(fixtures, query);
Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures); Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
fixtures = this.queryFixtures(fixtures, query);
if (fixtures) {
this.simulateRemoteCall(function() { this.simulateRemoteCall(function() {
array.load(fixtures); array.load(fixtures);
}, store, type); }, store, type);
}
}, },
createRecord: function(store, type, record) { createRecord: function(store, type, record) {
@ -4030,7 +4036,9 @@ DS.RESTAdapter = DS.Adapter.extend({
data: query, data: query,
success: function(json) { success: function(json) {
this.sideload(store, type, json, plural); this.sideload(store, type, json, plural);
setTimeout(function() {
recordArray.load(json[plural]); recordArray.load(json[plural]);
}, 10);
} }
}); });
}, },

View File

@ -1,5 +1,5 @@
// Version: v0.9.8.1-617-g2b213df // Version: v0.9.8.1-672-gd7e24ac
// Last commit: 2b213df (2012-07-14 16:44:53 -0700) // Last commit: d7e24ac (2012-07-26 09:35:30 -0700)
(function() { (function() {
@ -142,8 +142,8 @@ window.ember_deprecateFunc = Ember.deprecateFunc("ember_deprecateFunc is deprec
})(); })();
// Version: v0.9.8.1-658-gd4a9e46 // Version: v0.9.8.1-672-gd7e24ac
// Last commit: d4a9e46 (2012-07-21 19:16:48 -0700) // Last commit: d7e24ac (2012-07-26 09:35:30 -0700)
(function() { (function() {
@ -1406,7 +1406,11 @@ set = function set(obj, keyName, value, tolerant) {
if (value !== currentValue) { if (value !== currentValue) {
Ember.propertyWillChange(obj, keyName); Ember.propertyWillChange(obj, keyName);
if (MANDATORY_SETTER) { if (MANDATORY_SETTER) {
if (currentValue === undefined && !(keyName in obj)) {
Ember.defineProperty(obj, keyName, null, value); // setup mandatory setter
} else {
meta.values[keyName] = value; meta.values[keyName] = value;
}
} else { } else {
obj[keyName] = value; obj[keyName] = value;
} }
@ -11377,6 +11381,28 @@ Ember.ControllerMixin.reopen({
set(this, outletName, view); set(this, outletName, view);
return view; return view;
},
/**
Convenience method to connect controllers. This method makes other controllers
available on the controller the method was invoked on.
For example, to make the `personController` and the `postController` available
on the `overviewController`, you would call:
overviewController.connectControllers('person', 'post');
@param {String...} controllerNames the controllers to make available
*/
connectControllers: function() {
var controllers = get(this, 'controllers'),
controllerNames = Array.prototype.slice.apply(arguments),
controllerName;
for (var i=0, l=controllerNames.length; i<l; i++) {
controllerName = controllerNames[i] + 'Controller';
set(this, controllerName, get(controllers, controllerName));
}
} }
}); });
@ -14199,6 +14225,7 @@ Ember.ContainerView = Ember.View.extend({
if (currentView) { if (currentView) {
childViews.removeObject(currentView); childViews.removeObject(currentView);
currentView.destroy();
} }
}, 'currentView'), }, 'currentView'),
@ -14823,7 +14850,14 @@ Ember.State.reopenClass(
transitionTo: function(target) { transitionTo: function(target) {
var event = function(stateManager, context) { var event = function(stateManager, context) {
if (Event && context instanceof Event) { if (Event && context instanceof Event) {
if (context.hasOwnProperty('context')) {
context = context.context; context = context.context;
} else {
// If we received an event and it doesn't contain
// a context, don't pass along a superfluous
// context to the target of the event.
return stateManager.transitionTo(target);
}
} }
stateManager.transitionTo(target, context); stateManager.transitionTo(target, context);
@ -14842,6 +14876,167 @@ Ember.State.reopenClass(
(function() { (function() {
var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt; var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
var arrayForEach = Ember.ArrayPolyfills.forEach; var arrayForEach = Ember.ArrayPolyfills.forEach;
/**
@private
A Transition takes the enter, exit and resolve states and normalizes
them:
* takes any passed in contexts into consideration
* adds in `initialState`s
*/
var Transition = function(raw) {
this.enterStates = raw.enterStates.slice();
this.exitStates = raw.exitStates.slice();
this.resolveState = raw.resolveState;
this.finalState = raw.enterStates[raw.enterStates.length - 1] || raw.resolveState;
};
Transition.prototype = {
/**
@private
Normalize the passed in enter, exit and resolve states.
This process also adds `finalState` and `contexts` to the Transition object.
@param {Ember.StateManager} manager the state manager running the transition
@param {Array} contexts a list of contexts passed into `transitionTo`
*/
normalize: function(manager, contexts) {
this.matchContextsToStates(contexts);
this.addInitialStates();
this.removeUnchangedContexts(manager);
return this;
},
/**
@private
Match each of the contexts passed to `transitionTo` to a state.
This process may also require adding additional enter and exit
states if there are more contexts than enter states.
@param {Array} contexts a list of contexts passed into `transitionTo`
*/
matchContextsToStates: function(contexts) {
var stateIdx = this.enterStates.length - 1,
matchedContexts = [],
state,
context;
// Next, we will match the passed in contexts to the states they
// represent.
//
// First, assign a context to each enter state in reverse order. If
// any contexts are left, add a parent state to the list of states
// to enter and exit, and assign a context to the parent state.
//
// If there are still contexts left when the state manager is
// reached, raise an exception.
//
// This allows the following:
//
// |- root
// | |- post
// | | |- comments
// | |- about (* current state)
//
// For `transitionTo('post.comments', post, post.get('comments')`,
// the first context (`post`) will be assigned to `root.post`, and
// the second context (`post.get('comments')`) will be assigned
// to `root.post.comments`.
//
// For the following:
//
// |- root
// | |- post
// | | |- index (* current state)
// | | |- comments
//
// For `transitionTo('post.comments', otherPost, otherPost.get('comments')`,
// the `<root.post>` state will be added to the list of enter and exit
// states because its context has changed.
while (contexts.length > 0) {
if (stateIdx >= 0) {
state = this.enterStates[stateIdx--];
} else {
if (this.enterStates.length) {
state = get(this.enterStates[0], 'parentState');
if (!state) { throw "Cannot match all contexts to states"; }
} else {
// If re-entering the current state with a context, the resolve
// state will be the current state.
state = this.resolveState;
}
this.enterStates.unshift(state);
this.exitStates.unshift(state);
}
// in routers, only states with dynamic segments have a context
if (get(state, 'hasContext')) {
context = contexts.pop();
} else {
context = null;
}
matchedContexts.unshift(context);
}
this.contexts = matchedContexts;
},
/**
@private
Add any `initialState`s to the list of enter states.
*/
addInitialStates: function() {
var finalState = this.finalState, initialState;
while(true) {
initialState = get(finalState, 'initialState') || 'start';
finalState = get(finalState, 'states.' + initialState);
if (!finalState) { break; }
this.finalState = finalState;
this.enterStates.push(finalState);
this.contexts.push(undefined);
}
},
/**
@private
Remove any states that were added because the number of contexts
exceeded the number of explicit enter states, but the context has
not changed since the last time the state was entered.
@param {Ember.StateManager} manager passed in to look up the last
context for a states
*/
removeUnchangedContexts: function(manager) {
// Start from the beginning of the enter states. If the state was added
// to the list during the context matching phase, make sure the context
// has actually changed since the last time the state was entered.
while (this.enterStates.length > 0) {
if (this.enterStates[0] !== this.exitStates[0]) { break; }
if (this.enterStates.length === this.contexts.length) {
if (manager.getStateMeta(this.enterStates[0], 'context') !== this.contexts[0]) { break; }
this.contexts.shift();
}
this.resolveState = this.enterStates.shift();
this.exitStates.shift();
}
}
};
/** /**
@class @class
@ -15351,18 +15546,29 @@ Ember.StateManager = Ember.State.extend(
return possible; return possible;
}, },
findStatesByPath: function(state, path) { /**
@private
A state stores its child states in its `states` hash.
This code takes a path like `posts.show` and looks
up `origin.states.posts.states.show`.
It returns a list of all of the states from the
origin, which is the list of states to call `enter`
on.
*/
findStatesByPath: function(origin, path) {
if (!path || path === "") { return undefined; } if (!path || path === "") { return undefined; }
var r = path.split('.'), var r = path.split('.'),
ret = []; ret = [];
for (var i=0, len = r.length; i < len; i++) { for (var i=0, len = r.length; i < len; i++) {
var states = get(state, 'states'); var states = get(origin, 'states');
if (!states) { return undefined; } if (!states) { return undefined; }
var s = get(states, r[i]); var s = get(states, r[i]);
if (s) { state = s; ret.push(s); } if (s) { origin = s; ret.push(s); }
else { return undefined; } else { return undefined; }
} }
@ -15376,45 +15582,65 @@ Ember.StateManager = Ember.State.extend(
}, },
transitionTo: function(path, context) { transitionTo: function(path, context) {
// 1. Normalize arguments // XXX When is transitionTo called with no path
// 2. Ensure that we are in the correct state
// 3. Map provided path to context objects and send
// appropriate transitionEvent events
if (Ember.empty(path)) { return; } if (Ember.empty(path)) { return; }
// The ES6 signature of this function is `path, ...contexts`
var contexts = context ? Array.prototype.slice.call(arguments, 1) : [], var contexts = context ? Array.prototype.slice.call(arguments, 1) : [],
currentState = get(this, 'currentState') || this, currentState = get(this, 'currentState') || this;
resolveState = currentState,
// First, get the enter, exit and resolve states for the current state
// and specified path. If possible, use an existing cache.
var hash = this.contextFreeTransition(currentState, path);
// Next, process the raw state information for the contexts passed in.
var transition = new Transition(hash).normalize(this, contexts);
this.enterState(transition);
this.triggerSetupContext(transition);
},
contextFreeTransition: function(currentState, path) {
var cache = currentState.pathsCache[path];
if (cache) { return cache; }
var enterStates = this.findStatesByPath(currentState, path),
exitStates = [], exitStates = [],
matchedContexts = [], resolveState = currentState;
cachedPath,
enterStates,
state,
initialState,
stateIdx,
useContext;
if (!context && (cachedPath = currentState.pathsCacheNoContext[path])) {
// fast path
exitStates = cachedPath.exitStates;
enterStates = cachedPath.enterStates;
resolveState = cachedPath.resolveState;
} else {
// normal path
if ((cachedPath = currentState.pathsCache[path])) {
// cache hit
exitStates = cachedPath.exitStates;
enterStates = cachedPath.enterStates;
resolveState = cachedPath.resolveState;
} else {
// cache miss
enterStates = this.findStatesByPath(currentState, path);
// Walk up the states. For each state, check whether a state matching
// the `path` is nested underneath. This will find the closest
// parent state containing `path`.
//
// This allows the user to pass in a relative path. For example, for
// the following state hierarchy:
//
// | |root
// | |- posts
// | | |- show (* current)
// | |- comments
// | | |- show
//
// If the current state is `<root.posts.show>`, an attempt to
// transition to `comments.show` will match `<root.comments.show>`.
//
// First, this code will look for root.posts.show.comments.show.
// Next, it will look for root.posts.comments.show. Finally,
// it will look for `root.comments.show`, and find the state.
//
// After this process, the following variables will exist:
//
// * resolveState: a common parent state between the current
// and target state. In the above example, `<root>` is the
// `resolveState`.
// * enterStates: a list of all of the states represented
// by the path from the `resolveState`. For example, for
// the path `root.comments.show`, `enterStates` would have
// `[<root.comments>, <root.comments.show>]`
// * exitStates: a list of all of the states from the
// `resolveState` to the `currentState`. In the above
// example, `exitStates` would have
// `[<root.posts>`, `<root.posts.show>]`.
while (resolveState && !enterStates) { while (resolveState && !enterStates) {
exitStates.unshift(resolveState); exitStates.unshift(resolveState);
@ -15429,69 +15655,57 @@ Ember.StateManager = Ember.State.extend(
enterStates = this.findStatesByPath(resolveState, path); enterStates = this.findStatesByPath(resolveState, path);
} }
// If the path contains some states that are parents of both the
// current state and the target state, remove them.
//
// For example, in the following hierarchy:
//
// |- root
// | |- post
// | | |- index (* current)
// | | |- show
//
// If the `path` is `root.post.show`, the three variables will
// be:
//
// * resolveState: `<state manager>`
// * enterStates: `[<root>, <root.post>, <root.post.show>]`
// * exitStates: `[<root>, <root.post>, <root.post.index>]`
//
// The goal of this code is to remove the common states, so we
// have:
//
// * resolveState: `<root.post>`
// * enterStates: `[<root.post.show>]`
// * exitStates: `[<root.post.index>]`
//
// This avoid unnecessary calls to the enter and exit transitions.
while (enterStates.length > 0 && enterStates[0] === exitStates[0]) { while (enterStates.length > 0 && enterStates[0] === exitStates[0]) {
resolveState = enterStates.shift(); resolveState = enterStates.shift();
exitStates.shift(); exitStates.shift();
} }
currentState.pathsCache[path] = { // Cache the enterStates, exitStates, and resolveState for the
// current state and the `path`.
var transitions = currentState.pathsCache[path] = {
exitStates: exitStates, exitStates: exitStates,
enterStates: enterStates, enterStates: enterStates,
resolveState: resolveState resolveState: resolveState
}; };
}
// Don't modify the cached versions return transitions;
enterStates = enterStates.slice();
exitStates = exitStates.slice();
stateIdx = enterStates.length-1;
while (contexts.length > 0) {
if (stateIdx >= 0) {
state = enterStates[stateIdx--];
} else {
state = enterStates[0] ? get(enterStates[0], 'parentState') : resolveState;
if (!state) { throw "Cannot match all contexts to states"; }
enterStates.unshift(state);
exitStates.unshift(state);
}
useContext = context && get(state, 'hasContext');
matchedContexts.unshift(useContext ? contexts.pop() : null);
}
state = enterStates[enterStates.length - 1] || resolveState;
while(true) {
initialState = get(state, 'initialState') || 'start';
state = get(state, 'states.'+initialState);
if (!state) { break; }
enterStates.push(state);
matchedContexts.push(undefined);
}
while (enterStates.length > 0) {
if (enterStates[0] !== exitStates[0]) { break; }
if (enterStates.length === matchedContexts.length) {
if (this.getStateMeta(enterStates[0], 'context') !== matchedContexts[0]) { break; }
matchedContexts.shift();
}
resolveState = enterStates.shift();
exitStates.shift();
}
}
this.enterState(exitStates, enterStates, enterStates[enterStates.length-1] || resolveState);
this.triggerSetupContext(enterStates, matchedContexts);
}, },
triggerSetupContext: function(enterStates, contexts) { triggerSetupContext: function(transitions) {
var offset = enterStates.length - contexts.length; var contexts = transitions.contexts,
offset = transitions.enterStates.length - contexts.length,
enterStates = transitions.enterStates,
transitionEvent = get(this, 'transitionEvent');
Ember.assert("More contexts provided than states", offset >= 0); Ember.assert("More contexts provided than states", offset >= 0);
arrayForEach.call(enterStates, function(state, idx) { arrayForEach.call(enterStates, function(state, idx) {
state.trigger(get(this, 'transitionEvent'), this, contexts[idx-offset]); state.trigger(transitionEvent, this, contexts[idx-offset]);
}, this); }, this);
}, },
@ -15506,21 +15720,20 @@ Ember.StateManager = Ember.State.extend(
} }
}, },
enterState: function(exitStates, enterStates, state) { enterState: function(transition) {
var log = this.enableLogging, var log = this.enableLogging;
stateManager = this;
exitStates = exitStates.slice(0).reverse(); var exitStates = transition.exitStates.slice(0).reverse();
arrayForEach.call(exitStates, function(state) { arrayForEach.call(exitStates, function(state) {
state.trigger('exit', stateManager); state.trigger('exit', this);
}); }, this);
arrayForEach.call(enterStates, function(state) { arrayForEach.call(transition.enterStates, function(state) {
if (log) { Ember.Logger.log("STATEMANAGER: Entering " + get(state, 'path')); } if (log) { Ember.Logger.log("STATEMANAGER: Entering " + get(state, 'path')); }
state.trigger('enter', stateManager); state.trigger('enter', this);
}); }, this);
set(this, 'currentState', state); set(this, 'currentState', transition.finalState);
} }
}); });
@ -16109,9 +16322,9 @@ var get = Ember.get, set = Ember.set;
## Adding Routes to a Router ## Adding Routes to a Router
The `initialState` property of Ember.Router instances is named `root`. The state stored in this The `initialState` property of Ember.Router instances is named `root`. The state stored in this
property should be a subclass of Ember.Route. The `root` route should itself have states that are property must be a subclass of Ember.Route. The `root` route acts as the container for the
also subclasses of Ember.Route and have `route` properties describing the URL pattern you would set of routable states but is not routable itself. It should have states that are also subclasses
like to detect. of Ember.Route which each have a `route` property describing the URL pattern you would like to detect.
App = Ember.Application.create({ App = Ember.Application.create({
Router: Ember.Router.extend({ Router: Ember.Router.extend({
@ -16153,6 +16366,46 @@ var get = Ember.get, set = Ember.set;
'root.bRoute' ('/alphabeta') and transition the router first to the state named 'root' and 'root.bRoute' ('/alphabeta') and transition the router first to the state named 'root' and
then to the substate 'bRoute'. then to the substate 'bRoute'.
## Adding Nested Routes to a Router
Routes can contain nested subroutes each with their own `route` property describing the nested
portion of the URL they would like to detect and handle. Router, like all instances of StateManager,
cannot call `transitonTo` with an intermediary state. To avoid transitioning the Router into an
intermediary state when detecting URLs, a Route with nested routes must define both a base `route`
property for itself and a child Route with a `route` property of `'/'` which will be transitioned
to when the base route is detected in the URL:
Given the following application code:
App = Ember.Application.create({
Router: Ember.Router.extend({
root: Ember.Route.extend({
aRoute: Ember.Route.extend({
route: '/theBaseRouteForThisSet',
indexSubRoute: Ember.Route.extend({
route: '/',
}),
subRouteOne: Ember.Route.extend({
route: '/subroute1
}),
subRouteTwo: Ember.Route.extend({
route: '/subRoute2'
})
})
})
})
});
App.initialize();
When the application is loaded at '/theBaseRouteForThisSet' the Router will transition to the route
at path 'root.aRoute' and then transition to state 'indexSubRoute'.
When the application is loaded at '/theBaseRouteForThisSet/subRoute1' the Router will transition to
the route at path 'root.aRoute' and then transition to state 'subRouteOne'.
## Route Transition Events ## Route Transition Events
Transitioning between Ember.Route instances (including the transition into the detected Transitioning between Ember.Route instances (including the transition into the detected
route when loading the application) triggers the same transition events as state transitions for route when loading the application) triggers the same transition events as state transitions for
@ -16383,7 +16636,7 @@ var get = Ember.get, set = Ember.set;
<script type="text/x-handlebars" data-template-name="photos"> <script type="text/x-handlebars" data-template-name="photos">
{{#each photo in controller}} {{#each photo in controller}}
<h1><a {{action showPhoto context="photo"}}>{{title}}</a></h1> <h1><a {{action showPhoto photo}}>{{title}}</a></h1>
{{/each}} {{/each}}
</script> </script>
@ -19050,27 +19303,39 @@ Ember.Handlebars.registerHelper('template', function(name, options) {
(function() { (function() {
var EmberHandlebars = Ember.Handlebars, getPath = EmberHandlebars.getPath, get = Ember.get; var EmberHandlebars = Ember.Handlebars,
getPath = EmberHandlebars.getPath,
get = Ember.get,
a_slice = Array.prototype.slice;
var ActionHelper = EmberHandlebars.ActionHelper = { var ActionHelper = EmberHandlebars.ActionHelper = {
registeredActions: {} registeredActions: {}
}; };
ActionHelper.registerAction = function(actionName, eventName, target, view, context, link) { ActionHelper.registerAction = function(actionName, options) {
var actionId = (++Ember.$.uuid).toString(); var actionId = (++Ember.$.uuid).toString();
ActionHelper.registeredActions[actionId] = { ActionHelper.registeredActions[actionId] = {
eventName: eventName, eventName: options.eventName,
handler: function(event) { handler: function(event) {
if (link && (event.button !== 0 || event.shiftKey || event.metaKey || event.altKey || event.ctrlKey)) { var modifier = event.shiftKey || event.metaKey || event.altKey || event.ctrlKey,
secondaryClick = event.which > 1, // IE9 may return undefined
nonStandard = modifier || secondaryClick;
if (options.link && nonStandard) {
// Allow the browser to handle special link clicks normally // Allow the browser to handle special link clicks normally
return; return;
} }
event.preventDefault(); event.preventDefault();
event.view = view; event.view = options.view;
event.context = context;
if (options.hasOwnProperty('context')) {
event.context = options.context;
}
var target = options.target;
// Check for StateManager (or compatible object) // Check for StateManager (or compatible object)
if (target.isState && typeof target.send === 'function') { if (target.isState && typeof target.send === 'function') {
@ -19082,7 +19347,7 @@ ActionHelper.registerAction = function(actionName, eventName, target, view, cont
} }
}; };
view.on('willRerender', function() { options.view.on('willRerender', function() {
delete ActionHelper.registeredActions[actionId]; delete ActionHelper.registeredActions[actionId];
}); });
@ -19091,14 +19356,16 @@ ActionHelper.registerAction = function(actionName, eventName, target, view, cont
/** /**
The `{{action}}` helper registers an HTML element within a template for The `{{action}}` helper registers an HTML element within a template for
DOM event handling. User interaction with that element will call the method DOM event handling and forwards that interaction to the Application's router,
on the template's associated `Ember.View` instance that has the same name the template's `Ember.View` instance, or supplied `target` option (see 'Specifiying a Target').
as the first provided argument to `{{action}}`:
User interaction with that element will invoke the supplied action name on
the appropriate target.
Given the following Handlebars template on the page Given the following Handlebars template on the page
<script type="text/x-handlebars" data-template-name='a-template'> <script type="text/x-handlebars" data-template-name='a-template'>
<div {{action "anActionName"}}> <div {{action anActionName}}>
click me click me
</div> </div>
</script> </script>
@ -19134,15 +19401,51 @@ ActionHelper.registerAction = function(actionName, eventName, target, view, cont
handler. handler.
If you need the default handler to trigger you should either register your If you need the default handler to trigger you should either register your
own event handler, or use event methods on your view class. own event handler, or use event methods on your view class. See Ember.View
'Responding to Browser Events' for more information.
### Specifying an Action Target ### Specifying DOM event type
A `target` option can be provided to change which object will receive the By default the `{{action}}` helper registers for DOM `click` events. You can
method call. This option must be a string representing a path to an object: supply an `on` option to the helper to specify a different DOM event name:
<script type="text/x-handlebars" data-template-name='a-template'> <script type="text/x-handlebars" data-template-name='a-template'>
<div {{action "anActionName" target="MyApplication.someObject"}}> <div {{action anActionName on="doubleClick"}}>
click me
</div>
</script>
See Ember.View 'Responding to Browser Events' for a list of
acceptable DOM event names.
Because `{{action}}` depends on Ember's event dispatch system it will only
function if an `Ember.EventDispatcher` instance is available. An
`Ember.EventDispatcher` instance will be created when a new
`Ember.Application` is created. Having an instance of `Ember.Application`
will satisfy this requirement.
### Specifying a Target
There are several possible target objects for `{{action}}` helpers:
In a typical `Ember.Router`-backed Application where views are managed
through use of the `{{outlet}}` helper, actions will be forwarded to the
current state of the Applications's Router. See Ember.Router 'Responding
to User-initiated Events' for more information.
If you manaully set the `target` property on the controller of a template's
`Ember.View` instance, the specifed `controller.target` will become the target
for any actions. Likely custom values for a controller's `target` are the
controller itself or a StateManager other than the Application's Router.
If the templates's view lacks a controller property the view itself is the target.
Finally, a `target` option can be provided to the helper to change which object
will receive the method call. This option must be a string representing a
path to an object:
<script type="text/x-handlebars" data-template-name='a-template'>
<div {{action anActionName target="MyApplication.someObject"}}>
click me click me
</div> </div>
</script> </script>
@ -19156,7 +19459,7 @@ ActionHelper.registerAction = function(actionName, eventName, target, view, cont
a target: a target:
<script type="text/x-handlebars" data-template-name='a-template'> <script type="text/x-handlebars" data-template-name='a-template'>
<div {{action "anActionName" target="parentView"}}> <div {{action anActionName target="parentView"}}>
click me click me
</div> </div>
</script> </script>
@ -19173,7 +19476,7 @@ ActionHelper.registerAction = function(actionName, eventName, target, view, cont
action name an error will be thrown. action name an error will be thrown.
<script type="text/x-handlebars" data-template-name='a-template'> <script type="text/x-handlebars" data-template-name='a-template'>
<div {{action "aMethodNameThatIsMissing"}}> <div {{action aMethodNameThatIsMissing}}>
click me click me
</div> </div>
</script> </script>
@ -19192,34 +19495,15 @@ ActionHelper.registerAction = function(actionName, eventName, target, view, cont
Will throw `Uncaught TypeError: Cannot call method 'call' of undefined` when Will throw `Uncaught TypeError: Cannot call method 'call' of undefined` when
"click me" is clicked. "click me" is clicked.
### Specifying DOM event type
By default the `{{action}}` helper registers for DOM `click` events. You can
supply an `on` option to the helper to specify a different DOM event name:
<script type="text/x-handlebars" data-template-name='a-template'>
<div {{action "aMethodNameThatIsMissing" on="doubleClick"}}>
click me
</div>
</script>
See `Ember.EventDispatcher` for a list of acceptable DOM event names.
Because `{{action}}` depends on Ember's event dispatch system it will only
function if an `Ember.EventDispatcher` instance is available. An
`Ember.EventDispatcher` instance will be created when a new
`Ember.Application` is created. Having an instance of `Ember.Application`
will satisfy this requirement.
### Specifying a context ### Specifying a context
By default the `{{action}}` helper passes the current Handlebars context By default the `{{action}}` helper passes the current Handlebars context
along in the `jQuery.Event` object. You may specify an alternative object to along in the `jQuery.Event` object. You may specify an alternate object to
pass as the context by providing a property path: pass as the context by providing a property path:
<script type="text/x-handlebars" data-template-name='a-template'> <script type="text/x-handlebars" data-template-name='a-template'>
{{#each person in people}} {{#each person in people}}
<div {{action "edit" context="person"}}> <div {{action edit person}}>
click me click me
</div> </div>
{{/each}} {{/each}}
@ -19227,15 +19511,23 @@ ActionHelper.registerAction = function(actionName, eventName, target, view, cont
@name Handlebars.helpers.action @name Handlebars.helpers.action
@param {String} actionName @param {String} actionName
@param {Object...} contexts
@param {Hash} options @param {Hash} options
*/ */
EmberHandlebars.registerHelper('action', function(actionName, options) { EmberHandlebars.registerHelper('action', function(actionName) {
var options = arguments[arguments.length - 1],
contexts = a_slice.call(arguments, 1, -1);
var hash = options.hash, var hash = options.hash,
eventName = hash.on || "click",
view = options.data.view, view = options.data.view,
target, context, controller, link; target, context, controller, link;
view = get(view, 'concreteView'); // create a hash to pass along to registerAction
var action = {
eventName: hash.on || "click"
};
action.view = view = get(view, 'concreteView');
if (hash.target) { if (hash.target) {
target = getPath(this, hash.target, options); target = getPath(this, hash.target, options);
@ -19243,19 +19535,22 @@ EmberHandlebars.registerHelper('action', function(actionName, options) {
target = get(controller, 'target'); target = get(controller, 'target');
} }
target = target || view; action.target = target = target || view;
context = hash.context ? getPath(this, hash.context, options) : options.contexts[0]; // TODO: Support multiple contexts
if (contexts.length) {
action.context = context = getPath(this, contexts[0], options);
}
var output = [], url; var output = [], url;
if (hash.href && target.urlForEvent) { if (hash.href && target.urlForEvent) {
url = target.urlForEvent(actionName, context); url = target.urlForEvent(actionName, context);
output.push('href="' + url + '"'); output.push('href="' + url + '"');
link = true; action.link = true;
} }
var actionId = ActionHelper.registerAction(actionName, eventName, target, view, context, link); var actionId = ActionHelper.registerAction(actionName, action);
output.push('data-ember-action="' + actionId + '"'); output.push('data-ember-action="' + actionId + '"');
return new EmberHandlebars.SafeString(output.join(" ")); return new EmberHandlebars.SafeString(output.join(" "));
@ -20275,8 +20570,8 @@ Ember.onLoad('application', bootstrap);
})(); })();
// Version: v0.9.8.1-658-gd4a9e46 // Version: v0.9.8.1-672-gd7e24ac
// Last commit: d4a9e46 (2012-07-21 19:16:48 -0700) // Last commit: d7e24ac (2012-07-26 09:35:30 -0700)
(function() { (function() {

View File

@ -1952,8 +1952,8 @@ Handlebars.VM = {
Handlebars.template = Handlebars.VM.template; Handlebars.template = Handlebars.VM.template;
; ;
// Version: v0.9.8.1-617-g2b213df // Version: v0.9.8.1-672-gd7e24ac
// Last commit: 2b213df (2012-07-14 16:44:53 -0700) // Last commit: d7e24ac (2012-07-26 09:35:30 -0700)
(function() { (function() {
@ -2096,8 +2096,8 @@ window.ember_deprecateFunc = Ember.deprecateFunc("ember_deprecateFunc is deprec
})(); })();
// Version: v0.9.8.1-658-gd4a9e46 // Version: v0.9.8.1-672-gd7e24ac
// Last commit: d4a9e46 (2012-07-21 19:16:48 -0700) // Last commit: d7e24ac (2012-07-26 09:35:30 -0700)
(function() { (function() {
@ -3360,7 +3360,11 @@ set = function set(obj, keyName, value, tolerant) {
if (value !== currentValue) { if (value !== currentValue) {
Ember.propertyWillChange(obj, keyName); Ember.propertyWillChange(obj, keyName);
if (MANDATORY_SETTER) { if (MANDATORY_SETTER) {
if (currentValue === undefined && !(keyName in obj)) {
Ember.defineProperty(obj, keyName, null, value); // setup mandatory setter
} else {
meta.values[keyName] = value; meta.values[keyName] = value;
}
} else { } else {
obj[keyName] = value; obj[keyName] = value;
} }
@ -13331,6 +13335,28 @@ Ember.ControllerMixin.reopen({
set(this, outletName, view); set(this, outletName, view);
return view; return view;
},
/**
Convenience method to connect controllers. This method makes other controllers
available on the controller the method was invoked on.
For example, to make the `personController` and the `postController` available
on the `overviewController`, you would call:
overviewController.connectControllers('person', 'post');
@param {String...} controllerNames the controllers to make available
*/
connectControllers: function() {
var controllers = get(this, 'controllers'),
controllerNames = Array.prototype.slice.apply(arguments),
controllerName;
for (var i=0, l=controllerNames.length; i<l; i++) {
controllerName = controllerNames[i] + 'Controller';
set(this, controllerName, get(controllers, controllerName));
}
} }
}); });
@ -16153,6 +16179,7 @@ Ember.ContainerView = Ember.View.extend({
if (currentView) { if (currentView) {
childViews.removeObject(currentView); childViews.removeObject(currentView);
currentView.destroy();
} }
}, 'currentView'), }, 'currentView'),
@ -16777,7 +16804,14 @@ Ember.State.reopenClass(
transitionTo: function(target) { transitionTo: function(target) {
var event = function(stateManager, context) { var event = function(stateManager, context) {
if (Event && context instanceof Event) { if (Event && context instanceof Event) {
if (context.hasOwnProperty('context')) {
context = context.context; context = context.context;
} else {
// If we received an event and it doesn't contain
// a context, don't pass along a superfluous
// context to the target of the event.
return stateManager.transitionTo(target);
}
} }
stateManager.transitionTo(target, context); stateManager.transitionTo(target, context);
@ -16796,6 +16830,167 @@ Ember.State.reopenClass(
(function() { (function() {
var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt; var get = Ember.get, set = Ember.set, fmt = Ember.String.fmt;
var arrayForEach = Ember.ArrayPolyfills.forEach; var arrayForEach = Ember.ArrayPolyfills.forEach;
/**
@private
A Transition takes the enter, exit and resolve states and normalizes
them:
* takes any passed in contexts into consideration
* adds in `initialState`s
*/
var Transition = function(raw) {
this.enterStates = raw.enterStates.slice();
this.exitStates = raw.exitStates.slice();
this.resolveState = raw.resolveState;
this.finalState = raw.enterStates[raw.enterStates.length - 1] || raw.resolveState;
};
Transition.prototype = {
/**
@private
Normalize the passed in enter, exit and resolve states.
This process also adds `finalState` and `contexts` to the Transition object.
@param {Ember.StateManager} manager the state manager running the transition
@param {Array} contexts a list of contexts passed into `transitionTo`
*/
normalize: function(manager, contexts) {
this.matchContextsToStates(contexts);
this.addInitialStates();
this.removeUnchangedContexts(manager);
return this;
},
/**
@private
Match each of the contexts passed to `transitionTo` to a state.
This process may also require adding additional enter and exit
states if there are more contexts than enter states.
@param {Array} contexts a list of contexts passed into `transitionTo`
*/
matchContextsToStates: function(contexts) {
var stateIdx = this.enterStates.length - 1,
matchedContexts = [],
state,
context;
// Next, we will match the passed in contexts to the states they
// represent.
//
// First, assign a context to each enter state in reverse order. If
// any contexts are left, add a parent state to the list of states
// to enter and exit, and assign a context to the parent state.
//
// If there are still contexts left when the state manager is
// reached, raise an exception.
//
// This allows the following:
//
// |- root
// | |- post
// | | |- comments
// | |- about (* current state)
//
// For `transitionTo('post.comments', post, post.get('comments')`,
// the first context (`post`) will be assigned to `root.post`, and
// the second context (`post.get('comments')`) will be assigned
// to `root.post.comments`.
//
// For the following:
//
// |- root
// | |- post
// | | |- index (* current state)
// | | |- comments
//
// For `transitionTo('post.comments', otherPost, otherPost.get('comments')`,
// the `<root.post>` state will be added to the list of enter and exit
// states because its context has changed.
while (contexts.length > 0) {
if (stateIdx >= 0) {
state = this.enterStates[stateIdx--];
} else {
if (this.enterStates.length) {
state = get(this.enterStates[0], 'parentState');
if (!state) { throw "Cannot match all contexts to states"; }
} else {
// If re-entering the current state with a context, the resolve
// state will be the current state.
state = this.resolveState;
}
this.enterStates.unshift(state);
this.exitStates.unshift(state);
}
// in routers, only states with dynamic segments have a context
if (get(state, 'hasContext')) {
context = contexts.pop();
} else {
context = null;
}
matchedContexts.unshift(context);
}
this.contexts = matchedContexts;
},
/**
@private
Add any `initialState`s to the list of enter states.
*/
addInitialStates: function() {
var finalState = this.finalState, initialState;
while(true) {
initialState = get(finalState, 'initialState') || 'start';
finalState = get(finalState, 'states.' + initialState);
if (!finalState) { break; }
this.finalState = finalState;
this.enterStates.push(finalState);
this.contexts.push(undefined);
}
},
/**
@private
Remove any states that were added because the number of contexts
exceeded the number of explicit enter states, but the context has
not changed since the last time the state was entered.
@param {Ember.StateManager} manager passed in to look up the last
context for a states
*/
removeUnchangedContexts: function(manager) {
// Start from the beginning of the enter states. If the state was added
// to the list during the context matching phase, make sure the context
// has actually changed since the last time the state was entered.
while (this.enterStates.length > 0) {
if (this.enterStates[0] !== this.exitStates[0]) { break; }
if (this.enterStates.length === this.contexts.length) {
if (manager.getStateMeta(this.enterStates[0], 'context') !== this.contexts[0]) { break; }
this.contexts.shift();
}
this.resolveState = this.enterStates.shift();
this.exitStates.shift();
}
}
};
/** /**
@class @class
@ -17305,18 +17500,29 @@ Ember.StateManager = Ember.State.extend(
return possible; return possible;
}, },
findStatesByPath: function(state, path) { /**
@private
A state stores its child states in its `states` hash.
This code takes a path like `posts.show` and looks
up `origin.states.posts.states.show`.
It returns a list of all of the states from the
origin, which is the list of states to call `enter`
on.
*/
findStatesByPath: function(origin, path) {
if (!path || path === "") { return undefined; } if (!path || path === "") { return undefined; }
var r = path.split('.'), var r = path.split('.'),
ret = []; ret = [];
for (var i=0, len = r.length; i < len; i++) { for (var i=0, len = r.length; i < len; i++) {
var states = get(state, 'states'); var states = get(origin, 'states');
if (!states) { return undefined; } if (!states) { return undefined; }
var s = get(states, r[i]); var s = get(states, r[i]);
if (s) { state = s; ret.push(s); } if (s) { origin = s; ret.push(s); }
else { return undefined; } else { return undefined; }
} }
@ -17330,45 +17536,65 @@ Ember.StateManager = Ember.State.extend(
}, },
transitionTo: function(path, context) { transitionTo: function(path, context) {
// 1. Normalize arguments // XXX When is transitionTo called with no path
// 2. Ensure that we are in the correct state
// 3. Map provided path to context objects and send
// appropriate transitionEvent events
if (Ember.empty(path)) { return; } if (Ember.empty(path)) { return; }
// The ES6 signature of this function is `path, ...contexts`
var contexts = context ? Array.prototype.slice.call(arguments, 1) : [], var contexts = context ? Array.prototype.slice.call(arguments, 1) : [],
currentState = get(this, 'currentState') || this, currentState = get(this, 'currentState') || this;
resolveState = currentState,
// First, get the enter, exit and resolve states for the current state
// and specified path. If possible, use an existing cache.
var hash = this.contextFreeTransition(currentState, path);
// Next, process the raw state information for the contexts passed in.
var transition = new Transition(hash).normalize(this, contexts);
this.enterState(transition);
this.triggerSetupContext(transition);
},
contextFreeTransition: function(currentState, path) {
var cache = currentState.pathsCache[path];
if (cache) { return cache; }
var enterStates = this.findStatesByPath(currentState, path),
exitStates = [], exitStates = [],
matchedContexts = [], resolveState = currentState;
cachedPath,
enterStates,
state,
initialState,
stateIdx,
useContext;
if (!context && (cachedPath = currentState.pathsCacheNoContext[path])) {
// fast path
exitStates = cachedPath.exitStates;
enterStates = cachedPath.enterStates;
resolveState = cachedPath.resolveState;
} else {
// normal path
if ((cachedPath = currentState.pathsCache[path])) {
// cache hit
exitStates = cachedPath.exitStates;
enterStates = cachedPath.enterStates;
resolveState = cachedPath.resolveState;
} else {
// cache miss
enterStates = this.findStatesByPath(currentState, path);
// Walk up the states. For each state, check whether a state matching
// the `path` is nested underneath. This will find the closest
// parent state containing `path`.
//
// This allows the user to pass in a relative path. For example, for
// the following state hierarchy:
//
// | |root
// | |- posts
// | | |- show (* current)
// | |- comments
// | | |- show
//
// If the current state is `<root.posts.show>`, an attempt to
// transition to `comments.show` will match `<root.comments.show>`.
//
// First, this code will look for root.posts.show.comments.show.
// Next, it will look for root.posts.comments.show. Finally,
// it will look for `root.comments.show`, and find the state.
//
// After this process, the following variables will exist:
//
// * resolveState: a common parent state between the current
// and target state. In the above example, `<root>` is the
// `resolveState`.
// * enterStates: a list of all of the states represented
// by the path from the `resolveState`. For example, for
// the path `root.comments.show`, `enterStates` would have
// `[<root.comments>, <root.comments.show>]`
// * exitStates: a list of all of the states from the
// `resolveState` to the `currentState`. In the above
// example, `exitStates` would have
// `[<root.posts>`, `<root.posts.show>]`.
while (resolveState && !enterStates) { while (resolveState && !enterStates) {
exitStates.unshift(resolveState); exitStates.unshift(resolveState);
@ -17383,69 +17609,57 @@ Ember.StateManager = Ember.State.extend(
enterStates = this.findStatesByPath(resolveState, path); enterStates = this.findStatesByPath(resolveState, path);
} }
// If the path contains some states that are parents of both the
// current state and the target state, remove them.
//
// For example, in the following hierarchy:
//
// |- root
// | |- post
// | | |- index (* current)
// | | |- show
//
// If the `path` is `root.post.show`, the three variables will
// be:
//
// * resolveState: `<state manager>`
// * enterStates: `[<root>, <root.post>, <root.post.show>]`
// * exitStates: `[<root>, <root.post>, <root.post.index>]`
//
// The goal of this code is to remove the common states, so we
// have:
//
// * resolveState: `<root.post>`
// * enterStates: `[<root.post.show>]`
// * exitStates: `[<root.post.index>]`
//
// This avoid unnecessary calls to the enter and exit transitions.
while (enterStates.length > 0 && enterStates[0] === exitStates[0]) { while (enterStates.length > 0 && enterStates[0] === exitStates[0]) {
resolveState = enterStates.shift(); resolveState = enterStates.shift();
exitStates.shift(); exitStates.shift();
} }
currentState.pathsCache[path] = { // Cache the enterStates, exitStates, and resolveState for the
// current state and the `path`.
var transitions = currentState.pathsCache[path] = {
exitStates: exitStates, exitStates: exitStates,
enterStates: enterStates, enterStates: enterStates,
resolveState: resolveState resolveState: resolveState
}; };
}
// Don't modify the cached versions return transitions;
enterStates = enterStates.slice();
exitStates = exitStates.slice();
stateIdx = enterStates.length-1;
while (contexts.length > 0) {
if (stateIdx >= 0) {
state = enterStates[stateIdx--];
} else {
state = enterStates[0] ? get(enterStates[0], 'parentState') : resolveState;
if (!state) { throw "Cannot match all contexts to states"; }
enterStates.unshift(state);
exitStates.unshift(state);
}
useContext = context && get(state, 'hasContext');
matchedContexts.unshift(useContext ? contexts.pop() : null);
}
state = enterStates[enterStates.length - 1] || resolveState;
while(true) {
initialState = get(state, 'initialState') || 'start';
state = get(state, 'states.'+initialState);
if (!state) { break; }
enterStates.push(state);
matchedContexts.push(undefined);
}
while (enterStates.length > 0) {
if (enterStates[0] !== exitStates[0]) { break; }
if (enterStates.length === matchedContexts.length) {
if (this.getStateMeta(enterStates[0], 'context') !== matchedContexts[0]) { break; }
matchedContexts.shift();
}
resolveState = enterStates.shift();
exitStates.shift();
}
}
this.enterState(exitStates, enterStates, enterStates[enterStates.length-1] || resolveState);
this.triggerSetupContext(enterStates, matchedContexts);
}, },
triggerSetupContext: function(enterStates, contexts) { triggerSetupContext: function(transitions) {
var offset = enterStates.length - contexts.length; var contexts = transitions.contexts,
offset = transitions.enterStates.length - contexts.length,
enterStates = transitions.enterStates,
transitionEvent = get(this, 'transitionEvent');
Ember.assert("More contexts provided than states", offset >= 0); Ember.assert("More contexts provided than states", offset >= 0);
arrayForEach.call(enterStates, function(state, idx) { arrayForEach.call(enterStates, function(state, idx) {
state.trigger(get(this, 'transitionEvent'), this, contexts[idx-offset]); state.trigger(transitionEvent, this, contexts[idx-offset]);
}, this); }, this);
}, },
@ -17460,21 +17674,20 @@ Ember.StateManager = Ember.State.extend(
} }
}, },
enterState: function(exitStates, enterStates, state) { enterState: function(transition) {
var log = this.enableLogging, var log = this.enableLogging;
stateManager = this;
exitStates = exitStates.slice(0).reverse(); var exitStates = transition.exitStates.slice(0).reverse();
arrayForEach.call(exitStates, function(state) { arrayForEach.call(exitStates, function(state) {
state.trigger('exit', stateManager); state.trigger('exit', this);
}); }, this);
arrayForEach.call(enterStates, function(state) { arrayForEach.call(transition.enterStates, function(state) {
if (log) { Ember.Logger.log("STATEMANAGER: Entering " + get(state, 'path')); } if (log) { Ember.Logger.log("STATEMANAGER: Entering " + get(state, 'path')); }
state.trigger('enter', stateManager); state.trigger('enter', this);
}); }, this);
set(this, 'currentState', state); set(this, 'currentState', transition.finalState);
} }
}); });
@ -18063,9 +18276,9 @@ var get = Ember.get, set = Ember.set;
## Adding Routes to a Router ## Adding Routes to a Router
The `initialState` property of Ember.Router instances is named `root`. The state stored in this The `initialState` property of Ember.Router instances is named `root`. The state stored in this
property should be a subclass of Ember.Route. The `root` route should itself have states that are property must be a subclass of Ember.Route. The `root` route acts as the container for the
also subclasses of Ember.Route and have `route` properties describing the URL pattern you would set of routable states but is not routable itself. It should have states that are also subclasses
like to detect. of Ember.Route which each have a `route` property describing the URL pattern you would like to detect.
App = Ember.Application.create({ App = Ember.Application.create({
Router: Ember.Router.extend({ Router: Ember.Router.extend({
@ -18107,6 +18320,46 @@ var get = Ember.get, set = Ember.set;
'root.bRoute' ('/alphabeta') and transition the router first to the state named 'root' and 'root.bRoute' ('/alphabeta') and transition the router first to the state named 'root' and
then to the substate 'bRoute'. then to the substate 'bRoute'.
## Adding Nested Routes to a Router
Routes can contain nested subroutes each with their own `route` property describing the nested
portion of the URL they would like to detect and handle. Router, like all instances of StateManager,
cannot call `transitonTo` with an intermediary state. To avoid transitioning the Router into an
intermediary state when detecting URLs, a Route with nested routes must define both a base `route`
property for itself and a child Route with a `route` property of `'/'` which will be transitioned
to when the base route is detected in the URL:
Given the following application code:
App = Ember.Application.create({
Router: Ember.Router.extend({
root: Ember.Route.extend({
aRoute: Ember.Route.extend({
route: '/theBaseRouteForThisSet',
indexSubRoute: Ember.Route.extend({
route: '/',
}),
subRouteOne: Ember.Route.extend({
route: '/subroute1
}),
subRouteTwo: Ember.Route.extend({
route: '/subRoute2'
})
})
})
})
});
App.initialize();
When the application is loaded at '/theBaseRouteForThisSet' the Router will transition to the route
at path 'root.aRoute' and then transition to state 'indexSubRoute'.
When the application is loaded at '/theBaseRouteForThisSet/subRoute1' the Router will transition to
the route at path 'root.aRoute' and then transition to state 'subRouteOne'.
## Route Transition Events ## Route Transition Events
Transitioning between Ember.Route instances (including the transition into the detected Transitioning between Ember.Route instances (including the transition into the detected
route when loading the application) triggers the same transition events as state transitions for route when loading the application) triggers the same transition events as state transitions for
@ -18337,7 +18590,7 @@ var get = Ember.get, set = Ember.set;
<script type="text/x-handlebars" data-template-name="photos"> <script type="text/x-handlebars" data-template-name="photos">
{{#each photo in controller}} {{#each photo in controller}}
<h1><a {{action showPhoto context="photo"}}>{{title}}</a></h1> <h1><a {{action showPhoto photo}}>{{title}}</a></h1>
{{/each}} {{/each}}
</script> </script>
@ -21004,27 +21257,39 @@ Ember.Handlebars.registerHelper('template', function(name, options) {
(function() { (function() {
var EmberHandlebars = Ember.Handlebars, getPath = EmberHandlebars.getPath, get = Ember.get; var EmberHandlebars = Ember.Handlebars,
getPath = EmberHandlebars.getPath,
get = Ember.get,
a_slice = Array.prototype.slice;
var ActionHelper = EmberHandlebars.ActionHelper = { var ActionHelper = EmberHandlebars.ActionHelper = {
registeredActions: {} registeredActions: {}
}; };
ActionHelper.registerAction = function(actionName, eventName, target, view, context, link) { ActionHelper.registerAction = function(actionName, options) {
var actionId = (++Ember.$.uuid).toString(); var actionId = (++Ember.$.uuid).toString();
ActionHelper.registeredActions[actionId] = { ActionHelper.registeredActions[actionId] = {
eventName: eventName, eventName: options.eventName,
handler: function(event) { handler: function(event) {
if (link && (event.button !== 0 || event.shiftKey || event.metaKey || event.altKey || event.ctrlKey)) { var modifier = event.shiftKey || event.metaKey || event.altKey || event.ctrlKey,
secondaryClick = event.which > 1, // IE9 may return undefined
nonStandard = modifier || secondaryClick;
if (options.link && nonStandard) {
// Allow the browser to handle special link clicks normally // Allow the browser to handle special link clicks normally
return; return;
} }
event.preventDefault(); event.preventDefault();
event.view = view; event.view = options.view;
event.context = context;
if (options.hasOwnProperty('context')) {
event.context = options.context;
}
var target = options.target;
// Check for StateManager (or compatible object) // Check for StateManager (or compatible object)
if (target.isState && typeof target.send === 'function') { if (target.isState && typeof target.send === 'function') {
@ -21036,7 +21301,7 @@ ActionHelper.registerAction = function(actionName, eventName, target, view, cont
} }
}; };
view.on('willRerender', function() { options.view.on('willRerender', function() {
delete ActionHelper.registeredActions[actionId]; delete ActionHelper.registeredActions[actionId];
}); });
@ -21045,14 +21310,16 @@ ActionHelper.registerAction = function(actionName, eventName, target, view, cont
/** /**
The `{{action}}` helper registers an HTML element within a template for The `{{action}}` helper registers an HTML element within a template for
DOM event handling. User interaction with that element will call the method DOM event handling and forwards that interaction to the Application's router,
on the template's associated `Ember.View` instance that has the same name the template's `Ember.View` instance, or supplied `target` option (see 'Specifiying a Target').
as the first provided argument to `{{action}}`:
User interaction with that element will invoke the supplied action name on
the appropriate target.
Given the following Handlebars template on the page Given the following Handlebars template on the page
<script type="text/x-handlebars" data-template-name='a-template'> <script type="text/x-handlebars" data-template-name='a-template'>
<div {{action "anActionName"}}> <div {{action anActionName}}>
click me click me
</div> </div>
</script> </script>
@ -21088,15 +21355,51 @@ ActionHelper.registerAction = function(actionName, eventName, target, view, cont
handler. handler.
If you need the default handler to trigger you should either register your If you need the default handler to trigger you should either register your
own event handler, or use event methods on your view class. own event handler, or use event methods on your view class. See Ember.View
'Responding to Browser Events' for more information.
### Specifying an Action Target ### Specifying DOM event type
A `target` option can be provided to change which object will receive the By default the `{{action}}` helper registers for DOM `click` events. You can
method call. This option must be a string representing a path to an object: supply an `on` option to the helper to specify a different DOM event name:
<script type="text/x-handlebars" data-template-name='a-template'> <script type="text/x-handlebars" data-template-name='a-template'>
<div {{action "anActionName" target="MyApplication.someObject"}}> <div {{action anActionName on="doubleClick"}}>
click me
</div>
</script>
See Ember.View 'Responding to Browser Events' for a list of
acceptable DOM event names.
Because `{{action}}` depends on Ember's event dispatch system it will only
function if an `Ember.EventDispatcher` instance is available. An
`Ember.EventDispatcher` instance will be created when a new
`Ember.Application` is created. Having an instance of `Ember.Application`
will satisfy this requirement.
### Specifying a Target
There are several possible target objects for `{{action}}` helpers:
In a typical `Ember.Router`-backed Application where views are managed
through use of the `{{outlet}}` helper, actions will be forwarded to the
current state of the Applications's Router. See Ember.Router 'Responding
to User-initiated Events' for more information.
If you manaully set the `target` property on the controller of a template's
`Ember.View` instance, the specifed `controller.target` will become the target
for any actions. Likely custom values for a controller's `target` are the
controller itself or a StateManager other than the Application's Router.
If the templates's view lacks a controller property the view itself is the target.
Finally, a `target` option can be provided to the helper to change which object
will receive the method call. This option must be a string representing a
path to an object:
<script type="text/x-handlebars" data-template-name='a-template'>
<div {{action anActionName target="MyApplication.someObject"}}>
click me click me
</div> </div>
</script> </script>
@ -21110,7 +21413,7 @@ ActionHelper.registerAction = function(actionName, eventName, target, view, cont
a target: a target:
<script type="text/x-handlebars" data-template-name='a-template'> <script type="text/x-handlebars" data-template-name='a-template'>
<div {{action "anActionName" target="parentView"}}> <div {{action anActionName target="parentView"}}>
click me click me
</div> </div>
</script> </script>
@ -21127,7 +21430,7 @@ ActionHelper.registerAction = function(actionName, eventName, target, view, cont
action name an error will be thrown. action name an error will be thrown.
<script type="text/x-handlebars" data-template-name='a-template'> <script type="text/x-handlebars" data-template-name='a-template'>
<div {{action "aMethodNameThatIsMissing"}}> <div {{action aMethodNameThatIsMissing}}>
click me click me
</div> </div>
</script> </script>
@ -21146,34 +21449,15 @@ ActionHelper.registerAction = function(actionName, eventName, target, view, cont
Will throw `Uncaught TypeError: Cannot call method 'call' of undefined` when Will throw `Uncaught TypeError: Cannot call method 'call' of undefined` when
"click me" is clicked. "click me" is clicked.
### Specifying DOM event type
By default the `{{action}}` helper registers for DOM `click` events. You can
supply an `on` option to the helper to specify a different DOM event name:
<script type="text/x-handlebars" data-template-name='a-template'>
<div {{action "aMethodNameThatIsMissing" on="doubleClick"}}>
click me
</div>
</script>
See `Ember.EventDispatcher` for a list of acceptable DOM event names.
Because `{{action}}` depends on Ember's event dispatch system it will only
function if an `Ember.EventDispatcher` instance is available. An
`Ember.EventDispatcher` instance will be created when a new
`Ember.Application` is created. Having an instance of `Ember.Application`
will satisfy this requirement.
### Specifying a context ### Specifying a context
By default the `{{action}}` helper passes the current Handlebars context By default the `{{action}}` helper passes the current Handlebars context
along in the `jQuery.Event` object. You may specify an alternative object to along in the `jQuery.Event` object. You may specify an alternate object to
pass as the context by providing a property path: pass as the context by providing a property path:
<script type="text/x-handlebars" data-template-name='a-template'> <script type="text/x-handlebars" data-template-name='a-template'>
{{#each person in people}} {{#each person in people}}
<div {{action "edit" context="person"}}> <div {{action edit person}}>
click me click me
</div> </div>
{{/each}} {{/each}}
@ -21181,15 +21465,23 @@ ActionHelper.registerAction = function(actionName, eventName, target, view, cont
@name Handlebars.helpers.action @name Handlebars.helpers.action
@param {String} actionName @param {String} actionName
@param {Object...} contexts
@param {Hash} options @param {Hash} options
*/ */
EmberHandlebars.registerHelper('action', function(actionName, options) { EmberHandlebars.registerHelper('action', function(actionName) {
var options = arguments[arguments.length - 1],
contexts = a_slice.call(arguments, 1, -1);
var hash = options.hash, var hash = options.hash,
eventName = hash.on || "click",
view = options.data.view, view = options.data.view,
target, context, controller, link; target, context, controller, link;
view = get(view, 'concreteView'); // create a hash to pass along to registerAction
var action = {
eventName: hash.on || "click"
};
action.view = view = get(view, 'concreteView');
if (hash.target) { if (hash.target) {
target = getPath(this, hash.target, options); target = getPath(this, hash.target, options);
@ -21197,19 +21489,22 @@ EmberHandlebars.registerHelper('action', function(actionName, options) {
target = get(controller, 'target'); target = get(controller, 'target');
} }
target = target || view; action.target = target = target || view;
context = hash.context ? getPath(this, hash.context, options) : options.contexts[0]; // TODO: Support multiple contexts
if (contexts.length) {
action.context = context = getPath(this, contexts[0], options);
}
var output = [], url; var output = [], url;
if (hash.href && target.urlForEvent) { if (hash.href && target.urlForEvent) {
url = target.urlForEvent(actionName, context); url = target.urlForEvent(actionName, context);
output.push('href="' + url + '"'); output.push('href="' + url + '"');
link = true; action.link = true;
} }
var actionId = ActionHelper.registerAction(actionName, eventName, target, view, context, link); var actionId = ActionHelper.registerAction(actionName, action);
output.push('data-ember-action="' + actionId + '"'); output.push('data-ember-action="' + actionId + '"');
return new EmberHandlebars.SafeString(output.join(" ")); return new EmberHandlebars.SafeString(output.join(" "));
@ -22229,8 +22524,8 @@ Ember.onLoad('application', bootstrap);
})(); })();
// Version: v0.9.8.1-658-gd4a9e46 // Version: v0.9.8.1-672-gd7e24ac
// Last commit: d4a9e46 (2012-07-21 19:16:48 -0700) // Last commit: d7e24ac (2012-07-26 09:35:30 -0700)
(function() { (function() {
@ -22492,7 +22787,7 @@ DS.ManyArrayStateManager = Ember.StateManager.extend({
(function() { (function() {
var get = Ember.get, set = Ember.set, set = Ember.set; var get = Ember.get, set = Ember.set;
DS.ManyArray = DS.RecordArray.extend({ DS.ManyArray = DS.RecordArray.extend({
init: function() { init: function() {
@ -25987,31 +26282,35 @@ DS.FixtureAdapter = DS.Adapter.extend({
find: function(store, type, id) { find: function(store, type, id) {
var fixtures = this.fixturesForType(type); var fixtures = this.fixturesForType(type);
Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
if (fixtures) { if (fixtures) {
fixtures = fixtures.findProperty('id', id); fixtures = fixtures.findProperty('id', id);
} }
Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures); if (fixtures) {
this.simulateRemoteCall(function() { this.simulateRemoteCall(function() {
store.load(type, fixtures); store.load(type, fixtures);
}, store, type); }, store, type);
}
}, },
findMany: function(store, type, ids) { findMany: function(store, type, ids) {
var fixtures = this.fixturesForType(type); var fixtures = this.fixturesForType(type);
Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
if (fixtures) { if (fixtures) {
fixtures = fixtures.filter(function(item) { fixtures = fixtures.filter(function(item) {
return ids.indexOf(item.id) !== -1; return ids.indexOf(item.id) !== -1;
}); });
} }
Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures); if (fixtures) {
this.simulateRemoteCall(function() { this.simulateRemoteCall(function() {
store.loadMany(type, fixtures); store.loadMany(type, fixtures);
}, store, type); }, store, type);
}
}, },
findAll: function(store, type) { findAll: function(store, type) {
@ -26027,13 +26326,15 @@ DS.FixtureAdapter = DS.Adapter.extend({
findQuery: function(store, type, query, array) { findQuery: function(store, type, query, array) {
var fixtures = this.fixturesForType(type); var fixtures = this.fixturesForType(type);
fixtures = this.queryFixtures(fixtures, query);
Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures); Ember.assert("Unable to find fixtures for model type "+type.toString(), !!fixtures);
fixtures = this.queryFixtures(fixtures, query);
if (fixtures) {
this.simulateRemoteCall(function() { this.simulateRemoteCall(function() {
array.load(fixtures); array.load(fixtures);
}, store, type); }, store, type);
}
}, },
createRecord: function(store, type, record) { createRecord: function(store, type, record) {
@ -26274,7 +26575,9 @@ DS.RESTAdapter = DS.Adapter.extend({
data: query, data: query,
success: function(json) { success: function(json) {
this.sideload(store, type, json, plural); this.sideload(store, type, json, plural);
setTimeout(function() {
recordArray.load(json[plural]); recordArray.load(json[plural]);
}, 10);
} }
}); });
}, },