Skip to content

Instantly share code, notes, and snippets.

@machty
Created February 28, 2014 17:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save machty/d687cab552ecee2f9162 to your computer and use it in GitHub Desktop.
Save machty/d687cab552ecee2f9162 to your computer and use it in GitHub Desktop.
Proposal for next iteration of query params

query-params-new

It's amazing, but not good enough.

Things that suck

Consider:

App.PeopleController = Ember.ArrayController.extend({
  queryParams: ['page'],
  page: 1
});

Here's what's wrong with the above:

  • As the url changes, page will become a string (e.g. "2", "3", etc), so you have to do a lot of parseInting if you need it to be treated as a number, which is obviously annoying.
  • page property is annoyingly sticky; if you link-to 'people' and don't specify QP props in the query-params sexpr, the first transition into it will use the default value of 1, but if you change the value of page to '2', navigate away, and click that same link-to to get back into 'people', page stickily retains its value of '2' on the grounds that link-to doesn't specify any QP values to override. This is very bizarre, see a more detailed explanation here
  • Having all this stuff on controllers contradicts our goal of getting the lazy-loading of routes/controllers/templates/sections of your app.

Proposal

App.PeopleRoute = Ember.Route.extend({
  queryParams: {
    peoplePage: { 
      defaultValue: 1,

      // default is false; `replace: true` means that when a controller
      // changes `queryParams.peoplePage`, the URL will be updated
      // via replaceState rather than the default pushState.
      // (fwiw: this mimics the existing api of `link-to 'people' replace=true`)
      replace: true,

      // default is false; this opts into a full transition when
      // this QP changes.
      refreshModel: true 
    }
  }
});

App.PeopleController = Ember.ArrayController.extend({
  // This is how controllers can pick a more appropriate
  // name/interface for a query param prop; the router
  // knows it as its url-serialized key `peoplePage`, 
  // but the PeopleController can just call it `page`
  page: Ember.computed.alias('queryParams.peoplePage')
});

Here's how this fixes the above concerns:

  • When the url changes to ?peoplePage=123, we can infer from the default value to deserialize into a number, so PeopleController sees queryParams.peoplePage as 123 rather than "123".
  • Things are appropriately less sticky; if you link to a route without specifying a QP value (via query-params sexpr), and there's no serialized key-value pair in the URL already, then clicking that link will 1) not even bother serializing anything into the final URL for that QP, 2) the controller will see queryParams.peoplePage as the defaultValue specified, or null if none specified.
  • link-to won't require eager Controller initialization (or prototype hacks); if you say link-to 'people' (query-params lol="wat"), it'll serialize an href of "/people?lol=wat" even if that qp is totally unused (thereafter, if you click a link elsewhere, the lol=wat will disappear from the url unless someone claims it).

Other implications

  • link-to will have to specify Route-level query param keys, rather than the present day of specifying property names of controllers in destination route hierarchies.
@iStefo
Copy link

iStefo commented Feb 28, 2014

Adding the possibility to describe QPs in the route a QP belongs to is definitely a good choice. I think it's better to give the option to specify every detail instead of having only pre-defined behavior which may prevent the feature from being used. Also, custom serializers/deserializers as the last resort would be much appreciated.

As I wrote down my points, I had not even thought about the problem with stickiness, but (as far as I understand your explanation), this seems quiet reasonable.

One thing I want to make sure is not forgotten regarding the defaultValue: When a QP takes its defaultValue, it should not be visible in the URL, right? This is an important thing when trying to keep clean URLs while having many QPs on one route.

Also (but this may be a more handlebars related question): With @alexspeller's implementation of queryParams it was possible to bind the whole QP-thingy in a link-to roughly like this: {{#link-to "route" queryParams=params}} where params is a hash generated in the controller. I used this to render a list of "predefined filters" by using {{each}} and a list of controller-generated QP options. Would this be possible without listing every possible QP key in the (query-params) subexpression like (query-param author=author search=search state=state ...)?

Again, thanks for your work and open discussion of this feature!

@manuelmitasch
Copy link

  • Love the type inference based on the default value.
  • I think it's great to move the QP definition into the route. This way all routing details are moved into the router + routes.
  • I like how the refreshModel replaces the needed this.refresh in the queryParamsDidChange action handler. Adds a more fine grained option and a proper API.
  • As Stefan mentioned: default values should be hidden from the URLs.

As I tried to describe through Twitter stickiness could sometimes be the desired behavior or it should be possible to emulate it.

My use case was (something similar to):

Router.map(function() {
  this.resource('posts', { path: '/'});
  this.resource('post', { path: '/:id'});
});

The posts route renders a paginated list of posts (QP page). After clicking a post you can view the details on the post route. In the post template I wanted to link back to the posts route with the last active page QP. In that way the user can continue browsing the posts list from the last position/page.

This works out of the box in the current canary version due to the stickiness of QP. How could this behavior be emulated with your proposal? I believe we would need to loop through the QP from posts to post. Just to be able to set the page QP on the link-to from post afterwards.

In Posts Template

{{link-to "post" (query-params page=queryParams.page)}}     

In Post Template

{{link-to "posts" (query-params page=queryParams.page)}}     

Not very elegant, but would work this way, right?

@joefiorini
Copy link

@machty 👍 on this proposal. The route is where other URL-based work goes, so it makes sense to put queryParams there too.

@jerel
Copy link

jerel commented Mar 4, 2014

I really like the defaultValue proposal as I have been unable to keep a clean url with the canary version. As manuelmitasch mentions I do rely on the sticky behavior as I have a list filtered by query params and also a map filtered by the same query params. When the user configures the list the way they want and then link-to to the map and back the params need to persist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment