Query-String support in Ember Router


#1

This issue has been alive for 7 months: https://github.com/emberjs/ember.js/issues/1773

I don’t know if this is a 1.0 thing or not, but I’d really appreciate some extra eyes on it and expand the discussion just beyond the GitHub crowd.

If you’ve contributed your own PR’s / attempts at query string support, please comment on this thread so we can get the ball rolling. Specifically, it’d be good to compare/contrast the various approaches, implications of each, and status of implementation.

In general though, I think Ember’s ability to really handle the list-filtering use case via routes leaves something to be desired, and query-strings seem like the right solution, so let’s get to talking.


Nested UI, Routes and Resources: Pro and Con
#2

I have been trying to use ember-query-params by Elte Hupkes on a project I’m working on and it works for the most part. I will (once I have permission) post snippets of the source code and explain the difficulties I had. Unfortunately it is a very complicated problem to solve and so there aren’t a lot of examples of people doing this. In other words, my code may very well suck. I’ll follow up later with more info.


#3

Hi, I’m the developer of ember-query, which I’m currently working to update to support RC6. Once I’ve done that, I will post some more details here, but in the meantime it’s worth mentioning that I plan to work with @Elte_Hupkes on merging our libraries into a single project.

In the meantime, @machty (or someone else on the core team) merging this pull request would make life a lot easier for us poor query string hackers!


#4

So, essentially there are various approaches to implementation which would be transparent to the user and not productive to discuss here. Really, I think that the only real issue to be decided is how to figure out which query params affect which routes.

