diff --git a/packages/ember-routing-htmlbars/tests/helpers/link-to_test.js b/packages/ember-routing-htmlbars/tests/helpers/link-to_test.js
index a13e48db319..e9f1a643ef1 100644
--- a/packages/ember-routing-htmlbars/tests/helpers/link-to_test.js
+++ b/packages/ember-routing-htmlbars/tests/helpers/link-to_test.js
@@ -4,11 +4,32 @@ import EmberView from "ember-views/views/view";
import compile from "ember-template-compiler/system/compile";
import { set } from "ember-metal/property_set";
import Controller from "ember-runtime/controllers/controller";
+import { Registry } from "ember-runtime/system/container";
import { runAppend, runDestroy } from "ember-runtime/tests/utils";
+import EmberObject from "ember-runtime/system/object";
var view;
+var container;
+var registry = new Registry();
+
+// These tests don't rely on the routing service, but LinkView makes
+// some assumptions that it will exist. This small stub service ensures
+// that the LinkView can render without raising an exception.
+//
+// TODO: Add tests that test actual behavior. Currently, all behavior
+// is tested integration-style in the `ember` package.
+registry.register('service:-routing', EmberObject.extend({
+ availableRoutes: function() { return ['index']; },
+ hasRoute: function(name) { return name === 'index'; },
+ isActiveForRoute: function() { return true; },
+ generateURL: function() { return "/"; }
+}));
QUnit.module("ember-routing-htmlbars: link-to helper", {
+ setup: function() {
+ container = registry.container();
+ },
+
teardown: function() {
runDestroy(view);
}
@@ -18,7 +39,8 @@ QUnit.module("ember-routing-htmlbars: link-to helper", {
QUnit.test("should be able to be inserted in DOM when the router is not present", function() {
var template = "{{#link-to 'index'}}Go to Index{{/link-to}}";
view = EmberView.create({
- template: compile(template)
+ template: compile(template),
+ container: container
});
runAppend(view);
@@ -33,7 +55,8 @@ QUnit.test("re-renders when title changes", function() {
title: 'foo',
routeName: 'index'
},
- template: compile(template)
+ template: compile(template),
+ container: container
});
runAppend(view);
@@ -54,7 +77,8 @@ QUnit.test("can read bound title", function() {
title: 'foo',
routeName: 'index'
},
- template: compile(template)
+ template: compile(template),
+ container: container
});
runAppend(view);
@@ -65,7 +89,8 @@ QUnit.test("can read bound title", function() {
QUnit.test("escaped inline form (double curlies) escapes link title", function() {
view = EmberView.create({
title: "blah",
- template: compile("{{link-to view.title}}")
+ template: compile("{{link-to view.title}}"),
+ container: container
});
runAppend(view);
@@ -76,7 +101,8 @@ QUnit.test("escaped inline form (double curlies) escapes link title", function()
QUnit.test("unescaped inline form (triple curlies) does not escape link title", function() {
view = EmberView.create({
title: "blah",
- template: compile("{{{link-to view.title}}}")
+ template: compile("{{{link-to view.title}}}"),
+ container: container
});
runAppend(view);
@@ -92,7 +118,8 @@ QUnit.test("unwraps controllers", function() {
model: 'foo'
}),
- template: compile(template)
+ template: compile(template),
+ container: container
});
expectDeprecation(function() {
diff --git a/packages/ember-routing-views/lib/views/link.js b/packages/ember-routing-views/lib/views/link.js
index d9ab390b8ba..f0b16b1a5aa 100644
--- a/packages/ember-routing-views/lib/views/link.js
+++ b/packages/ember-routing-views/lib/views/link.js
@@ -6,27 +6,12 @@
import Ember from "ember-metal/core"; // FEATURES, Logger, assert
import { get } from "ember-metal/property_get";
-import merge from "ember-metal/merge";
-import run from "ember-metal/run_loop";
import { computed } from "ember-metal/computed";
import { fmt } from "ember-runtime/system/string";
-import keys from "ember-metal/keys";
import { isSimpleClick } from "ember-views/system/utils";
import EmberComponent from "ember-views/views/component";
-import { routeArgs } from "ember-routing/utils";
import { read, subscribe } from "ember-metal/streams/utils";
-
-var numberOfContextsAcceptedByHandler = function(handler, handlerInfos) {
- var req = 0;
- for (var i = 0, l = handlerInfos.length; i < l; i++) {
- req = req + handlerInfos[i].names.length;
- if (handlerInfos[i].handler === handler) {
- break;
- }
- }
-
- return req;
-};
+import inject from "ember-runtime/inject";
var linkViewClassNameBindings = ['active', 'loading', 'disabled'];
if (Ember.FEATURES.isEnabled('ember-routing-transitioning-classes')) {
@@ -216,6 +201,8 @@ var LinkView = EmberComponent.extend({
this.on(eventName, this, this._invoke);
},
+ _routing: inject.service('-routing'),
+
/**
This method is invoked by observers installed during `init` that fire
whenever the params change
@@ -290,16 +277,14 @@ var LinkView = EmberComponent.extend({
@property active
**/
active: computed('loadedParams', function computeLinkViewActive() {
- var router = get(this, 'router');
- if (!router) { return; }
- return computeActive(this, router.currentState);
+ var currentState = get(this, '_routing.currentState');
+ return computeActive(this, currentState);
}),
- willBeActive: computed('router.targetState', function() {
- var router = get(this, 'router');
- if (!router) { return; }
- var targetState = router.targetState;
- if (router.currentState === targetState) { return; }
+ willBeActive: computed('_routing.targetState', function() {
+ var routing = get(this, '_routing');
+ var targetState = get(routing, 'targetState');
+ if (get(routing, 'currentState') === targetState) { return; }
return !!computeActive(this, targetState);
}),
@@ -333,19 +318,6 @@ var LinkView = EmberComponent.extend({
if (!get(this, 'loadedParams')) { return get(this, 'loadingClass'); }
}),
- /**
- Returns the application's main router from the container.
-
- @private
- @property router
- **/
- router: computed(function() {
- var controller = get(this, 'controller');
- if (controller && controller.container) {
- return controller.container.lookup('router:main');
- }
- }),
-
/**
Event handler that invokes the link, activating the associated route.
@@ -377,57 +349,8 @@ var LinkView = EmberComponent.extend({
return false;
}
- var router = get(this, 'router');
- var loadedParams = get(this, 'loadedParams');
-
- var transition = router._doTransition(loadedParams.targetRouteName, loadedParams.models, loadedParams.queryParams);
- if (get(this, 'replace')) {
- transition.method('replace');
- }
-
- if (Ember.FEATURES.isEnabled('ember-routing-transitioning-classes')) {
- return;
- }
-
- // Schedule eager URL update, but after we've given the transition
- // a chance to synchronously redirect.
- // We need to always generate the URL instead of using the href because
- // the href will include any rootURL set, but the router expects a URL
- // without it! Note that we don't use the first level router because it
- // calls location.formatURL(), which also would add the rootURL!
- var args = routeArgs(loadedParams.targetRouteName, loadedParams.models, transition.state.queryParams);
- var url = router.router.generate.apply(router.router, args);
-
- run.scheduleOnce('routerTransitions', this, this._eagerUpdateUrl, transition, url);
- },
-
- /**
- @private
- @method _eagerUpdateUrl
- @param transition
- @param href
- */
- _eagerUpdateUrl: function(transition, href) {
- if (!transition.isActive || !transition.urlMethod) {
- // transition was aborted, already ran to completion,
- // or it has a null url-updated method.
- return;
- }
-
- if (href.indexOf('#') === 0) {
- href = href.slice(1);
- }
-
- // Re-use the routerjs hooks set up by the Ember router.
- var routerjs = get(this, 'router.router');
- if (transition.urlMethod === 'update') {
- routerjs.updateURL(href);
- } else if (transition.urlMethod === 'replace') {
- routerjs.replaceURL(href);
- }
-
- // Prevent later update url refire.
- transition.method(null);
+ var params = get(this, 'loadedParams');
+ get(this, '_routing').transitionTo(params.targetRouteName, params.models, params.queryParams, get(this, 'replace'));
},
/**
@@ -449,15 +372,14 @@ var LinkView = EmberComponent.extend({
@property
@return {Array}
*/
- resolvedParams: computed('router.url', function() {
+ resolvedParams: computed('_routing.currentState', function() {
var params = this.params;
var targetRouteName;
var models = [];
var onlyQueryParamsSupplied = (params.length === 0);
if (onlyQueryParamsSupplied) {
- var appController = this.container.lookup('controller:application');
- targetRouteName = get(appController, 'currentRouteName');
+ targetRouteName = get(this, '_routing.currentRouteName');
} else {
targetRouteName = read(params[0]);
@@ -486,8 +408,8 @@ var LinkView = EmberComponent.extend({
@return {Array} An array with the route name and any dynamic segments
**/
loadedParams: computed('resolvedParams', function computeLinkViewRouteArgs() {
- var router = get(this, 'router');
- if (!router) { return; }
+ var routing = get(this, '_routing');
+ if (!routing) { return; }
var resolvedParams = get(this, 'resolvedParams');
var namedRoute = resolvedParams.targetRouteName;
@@ -496,8 +418,8 @@ var LinkView = EmberComponent.extend({
Ember.assert(fmt("The attempt to link-to route '%@' failed. " +
"The router did not find '%@' in its possible routes: '%@'",
- [namedRoute, namedRoute, keys(router.router.recognizer.names).join("', '")]),
- router.hasRoute(namedRoute));
+ [namedRoute, namedRoute, routing.availableRoutes().join("', '")]),
+ routing.hasRoute(namedRoute));
if (!paramsAreLoaded(resolvedParams.models)) { return; }
@@ -518,20 +440,14 @@ var LinkView = EmberComponent.extend({
href: computed('loadedParams', function computeLinkViewHref() {
if (get(this, 'tagName') !== 'a') { return; }
- var router = get(this, 'router');
- var loadedParams = get(this, 'loadedParams');
+ var routing = get(this, '_routing');
+ var params = get(this, 'loadedParams');
- if (!loadedParams) {
+ if (!params) {
return get(this, 'loadingHref');
}
- var visibleQueryParams = {};
- merge(visibleQueryParams, loadedParams.queryParams);
- router._prepareQueryParams(loadedParams.targetRouteName, loadedParams.models, visibleQueryParams);
-
- var args = routeArgs(loadedParams.targetRouteName, loadedParams.models, visibleQueryParams);
- var result = router.generate.apply(router, args);
- return result;
+ return routing.generateURL(params.targetRouteName, params.models, params.queryParams);
}),
/**
@@ -572,46 +488,26 @@ function paramsAreLoaded(params) {
return true;
}
-function computeActive(route, routerState) {
- if (get(route, 'loading')) { return false; }
+function computeActive(view, routerState) {
+ if (get(view, 'loading')) { return false; }
- var currentWhen = route['current-when'] || route.currentWhen;
+ var currentWhen = view['current-when'] || view.currentWhen;
var isCurrentWhenSpecified = !!currentWhen;
- currentWhen = currentWhen || get(route, 'loadedParams').targetRouteName;
+ currentWhen = currentWhen || get(view, 'loadedParams').targetRouteName;
currentWhen = currentWhen.split(' ');
for (var i = 0, len = currentWhen.length; i < len; i++) {
- if (isActiveForRoute(route, currentWhen[i], isCurrentWhenSpecified, routerState)) {
- return get(route, 'activeClass');
+ if (isActiveForRoute(view, currentWhen[i], isCurrentWhenSpecified, routerState)) {
+ return get(view, 'activeClass');
}
}
return false;
}
-function isActiveForRoute(route, routeName, isCurrentWhenSpecified, routerState) {
- var router = get(route, 'router');
- var loadedParams = get(route, 'loadedParams');
- var contexts = loadedParams.models;
-
- var handlers = router.router.recognizer.handlersFor(routeName);
- var leafName = handlers[handlers.length-1].handler;
- var maximumContexts = numberOfContextsAcceptedByHandler(routeName, handlers);
-
- // NOTE: any ugliness in the calculation of activeness is largely
- // due to the fact that we support automatic normalizing of
- // `resource` -> `resource.index`, even though there might be
- // dynamic segments / query params defined on `resource.index`
- // which complicates (and makes somewhat ambiguous) the calculation
- // of activeness for links that link to `resource` instead of
- // directly to `resource.index`.
-
- // if we don't have enough contexts revert back to full route name
- // this is because the leaf route will use one of the contexts
- if (contexts.length > maximumContexts) {
- routeName = leafName;
- }
-
- return routerState.isActiveIntent(routeName, contexts, loadedParams.queryParams, !isCurrentWhenSpecified);
+function isActiveForRoute(view, routeName, isCurrentWhenSpecified, routerState) {
+ var params = get(view, 'loadedParams');
+ var service = get(view, '_routing');
+ return service.isActiveForRoute(params, routeName, routerState, isCurrentWhenSpecified);
}
export {
diff --git a/packages/ember-routing/lib/initializers/routing-service.js b/packages/ember-routing/lib/initializers/routing-service.js
new file mode 100644
index 00000000000..b9e9f3837da
--- /dev/null
+++ b/packages/ember-routing/lib/initializers/routing-service.js
@@ -0,0 +1,14 @@
+import { onLoad } from "ember-runtime/system/lazy_load";
+import RoutingService from "ember-routing/services/routing";
+
+onLoad('Ember.Application', function(Application) {
+ Application.initializer({
+ name: 'routing-service',
+ initialize: function(registry) {
+ // Register the routing service...
+ registry.register('service:-routing', RoutingService);
+ // Then inject the app router into it
+ registry.injection('service:-routing', 'router', 'router:main');
+ }
+ });
+});
diff --git a/packages/ember-routing/lib/main.js b/packages/ember-routing/lib/main.js
index 93e620bf82d..bc448775837 100644
--- a/packages/ember-routing/lib/main.js
+++ b/packages/ember-routing/lib/main.js
@@ -27,6 +27,8 @@ import RouterDSL from "ember-routing/system/dsl";
import Router from "ember-routing/system/router";
import Route from "ember-routing/system/route";
+import "ember-routing/initializers/routing-service";
+
Ember.Location = EmberLocation;
Ember.AutoLocation = AutoLocation;
Ember.HashLocation = HashLocation;
diff --git a/packages/ember-routing/lib/services/routing.js b/packages/ember-routing/lib/services/routing.js
new file mode 100644
index 00000000000..613551c4399
--- /dev/null
+++ b/packages/ember-routing/lib/services/routing.js
@@ -0,0 +1,102 @@
+/**
+@module ember
+@submodule ember-routing
+*/
+
+import Service from "ember-runtime/system/service";
+
+import { get } from "ember-metal/property_get";
+import { readOnly } from "ember-metal/computed_macros";
+import { routeArgs } from "ember-routing/utils";
+import keys from "ember-metal/keys";
+import merge from "ember-metal/merge";
+
+/** @private
+ The Routing service is used by LinkView, and provides facilities for
+ the component/view layer to interact with the router.
+
+ While still private, this service can eventually be opened up, and provides
+ the set of API needed for components to control routing without interacting
+ with router internals.
+*/
+
+var RoutingService = Service.extend({
+ router: null,
+
+ targetState: readOnly('router.targetState'),
+ currentState: readOnly('router.currentState'),
+ currentRouteName: readOnly('router.currentRouteName'),
+
+ availableRoutes: function() {
+ return keys(get(this, 'router').router.recognizer.names);
+ },
+
+ hasRoute: function(routeName) {
+ return get(this, 'router').hasRoute(routeName);
+ },
+
+ transitionTo: function(routeName, models, queryParams, shouldReplace) {
+ var router = get(this, 'router');
+
+ var transition = router._doTransition(routeName, models, queryParams);
+
+ if (shouldReplace) {
+ transition.method('replace');
+ }
+ },
+
+ normalizeQueryParams: function(routeName, models, queryParams) {
+ get(this, 'router')._prepareQueryParams(routeName, models, queryParams);
+ },
+
+ generateURL: function(routeName, models, queryParams) {
+ var router = get(this, 'router');
+
+ var visibleQueryParams = {};
+ merge(visibleQueryParams, queryParams);
+
+ this.normalizeQueryParams(routeName, models, visibleQueryParams);
+
+ var args = routeArgs(routeName, models, visibleQueryParams);
+ return router.generate.apply(router, args);
+ },
+
+ isActiveForRoute: function(params, routeName, routerState, isCurrentWhenSpecified) {
+ var router = get(this, 'router');
+ var contexts = params.models;
+
+ var handlers = router.router.recognizer.handlersFor(routeName);
+ var leafName = handlers[handlers.length-1].handler;
+ var maximumContexts = numberOfContextsAcceptedByHandler(routeName, handlers);
+
+ // NOTE: any ugliness in the calculation of activeness is largely
+ // due to the fact that we support automatic normalizing of
+ // `resource` -> `resource.index`, even though there might be
+ // dynamic segments / query params defined on `resource.index`
+ // which complicates (and makes somewhat ambiguous) the calculation
+ // of activeness for links that link to `resource` instead of
+ // directly to `resource.index`.
+
+ // if we don't have enough contexts revert back to full route name
+ // this is because the leaf route will use one of the contexts
+ if (contexts.length > maximumContexts) {
+ routeName = leafName;
+ }
+
+ return routerState.isActiveIntent(routeName, contexts, params.queryParams, !isCurrentWhenSpecified);
+ }
+});
+
+var numberOfContextsAcceptedByHandler = function(handler, handlerInfos) {
+ var req = 0;
+ for (var i = 0, l = handlerInfos.length; i < l; i++) {
+ req = req + handlerInfos[i].names.length;
+ if (handlerInfos[i].handler === handler) {
+ break;
+ }
+ }
+
+ return req;
+};
+
+export default RoutingService;
diff --git a/packages/ember-routing/lib/system/router.js b/packages/ember-routing/lib/system/router.js
index 4863c41c52b..32688ef03a5 100644
--- a/packages/ember-routing/lib/system/router.js
+++ b/packages/ember-routing/lib/system/router.js
@@ -875,12 +875,14 @@ function updatePaths(router) {
}
set(appController, 'currentPath', path);
+ set(router, 'currentPath', path);
if (!('currentRouteName' in appController)) {
defineProperty(appController, 'currentRouteName');
}
set(appController, 'currentRouteName', infos[infos.length - 1].name);
+ set(router, 'currentRouteName', infos[infos.length - 1].name);
}
EmberRouter.reopenClass({