Skip to content

Commit

Permalink
[FEATURE ember-routing-will-change-hooks] finer-grain transition hooks
Browse files Browse the repository at this point in the history
Brought to you by @ef4, and demonstrated at his
EmberConf talk:

http://confreaks.com/videos/3302-emberconf2014-animations-and-transitions-in-an-ember-app

See this router.js PR for details:

tildeio/router.js#89
  • Loading branch information
machty committed Apr 23, 2014
1 parent 5840fdf commit 5d7b67a
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 18 deletions.
16 changes: 16 additions & 0 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,19 @@ for a detailed explanation.
underlying test framework to start/stop between async steps.

Added in [#4176](https://github.com/emberjs/ember.js/pull/4176)

* `ember-routing-will-change-hooks`
Finer-grained `willTransition`-esque actions:

- `willLeave`: fires on routes that will no longer be active after
the transition
- `willChangeModel`: fires on routes that will still be active
but will re-resolve their models

Both of these hooks act like willTransition in the sense that they
give you an opportunity to abort the transition before it happens.
Common use cases include animating things away or prompting to user
to deal with unsaved changes.

Added in [#4760](https://github.com/emberjs/ember.js/pull/4760)

3 changes: 2 additions & 1 deletion features.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
"ember-routing-drop-deprecated-action-style": null,
"ember-runtime-test-friendly-promises": true,
"ember-routing-add-model-option": true,
"ember-routing-linkto-target-attribute": null
"ember-routing-linkto-target-attribute": null,
"ember-routing-will-change-hooks": null
},
"debugStatements": [
"Ember.warn",
Expand Down
72 changes: 72 additions & 0 deletions packages/ember/tests/routing/basic_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3092,3 +3092,75 @@ test("rejecting the model hooks promise with a string shows a good error", funct

Ember.Logger.error = originalLoggerError;
});

if (Ember.FEATURES.isEnabled("ember-routing-will-change-hooks")) {
test("willLeave, willChangeModel actions fire on routes", function() {
expect(2);

App.Router.map(function() {
this.route('user', { path: '/user/:id' });
});

function shouldNotFire() {
ok(false, "this action shouldn't have been received");
}

var willChangeFired = false, willLeaveFired = false;
App.IndexRoute = Ember.Route.extend({
actions: {
willChangeModel: shouldNotFire,
willChangeContext: shouldNotFire,
willLeave: function() {
willLeaveFired = true;
}
}
});

App.UserRoute = Ember.Route.extend({
actions: {
willChangeModel: function() {
willChangeFired = true;
},
willChangeContext: shouldNotFire,
willLeave: shouldNotFire
}
});

bootApplication();

Ember.run(router, 'transitionTo', 'user', { id: 'wat' });
ok(willLeaveFired, "#actions.willLeaveFired");
Ember.run(router, 'transitionTo', 'user', { id: 'lol' });
ok(willChangeFired, "user#actions.willChangeModel");
});
} else {
test("willLeave, willChangeContext, willChangeModel actions don't fire unless feature flag enabled", function() {
expect(1);

App.Router.map(function() {
this.route('about');
});

function shouldNotFire() {
ok(false, "this action shouldn't have been received");
}

App.IndexRoute = Ember.Route.extend({
actions: {
willChangeModel: shouldNotFire,
willChangeContext: shouldNotFire,
willLeave: shouldNotFire
}
});

App.AboutRoute = Ember.Route.extend({
setupController: function() {
ok(true, "about route was entered");
}
});

bootApplication();
Ember.run(router, 'transitionTo', 'about');
});
}

8 changes: 8 additions & 0 deletions packages_es6/ember-routing/lib/system/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,14 @@ EmberRouter.reopenClass({
var router = this.router;
if (!router) {
router = new Router();

if (Ember.FEATURES.isEnabled("ember-routing-will-change-hooks")) {
router._willChangeContextEvent = 'willChangeModel';
} else {
router._triggerWillChangeContext = Ember.K;
router._triggerWillLeave = Ember.K;
}

router.callbacks = [];
router.triggerEvent = triggerEvent;
this.reopenClass({ router: router });
Expand Down
85 changes: 68 additions & 17 deletions packages_es6/ember-routing/lib/vendor/router.amd.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
define("router/handler-info",
define("router/handler-info",
["./utils","rsvp/promise","exports"],
function(__dependency1__, __dependency2__, __exports__) {
"use strict";
Expand Down Expand Up @@ -175,7 +175,7 @@ define("router/handler-info",

__exports__["default"] = HandlerInfo;
});
define("router/handler-info/factory",
define("router/handler-info/factory",
["router/handler-info/resolved-handler-info","router/handler-info/unresolved-handler-info-by-object","router/handler-info/unresolved-handler-info-by-param","exports"],
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
"use strict";
Expand All @@ -198,7 +198,7 @@ define("router/handler-info/factory",

__exports__["default"] = handlerInfoFactory;
});
define("router/handler-info/resolved-handler-info",
define("router/handler-info/resolved-handler-info",
["../handler-info","router/utils","rsvp/promise","exports"],
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
"use strict";
Expand Down Expand Up @@ -229,7 +229,7 @@ define("router/handler-info/resolved-handler-info",

__exports__["default"] = ResolvedHandlerInfo;
});
define("router/handler-info/unresolved-handler-info-by-object",
define("router/handler-info/unresolved-handler-info-by-object",
["../handler-info","router/utils","rsvp/promise","exports"],
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
"use strict";
Expand Down Expand Up @@ -291,7 +291,7 @@ define("router/handler-info/unresolved-handler-info-by-object",

__exports__["default"] = UnresolvedHandlerInfoByObject;
});
define("router/handler-info/unresolved-handler-info-by-param",
define("router/handler-info/unresolved-handler-info-by-param",
["../handler-info","router/utils","exports"],
function(__dependency1__, __dependency2__, __exports__) {
"use strict";
Expand Down Expand Up @@ -323,9 +323,9 @@ define("router/handler-info/unresolved-handler-info-by-param",

__exports__["default"] = UnresolvedHandlerInfoByParam;
});
define("router/router",
["route-recognizer","rsvp/promise","./utils","./transition-state","./transition","./transition-intent/named-transition-intent","./transition-intent/url-transition-intent","exports"],
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __exports__) {
define("router/router",
["route-recognizer","rsvp/promise","./utils","./transition-state","./transition","./transition-intent/named-transition-intent","./transition-intent/url-transition-intent","./handler-info","exports"],
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __dependency5__, __dependency6__, __dependency7__, __dependency8__, __exports__) {
"use strict";
var RouteRecognizer = __dependency1__["default"];
var Promise = __dependency2__["default"];
Expand All @@ -344,6 +344,7 @@ define("router/router",
var TransitionAborted = __dependency5__.TransitionAborted;
var NamedTransitionIntent = __dependency6__["default"];
var URLTransitionIntent = __dependency7__["default"];
var ResolvedHandlerInfo = __dependency8__.ResolvedHandlerInfo;

var pop = Array.prototype.pop;

Expand Down Expand Up @@ -463,7 +464,7 @@ define("router/router",
}, null, promiseLabel("Settle transition promise when transition is finalized"));

if (!wasTransitioning) {
trigger(this, this.state.handlerInfos, true, ['willTransition', newTransition]);
notifyExistingHandlers(this, newState, newTransition);
}

return newTransition;
Expand Down Expand Up @@ -677,7 +678,16 @@ define("router/router",
@param {String} message The message to log.
*/
log: null
log: null,

_willChangeContextEvent: 'willChangeContext',
_triggerWillChangeContext: function(handlerInfos, newTransition) {
trigger(this, handlerInfos, true, [this._willChangeContextEvent, newTransition]);
},

_triggerWillLeave: function(handlerInfos, newTransition, leavingChecker) {
trigger(this, handlerInfos, true, ['willLeave', newTransition, leavingChecker]);
}
};

/**
Expand Down Expand Up @@ -1045,9 +1055,50 @@ define("router/router",
return finalQueryParams;
}

function notifyExistingHandlers(router, newState, newTransition) {
var oldHandlers = router.state.handlerInfos,
changing = [],
leavingIndex = null,
leaving, leavingChecker, i, oldHandler, newHandler;

for (i = 0; i < oldHandlers.length; i++) {
oldHandler = oldHandlers[i];
newHandler = newState.handlerInfos[i];

if (!newHandler || oldHandler.name !== newHandler.name) {
leavingIndex = i;
break;
}

if (!newHandler.isResolved) {
changing.push(oldHandler);
}
}

if (leavingIndex !== null) {
leaving = oldHandlers.slice(leavingIndex, oldHandlers.length);
leavingChecker = function(name) {
for (var h = 0; h < leaving.length; h++) {
if (leaving[h].name === name) {
return true;
}
}
return false;
};

router._triggerWillLeave(leaving, newTransition, leavingChecker);
}

if (changing.length > 0) {
router._triggerWillChangeContext(changing, newTransition);
}

trigger(router, oldHandlers, true, ['willTransition', newTransition]);
}

__exports__["default"] = Router;
});
define("router/transition-intent",
define("router/transition-intent",
["./utils","exports"],
function(__dependency1__, __exports__) {
"use strict";
Expand All @@ -1067,7 +1118,7 @@ define("router/transition-intent",

__exports__["default"] = TransitionIntent;
});
define("router/transition-intent/named-transition-intent",
define("router/transition-intent/named-transition-intent",
["../transition-intent","../transition-state","../handler-info/factory","../utils","exports"],
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
"use strict";
Expand Down Expand Up @@ -1269,7 +1320,7 @@ define("router/transition-intent/named-transition-intent",
}
});
});
define("router/transition-intent/url-transition-intent",
define("router/transition-intent/url-transition-intent",
["../transition-intent","../transition-state","../handler-info/factory","../utils","exports"],
function(__dependency1__, __dependency2__, __dependency3__, __dependency4__, __exports__) {
"use strict";
Expand Down Expand Up @@ -1339,7 +1390,7 @@ define("router/transition-intent/url-transition-intent",
this.name = "UnrecognizedURLError";
}
});
define("router/transition-state",
define("router/transition-state",
["./handler-info","./utils","rsvp/promise","exports"],
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
"use strict";
Expand Down Expand Up @@ -1452,7 +1503,7 @@ define("router/transition-state",

__exports__["default"] = TransitionState;
});
define("router/transition",
define("router/transition",
["rsvp/promise","./handler-info","./utils","exports"],
function(__dependency1__, __dependency2__, __dependency3__, __exports__) {
"use strict";
Expand Down Expand Up @@ -1713,7 +1764,7 @@ define("router/transition",
__exports__.logAbort = logAbort;
__exports__.TransitionAborted = TransitionAborted;
});
define("router/utils",
define("router/utils",
["exports"],
function(__exports__) {
"use strict";
Expand Down Expand Up @@ -1910,7 +1961,7 @@ define("router/utils",
__exports__.isParam = isParam;
__exports__.coerceQueryParamsToString = coerceQueryParamsToString;
});
define("router",
define("router",
["./router/router","exports"],
function(__dependency1__, __exports__) {
"use strict";
Expand Down

0 comments on commit 5d7b67a

Please sign in to comment.