Persisting multiple query params of the same name


#1

First, some background: I work on a project that has an established request API pattern. One of the patterns is to use the same param name for OR and range filters in the query params. For example:

A date range of 2015-02-01 to 2015-02-15 is to be constructed as:

-http://my-project.dev?dateRange=2015-02-01&dateRange=2015-02-15

Similarly, looking for statuses of active and deleted is expected to be:

-http://my-project.dev?status=active&status=deleted

Under normal circumstances, this is just a simple request to REST API and I can construct the request anyway the API expects. But the tricky thing is, this URL is also expected to populate the browser location bar. And when this happens, Ember collapses the repetitive param names into a single param of the said name and takes the last value even if the location bar shows them repeated. And the problem is compounded as the user starts to sort the table…

The end result is:

-http://my-project.dev?status=deleted&sortProperties=…< snip >

So my question is: How does one go about keeping the states of request params of the same name? Do I have to override the serializeQueryParam and deserializeQueryParam methods in the route somehow?

Thanks for your time.

Correction: Previously I’d mis-stated that the “browser” collapses the query param keys. It’s actually Ember that performed the collapsing as it deserializes the query params into a JSON object and uses it to build a query string with.


#2

I had this problem at first. How are you making the query? Through the normal Ember way in the route? I ended up using the ajax request below. It’s not a pretty url but it works. One of my params can have many values so it gets put in the params object like this:

id = [2, 19, 52, 99, 33].

The url for this would repeat id 5x.

id=2&id=19&id=52&id=99&id=33.

This wasn’t for a model though. I’m not sure whether you could just stick it in the model hook in the route or not.

Ember.$.ajax({
        url: 'api/the_data?',
        data: Ember.$.param(params, false)})

#3

@JKGisMe, thanks for the reply.

Yes, I’m making this request the “Ember way” in the routes:

app/routes/my-model/search-results.js

export default Ember.Route.extend({
  model: function(params) {
    return this.store.find('myModel', params);
  }
});

This was great because Ember took care of keeping track of params. I originally man-handled requests and request URI construction like you did. But when sorting came into play, I thought it was probably easier to go with Ember’s conventions (so that sorting and everything else would “just work” without me having to keep track of states).


#4

Yes I can see that. In my situation I didn’t want it to be a route really because it’s sort of a dynamic data lookup with no persistance/records. If it was one of my models I would feel the same. Actually now that I reread your post, I did hack together a regex type situation to pass my dateRange in one param

dateRange=date1 + '-' +  date2

and then in the backend I split it. And I did this after you posted this! :smile: Not my most elegant solution for sure. Have you figured out a better way?


#5

So I think I have a working solution. And it’s a lot simpler than everything @JKGisMe and I have tried so far.

Disclaimer: I don’t know if this is the best solution. Anyone who knows more about inner workings of Ember please verify and advise.

The solution takes advantage of:

There are a few of steps to this:

  • First, in the route, right before the query, I set the $.ajaxSettings.traditional to true and then turn it off once the request is done (since the rest of my app doesn’t need this property turned on); tiny tweak to the deserializeQueryParam method

/app/routes/search-results/search-results.js

export default Ember.Route.extend({
  model: function(params) {
    Ember.$.ajaxSettings.traditional = true;
    return this.store.findQuery('searchResult', params).then(function(data) {
      Ember.$.ajaxSettings.traditional = false;
      return data;
    });
  },
  serializeQueryParam: function(value, urlKey, defaultValueType) {
    return defaultValueType === 'array' ? value : this._super(value, urlKey, defaultValueType);
  }
  deserializeQueryParam: function(value, urlKey, defaultValueType) {
    return defaultValueType === 'array' ? value : this._super(value, urlKey, defaultValueType);
  }
});
  • Next, in the controller where I need the query params to be kept track of, Ember.SortableMixin is used
  • Then for all the params that need to have the keys repeated, they are set as array attributes of the controller

/app/controller/some-model/search-results.js

import Ember from 'ember';
export default Ember.Controller.create(Ember.SortableMixin, {
  /* the whole point of using queryParams and SortableMixin is so that when user sorts columns, query param states can be preserved automatically by Ember */
  queryParams: ['sortProperties', 'sortAscending', 'dateRange', 'status'],
  // "dateRange" and "status" are the keys that need to repeat
  dateRange: [],
  status: []
});
  • Finally, the the query param of the concerning key should be named with square brackets like such:

-http://my-project.dev/search-results?dateRange[]=2015-02-01&dateRange[]=2015-02-15&status[]=active&status[]=deleted

The result is the URL will always appear as the above (with square brackets), but the queries made by Ember will appear without the square brackets:

-http://my-project.dev/search?dateRange=2015-02-01&dateRange=2015-02-15&status=active&status=deleted

The set up is a bit on the tedious side. But for my project, this beats the alternative of writing my own queryParams parser to support repeating param keys.


#6

Just a note that the sortableMixin is now private. I just removed it from my controller and it’s working exactly the same. But I’ve moved my sorting to the server anyway.