diff --git a/FEATURES.md b/FEATURES.md index b5769099cfb..6f5d41bbdba 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -96,3 +96,10 @@ for a detailed explanation. inherit from `Ember.ObjectProxy` directly. Added in [#5156](https://github.com/emberjs/ember.js/pull/5156) + +* `ember-routing-multi-current-when` + + Allows the `link-to` helper's currentWhen property to accept multiple routes + using a `|` delimiter, for more control over a link's active state. + + Added in [#3673](https://github.com/emberjs/ember.js/pull/3673) diff --git a/features.json b/features.json index 2663fd3cd11..ca495994a03 100644 --- a/features.json +++ b/features.json @@ -8,6 +8,7 @@ "ember-routing-linkto-target-attribute": null, "ember-routing-will-change-hooks": null, "ember-routing-consistent-resources": true, + "ember-routing-multi-current-when": null, "event-dispatcher-can-disable-event-manager": null, "ember-metal-is-present": null, "property-brace-expansion-improvement": null, diff --git a/packages/ember-routing-handlebars/lib/helpers/link_to.js b/packages/ember-routing-handlebars/lib/helpers/link_to.js index 50226019aca..03835837070 100644 --- a/packages/ember-routing-handlebars/lib/helpers/link_to.js +++ b/packages/ember-routing-handlebars/lib/helpers/link_to.js @@ -290,47 +290,67 @@ var LinkView = Ember.LinkView = EmberComponent.extend({ or the application's current route is the route the `LinkView` would trigger transitions into. + The `currentWhen` property can match against multiple routes by separating + route names using the `|` character. + @property active **/ active: computed('loadedParams', function computeLinkViewActive() { if (get(this, 'loading')) { return false; } - var router = get(this, 'router'), - loadedParams = get(this, 'loadedParams'), - contexts = loadedParams.models, - currentWhen = this.currentWhen || loadedParams.targetRouteName, - handlers = router.router.recognizer.handlersFor(currentWhen), - leafName = handlers[handlers.length-1].handler, - maximumContexts = numberOfContextsAcceptedByHandler(currentWhen, 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) { - currentWhen = leafName; - } + var router = get(this, 'router'); + var loadedParams = get(this, 'loadedParams'); + var contexts = loadedParams.models; + var currentWhen = this.currentWhen; + var isCurrentWhenSpecified = Boolean(currentWhen); + currentWhen = currentWhen || loadedParams.targetRouteName; + + function isActiveForRoute(routeName) { + 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; + } - var args = routeArgs(currentWhen, contexts, null); - var isActive = router.isActive.apply(router, args); - if (!isActive) { return false; } + var args = routeArgs(routeName, contexts, null); + var isActive = router.isActive.apply(router, args); + if (!isActive) { return false; } - if (Ember.FEATURES.isEnabled("query-params-new")) { - if (!this.currentWhen && leafName === loadedParams.targetRouteName) { - var visibleQueryParams = {}; - merge(visibleQueryParams, loadedParams.queryParams); - router._prepareQueryParams(loadedParams.targetRouteName, loadedParams.models, visibleQueryParams); - isActive = shallowEqual(visibleQueryParams, router.router.state.queryParams); + if (Ember.FEATURES.isEnabled("query-params-new")) { + if (!isCurrentWhenSpecified && leafName === loadedParams.targetRouteName) { + var visibleQueryParams = {}; + merge(visibleQueryParams, loadedParams.queryParams); + router._prepareQueryParams(loadedParams.targetRouteName, loadedParams.models, visibleQueryParams); + isActive = shallowEqual(visibleQueryParams, router.router.state.queryParams); + } } + return isActive; } - if (isActive) { return get(this, 'activeClass'); } + if (Ember.FEATURES.isEnabled("ember-routing-multi-current-when")) { + currentWhen = currentWhen.split('|'); + for (var i = 0, len = currentWhen.length; i < len; i++) { + if (isActiveForRoute(currentWhen[i])) { + return get(this, 'activeClass'); + } + } + } else { + if (isActiveForRoute(currentWhen)) { + return get(this, 'activeClass'); + } + } }), /** diff --git a/packages/ember/tests/helpers/link_to_test.js b/packages/ember/tests/helpers/link_to_test.js index e8026a24c45..4270ae83c70 100644 --- a/packages/ember/tests/helpers/link_to_test.js +++ b/packages/ember/tests/helpers/link_to_test.js @@ -311,6 +311,43 @@ test("The {{link-to}} helper does not disregard currentWhen when it is given exp equal(Ember.$('#other-link.active', '#qunit-fixture').length, 1, "The link is active when currentWhen is given for explicitly for a resource"); }); +if (Ember.FEATURES.isEnabled("ember-routing-multi-current-when")) { + test("The {{link-to}} helper supports multiple currentWhen routes", function() { + Router.map(function(match) { + this.resource("index", { path: "/" }, function() { + this.route("about"); + }); + this.route("item"); + this.route("foo"); + }); + + Ember.TEMPLATES.index = Ember.Handlebars.compile("

Home

{{outlet}}"); + Ember.TEMPLATES['index/about'] = Ember.Handlebars.compile("{{#link-to 'item' id='link1' currentWhen='item|index'}}ITEM{{/link-to}}"); + Ember.TEMPLATES['item'] = Ember.Handlebars.compile("{{#link-to 'item' id='link2' currentWhen='item|index'}}ITEM{{/link-to}}"); + Ember.TEMPLATES['foo'] = Ember.Handlebars.compile("{{#link-to 'item' id='link3' currentWhen='item|index'}}ITEM{{/link-to}}"); + + bootApplication(); + + Ember.run(function() { + router.handleURL("/about"); + }); + + equal(Ember.$('#link1.active', '#qunit-fixture').length, 1, "The link is active since currentWhen contains the parent route"); + + Ember.run(function() { + router.handleURL("/item"); + }); + + equal(Ember.$('#link2.active', '#qunit-fixture').length, 1, "The link is active since you are on the active route"); + + Ember.run(function() { + router.handleURL("/foo"); + }); + + equal(Ember.$('#link3.active', '#qunit-fixture').length, 0, "The link is not active since currentWhen does not contain the active route"); + }); +} + test("The {{link-to}} helper defaults to bubbling", function() { Ember.TEMPLATES.about = Ember.Handlebars.compile("
{{#link-to 'about.contact' id='about-contact'}}About{{/link-to}}
{{outlet}}"); Ember.TEMPLATES['about/contact'] = Ember.Handlebars.compile("

Contact

");