diff --git a/README.md b/README.md index c29bc60e..6ff9f171 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,23 @@ You can also mix and match the parameter styles, however you like. {{/let}} ``` +##### `fromURL` + +Instead of the positional & named link parameters described above, you can also +create a `Link` instance from a serialized URL. + +```hbs +{{! someURL = "/blogs/tech/posts/dont-break-the-web" }} +{{#let (link fromURL=this.someURL) as |l|}} + + Read the next great post. + +{{/let}} +``` + +`fromURL` is mutually exclusive with the other link parameters: `route`, `model` +& `models`, `query` + ### Parameters In addition to the parameters shown above, the `{{link}}` helper also accepts a @@ -280,6 +297,25 @@ Query Params object. {{/link-to}} ``` +##### `@fromURL` + +Optional. Mutually exclusive with [`@route`](#route), [`@model`](#model) / +[`@models`](#models), [`@query`](#query). + +**Example** + +```hbs + + + Click me + + +``` + ##### `@preventDefault` Optional. Default: `true` diff --git a/addon/components/link/template.hbs b/addon/components/link/template.hbs index dce2be7c..6a8050fd 100644 --- a/addon/components/link/template.hbs +++ b/addon/components/link/template.hbs @@ -4,6 +4,7 @@ models=@models model=@model query=@query + fromURL=@fromURL preventDefault=@preventDefault ) ~}} \ No newline at end of file diff --git a/addon/helpers/link.ts b/addon/helpers/link.ts index 8ba823f9..665958b6 100644 --- a/addon/helpers/link.ts +++ b/addon/helpers/link.ts @@ -22,6 +22,14 @@ export interface LinkHelperNamedParams * Optional shortcut for `models={{array model}}`. */ model?: RouteModel; + + /** + * Instead of any of the other `LinkParams` used to construct a + * `LinkInstance`, you can also provide a serialized URL instead. + * + * This is mutually exclusive with any other `LinkParams`. + */ + fromURL?: string; } export default class LinkHelper extends Helper { @@ -47,6 +55,20 @@ export default class LinkHelper extends Helper { !('routeName' in named) ); + if (named.fromURL) { + assert( + `When specifying a serialized 'fromURL' ('${named.fromURL}'), you can't provide any further 'LinkParams'.`, + !([ + 'route', + 'models', + 'model', + 'query' + ] as (keyof LinkHelperNamedParams)[]).some(name => named[name]) + ); + + return this.linkManager.getLinkPramsFromURL(named.fromURL); + } + assert( `Either pass the target route name as a positional parameter ('${positional[0]}') or pass it as a named parameter ('${named.route}').`, !(positional[0] && named.route) diff --git a/addon/services/link-manager.ts b/addon/services/link-manager.ts index 643507e1..4a0a91f7 100644 --- a/addon/services/link-manager.ts +++ b/addon/services/link-manager.ts @@ -1,5 +1,6 @@ import { action } from '@ember/object'; import { addListener, removeListener } from '@ember/object/events'; +import RouteInfo from '@ember/routing/-private/route-info'; import Transition from '@ember/routing/-private/transition'; import RouterService from '@ember/routing/router-service'; import Service from '@ember/service'; @@ -8,6 +9,10 @@ import { tracked } from '@glimmer/tracking'; import Link, { LinkParams, UILinkParams, UILink } from '../link'; +interface RouterServiceWithRecognize extends RouterService { + recognize(url: string): RouteInfo; +} + export default class LinkManagerService extends Service { @tracked private _currentTransitionStack?: Transition[]; @@ -16,7 +21,7 @@ export default class LinkManagerService extends Service { * The `RouterService` instance to be used by the generated `Link` instances. */ @service('router') - readonly router!: RouterService; + readonly router!: RouterServiceWithRecognize; /** * Whether the router has been initialized. @@ -50,6 +55,29 @@ export default class LinkManagerService extends Service { return new UILink(this, { ...linkParams, ...uiParams }); } + /** + * Deserializes the `LinkParams` to be passed to `createLink` / `createUILink` + * from a URL. + * + * If the URL cannot be recognized by the router, an error is thrown. + */ + getLinkPramsFromURL(url: string): LinkParams { + const routeInfo = this.router.recognize(url); + return LinkManagerService.getLinkParamsFromRouteInfo(routeInfo); + } + + /** + * Converts a `RouteInfo` object into `LinkParams`. + */ + static getLinkParamsFromRouteInfo(routeInfo: RouteInfo): LinkParams { + const models = routeInfo.paramNames.map(name => routeInfo.params[name]!); + return { + route: routeInfo.name, + query: routeInfo.queryParams, + models + }; + } + constructor(properties?: object) { super(properties); diff --git a/tests/acceptance/link-test.ts b/tests/acceptance/link-test.ts index b48338ce..b1c78db6 100644 --- a/tests/acceptance/link-test.ts +++ b/tests/acceptance/link-test.ts @@ -421,20 +421,19 @@ module('Acceptance | link', function(hooks) { ); }); - test('toString()', async function(this: TestContext, assert) { + test('fromURL', async function(this: TestContext, assert) { this.Router.map(function() { - this.route('foo'); - this.route('bar'); + this.route('foo', { path: 'foo/:id' }); }); this.owner.register( 'template:application', - hbs`{{get (link "foo" query=(hash bar=(link "bar"))) "url"}}` + hbs`{{get (link fromURL="/foo/123?bar=qux") "url"}}` ); await visit('/'); assert.equal(currentURL(), '/'); - assert.dom().hasText('/foo?bar=%2Fbar'); + assert.dom().hasText('/foo/123?bar=qux'); }); });