As I see it, there are several options, none of which are perfect.

  1. Query params are global, and when they change they are applied to all routes. This is the simplest solution, but can be a pain when child routes change query params and this affects a parent route. ember-query currently takes a similar approach. If query parameters change, model is called on the leaf route but any parent routes don’t have model called, only setupController.
  2. Query params have a specialized naming scheme such that it’s easy to determine which query parameter applies to each route. For example, /foos/2/bars?barsIndex[page]=2&foos[category]=awesome. This approach is problematic as it exposes implementation details (route names) in the url.
  3. Registering on the route which params a route handler is interested in. For example: App.PostsRoute = Ember.Route.extend({ observesParameters: ['sort', 'search'] }); this is the approach taken by ember-query-params and IMO is a good compromise.
  4. Local params through the use of Matrix URIs or something similar. URIs would look like this: /foos;category=awesome/bars;page=2 In some ways this it the most elegant solution, but would require a rewrite of the route recognizer. It also is not a very usual type of url and people may not expect it (also, is it possible it would break loads of stuff on the web? How many bad regex URL recognizers will break off urls in this format at the first “;”? http://example.com/foos;a=b/bars/c=d <– discourse handles it fine evidently, but I wouldn’t be surprised if a load of naively written software broke. Maybe that’s not our problem. Mainly, this issue with this is that it’s not a usual thing to see and is a bit weird at first viewing. However, it does fit very well with the conceptual model of ember, where unlike server side frameworks like rails, multiple routes are active at once.

@Elte_Hupkes as usual sorry if I have misrepresented anything about your library.

So, now any comments and opinions welcome. It would be great if we could get this single point sorted out; if we can, then we can have a PR to get query params implemented in core very quickly.


#5

I agree with option 4 above. It does seem to be the most elegant solution for the problem. I also appreciate yours and Elte’s hard work on this. I have been struggling with an application which requires this functionality and I am eager to re-write it once your solution is in place. I am also happy to help where I can.

For example purposes only on usage, here is the app I wrote: http://www.classesandcareers.com/onlinecourses


#6

I know Rails used semicolon URLs at one point and abandoned them because of inconsistent support. However, since we’re strictly client side we might avoid some of the problems they had. I’m also a little bit worried that semi-colon URLs just look bad/unfamiliar and people will troll us a bit for that.


#7

Brain barf:

  • I’m happy to dig into route-recognizer to help out with this if that’s what’s possibly scaring people
  • I don’t love the semi-colon per-route query string stuff, and I think the use case is strong enough for at least allowing global query strings, e.g. if you want a query string param to affect the behavior of a routeless controller, it would need to be a global query string param (which I guess would be “consumed” by the ApplicationRoute and its value passed to whatever routeless controller)

#8

I’d forgotten about this; looking into it in more detail, I dug up some info on why they were removed:

https://groups.google.com/forum/#!topic/rubyonrails-core/tlmJMtkTN8Y

Doesn’t work with page caching, safari has bugs with authentication with urls with semi colons in it. Various libraries (like mongrel) mistakenly consider ; to be part of the query string.

Overall it simply wasn’t worth the theoretical ‘peers in the hierarchy’ benefit that we put it in for.

Peter, when you say:

However, since we’re strictly client side we might avoid some of the problems they had

This certainly doesn’t apply to my app that’s exclusively pushstate - the whole url, semicolons and all is sent to the server. If any intermediary can’t handle it it will still cause breakages.

Also, I found some other references to lots of software e.g. autolinking libraries, which would cut off the url at the first semicolon.

Ultimately, I think that semicolon URIs are going to be more hassle than they are worth, and for that reason I am tentatively changing my position to “We should use normal ?foo=bar params like normal sensible people”.

I’m also a little bit worried that semi-colon URLs just look bad/unfamiliar and people will troll us a bit for that.

I don’t want to take into account being trolled into that decision (we already get trolled, we’ll get trolled whatever we do, and we shouldn’t factor that into what’s the best decision).


#9

I’m going to reply for completeness, but as mentioned in my reply to @pwagenet I currently think the semicolon params are a bad idea.

Matrix params don’t prohibit the global param case, it’s just a bit ugly:

http://example.com/;foo=bar
http://example.com/;foo=bar/bazroute
http://example.com/;foo=bar/bazroute;qux=noo

#10

So, all things considered, it looks like there are only 3 options and only one of them doesn’t suck, which is:

Registering on the route which params a route handler is interested in. For example:

observesParameters: [‘sort’, ‘search’] });```

this is the approach taken by ember-query-params and IMO is a good compromise.

This is @Elte_Hupkes approach and between us I think we can work up a pull request. I’ll wait a day or so to hear if there are any objections to this before spending a load of time on it, or if anyone else suddenly has a flash of insight and can think of something better.


#11

I had another idea: I think it’s reasonable to specify which routes query params apply to, but it shouldn’t be in the route itself with the observesParams property, it should be in the Router.map function:

App.Router.map(function() {
  this.resource('foos', {queryParams: ['page', 'sortField']}, function() {
    this.resource('foo', {path: '/foos/:id', queryParams: ['sortField', 'bar']});
  });
});

// in the url /foos?page=2&sortField=name, both params apply to the foos router
// in the url /foos/1?bar=something, the bar param only applies to the foo route
// in the url /foos/1?sortField=name, the sortField param applies to both routes

Does this API look good?


#12

So we’ve added query string parameters to our app with a few patches to the router and reopening Ember.Route, it’s ugly, but has worked thus far. I’d like to share a few of my learnings in this case, at least with things that relate to our app.

First off, we don’t specify a set of parameters for each route, instead we just assume that all the parameters are intended for the deepest route (or leaf node). Admittedly, that is not as powerful as the proposed design here, but I’ve never come across a case were a resource route needs parameters. It’s almost always search/sort/filter for a list.

Second, I’ve seen some debate about whether the parameter should before or after the route. E.g. /#/foos/1?bar=something or /?bar=something#/foos/1. I can tell you from experience the former, is far better than the later. Primarily because the with the later, your parameters become part of window.location.search while with the former the entire thing is part of window.location.hash. That makes a big difference on how the browser behaves.

I guess those are my two main learnings. The rest is obvious. Thanks so much for all your hard work @machty, @alexspeller and others. I would love very much for this to be in the core.


#14

@cavneb (and everyone else) I just pushed support for ember master here


#15

This is definitely needed - some of my use cases are for leaf routes but we do a lot of filtering, and in that case the filters need to apply to all child routes, e.g. if you are filtering by date and have e.g. a fromDate param, you want that to apply to all routes that load any dated data

This makes sense, if you’re not using history location, then you shouldn’t expect anything other than window.location.hash to update. Is this supported on all browsers that ember supports, though?


#16

First off, thanks everyone for your efforts!

The “observesParameters” with global query parameters approach sounds very reasonable to me and I’d love to see this in Ember.

The resource() syntax looks fine to me, too, but what exactly are the benefits of that? I personally tend to prefer the observesParameters-style, which also can be used in Mixins etc.


#17

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.


#18

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?


#19

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?


#20

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.


#21

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