Query-String support in Ember Router

The idea is you use App.Router.map to specify how your app handles urls all in one place. This could set an observesParams property on the respective route so that you can use it in mixins I suppose, meaning you can have the best of both worlds.

I like where this is going. I think “normal” global style query params is the way to go and using something like observesParams on the route works well for my app. I implemented ember-query-params and am mostly pleased with it so far. I’d like to bring up another point, though: how should the view helpers such as linkTo interact with the query params?

ember-query-params uses a special QueryParameters object, but this feels unwieldy to me. I’d like to be able to pass in query params as part of the route I name in the linkTo helper. What do you all think?

There are 4 options I have thought of for this:

  • Using the QueryParameters object. This is annoying because you can’t just use it one off in a view, you have to define it in a controller.
App.Controller = Em.Controller.extend({
  nextPageParams: function() {
    return Ember.QueryParameters.create({page: this.get('page') + 1});
  }.property('page')
});

//In view: {{#linkTo nextPageParams}}Next Page{{/linkTo}}

This is the approach taken by ember-query-params

  • Provide an already serialized query string:
{{#linkTo foo query="page=2"}}Next Page{{/linkTo}}
(You can build a serialized query param in the controller if you need to)

This is the approach taken by ember-query

  • Just use plain params. I think this is stupid and will break all the time. It might work if you have defined query params for a route and can work out dynamically which arguments should be query params, but that seems quite hard. What if you have a query param that conflicts with a parameter name to the ‘linkTo’ helper? Is this a real issue or paranoia?
{{#linkTo foo page=2}}Page 2{{/linkTo}}
  • Use a naming convention. This lets you figure out which args to linkTo are query params and which are just normal args. It should also allow binding, so we could do something similar to the route name itself where quoted params are literal and non-quoted params are key paths.
{{#linkTo foo queryPage="2" queryFoo=barPath}}Page 2{{/linkTo}}

Currently, the last approach seems to be the least annoying and most viable at the moment. Anyone have any thoughts on this?

Also, which other view helpers am I not thinking about here? I’ve been using my library extensively in a large project and haven’t needed to patch any other helpers yet.

You’re probably not forgetting anything. I’m just new to Ember in general :slight_smile:

I agree that your last suggestion looks best. To me this makes it feel more like the rails link_to helper.

My handlebars is a little basic, but could you do something similar to:

{{#linkTo foo query={page: "2", foo:barPath} }}Page 2{{/linkTo}}

I assumed that you couldn’t do that, at least not without changing handlebars, although I may be mistaken.

I’ve had really good luck with the Elte Hupkes version -the only thing missing would be a custom handlebars helper to keep the params part in the template as I’d prefer not to write each custom route javascript side

https://github.com/ElteHupkes/ember-query-params

Is this solution just a proof of concept right now? If not, does anyone have plans to start test driving out scenarios that use this to get it solid for some long term development? The integration testing support with QUnit and ember-testing should be a great place to start assuming this is the agreed upon solution.

Elte Hupkes is away on holiday at the moment but we will be working together when he gets back at some point, I’m currently working towards combining the best features from both libraries into something that works better with core ember. Should be finished this week or weekend some time - stay tuned!

Is what you are working on now public? Could I start adding a test suite to help us trust / refactor / etc as the project grows over time? I’d love to use this in a production app and having a nice integration test suite at the very least is a win for anyone who wants to understand / reason about / maintain it /etc

Did you start from scratch? I assume the decisions above (global for example) are what you are using to drive this out?

I’m struggling with a query-string type issue at the moment. I need to implement the following:

  • URL in the form /user/username?version=07-08-2013
  • Query parameters aren’t affecting the model as that should always load the same User object
  • Access to the query params should be available in setupController so that I can load the optionally requested version of a Profile object

I’m trying to use @Elte_Hupkes version but the query params are disappearing from the URL as soon as the page is loaded. I think it may be due to the initial transition being aborted by ember-auth so it can load the authenticated user before restarting the initial transition.

Are there any other ways of working with query strings in Ember at the moment or is there a different approach I could take to this issue?

You should try my library, I think it will support what you want to do better.

It’s not public yet, it will be shortly. It is a set of patches to ember and routing libraries. It will be fully tested.[quote=“toranb, post:27, topic:1962”] Did you start from scratch? I assume the decisions above (global for example) are what you are using to drive this out? [/quote]

I started from scratch, designing as discussed above, and using mine and @Elte_Hupkes’ library as inspiration.

@alexspeller thanks for the update (hopefully I didn’t come off negative in that reply). I just wanted to help test drive the rewrite you are working on if/when it’s public and open source :slight_smile:

I really like the idea of matrix parameters but concede that they might not be practical. Another way to scope parameters though would be to use the nested format that rails uses i.e.

/foos/bars?foos[category]=awesome&bars[page]=2

This allows the parameters to be scoped in the same way as matrix parameters but without the previously mentioned issues.

No not at all, and I really want to share this early. It just is not even remotely working at the moment. I will share the first, ugliest version that works :wink:

This approach was mentioned in my post above, the problem is that it exposes implementation details (route names) in the url

Alright I’m back from my holiday, so let’s dive into this :).

The way I see it, there are essentially two issues to settle:

  1. Determining the URL-scheme / style to use, which encompasses the scoping of parameters and finding out which route they belong to.
  2. Determining the course of action when these parameters change.

We’ll also have to determine how to link / transition with query parameters, but I think they’re mostly semantics which can be settled quickly when we determine these first two.

The first point has been discussed quite a lot, and while personally I think matrix parameters are the most elegant solution they might not be practical or even viable. Global parameters with some kind of configurable scoping (e.g. an observesParameters option) are a good second, I sense some consensus towards that solution in this thread. In 90% of the situations it probably won’t make a difference, the other 10% will have to be careful with their parameter naming, which is inconvenient but probably not disastrous. @alexspeller I kind of like the idea of defining parameters in Router.map, on the other hand being able to inherit parameter observing from a parent route is also nice.

The second point is equally important and actually more complicated. The main issue here is that without any knowledge of the specific application you can’t determine exactly what parameters are supposed to do, most importantly if and how they affect their route’s model. Most cases involve some kind of API sorting / filtering (so then yes, they affect the model), but I can think of some perfectly valid examples in which the route’s model doesn’t change when a parameter changes (you could use client-side sorting in the controller, for instance). I built ember-query-params around the filter / sort case where the parameters affect the model. This happens to be all I need at the moment, but @lookingsideways is already showing a use case in which it doesn’t suffice. (Ember-query-params doesn’t currently work properly on routes with dynamic segments by the way, which is probably part of the problem there).

I’ll ignore the “query doesn’t affect model” case for now because I think it is the least complicated and can probably be solved by having some sort of route hook that can override the default behavior. The desired behavior when the model is affected is more interesting, because we need to implement this in a way that doesn’t break routing and doesn’t require a lot of additional code. Ember-query-params currently only refreshes active routes with changed parameters, which works in most cases but will break with undefined behavior when it doesn’t - there is no transition to error / abort when loading the data fails, so you could get stuck in an invalid state. Using the existing routing mechanism would be preferable, i.e. exiting and re-entering all routes below the highest-level route that has changed. It is currently not possible to “refresh” active routes this way though (which is why ember-query-params works the way it does).

So, TL;DR, taking everything into account here’s what I propose:

  1. Use global query parameters (for reasons mentioned above) and let routes specify which ones they use.
  2. Assume, by default, that query parameters affect a route’s model, and update using the existing routing mechanism. This means we need a way to “refresh” a certain (active) route and all its child routes, a functionality which by the way I think would be great to have regardless of query parameters.

Love to hear you guys’ thoughts :smiley:.

(sigh I’m not allowed to make “ember-query-params” a link more than twice… Oh well ;]).

Welcome back Elte, hope you had a good holiday!

I will be publishing what I’ve been working on tonight, so be good to see what you think. I thought I’d respond to some of your points before that though:

I think global parameters are the best compromise, and naming conflicts are easy enough to avoid. You’re free to namespace them yourself e.g. /parent/child?parent:sort=name&child:sort=date - I don’t think that this is too much of a burden to make the complexity and potential issues of matrix params necessary.

W.r.t. defining routes in router.map, I now think that this is the only possible solution, due to how the router and recognizers work. Unfortunately, this will mean that you won’t be able to share observesParams via inheritance, but the way the ember router and route recognizer are built and work, they just don’t know anything about controllers. I’ll show you what I mean later this evening. I don’t know that this is the end of the world though, and IMO it’s actually semantically correct - the URL is a routing concern, and therefore the router should deal with it.

I don’t know if it makes sense to ignore the “query doesn’t affect model” use case in the overall design - I think that is actually the primary use case for query params. Whilst ember-query supports both, in my large, complex app I don’t use the query params to affect the model at all, not even once. In fact, affecting the model is really easy to do without query params because you can just override model and deserialize to embed the details in the dynamic segment itself. I think the main use case for query params is for routing client side state that is controller state, i.e. sort and filter on the client side, because that’s hard to do currently. It would be good to get some feedback from others in this thread as your opinions on these two different usecases.

With regard to what happens when the query params change, the only way to do this properly is in the router, extending the logic that happens when dynamic segments change. That’s what I’m currently working on, to ensure it works very smoothly with the current mechanisms that happen when working with transitions.

I’ll post again here later with links to stuff :smile: