millermedeiros / crossroads.js

JavaScript Routes
http://millermedeiros.github.com/crossroads.js/
1.44k stars 156 forks source link

Passing route data you don't want to end up in the URI #87

Open jaxley opened 11 years ago

jaxley commented 11 years ago

For workflow cases, I've wanted to use routing as a way to transition from view to view. This works, however as a pub/sub approach, you are limited to passing only data that appears in the URI to the route handler. However, there are cases where you might have sensitive data that you want to pass from the previous state into the next route handler without it appearing in the URI. There doesn't seem to be a way to do this with hasher/crossroads as-is as both are inextricably tied to the URI to serialize/deserialize the route state. What are the thoughts for adding a way to pass context information in the route data without it appearing in the URI?

Also, hasher seems to oversimplify the hash construction by forcing all routes into REST-like format. Other routing systems allow managing route parameters inside the query string as well and rendering them that way.

/foo/bar/123?debug=true&nextStep=blah

Could be represented as a route:

{controller}/{view}/:id:?{debug}&{next}

Which could result in the route data being parsed out as:

{
    controller: "foo",
    view: "bar",
    id: "123",
    debug: "true",
    next: "blah"
}

The query string support currently treats them all like a bucket of properties without a way to match to named items, which limits the ability to serialize the data into the expected route format.

millermedeiros commented 11 years ago

currently there are 3 ways to pass arbitrary data together with the captured groups:

// crossroads.parse() accepts and array with default arguments
// (values will be passed as first arguments of the route.matched callback)
crossroads.parse('lorem/ipsum', ['first arg', 'seconds arg']);

You can use the route.rules.normalize_ (see route.rules)

var myRoute = crossroads.addRoute('foo/{id}');
myRoute.rules = {
  normalize_ : function(request, values){
    // normalize_ should return an Array
    return [{
      id : values.id,
      customData : 'this is not part of URI'
    }];
  }
};

or you could add some default arguments to the route.matched SignalBinding.

var myRoute = crossroads.addRoute('foo/{id}');
var signalBinding = myRoute.matched.add(doSomething);
signalBinding.params = [customData];

This will make the customData to be passed as first argument to the doSomething handler.

PS: the normalize function can be set globally to all routes.

millermedeiros commented 11 years ago

I coded crossroads to be kind of low-level on purpose, most of the times I just need the basic features and when I need more stuff I usually write another layer of abstraction on top of it (eg. SectionController)

Please let me know if the solutions above are enough and/or if you have any idea on how to improve it. Hasher was created just to handle the window.location.hash, crossroads came afterwards and is more generic (I use it a lot like a pub/sub).

I'm planning a v2.0 release (see #82) on the next couple months which should simplify some things and change the behavior a little bit, feedback is very welcome. Thx.

jaxley commented 11 years ago

Unfortunately, this won't quite work. The above approaches are deterministic and only work for data that you know ahead of time that is fixed when your page inits. I've got workflows where we get data back from a service though and when that happens, I need to switch routes at that point and would want to pass along the additional context when I invoke the code to change the route. i.e. when the route changes, my loosely coupled code wants to communicate extra data to whoever is listening for that event. Does that clarify?

In my POC, I couldn't even use the hasher.setHash() APIs to trigger the route change since they don't support building more complex routes with params such as query string values or appending them. I had to write my own helper to construct that. I also found that hasher's APIs just call toString() on the values so objects don't get converted into URL-friendly structures like jquery param/deparam and other systems. I presume that changing the route hash is the mechanism by which one is supposed to trigger route transitions (that was not clear from the docs and examples)? You can call crossroads.parse() to parse out a route string and trigger the callbacks, but that necessitates you constructing the route as a string again yourself. Plus, that doesn't update the hashbang nor does it provide a way to pass other data to the signal callbacks.

millermedeiros commented 11 years ago

Do you have any recommendation about how you want it implemented?

I forgot to mention on the previous comment that crossroads.parse() accepts a second argument (Array) with the default arguments that should be passed to the matched route. I totally forgot about this feature.

crissdev commented 11 years ago

@ jaxley I had the same issue a while ago and my option was to not include it in the URL / route in the first place. The problem was that if the user refreshed the page the custom data was lost. So, as long as you can live with that, instead of relying on crossroads / hasher to pass your custom data, just use your other components that can do this.

For example:

Instead of using


hasher.setHash('/user/action/{custom_data}')

use


myRouter.redirect('/user/action/', customData);

// myRouter is your custom component that will know how to send customData 
// to other components
bchoddny commented 7 years ago

@jaxley, were you able to solve this? I would also like to avoid passing data in query string