Skip to content

Proposal: Routing Guide (Work In Progress) #122

@justinbmeyer

Description

@justinbmeyer

tldr; We propose a guide that teaches how to setup can-route to handle common use cases. We will survey common problems and design a guide or a series of small examples addressing them.

This was discussed on a recent live stream (35:49)

Problems

The following lists different problems we would like to see addressed:

  • Values with delimiters (ex: / )
  • Conflicting routes
  • Nested routing
  • Testing Routing Rules
  • Preventing history changes
  • Mounting pushState
  • Alternate routing (turning off routing, 404s, etc)
  • Switching between "page components" using convention
  • Trailing slash routing
  • Validating routes
  • Using can.route.data vs passing down your data.
  • AppVM data that shouldn't be on the route.

Please let us know in the comments below which are most important to you or others you would like to see.

Values with delimiters (ex: /)

Sometimes, people want values with / to show up as / in the url.

What you want:

route("{page}/{id}")
route.param({page: "edit", id:"c2/a1"}) //-> "edit/c2%2Fa1" NOT "edit/c2/a1"

This is also related to nested routing where you would want something like:

route.deparam("folder1/folder2/folder3/fileA/meta") //-> {path: "folder1/folder2/folder3/fileA", action: "meta"}

Conflicted routes:

Sometimes one routes rules might "swallow" another routing rule. For example:

route("{page}/{id}");
route("items/{itemId}");

route.deparam("items/5") -> {page: "items", id: "5", route: "{page}/{id}"}

I've seen the following rules also desired:

top -> {category: "all", sort: "top"}
sports -> {category: "sports", sort: "top"}
sports/latest -> {category: "sports", sort: "latest"}

The guide should cover how to handle these situations.

Nested routing and/or composite routing

Is there a good pattern to take multiple "little" apps, each with their own routing rules, and compose them?

var loginRules = [
  "{user}/{action}"
];
var recipeRules = [
  "{recipe}/{id}"
]

route("login/{user}/{action}");
route("recipe/{recipe}/{id}");

Testing Routing Rules

Lets detail how you can test your routing rules to make sure they work.

// routes.js
import route from "can-route";
route( ... )
route( ... )

// test.js 
import route from "can-route";
import "./routes"

assert.deepEqual( route.deparam("foo/bar"), {page: "foo", action: "bar"})
assert.deepEqual( route.param({page: "foo", action: "bar"}, "foo/bar" );

Preventing History Changes

With pushstate, it's possible to prevent a history entry when someone navigates to recipe/5/view to recipe/6/edit. Using replaceStateOn, replaceStateOnce, replaceStateOff. We should explain how.

Mounting pushState

can-route-pushstate defaults its mounting to /. It's possible to change this to /where/my/app/belongs. So only routes within /where/my/app/belongs will use pushState. Lets show how to do this.

Alternate Routing Scenarios

There are a few scenarios that would be good to demonstrate:

  • When the application is loading with the url /user/secrets, but the user is not logged in, we might want to show them the login page, but keep the url /user/secrets.
  • If the user goes to a page that is unavailable or broken, how do we send them a 404?
  • How do we let people go to a page that would normally be pushState-d? For example, a route like {page}, but we want the special checkout page to be loaded from the server.

Switching between "page components" using convention

Its very common to have some sort of convention to align a page property to similarly named component. For example, Bitballs does this. We should show how to do this. Bonus points if we can also show how to animate it.

Trailing slash routing

Often folks want /foo and /foo/ to route to the same page. Lets show how to do that.

Validating routes

If a user is on /user/2 and wants to go to /user/3/secrets with:

route.data.update({
  page: "user",
  id: 3,
  action: "secrets"
})

How can you check that this is an ok page transition?

Using can.route.data vs passing down your data

For connivence, many components use the global route.data to interact with the route.

For example:

VM = {
  saveAndReturnToView(){
    this.data.save().then(() => {
      route.data.page = "view";
    })
  }
}

Alternatively, the route's data can be passed to the component:

<my-component page:bind="appViewModel.page"/>
VM = {
  saveAndReturnToView(){
    this.data.save().then(() => {
      this.page = "view";
    })
  }
}

Which pattern should you use and why? If you do use the first pattern, how can you test it without breaking your tests. How can you make demo pages?

Preventing AppVM data from being on your route

When connecting a VM to the route, it's common for the VM to have properties that should not be serialized for the route.

DefineMap.extend({
  someData: {serialize: false} //-> will not be sent to the url
})

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions