From d40cff4d6e955b2b8acea690b650b13bb305cd99 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli Date: Tue, 8 Oct 2019 21:19:29 -0400 Subject: [PATCH 01/31] begin --- text/0000-url-primitives.md | 74 +++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 text/0000-url-primitives.md diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md new file mode 100644 index 0000000000..5f31a1563c --- /dev/null +++ b/text/0000-url-primitives.md @@ -0,0 +1,74 @@ +- Start Date: (fill me in with today's date, YYYY-MM-DD) +- Relevant Team(s): (fill this in with the [team(s)](README.md#relevant-teams) to which this RFC applies) +- RFC PR: (after opening the RFC PR, update this with a link to it and update the file name) +- Tracking: (leave this empty) + +# URL Primitives + +Supercedes [Add queryParams to the router service](https://github.com/emberjs/rfcs/pull/380) + +Notes from rwjblue: + +This RFC should include a bit more detail: + +a comprehensive list of the ways the current QP system can be used (the use cases at least), and how you would do the same things in the new APIs +examples of how we would teach the new API (e.g. for the guides) +examples of how current QP related addons would plausibly function over the new APIs + +I'd also like to see an exploration of why the core has to do this at all, what would a minimal API look like to allow this entire RFC to be implementable in user space? From my perspective, there are a few fundamental things that might unblock that: + +adding a public API to access Router.map defined metadata +adding a hook that can be used to customize the target of all .transitionTo / .replaceWith invocations (intercepting the QP properties, and setting state on the QP service) +adding a hook that can be used to customize how a URL is translated back into router params (intercepting the URL string and setting state on the QP service) +I'm willing to accept that this might be naive, but it seems like an interesting (smaller, more customizable, and easier to implement) alternative... + +## Summary + +> One paragraph explanation of the feature. + +## Motivation + +> Why are we doing this? What use cases does it support? What is the expected +outcome? + +## Detailed design + +> This is the bulk of the RFC. + +> Explain the design in enough detail for somebody +familiar with the framework to understand, and for somebody familiar with the +implementation to implement. This should get into specifics and corner-cases, +and include examples of how the feature is used. Any new terminology should be +defined here. + +## How we teach this + +> What names and terminology work best for these concepts and why? How is this +idea best presented? As a continuation of existing Ember patterns, or as a +wholly new one? + +> Would the acceptance of this proposal mean the Ember guides must be +re-organized or altered? Does it change how Ember is taught to new users +at any level? + +> How should this feature be introduced and taught to existing Ember +users? + +## Drawbacks + +> Why should we *not* do this? Please consider the impact on teaching Ember, +on the integration of this feature with other existing and planned features, +on the impact of the API churn on existing apps, etc. + +> There are tradeoffs to choosing any path, please attempt to identify them here. + +## Alternatives + +> What other designs have been considered? What is the impact of not doing this? + +> This section could also include prior art, that is, how other frameworks in the same domain have solved this problem. + +## Unresolved questions + +> Optional, but suggested for first drafts. What parts of the design are still +TBD? From 63b42f12d1f28ec4f1a36cb04f5ee42c742cedfd Mon Sep 17 00:00:00 2001 From: NullVoxPopuli Date: Mon, 23 Dec 2019 11:30:14 -0500 Subject: [PATCH 02/31] fleshing out the API --- text/0000-url-primitives.md | 144 +++++++++++++++++++++++++++++------- 1 file changed, 117 insertions(+), 27 deletions(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index 5f31a1563c..604b336bff 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -3,43 +3,133 @@ - RFC PR: (after opening the RFC PR, update this with a link to it and update the file name) - Tracking: (leave this empty) -# URL Primitives +# URL Manager -Supercedes [Add queryParams to the router service](https://github.com/emberjs/rfcs/pull/380) - -Notes from rwjblue: - -This RFC should include a bit more detail: - -a comprehensive list of the ways the current QP system can be used (the use cases at least), and how you would do the same things in the new APIs -examples of how we would teach the new API (e.g. for the guides) -examples of how current QP related addons would plausibly function over the new APIs - -I'd also like to see an exploration of why the core has to do this at all, what would a minimal API look like to allow this entire RFC to be implementable in user space? From my perspective, there are a few fundamental things that might unblock that: - -adding a public API to access Router.map defined metadata -adding a hook that can be used to customize the target of all .transitionTo / .replaceWith invocations (intercepting the QP properties, and setting state on the QP service) -adding a hook that can be used to customize how a URL is translated back into router params (intercepting the URL string and setting state on the QP service) -I'm willing to accept that this might be naive, but it seems like an interesting (smaller, more customizable, and easier to implement) alternative... +Supersedes [Add queryParams to the router service](https://github.com/emberjs/rfcs/pull/380) ## Summary -> One paragraph explanation of the feature. +An URL Manager will provide the ability to manipulate the URL -- +both when writing to the `window.location` (serializing), +and reading from the `window.location` (deserializing). +The ultimate goal of the URL Manager will be to enable +app and addon authors to customize the use of the URL -- +including i18n'd routes, dynamic segment slugs / aliases, +and alternate query params behavior -- though, +implementation of those specific things is outside the scope of *this* RFC. ## Motivation -> Why are we doing this? What use cases does it support? What is the expected -outcome? +There is currently no way to alter the router's behavior around the interpretation of the URL. +This means that we are unable to provide internationalized URLs, +we cannot customize the (de)serialization of query params +(which is important because there is no standard format for query params). -## Detailed design +These APIs will also provide an opportunity to iterate on a better default query params implementation that will enable an objectively better development experience for app devs +when interacting with query params. -> This is the bulk of the RFC. +The current URL behavior will be preserved, should the URL Manager be implemented. + +## Detailed design -> Explain the design in enough detail for somebody -familiar with the framework to understand, and for somebody familiar with the -implementation to implement. This should get into specifics and corner-cases, -and include examples of how the feature is used. Any new terminology should be -defined here. +The implementation of the URL Manager is 2 parts: + - the URL Manager itself + - access to the URL Manager / Router.map data from framework-objects + - required for preserving existing query params behavior where query params are, by default, allow-list only. + + +### Examples + +Custom URL Manager that implements i18n routes +```ts +// app/router.js/ts +import EmberRouter, { URLManager } from '@ember/routing/router'; +import { inject as service } from '@ember/service'; +import config from 'app-name/config/environment'; +import qs from 'qs'; + + +class CustomURLManager extends URLManager { + // current language: Korean + @service i18n; + @service router; + + // interprets the URL + deserialize(url: string): RouteInfo { // /블로그/1/게시물/2?foo=bar + let [path, query] = url.split('?'); + let segments = path.split('/'); + let english = + path.split('/') + .segments.map(segment => { + return this.i18n.lookup(`routes.from.${segment}`, 'en-us') || segment; + }) + .join('/'); + + let routeInfo = this.router.recognize(english); + let queryParams = qs.parse(query); + + return { + ...routeInfo, + queryParams, + }; + } + + // pushes the state to the URL + serialize(routeInfo: RouteInfo, dynamicSegments: object) { + let { + mapInfo: { + segments // [blogs, :blogId, posts, :postId] + }, // MapInfo + queryParams, // { foo: 'bar' } + name, // blog.posts + } = routeInfo; + + let url = segments.map(segment => { + return dynamicSegments[segment] || this.i18n.t(`routes.${segment}`); + }).join('/'); + + let query = qs.stringify(queryParams); + + return `/${url}?${query}`; // => /블로그/1/게시물/2?foo=bar + } +} + +export default class Router extends EmberRouter { + location = config.locationType; + rootURL = config.rootURL; + urlManager = CustomURLManager; +}); + +Router.map(function() { + +}); +``` + +**Accessing Router Map Info** +[RouteInfo](https://api.emberjs.com/ember/release/classes/RouteInfo) + - added property: `mapInfo`: + ```ts + interface MapInfo { + name: string; + segments: string[]; // [blogs, :blogId, posts, :postId] + options: { + path: string; + + }; + childRoutes: MapInfo[] + } + ``` + + +```ts +class Post extends Component { + @service router; + + get dynamicSegments() { + this.router.routeInfo.mapInfo.options; + } +} +``` ## How we teach this From 0a5ef0005e470185ab2463cb53d81d386b0ec329 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli Date: Thu, 26 Dec 2019 11:41:17 -0500 Subject: [PATCH 03/31] draft complete. need to verify existing capabilities --- text/0000-url-primitives.md | 289 +++++++++++++++++++++++++++++------- 1 file changed, 237 insertions(+), 52 deletions(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index 604b336bff..eb26db7268 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -5,6 +5,8 @@ # URL Manager +TODO: check if routeInfo or route has a way to build a URL from routeInfo (need full dynamic segments) + Supersedes [Add queryParams to the router service](https://github.com/emberjs/rfcs/pull/380) ## Summary @@ -28,36 +30,111 @@ we cannot customize the (de)serialization of query params These APIs will also provide an opportunity to iterate on a better default query params implementation that will enable an objectively better development experience for app devs when interacting with query params. -The current URL behavior will be preserved, should the URL Manager be implemented. +If no URL Manager is configured, Ember's current URL behavior will be preserved. -## Detailed design -The implementation of the URL Manager is 2 parts: - - the URL Manager itself - - access to the URL Manager / Router.map data from framework-objects - - required for preserving existing query params behavior where query params are, by default, allow-list only. +## Examples +The following examples of the proposed API are written in TypeScript in _userland / application-space_ to better +demonstrate intended API and available data. -### Examples +The same naming semantics and conventions in today's router system will still apply. -Custom URL Manager that implements i18n routes +All examples will have the following in common: ```ts -// app/router.js/ts +// router.js import EmberRouter, { URLManager } from '@ember/routing/router'; -import { inject as service } from '@ember/service'; import config from 'app-name/config/environment'; + +// ... Example CustomURLManager here ... + +export default class Router extends EmberRouter { + location = config.locationType; + rootURL = config.rootURL; + urlManager = CustomURLManager; // Defined in Examples +}); + +Router.map(function() { + this.route('blogs', function() { + // index route would be the "list" of blogs + this.route('blog', { path: ':id' }, function() { + this.route('posts', function() { + // index route would be the "list" of posts + this.route('post', { path: ':id' }); + }) + }); + }) +}); +``` + + +### Naiive Query Params without Controllers + +This example proposes the possibility of an alternate strategy for managing query params without the need for controllers. +Note that this would not change or alter the existing query param behavior in any way. + + +```ts +// app/router.js/ts +import { inject as service } from '@ember/service'; import qs from 'qs'; +class CustomURLManager extends URLManager { + @service router; + + fromURL(url: string): RouteInfo { // /blogs/1/posts/2?foo=bar + let [path, query] = url.split('?'); + + let routeInfo = this.router.recognize(path); + // Because query params have no standardized way of (de)serialization, + // there has been no way to transform deep objects or arrays. + // This gives control over this process, allowing existing code that hacked + // around this limitation to be deleted. + let queryParams = qs.parse(query); + + return { + ...routeInfo, + queryParams, + }; + } + toURL(routeInfo: RouteInfo) { + let { + mapInfo: { + segments // [blogs, :blogId, posts, :postId] + }, + queryParams, // { foo: 'bar' } + dynamicSegments, + } = routeInfo; + + let url = segments.map(segment => dynamicSegments[segment] || segment).join('/'); + + let query = qs.stringify(queryParams); + + return `/${url}?${query}`; // => /blogs/1/posts/2?foo=bar + } +} +``` + +### i18n routes +In this scenario, we may want to internationalized route segments. + +For example, in English, we may want a route to be `/blogs/1/posts/2`, +but in Korean, `/블로그/1/게시물/2`. + +```ts +// app/router.js/ts class CustomURLManager extends URLManager { // current language: Korean @service i18n; @service router; - // interprets the URL - deserialize(url: string): RouteInfo { // /블로그/1/게시물/2?foo=bar + fromURL(url: string): RouteInfo { // /블로그/1/게시물/2?foo=bar let [path, query] = url.split('?'); let segments = path.split('/'); + + // this dosen't have to be english, but it does have to match the names as + // defined in Router.map(...); let english = path.split('/') .segments.map(segment => { @@ -74,14 +151,13 @@ class CustomURLManager extends URLManager { }; } - // pushes the state to the URL - serialize(routeInfo: RouteInfo, dynamicSegments: object) { + toURL(routeInfo: RouteInfo) { let { - mapInfo: { - segments // [blogs, :blogId, posts, :postId] - }, // MapInfo - queryParams, // { foo: 'bar' } - name, // blog.posts + mapInfo: { + segments // [blogs, :blogId, posts, :postId] + }, + queryParams, // { foo: 'bar' } + dynamicSegments, } = routeInfo; let url = segments.map(segment => { @@ -94,71 +170,180 @@ class CustomURLManager extends URLManager { } } -export default class Router extends EmberRouter { - location = config.locationType; - rootURL = config.rootURL; - urlManager = CustomURLManager; -}); -Router.map(function() { +``` + +### Custom URL Management per-route +There are situations in which links and their routes don't conform to the rest of the application. For these situations, here is an example showing how manage the URL separately for those routes. +```ts +// app/router.js/ts +Router.map(function() { + this.route('no-query-params', { + fromURL(url: string): RouteInfo { + let [path, query] = url.split('?'); + + return this.router.recognize(path); + }, + toURL(routeInfo: RouteInfo, dynamicSegments: object) { + return this.router.urlFor({routeInfo, queryParams: undefined }); + } + }); + // this.route('posts', ...) }); + +class CustomURLManager extends URLManager { + @service router; + + fromURL(url: string): RouteInfo { + let [path, query] = url.split('?'); + // NOTE: this method could be named anything, as long as it matches + // what is in the options / mapInfo object of the Router.map + let { fromURL } = this.router.mapInfoFrom(path) + + if (fromURL) return fromURL(url); + + // ... + } + + toURL(routeInfo: RouteInfo, dynamicSegments: object) { + let { mapInfo: { toURL } } = routeInfo; + + if (toURL) return toURL(routeInfo, dynamicSegments); + + // ... + } +} ``` -**Accessing Router Map Info** -[RouteInfo](https://api.emberjs.com/ember/release/classes/RouteInfo) - - added property: `mapInfo`: +## Detailed design + +### Additions to existing Public APIs +1. add full list of dynamicSegments to RouteInfo so that the task of building out the map of dynamic segments to their values isn't in user-space +2. Restriction of Query Params on controllers / `` needs to have a way to opt-out. + Today, if a query param is added to a `` and that query param is not present on the target `route`'s controller, the query param is removed from the link. + Query Param allow/deny lists could be re-implemented using the Router `MapInfo` / options. +3. The router's urlFor should be able to take a RouteInfo / RouteInfo should be able to be converted to an URL +4. Static `MapInfo` object reference added to each `RouteInfo`. +5. routerService.mapInfoFrom should take: path / url / routeInfo -- uses existing recognize method + + +### Changes to Internals +1. `` and other related router helpers use `toURL` and `fromURL` from the URL Manager + This allows more control over the allow/deny list behavior of query params filtering. + +### Additional APIs / Behavior +1. URLManager is a container-controlled object, so that it may utilize the dependency injection system. + Primary need for this is to access the router service to utilize existing APIs for transforming URLs and `RouteInfo`s + +### Notes +1. `MapInfo` is static or "frozen" -- it is only constructed at the time of router setup. +2. `MapInfo` represents the optional `object` argument of `Router.map`'s `this.route`. Empty object if not configured. +3. If needed, the URL Manager may be used from a component: ```ts - interface MapInfo { - name: string; - segments: string[]; // [blogs, :blogId, posts, :postId] - options: { - path: string; - - }; - childRoutes: MapInfo[] + class Post extends Component { + @service router; + + get urlForCurrentRoute_butTheLongWay() { + // the same as this.router.currentURL (if currentURL also had queryParams) + return this.router.urlManager.toURL(this.router.routeInfo); + } } ``` +### The Default URL Manager + +Once implemented, the URL behavior, by default, will function as before the URL Manager -- in that the standard router.js script: ```ts -class Post extends Component { - @service router; +// router.js +import EmberRouter from '@ember/routing/router'; +import config from 'app-name/config/environment'; - get dynamicSegments() { - this.router.routeInfo.mapInfo.options; - } -} +export default class Router extends EmberRouter { + location = config.locationType; + rootURL = config.rootURL; +}); + +Router.map(...); ``` +is sufficient for maintaining backwards compatibility. + ## How we teach this > What names and terminology work best for these concepts and why? How is this idea best presented? As a continuation of existing Ember patterns, or as a wholly new one? +Like the component-manager, and modifier-manager, +this should be considered a low-level API that most people shouldn't need to interact with. +Addon authors may implement different URL-handling techniques and export an URL manager +for app-devs to assign in the router.js file. + +Maybe after a bit of exploration (maybe of query params, specifically), a particular approach may be pulled in to Ember. + > Would the acceptance of this proposal mean the Ember guides must be re-organized or altered? Does it change how Ember is taught to new users at any level? -> How should this feature be introduced and taught to existing Ember -users? +The guides don't need any changes, but the API documentation would need to be thorough. ## Drawbacks -> Why should we *not* do this? Please consider the impact on teaching Ember, -on the integration of this feature with other existing and planned features, -on the impact of the API churn on existing apps, etc. +The biggest drawback is that it would be easy to break routing in the app. +During implementation, this could be mitigated by providing a generated route unit test that +symmetrically checks that toURL and fromURL are inverses of each other for the given route's expected URL / routeInfo. + +```ts +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | blogs/blog/posts/post', function(hooks) { + setupTest(hooks); + + test('it exists', function(assert) { + let route = this.owner.lookup('route:blogs/blog/posts/post'); + assert.ok(route); + }); + + test('urls are resolved', function(assert) { + let router = ; + let sampleUrl = '/blogs/1/posts/2'; + let resultUrl = toURL(fromURL(sampleURL)) -> There are tradeoffs to choosing any path, please attempt to identify them here. + assert.equal(resultUrl, sampleUrl); + + let mapInfo = router.mapInfoFor('blogs.blog.posts.post'); + + let sampleRouteInfo = { + mapInfo, + queryParams: {} + dynamicSegments: { blog: '1', post: '2' }, + } + let resultRouteInfo = fromURL(toURL(sampleRouteInfo)); + + assert.deepEqual(resultRouteInfo, sampleRouteInfo); + }); +}); +``` ## Alternatives -> What other designs have been considered? What is the impact of not doing this? +- Builtin regex matchers for the routes + - hard to debug + - can often match on incorrect portions of the Route if not thoroughly tested + +- Only adding QueryParams modifications (per RFC #380) + - not flexible enough + - forces a single query params implementation + +Prior Art: +- the manager patterns from elsewhere in Ember. -> This section could also include prior art, that is, how other frameworks in the same domain have solved this problem. ## Unresolved questions -> Optional, but suggested for first drafts. What parts of the design are still -TBD? +- Should the URL Manager exist as a static config or an instantiable object per-route? The above proposal is a static config / singleton, but allowing an instance per route would allow for more varied state buckets, but could also make debugging harder as there would be an URL Manager for each route segment. + +- `toURL` / `fromURL` or `serialize` / `deserialize`? \ No newline at end of file From be32f6ee88c8d18611410099d720678f0b2431e9 Mon Sep 17 00:00:00 2001 From: NullVoxPopuli Date: Tue, 31 Dec 2019 12:26:47 -0500 Subject: [PATCH 04/31] minor updates --- text/0000-url-primitives.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index eb26db7268..6f7c29510d 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -5,8 +5,6 @@ # URL Manager -TODO: check if routeInfo or route has a way to build a URL from routeInfo (need full dynamic segments) - Supersedes [Add queryParams to the router service](https://github.com/emberjs/rfcs/pull/380) ## Summary @@ -223,7 +221,8 @@ class CustomURLManager extends URLManager { 2. Restriction of Query Params on controllers / `` needs to have a way to opt-out. Today, if a query param is added to a `` and that query param is not present on the target `route`'s controller, the query param is removed from the link. Query Param allow/deny lists could be re-implemented using the Router `MapInfo` / options. -3. The router's urlFor should be able to take a RouteInfo / RouteInfo should be able to be converted to an URL +3. The router's urlFor helper function should be able to take a RouteInfo / RouteInfo should be able to be converted to an URL + - delegates to `urlManager.toURL` for `RouteInfo` 4. Static `MapInfo` object reference added to each `RouteInfo`. 5. routerService.mapInfoFrom should take: path / url / routeInfo -- uses existing recognize method @@ -250,6 +249,7 @@ class CustomURLManager extends URLManager { } } ``` +4. `transitionTo` and `replaceWith` APIs are unaffected. ### The Default URL Manager From dfa58152d77b74717db496364c598d603a5fb999 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sat, 4 Jan 2020 19:54:15 -0500 Subject: [PATCH 05/31] Update 0000-url-primitives.md --- text/0000-url-primitives.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index 6f7c29510d..4853d7a27a 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -1,6 +1,6 @@ -- Start Date: (fill me in with today's date, YYYY-MM-DD) -- Relevant Team(s): (fill this in with the [team(s)](README.md#relevant-teams) to which this RFC applies) -- RFC PR: (after opening the RFC PR, update this with a link to it and update the file name) +- Start Date: 2020-01-04 +- Relevant Team(s): Ember.js +- RFC PR: https://github.com/emberjs/rfcs/pull/570 - Tracking: (leave this empty) # URL Manager @@ -346,4 +346,4 @@ Prior Art: - Should the URL Manager exist as a static config or an instantiable object per-route? The above proposal is a static config / singleton, but allowing an instance per route would allow for more varied state buckets, but could also make debugging harder as there would be an URL Manager for each route segment. -- `toURL` / `fromURL` or `serialize` / `deserialize`? \ No newline at end of file +- `toURL` / `fromURL` or `serialize` / `deserialize`? From 52e241fe01254c9fdee7f9ac23fb62cb339820c7 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sat, 4 Jan 2020 20:50:03 -0500 Subject: [PATCH 06/31] Update text/0000-url-primitives.md Co-Authored-By: Ricardo Mendes --- text/0000-url-primitives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index 4853d7a27a..c5d8c293e1 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -9,7 +9,7 @@ Supersedes [Add queryParams to the router service](https://github.com/emberjs/rf ## Summary -An URL Manager will provide the ability to manipulate the URL -- +A URL Manager will provide the ability to manipulate the URL, both when writing to the `window.location` (serializing), and reading from the `window.location` (deserializing). The ultimate goal of the URL Manager will be to enable From c1c96c5e338bb70bd629b62d98ebfc9978b3b486 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sat, 4 Jan 2020 20:50:10 -0500 Subject: [PATCH 07/31] Update text/0000-url-primitives.md Co-Authored-By: Ricardo Mendes --- text/0000-url-primitives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index c5d8c293e1..77758a0b80 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -10,7 +10,7 @@ Supersedes [Add queryParams to the router service](https://github.com/emberjs/rf ## Summary A URL Manager will provide the ability to manipulate the URL, -both when writing to the `window.location` (serializing), +both when writing to `window.location` (serializing), and reading from the `window.location` (deserializing). The ultimate goal of the URL Manager will be to enable app and addon authors to customize the use of the URL -- From f20547a43723ecfef04d668d2e3a2330719c37c8 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sat, 4 Jan 2020 20:50:23 -0500 Subject: [PATCH 08/31] Update text/0000-url-primitives.md Co-Authored-By: Ricardo Mendes --- text/0000-url-primitives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index 77758a0b80..504b233cf3 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -11,7 +11,7 @@ Supersedes [Add queryParams to the router service](https://github.com/emberjs/rf A URL Manager will provide the ability to manipulate the URL, both when writing to `window.location` (serializing), -and reading from the `window.location` (deserializing). +and reading from `window.location` (deserializing). The ultimate goal of the URL Manager will be to enable app and addon authors to customize the use of the URL -- including i18n'd routes, dynamic segment slugs / aliases, From dfb1e31097812dbe350208404ec25b8c2c8ba22d Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sat, 4 Jan 2020 20:50:32 -0500 Subject: [PATCH 09/31] Update text/0000-url-primitives.md Co-Authored-By: Ricardo Mendes --- text/0000-url-primitives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index 504b233cf3..bf151857ff 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -14,7 +14,7 @@ both when writing to `window.location` (serializing), and reading from `window.location` (deserializing). The ultimate goal of the URL Manager will be to enable app and addon authors to customize the use of the URL -- -including i18n'd routes, dynamic segment slugs / aliases, +including internationalized routes, dynamic segment slugs / aliases, and alternate query params behavior -- though, implementation of those specific things is outside the scope of *this* RFC. From 900723ccf630fecf9dd49ac1250628d608599f7a Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sat, 4 Jan 2020 20:50:55 -0500 Subject: [PATCH 10/31] Update text/0000-url-primitives.md Co-Authored-By: Ricardo Mendes --- text/0000-url-primitives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index bf151857ff..dcd663be18 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -15,7 +15,7 @@ and reading from `window.location` (deserializing). The ultimate goal of the URL Manager will be to enable app and addon authors to customize the use of the URL -- including internationalized routes, dynamic segment slugs / aliases, -and alternate query params behavior -- though, +and alternative query params behavior—though, implementation of those specific things is outside the scope of *this* RFC. ## Motivation From 68d32a80cfcb86a9657ee856815bb6cd19805ea1 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sat, 4 Jan 2020 20:51:05 -0500 Subject: [PATCH 11/31] Update text/0000-url-primitives.md Co-Authored-By: Ricardo Mendes --- text/0000-url-primitives.md | 1 - 1 file changed, 1 deletion(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index dcd663be18..f394278023 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -341,7 +341,6 @@ module('Unit | Route | blogs/blog/posts/post', function(hooks) { Prior Art: - the manager patterns from elsewhere in Ember. - ## Unresolved questions - Should the URL Manager exist as a static config or an instantiable object per-route? The above proposal is a static config / singleton, but allowing an instance per route would allow for more varied state buckets, but could also make debugging harder as there would be an URL Manager for each route segment. From 487bbfe279e2bae0bbca421b25b818a7180593fe Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sat, 4 Jan 2020 20:51:15 -0500 Subject: [PATCH 12/31] Update text/0000-url-primitives.md Co-Authored-By: Ricardo Mendes --- text/0000-url-primitives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index f394278023..91f061610c 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -13,7 +13,7 @@ A URL Manager will provide the ability to manipulate the URL, both when writing to `window.location` (serializing), and reading from `window.location` (deserializing). The ultimate goal of the URL Manager will be to enable -app and addon authors to customize the use of the URL -- +app and addon authors to customize the use of the URL— including internationalized routes, dynamic segment slugs / aliases, and alternative query params behavior—though, implementation of those specific things is outside the scope of *this* RFC. From 5fe3edd3490f3c0e463338e6ba2f52d70c10ccc5 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sat, 4 Jan 2020 20:51:23 -0500 Subject: [PATCH 13/31] Update text/0000-url-primitives.md Co-Authored-By: Ricardo Mendes --- text/0000-url-primitives.md | 1 - 1 file changed, 1 deletion(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index 91f061610c..bc375c2589 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -71,7 +71,6 @@ Router.map(function() { This example proposes the possibility of an alternate strategy for managing query params without the need for controllers. Note that this would not change or alter the existing query param behavior in any way. - ```ts // app/router.js/ts import { inject as service } from '@ember/service'; From d4cdc5d8b976f7a13a66b4ea92950df4000524a8 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sat, 4 Jan 2020 20:51:38 -0500 Subject: [PATCH 14/31] Update text/0000-url-primitives.md Co-Authored-By: Ricardo Mendes --- text/0000-url-primitives.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index bc375c2589..4d4a83498a 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -227,7 +227,8 @@ class CustomURLManager extends URLManager { ### Changes to Internals -1. `` and other related router helpers use `toURL` and `fromURL` from the URL Manager + +1. `` and other related router helpers use `toURL` and `fromURL` from the URL Manager. This allows more control over the allow/deny list behavior of query params filtering. ### Additional APIs / Behavior From a7ed0889c4f9f17fb704079da4410820e2b2c3f0 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sat, 4 Jan 2020 20:51:49 -0500 Subject: [PATCH 15/31] Update text/0000-url-primitives.md Co-Authored-By: Ricardo Mendes --- text/0000-url-primitives.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index 4d4a83498a..a4e25fb518 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -232,6 +232,7 @@ class CustomURLManager extends URLManager { This allows more control over the allow/deny list behavior of query params filtering. ### Additional APIs / Behavior + 1. URLManager is a container-controlled object, so that it may utilize the dependency injection system. Primary need for this is to access the router service to utilize existing APIs for transforming URLs and `RouteInfo`s From 453b7a25f9af7a633fb2979ae16283fa66403959 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sat, 4 Jan 2020 20:52:00 -0500 Subject: [PATCH 16/31] Update text/0000-url-primitives.md Co-Authored-By: Ricardo Mendes --- text/0000-url-primitives.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index a4e25fb518..b53d11b22c 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -237,6 +237,7 @@ class CustomURLManager extends URLManager { Primary need for this is to access the router service to utilize existing APIs for transforming URLs and `RouteInfo`s ### Notes + 1. `MapInfo` is static or "frozen" -- it is only constructed at the time of router setup. 2. `MapInfo` represents the optional `object` argument of `Router.map`'s `this.route`. Empty object if not configured. 3. If needed, the URL Manager may be used from a component: From c0d093caa48a9b64c6e72469a78aee3db90efde2 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sat, 4 Jan 2020 20:52:27 -0500 Subject: [PATCH 17/31] Update text/0000-url-primitives.md Co-Authored-By: Ricardo Mendes --- text/0000-url-primitives.md | 1 - 1 file changed, 1 deletion(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index b53d11b22c..d880bb7297 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -30,7 +30,6 @@ when interacting with query params. If no URL Manager is configured, Ember's current URL behavior will be preserved. - ## Examples The following examples of the proposed API are written in TypeScript in _userland / application-space_ to better From 0e5b1927aded4068dbe028e6c63612b536a50783 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sat, 4 Jan 2020 20:53:06 -0500 Subject: [PATCH 18/31] Update text/0000-url-primitives.md Co-Authored-By: Ricardo Mendes --- text/0000-url-primitives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index d880bb7297..bd14f60d71 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -32,7 +32,7 @@ If no URL Manager is configured, Ember's current URL behavior will be preserved. ## Examples -The following examples of the proposed API are written in TypeScript in _userland / application-space_ to better +The following examples of the proposed API are written in TypeScript in _userland_/_application-space_ to better demonstrate intended API and available data. The same naming semantics and conventions in today's router system will still apply. From 40c23a9d65f347f618df742b4706c41142d53286 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sat, 4 Jan 2020 20:53:16 -0500 Subject: [PATCH 19/31] Update text/0000-url-primitives.md Co-Authored-By: Ricardo Mendes --- text/0000-url-primitives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index bd14f60d71..f08a79c2e2 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -65,7 +65,7 @@ Router.map(function() { ``` -### Naiive Query Params without Controllers +### Naïve Query Params without Controllers This example proposes the possibility of an alternate strategy for managing query params without the need for controllers. Note that this would not change or alter the existing query param behavior in any way. From b9b80dd734cc25c25b0f2bee3fbe7cc401c2fe0b Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sat, 4 Jan 2020 20:53:24 -0500 Subject: [PATCH 20/31] Update text/0000-url-primitives.md Co-Authored-By: Ricardo Mendes --- text/0000-url-primitives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index f08a79c2e2..fe9b26b2bf 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -67,7 +67,7 @@ Router.map(function() { ### Naïve Query Params without Controllers -This example proposes the possibility of an alternate strategy for managing query params without the need for controllers. +This example proposes the possibility of an alternative strategy for managing query params without the need for controllers. Note that this would not change or alter the existing query param behavior in any way. ```ts From fb2027bbf4e875c9f25448b0416211c044557411 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sat, 4 Jan 2020 20:53:33 -0500 Subject: [PATCH 21/31] Update text/0000-url-primitives.md Co-Authored-By: Ricardo Mendes --- text/0000-url-primitives.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index fe9b26b2bf..9263cd46f9 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -113,6 +113,7 @@ class CustomURLManager extends URLManager { ``` ### i18n routes + In this scenario, we may want to internationalized route segments. For example, in English, we may want a route to be `/blogs/1/posts/2`, From 0774eee92977f8fe0951c26d3624f073ba2814e1 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sat, 4 Jan 2020 20:53:42 -0500 Subject: [PATCH 22/31] Update text/0000-url-primitives.md Co-Authored-By: Ricardo Mendes --- text/0000-url-primitives.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index 9263cd46f9..0366d18da1 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -171,6 +171,7 @@ class CustomURLManager extends URLManager { ``` ### Custom URL Management per-route + There are situations in which links and their routes don't conform to the rest of the application. For these situations, here is an example showing how manage the URL separately for those routes. ```ts From 6823d795ca36c864b08aacc5c549aca07be4bd26 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sat, 4 Jan 2020 20:53:50 -0500 Subject: [PATCH 23/31] Update text/0000-url-primitives.md Co-Authored-By: Ricardo Mendes --- text/0000-url-primitives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index 0366d18da1..6de286e01b 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -172,7 +172,7 @@ class CustomURLManager extends URLManager { ### Custom URL Management per-route -There are situations in which links and their routes don't conform to the rest of the application. For these situations, here is an example showing how manage the URL separately for those routes. +There are situations in which links and their routes don't conform to the rest of the application. For these situations, here is an example showing how to manage the URL separately for those routes. ```ts // app/router.js/ts From ea16383d0f447ff5434ecc507ddce32a12965089 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sat, 4 Jan 2020 20:54:00 -0500 Subject: [PATCH 24/31] Update text/0000-url-primitives.md Co-Authored-By: Ricardo Mendes --- text/0000-url-primitives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index 6de286e01b..38ee1afb25 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -216,7 +216,7 @@ class CustomURLManager extends URLManager { ## Detailed design -### Additions to existing Public APIs +### Additions to existing public APIs 1. add full list of dynamicSegments to RouteInfo so that the task of building out the map of dynamic segments to their values isn't in user-space 2. Restriction of Query Params on controllers / `` needs to have a way to opt-out. Today, if a query param is added to a `` and that query param is not present on the target `route`'s controller, the query param is removed from the link. From 076408118af547ffb54058274b899f41443e0c9f Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sat, 4 Jan 2020 20:54:10 -0500 Subject: [PATCH 25/31] Update text/0000-url-primitives.md Co-Authored-By: Ricardo Mendes --- text/0000-url-primitives.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index 38ee1afb25..c69e4d0f0b 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -217,7 +217,8 @@ class CustomURLManager extends URLManager { ## Detailed design ### Additions to existing public APIs -1. add full list of dynamicSegments to RouteInfo so that the task of building out the map of dynamic segments to their values isn't in user-space + +1. Add full list of dynamicSegments to `RouteInfo` so that the task of building out the map of dynamic segments to their values isn't in userspace; 2. Restriction of Query Params on controllers / `` needs to have a way to opt-out. Today, if a query param is added to a `` and that query param is not present on the target `route`'s controller, the query param is removed from the link. Query Param allow/deny lists could be re-implemented using the Router `MapInfo` / options. From b4578bbd7f4474dba38c5f8149bddbb88ac5a5a7 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sun, 5 Jan 2020 09:10:31 -0500 Subject: [PATCH 26/31] Update text/0000-url-primitives.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Jan Buschtöns --- text/0000-url-primitives.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index c69e4d0f0b..e5914b3420 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -43,7 +43,9 @@ All examples will have the following in common: import EmberRouter, { URLManager } from '@ember/routing/router'; import config from 'app-name/config/environment'; -// ... Example CustomURLManager here ... +class CustomURLManager extends URLManager { + // Concrete example implementations are shown in the following sections. +} export default class Router extends EmberRouter { location = config.locationType; From 0d06b0499bb1c4e5908587dde0d141f2583f4af2 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sun, 5 Jan 2020 09:16:53 -0500 Subject: [PATCH 27/31] Update text/0000-url-primitives.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Jan Buschtöns --- text/0000-url-primitives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index e5914b3420..e40932e22c 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -50,7 +50,7 @@ class CustomURLManager extends URLManager { export default class Router extends EmberRouter { location = config.locationType; rootURL = config.rootURL; - urlManager = CustomURLManager; // Defined in Examples + urlManager = CustomURLManager; }); Router.map(function() { From 1eeaab6287d4553d4cba8238188698edfbaa8513 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sun, 5 Jan 2020 09:19:28 -0500 Subject: [PATCH 28/31] Update 0000-url-primitives.md --- text/0000-url-primitives.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index e40932e22c..db795ad11c 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -135,11 +135,10 @@ class CustomURLManager extends URLManager { // this dosen't have to be english, but it does have to match the names as // defined in Router.map(...); let english = - path.split('/') - .segments.map(segment => { - return this.i18n.lookup(`routes.from.${segment}`, 'en-us') || segment; - }) - .join('/'); + segments.map(segment => { + return this.i18n.lookup(`routes.from.${segment}`, 'en-us') || segment; + }) + .join('/'); let routeInfo = this.router.recognize(english); let queryParams = qs.parse(query); From d3aa12b20b1e774f2554fcb2c5a3506ff3553a4b Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sun, 5 Jan 2020 15:11:18 -0500 Subject: [PATCH 29/31] Update text/0000-url-primitives.md Co-Authored-By: Chris Krycho --- text/0000-url-primitives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index db795ad11c..bd5606bfbb 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -51,7 +51,7 @@ export default class Router extends EmberRouter { location = config.locationType; rootURL = config.rootURL; urlManager = CustomURLManager; -}); +} Router.map(function() { this.route('blogs', function() { From 1cabcb222fa00c578804e88227d48b5330b2a6e6 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sun, 5 Jan 2020 15:13:21 -0500 Subject: [PATCH 30/31] Update text/0000-url-primitives.md Co-Authored-By: Chris Krycho --- text/0000-url-primitives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index bd5606bfbb..c4d8a33d96 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -268,7 +268,7 @@ import config from 'app-name/config/environment'; export default class Router extends EmberRouter { location = config.locationType; rootURL = config.rootURL; -}); +} Router.map(...); ``` From bfd243d2b3feaa59859fd79a97d3fff27850bf27 Mon Sep 17 00:00:00 2001 From: "L. Preston Sego III" Date: Sun, 5 Jan 2020 15:25:51 -0500 Subject: [PATCH 31/31] Update 0000-url-primitives.md --- text/0000-url-primitives.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/text/0000-url-primitives.md b/text/0000-url-primitives.md index c4d8a33d96..7b2f0335d5 100644 --- a/text/0000-url-primitives.md +++ b/text/0000-url-primitives.md @@ -149,7 +149,7 @@ class CustomURLManager extends URLManager { }; } - toURL(routeInfo: RouteInfo) { + toURL(routeInfo: RouteInfo): string { let { mapInfo: { segments // [blogs, :blogId, posts, :postId] @@ -238,6 +238,25 @@ class CustomURLManager extends URLManager { 1. URLManager is a container-controlled object, so that it may utilize the dependency injection system. Primary need for this is to access the router service to utilize existing APIs for transforming URLs and `RouteInfo`s + +`URLManager` has the following API: + +```ts +class URLManager { + static create(injections: any) { + return new this(injections); + } + + constructor(injections: any) { + Object.assign(this, injections); + } + + public toURL(routeInfo: RouteInfo, dynamicSegments: object): string; + public fromURL(url: string): RouteInfo; +} +``` + +and `toURL` and `fromURL` implement today's URL (de)serialization. ### Notes