Query-String support in Ember Router

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:

I had an excellent holiday, thank you :smile:.

I’m not saying we should ignore it - just consider which should be the default. We’ll have to get some feedback on what the primary use case is, but I think they should both at least be possible to implement without too much of a hassle. I’m developing a large, complex app as well, and mine happens to only use model-affecting query params - so I guess both use cases are legitimate.

It’s only easy if your route has a dynamic segment. You can of course artificially add a parameters-segment, but you’ll have to fill it with something if there are no parameters, which means compromising the URL. Reluctance to do just that is what got me here in the first place :wink:.

EDIT: Wait that’s not true, there’s a more reason for not wanting to use dynamic segments as a query mechanism: transitioning and linking. If you want to link / transition to a dynamic-segment route you must supply a context object, but in a classic /posts list kind of scenario this context is only loaded by model, and if you supply one model is bypassed. You could create valid context objects for the links, but that would mean actually loading all sorts of lists you want to link to from the API which is obviously undesirable. This is what drove me away from using dynamic segments for query strings in the end.

OK, so here’s the progress so far:

  1. Route recognizer patch to support recognizing and generating URLs with query strings
  2. Router patch to support some of the basics of query param handling

This feels quite good, and I’m getting a lot of understanding of the router and how it works. The approach is going well, and it’s going to work very smoothly with transitions in general in ember as it’s hooked in at the right level of abstraction.

What remains to be done is to finish off work on the router stuff, ensuring all the cases are handled properly, and then actual work on ember.js itself to integrate these microlib changes. I don’t think I’ll get a chance to work on this any more until later in the week, but I think that the really hard part (actually understanding the complexities of how the router works and getting basic support working) has been achieved.

Well, it took a while but it’s finally here:

https://github.com/emberjs/ember.js/pull/3182

I hope it’s worth the wait, please try it out (there’s a JSFiddle linked in the pull request) and let me know what you think.

@ElteHupkes ironically I have not managed to work on my own use case yet, so currently the query parameters only affect the model. I need to make it work for my own use case but currently it’s inefficient for that.

All feedback welcome!

2 Likes

Woah, I somehow completely missed your previous reply (didn’t receive a notification for it, strangely). Nice job Alex! I’m going to take a look at it ASAP.

Hooray for @alexspeller! Love the work so far, hope to see it go through.