Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE ember-routing-router-service] isActive and Cleanup #15414

Merged
merged 2 commits into from
Jun 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/ember-application/lib/system/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -1045,7 +1045,7 @@ function commonSetupRegistry(registry) {

if (EMBER_ROUTING_ROUTER_SERVICE) {
registry.register('service:router', RouterService);
registry.injection('service:router', 'router', 'router:main');
registry.injection('service:router', '_router', 'router:main');
}
}

Expand Down
95 changes: 86 additions & 9 deletions packages/ember-routing/lib/services/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ import {
Service,
readOnly
} from 'ember-runtime';
import { assign } from 'ember-utils';
import RouterDSL from '../system/dsl';


function shallowEqual(a, b) {
let k;
for (k in a) {
if (a.hasOwnProperty(k) && a[k] !== b[k]) { return false; }
}
for (k in b) {
if (b.hasOwnProperty(k) && a[k] !== b[k]) { return false; }
}
return true;
}

/**
The Router service is the public API that provides component/view layer
Expand All @@ -17,11 +31,11 @@ import {
@category ember-routing-router-service
*/
const RouterService = Service.extend({
currentRouteName: readOnly('router.currentRouteName'),
currentURL: readOnly('router.currentURL'),
location: readOnly('router.location'),
rootURL: readOnly('router.rootURL'),
router: null,
currentRouteName: readOnly('_router.currentRouteName'),
currentURL: readOnly('_router.currentURL'),
location: readOnly('_router.location'),
rootURL: readOnly('_router.rootURL'),
_router: null,

/**
Transition the application into another route. The route may
Expand All @@ -40,8 +54,25 @@ const RouterService = Service.extend({
attempted transition
@public
*/
transitionTo(/* routeNameOrUrl, ...models, options */) {
return this.router.transitionTo(...arguments);
transitionTo(...args) {
let queryParams;
let arg = args[0];
if (resemblesURL(arg)) {
return this._router._doURLTransition('transitionTo', arg);
}

let possibleQueryParams = args[args.length - 1];
if (possibleQueryParams && possibleQueryParams.hasOwnProperty('queryParams')) {
queryParams = args.pop().queryParams;
} else {
queryParams = {};
}

let targetRouteName = args.shift();
let transition = this._router._doTransition(targetRouteName, args, queryParams, true);
transition._keepDefaultQueryParamValues = true;

return transition;
},

/**
Expand All @@ -62,13 +93,14 @@ const RouterService = Service.extend({
@public
*/
replaceWith(/* routeNameOrUrl, ...models, options */) {
return this.router.replaceWith(...arguments);
return this.transitionTo(...arguments).method('replace');
},

/**
Generate a URL based on the supplied route name.

@method urlFor
@category ember-routing-router-service
@param {String} routeName the name of the route
@param {...Object} models the model(s) or identifier(s) to be used while
transitioning to the route.
Expand All @@ -78,8 +110,53 @@ const RouterService = Service.extend({
@public
*/
urlFor(/* routeName, ...models, options */) {
return this.router.generate(...arguments);
return this._router.generate(...arguments);
},

/**
Determines whether a route is active.

@method isActive
@category ember-routing-router-service
@param {String} routeName the name of the route
@param {...Object} models the model(s) or identifier(s) to be used while
transitioning to the route.
@param {Object} [options] optional hash with a queryParams property
containing a mapping of query parameters
@return {boolean} true if the provided routeName/models/queryParams are active
@public
*/
isActive(/* routeName, ...models, options */) {
let { routeName, models, queryParams } = this._extractArguments(...arguments);
let routerMicrolib = this._router._routerMicrolib;
let state = routerMicrolib.state;

if (!routerMicrolib.isActiveIntent(routeName, models, null)) { return false; }
let hasQueryParams = Object.keys(queryParams).length > 0;

if (hasQueryParams) {
this._router._prepareQueryParams(routeName, models, queryParams, true /* fromRouterService */);
return shallowEqual(queryParams, state.queryParams);
}

return true;
},

_extractArguments(routeName, ...models) {
let possibleQueryParams = models[models.length - 1];
let queryParams = {};

if (possibleQueryParams && possibleQueryParams.hasOwnProperty('queryParams')) {
let options = models.pop();
queryParams = options.queryParams;
}

return { routeName, models, queryParams };
}
});

function resemblesURL(str) {
return typeof str === 'string' && (str === '' || str[0] === '/');
}

export default RouterService;
2 changes: 1 addition & 1 deletion packages/ember-routing/lib/system/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -873,7 +873,7 @@ let Route = EmberObject.extend(ActionHandler, Evented, {
qp.serializedValue = svalue;

let thisQueryParamHasDefaultValue = (qp.serializedDefaultValue === svalue);
if (!thisQueryParamHasDefaultValue) {
if (!thisQueryParamHasDefaultValue || transition._keepDefaultQueryParamValues) {
finalParams.push({
value: svalue,
visible: true,
Expand Down
35 changes: 27 additions & 8 deletions packages/ember-routing/lib/system/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -767,7 +767,7 @@ const EmberRouter = EmberObject.extend(Evented, {
}
},

_doTransition(_targetRouteName, models, _queryParams) {
_doTransition(_targetRouteName, models, _queryParams, _keepDefaultQueryParamValues) {
let targetRouteName = _targetRouteName || getActiveTargetName(this._routerMicrolib);
assert(`The route ${targetRouteName} was not found`, targetRouteName && this._routerMicrolib.hasRoute(targetRouteName));

Expand All @@ -776,7 +776,7 @@ const EmberRouter = EmberObject.extend(Evented, {
this._processActiveTransitionQueryParams(targetRouteName, models, queryParams, _queryParams);

assign(queryParams, _queryParams);
this._prepareQueryParams(targetRouteName, models, queryParams);
this._prepareQueryParams(targetRouteName, models, queryParams, _keepDefaultQueryParamValues);

let transitionArgs = routeArgs(targetRouteName, models, queryParams);
let transition = this._routerMicrolib.transitionTo(...transitionArgs);
Expand Down Expand Up @@ -817,13 +817,17 @@ const EmberRouter = EmberObject.extend(Evented, {
@param {String} targetRouteName
@param {Array<Object>} models
@param {Object} queryParams
@param {boolean} keepDefaultQueryParamValues
@return {Void}
*/
_prepareQueryParams(targetRouteName, models, queryParams) {
_prepareQueryParams(targetRouteName, models, queryParams, _fromRouterService) {
let state = calculatePostTransitionState(this, targetRouteName, models);
this._hydrateUnsuppliedQueryParams(state, queryParams);
this._hydrateUnsuppliedQueryParams(state, queryParams, _fromRouterService);
this._serializeQueryParams(state.handlerInfos, queryParams);
this._pruneDefaultQueryParamValues(state.handlerInfos, queryParams);

if (!_fromRouterService) {
this._pruneDefaultQueryParamValues(state.handlerInfos, queryParams);
}
},

/**
Expand Down Expand Up @@ -945,7 +949,7 @@ const EmberRouter = EmberObject.extend(Evented, {
@param {Object} queryParams
@return {Void}
*/
_hydrateUnsuppliedQueryParams(state, queryParams) {
_hydrateUnsuppliedQueryParams(state, queryParams, _fromRouterService) {
let handlerInfos = state.handlerInfos;
let appCache = this._bucketCache;

Expand All @@ -955,12 +959,27 @@ const EmberRouter = EmberObject.extend(Evented, {
if (!qpMeta) { continue; }

for (let j = 0, qpLen = qpMeta.qps.length; j < qpLen; ++j) {
let qp = qpMeta.qps[j];
var qp = qpMeta.qps[j];

let presentProp = qp.prop in queryParams && qp.prop ||
var presentProp = qp.prop in queryParams && qp.prop ||
qp.scopedPropertyName in queryParams && qp.scopedPropertyName ||
qp.urlKey in queryParams && qp.urlKey;

assert(
`You passed the \`${presentProp}\` query parameter during a transition into ${qp.route.routeName}, please update to ${qp.urlKey}`,
(function() {
if (qp.urlKey === presentProp) {
return true;
}

if (_fromRouterService) {
return false;
}

return true;
})()
);

if (presentProp) {
if (presentProp !== qp.scopedPropertyName) {
queryParams[qp.scopedPropertyName] = queryParams[presentProp];
Expand Down
109 changes: 109 additions & 0 deletions packages/ember/tests/routing/router_service_test/isActive_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import {
Controller,
inject,
String
} from 'ember-runtime';
import { Component } from 'ember-glimmer';
import { Route, NoneLocation } from 'ember-routing';
import {
get,
set
} from 'ember-metal';
import {
RouterTestCase,
moduleFor
} from 'internal-test-helpers';

import { EMBER_ROUTING_ROUTER_SERVICE } from 'ember/features';

if (EMBER_ROUTING_ROUTER_SERVICE) {
moduleFor('Router Service - isActive', class extends RouterTestCase {
['@test RouterService#isActive returns true for simple route'](assert) {
assert.expect(1);

return this.visit('/')
.then(() => {
return this.routerService.transitionTo('parent.child');
})
.then(() => {
return this.routerService.transitionTo('parent.sister');
})
.then(() => {
assert.ok(this.routerService.isActive('parent.sister'));
});
}

['@test RouterService#isActive returns true for simple route with dynamic segments'](assert) {
assert.expect(1);

let dynamicModel = { id: 1 };

return this.visit('/')
.then(() => {
return this.routerService.transitionTo('dynamic', dynamicModel);
})
.then(() => {
assert.ok(this.routerService.isActive('dynamic', dynamicModel));
});
}

['@test RouterService#isActive does not eagerly instantiate controller for query params'](assert) {
assert.expect(1);

let queryParams = this.buildQueryParams({ sort: 'ASC' });

this.add('controller:parent.sister', Controller.extend({
queryParams: ['sort'],
sort: 'ASC',

init() {
assert.ok(false, 'should never create');
this._super(...arguments);
}
}));

return this.visit('/')
.then(() => {
return this.routerService.transitionTo('parent.brother');
})
.then(() => {
assert.notOk(this.routerService.isActive('parent.sister', queryParams));
});
}

['@test RouterService#isActive is correct for simple route with basic query params'](assert) {
assert.expect(2);

let queryParams = this.buildQueryParams({ sort: 'ASC' });

this.add('controller:parent.child', Controller.extend({
queryParams: ['sort'],
sort: 'ASC'
})
);

return this.visit('/')
.then(() => {
return this.routerService.transitionTo('parent.child', queryParams);
})
.then(() => {
assert.ok(this.routerService.isActive('parent.child', queryParams));
assert.notOk(this.routerService.isActive('parent.child', this.buildQueryParams({ sort: 'DESC' })));
});
}

['@test RouterService#isActive for simple route with array as query params'](assert) {
assert.expect(1);

let queryParams = this.buildQueryParams({ sort: ['ascending'] });

return this.visit('/')
.then(() => {
return this.routerService.transitionTo('parent.child', queryParams);
})
.then(() => {
assert.notOk(this.routerService.isActive('parent.child', this.buildQueryParams({ sort: 'descending' })));
});
}
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
moduleFor
} from 'internal-test-helpers';
import { Transition } from 'router';
import { Controller } from 'ember-runtime';

import { EMBER_ROUTING_ROUTER_SERVICE } from 'ember/features';

Expand Down Expand Up @@ -105,5 +106,30 @@ if (EMBER_ROUTING_ROUTER_SERVICE) {
assert.deepEqual(this.state, ['/', '/child', '/sister', '/sister']);
});
}

['@test RouterService#replaceWith with basic query params does not remove query param defaults'](assert) {
assert.expect(1);

this.add('controller:parent.child', Controller.extend({
queryParams: ['sort'],
sort: 'ASC'
}));

let queryParams = this.buildQueryParams({ sort: 'ASC' });

return this.visit('/')
.then(() => {
return this.routerService.transitionTo('parent.brother');
})
.then(() => {
return this.routerService.replaceWith('parent.sister');
})
.then(() => {
return this.routerService.replaceWith('parent.child', queryParams);
})
.then(() => {
assert.deepEqual(this.state, ['/', '/child?sort=ASC']);
});
}
});
}
Loading