Skip to content

Commit ad675d5

Browse files
committed
Merge pull request #9919 from machty/transitioning-classes
[FEATURE "ember-routing-transitioning-classes”]
2 parents b973565 + 9c56bc4 commit ad675d5

File tree

7 files changed

+225
-67
lines changed

7 files changed

+225
-67
lines changed

FEATURES.md

+11
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,14 @@ for a detailed explanation.
111111
to perform the lookup instead. Replaces the need for `needs` in controllers.
112112

113113
Added in [#5162](https://github.com/emberjs/ember.js/pull/5162).
114+
115+
* `ember-routing-transitioning-classes`
116+
117+
Disables eager URL updates during slow transitions in favor of new CSS
118+
classes added to `link-to`s (in addition to `active` class):
119+
120+
- `transitioning-in`: link-to is not currently active, but will be
121+
when the current underway (slow) transition completes.
122+
- `transitioning-out`: link-to is currently active, but will no longer
123+
be active when the current underway (slow) transition completes.
124+

bower.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"devDependencies": {
1010
"backburner": "https://github.com/ebryn/backburner.js.git#f4bd6a2df221240ed36d140f0c53c036a7ecacad",
1111
"rsvp": "https://github.com/tildeio/rsvp.js.git#3.0.14",
12-
"router.js": "https://github.com/tildeio/router.js.git#9471aaaa28c11907d8fdf8969b70c6a93ad19fd1",
12+
"router.js": "https://github.com/tildeio/router.js.git#a1ffd97dc66a6d9d4e8dd89a72c1c4e21a3328c5",
1313
"route-recognizer": "https://github.com/tildeio/route-recognizer.git#8e1058e29de741b8e05690c69da9ec402a167c69",
1414
"dag-map": "https://github.com/krisselden/dag-map.git#e307363256fe918f426e5a646cb5f5062d3245be",
1515
"ember-dev": "https://github.com/emberjs/ember-dev.git#1c30a1666273ab2a9b134a42bad28c774f9ecdfc"

features.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"ember-htmlbars-block-params": true,
1313
"ember-htmlbars-component-generation": null,
1414
"ember-htmlbars-inline-if-helper": null,
15-
"ember-htmlbars-attribute-syntax": null
15+
"ember-htmlbars-attribute-syntax": null,
16+
"ember-routing-transitioning-classes": null
1617
},
1718
"debugStatements": [
1819
"Ember.warn",

packages/ember-routing-views/lib/views/link.js

+65-51
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ var numberOfContextsAcceptedByHandler = function(handler, handlerInfos) {
2727
return req;
2828
};
2929

30+
var linkViewClassNameBindings = ['active', 'loading', 'disabled'];
31+
if (Ember.FEATURES.isEnabled('ember-routing-transitioning-classes')) {
32+
linkViewClassNameBindings = ['active', 'loading', 'disabled', 'transitioningIn', 'transitioningOut'];
33+
}
34+
3035
/**
3136
`Ember.LinkView` renders an element whose `click` event triggers a
3237
transition of the application's instance of `Ember.Router` to
@@ -149,7 +154,7 @@ var LinkView = Ember.LinkView = EmberComponent.extend({
149154
@type Array
150155
@default ['active', 'loading', 'disabled']
151156
**/
152-
classNameBindings: ['active', 'loading', 'disabled'],
157+
classNameBindings: linkViewClassNameBindings,
153158

154159
/**
155160
By default the `{{link-to}}` helper responds to the `click` event. You
@@ -289,56 +294,32 @@ var LinkView = Ember.LinkView = EmberComponent.extend({
289294
@property active
290295
**/
291296
active: computed('loadedParams', function computeLinkViewActive() {
292-
if (get(this, 'loading')) { return false; }
297+
var router = get(this, 'router');
298+
if (!router) { return; }
299+
return computeActive(this, router.currentState);
300+
}),
293301

302+
willBeActive: computed('router.targetState', function() {
294303
var router = get(this, 'router');
295-
var loadedParams = get(this, 'loadedParams');
296-
var contexts = loadedParams.models;
297-
var currentWhen = this['current-when'] || this.currentWhen;
298-
var isCurrentWhenSpecified = Boolean(currentWhen);
299-
currentWhen = currentWhen || loadedParams.targetRouteName;
300-
301-
function isActiveForRoute(routeName) {
302-
var handlers = router.router.recognizer.handlersFor(routeName);
303-
var leafName = handlers[handlers.length-1].handler;
304-
var maximumContexts = numberOfContextsAcceptedByHandler(routeName, handlers);
305-
306-
// NOTE: any ugliness in the calculation of activeness is largely
307-
// due to the fact that we support automatic normalizing of
308-
// `resource` -> `resource.index`, even though there might be
309-
// dynamic segments / query params defined on `resource.index`
310-
// which complicates (and makes somewhat ambiguous) the calculation
311-
// of activeness for links that link to `resource` instead of
312-
// directly to `resource.index`.
313-
314-
// if we don't have enough contexts revert back to full route name
315-
// this is because the leaf route will use one of the contexts
316-
if (contexts.length > maximumContexts) {
317-
routeName = leafName;
318-
}
304+
if (!router) { return; }
305+
var targetState = router.targetState;
306+
if (router.currentState === targetState) { return; }
319307

320-
var args = routeArgs(routeName, contexts, null);
321-
var isActive = router.isActive.apply(router, args);
322-
if (!isActive) { return false; }
308+
return !!computeActive(this, targetState);
309+
}),
323310

324-
var emptyQueryParams = Ember.isEmpty(Ember.keys(loadedParams.queryParams));
311+
transitioningIn: computed('active', 'willBeActive', function() {
312+
var willBeActive = get(this, 'willBeActive');
313+
if (typeof willBeActive === 'undefined') { return false; }
325314

326-
if (!isCurrentWhenSpecified && !emptyQueryParams && isActive) {
327-
var visibleQueryParams = {};
328-
merge(visibleQueryParams, loadedParams.queryParams);
329-
router._prepareQueryParams(loadedParams.targetRouteName, loadedParams.models, visibleQueryParams);
330-
isActive = shallowEqual(visibleQueryParams, router.router.state.queryParams);
331-
}
315+
return !get(this, 'active') && willBeActive;
316+
}),
332317

333-
return isActive;
334-
}
318+
transitioningOut: computed('active', 'willBeActive', function() {
319+
var willBeActive = get(this, 'willBeActive');
320+
if (typeof willBeActive === 'undefined') { return false; }
335321

336-
currentWhen = currentWhen.split(' ');
337-
for (var i = 0, len = currentWhen.length; i < len; i++) {
338-
if (isActiveForRoute(currentWhen[i])) {
339-
return get(this, 'activeClass');
340-
}
341-
}
322+
return get(this, 'active') && !willBeActive;
342323
}),
343324

344325
/**
@@ -408,6 +389,10 @@ var LinkView = Ember.LinkView = EmberComponent.extend({
408389
transition.method('replace');
409390
}
410391

392+
if (Ember.FEATURES.isEnabled('ember-routing-transitioning-classes')) {
393+
return;
394+
}
395+
411396
// Schedule eager URL update, but after we've given the transition
412397
// a chance to synchronously redirect.
413398
// We need to always generate the URL instead of using the href because
@@ -591,15 +576,44 @@ function paramsAreLoaded(params) {
591576
return true;
592577
}
593578

594-
function shallowEqual(a, b) {
595-
var k;
596-
for (k in a) {
597-
if (a.hasOwnProperty(k) && a[k] !== b[k]) { return false; }
579+
function computeActive(route, routerState) {
580+
if (get(route, 'loading')) { return false; }
581+
582+
var currentWhen = route['current-when'] || route.currentWhen;
583+
var isCurrentWhenSpecified = !!currentWhen;
584+
currentWhen = currentWhen || get(route, 'loadedParams').targetRouteName;
585+
currentWhen = currentWhen.split(' ');
586+
for (var i = 0, len = currentWhen.length; i < len; i++) {
587+
if (isActiveForRoute(route, currentWhen[i], isCurrentWhenSpecified, routerState)) {
588+
return get(route, 'activeClass');
589+
}
598590
}
599-
for (k in b) {
600-
if (b.hasOwnProperty(k) && a[k] !== b[k]) { return false; }
591+
}
592+
593+
function isActiveForRoute(route, routeName, isCurrentWhenSpecified, routerState) {
594+
var router = get(route, 'router');
595+
var loadedParams = get(route, 'loadedParams');
596+
var contexts = loadedParams.models;
597+
598+
var handlers = router.router.recognizer.handlersFor(routeName);
599+
var leafName = handlers[handlers.length-1].handler;
600+
var maximumContexts = numberOfContextsAcceptedByHandler(routeName, handlers);
601+
602+
// NOTE: any ugliness in the calculation of activeness is largely
603+
// due to the fact that we support automatic normalizing of
604+
// `resource` -> `resource.index`, even though there might be
605+
// dynamic segments / query params defined on `resource.index`
606+
// which complicates (and makes somewhat ambiguous) the calculation
607+
// of activeness for links that link to `resource` instead of
608+
// directly to `resource.index`.
609+
610+
// if we don't have enough contexts revert back to full route name
611+
// this is because the leaf route will use one of the contexts
612+
if (contexts.length > maximumContexts) {
613+
routeName = leafName;
601614
}
602-
return true;
615+
616+
return routerState.isActiveIntent(routeName, contexts, loadedParams.queryParams, !isCurrentWhenSpecified);
603617
}
604618

605619
export {

packages/ember-routing/lib/system/router.js

+35-13
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {
2121
} from "ember-routing/utils";
2222
import { create } from "ember-metal/platform";
2323

24+
import RouterState from "./router_state";
25+
2426
/**
2527
@module ember
2628
@submodule ember-routing
@@ -147,9 +149,10 @@ var EmberRouter = EmberObject.extend(Evented, {
147149
didTransition: function(infos) {
148150
updatePaths(this);
149151

150-
this._cancelLoadingEvent();
152+
this._cancelSlowTransitionTimer();
151153

152154
this.notifyPropertyChange('url');
155+
this.set('currentState', this.targetState);
153156

154157
// Put this in the runloop so url will be accurate. Seems
155158
// less surprising than didTransition being out of sync.
@@ -169,7 +172,7 @@ var EmberRouter = EmberObject.extend(Evented, {
169172

170173
_doURLTransition: function(routerJsMethod, url) {
171174
var transition = this.router[routerJsMethod](url || '/');
172-
listenForTransitionErrors(transition);
175+
didBeginTransition(transition, this);
173176
return transition;
174177
},
175178

@@ -237,8 +240,7 @@ var EmberRouter = EmberObject.extend(Evented, {
237240
@since 1.7.0
238241
*/
239242
isActiveIntent: function(routeName, models, queryParams) {
240-
var router = this.router;
241-
return router.isActive.apply(router, arguments);
243+
return this.currentState.isActiveIntent(routeName, models, queryParams);
242244
},
243245

244246
send: function(name, context) {
@@ -441,7 +443,7 @@ var EmberRouter = EmberObject.extend(Evented, {
441443
var transitionArgs = routeArgs(targetRouteName, models, queryParams);
442444
var transitionPromise = this.router.transitionTo.apply(this.router, transitionArgs);
443445

444-
listenForTransitionErrors(transitionPromise);
446+
didBeginTransition(transitionPromise, this);
445447

446448
return transitionPromise;
447449
},
@@ -539,25 +541,34 @@ var EmberRouter = EmberObject.extend(Evented, {
539541
},
540542

541543
_scheduleLoadingEvent: function(transition, originRoute) {
542-
this._cancelLoadingEvent();
543-
this._loadingStateTimer = run.scheduleOnce('routerTransitions', this, '_fireLoadingEvent', transition, originRoute);
544+
this._cancelSlowTransitionTimer();
545+
this._slowTransitionTimer = run.scheduleOnce('routerTransitions', this, '_handleSlowTransition', transition, originRoute);
544546
},
545547

546-
_fireLoadingEvent: function(transition, originRoute) {
548+
currentState: null,
549+
targetState: null,
550+
551+
_handleSlowTransition: function(transition, originRoute) {
547552
if (!this.router.activeTransition) {
548553
// Don't fire an event if we've since moved on from
549554
// the transition that put us in a loading state.
550555
return;
551556
}
552557

558+
this.set('targetState', RouterState.create({
559+
emberRouter: this,
560+
routerJs: this.router,
561+
routerJsState: this.router.activeTransition.state
562+
}));
563+
553564
transition.trigger(true, 'loading', transition, originRoute);
554565
},
555566

556-
_cancelLoadingEvent: function () {
557-
if (this._loadingStateTimer) {
558-
run.cancel(this._loadingStateTimer);
567+
_cancelSlowTransitionTimer: function () {
568+
if (this._slowTransitionTimer) {
569+
run.cancel(this._slowTransitionTimer);
559570
}
560-
this._loadingStateTimer = null;
571+
this._slowTransitionTimer = null;
561572
}
562573
});
563574

@@ -859,7 +870,18 @@ EmberRouter.reopenClass({
859870
}
860871
});
861872

862-
function listenForTransitionErrors(transition) {
873+
function didBeginTransition(transition, router) {
874+
var routerState = RouterState.create({
875+
emberRouter: router,
876+
routerJs: router.router,
877+
routerJsState: transition.state
878+
});
879+
880+
if (!router.currentState) {
881+
router.set('currentState', routerState);
882+
}
883+
router.set('targetState', routerState);
884+
863885
transition.then(null, function(error) {
864886
if (!error || !error.name) { return; }
865887

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import Ember from "ember-metal/core";
2+
import EmberObject from "ember-runtime/system/object";
3+
import merge from "ember-metal/merge";
4+
5+
var RouterState = EmberObject.extend({
6+
emberRouter: null,
7+
routerJs: null,
8+
routerJsState: null,
9+
10+
isActiveIntent: function(routeName, models, queryParams, queryParamsMustMatch) {
11+
var state = this.routerJsState;
12+
if (!this.routerJs.isActiveIntent(routeName, models, null, state)) { return false; }
13+
14+
var emptyQueryParams = Ember.isEmpty(Ember.keys(queryParams));
15+
16+
if (queryParamsMustMatch && !emptyQueryParams) {
17+
var visibleQueryParams = {};
18+
merge(visibleQueryParams, queryParams);
19+
20+
this.emberRouter._prepareQueryParams(routeName, models, visibleQueryParams);
21+
return shallowEqual(visibleQueryParams, state.queryParams);
22+
}
23+
24+
return true;
25+
}
26+
});
27+
28+
function shallowEqual(a, b) {
29+
var k;
30+
for (k in a) {
31+
if (a.hasOwnProperty(k) && a[k] !== b[k]) { return false; }
32+
}
33+
for (k in b) {
34+
if (b.hasOwnProperty(k) && a[k] !== b[k]) { return false; }
35+
}
36+
return true;
37+
}
38+
39+
export default RouterState;
40+

0 commit comments

Comments
 (0)