Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

query string route syntax & parameter-based URI decoding #668

Closed
wants to merge 18 commits into from

11 participants

Joe Hudson Ryan Eastridge Ricardo Fritsche lapidus Aaron Greenlee Sam Breed Jeremy Ashkenas Matt Perpick Chris Mountford Russell Davis naid
Joe Hudson

Query string route syntax

Any route except those ending with a wildcard will automatically accept additional content using the '?' separator. This content is a set of key value pairs using '&' as the pair separator and '=' as the key/value separator - just like the URL query string syntax.

If a query string exists in the route hash, the routing function (defined in the routes hash) will be given an additional parameter (in last ordinal position) containing hash of the key/value data.

routes: {
'foo/:bar': 'myRoute'
}
...
myRoute: function(bar, params) {
// the params attribute will be undefined unless there is a route containing a query string
}

Example route patterns

#foo/abc -> myRoute( 'abc', undefined )

#foo/abc?foo=bar -> myRoute( 'abc', {'foo': 'bar'} )

Nested query string Keys

Any keys containing '.' will represent a nested structure.

#foo/abc?me.fname=Joe&me.lname=Hudson -> myRoute('abc', {'me': {'fname': 'Joe', 'lname': 'Hudson'}} )

Query string value arrays

Any values containing '|' will assume an array structure. Note: you can still have non-array values containing '|' but it must be URI encoded (%7C).

You can prefix the value with '|' to ensure an array in case there is only a single value.

#foo/abc?animals=cat|dog -> myRoute( 'abc', ['cat', 'dog'] )

#foo/abc?animals=|cat -> myRoute( 'abc', ['cat'] )

Generating route with query string=========================

You can easily create a route string from a route + parameter hash using the router.toFragment(route, parameters) method. It can contain a nested hash structure or arrays. ex:
var route = router.toFragment('myroute', {
a:'l', b:{c: 'n', d:'m', e:{f: 'o'}}, array1:['p'], array2:['q', 'r'], array3:['s','t','|']
});

Parameter-based URI decoding

Current backbone will decode the complete hash value. This requires route parameters to be encoded multiple times if they contain reserved characters ('/' for example).

This patch moves all decoding to route parameters when they are in parameter form (after the hash has been parsed).

jhudson8 added some commits
Joe Hudson jhudson8 - changed hash decoding to individual parameters (so parameter data d…
…oes not have to be encoded twice if it contains route reserved characters)

- added new route syntax routeName/?
  - will match route 'routeName/a=b&c=d'
  - can still use route parameters -> routeName/:foo/?
  - the last route controller function argument is a hash containing key/values matching the query string (with values URI decoded)
9d9e5ec
Joe Hudson jhudson8 fixed query string route test bug f4798d5
Joe Hudson jhudson8 added query string and URI decoding tests 36afe19
Ricardo Fritsche

Nice!

Would be nice to have an method to set/update the query string on the current URL.

lapidus

+1

jhudson8 added some commits
Joe Hudson jhudson8 change credit to Kevin Decker
This allows for routes that do not have a / separating the param section and the path section, i.e.

        "ip/:name/:id?": "item",
        "ip/:id?": "item",
34da7a9
Joe Hudson jhudson8 added test case and fixed bug 30779b8
Joe Hudson jhudson8 no longer need to explicitely define the ? in the route definition fo…
…r query parameters.

any route ending with ?... will get an aditional hash with the parameters
3a2cf52
Joe Hudson jhudson8 removed console log statements b2ad52c
Joe Hudson jhudson8 added reserved char test case that was removed a01c507
Sam Breed
Collaborator

maybe a good call to add some updated documentation to go along with this?

lapidus

Does it also decode objects encoded in the query string? I.e. "?a[b]=c"
(I tried, but only seemed to get "primitive types".)

Ryan Eastridge

Joe, I believe we discussed this in Campfire but ++ decoding nested objects in the query string.

Joe Hudson

It doesn't currently but can be done - I'll see if I can get that in there

lapidus

Nice. I've been using this in the past:
http://benalman.com/code/projects/jquery-bbq/examples/deparam/

Something along these lines:

routeHandler: function(params) {
var paramsObject = jQuery.deparam( params );
}

Joe Hudson

Question to anyone participating in this pull request:

Would you rather have a helper method for creating a url with parameters
1) directly on Backbone.history.navigate - more convienant to use
2) in the router - in the same place that parameter decoding is
3) not at all

I've just added it to the router but want to get other opinions... Thanks.

Ricardo Fritsche

jhudson8, great work. I think it should be a method on the Router, so i can use it stand alone and for other uses, like to put it in href of some a tags and not really change the url to it.

Ryan Eastridge

Great work Joe. Since navigate already has extra arguments on it I'd say keep it separate. The example you posts with the encoding done in toFragment seems like it would suffice.

Is there a reason you went with "." as the object separator in the query string? I'd prefer to see:

"address[street]" rather than "address.street" to match PHP and Rails.

Ricardo Fritsche

I agree with syntacticx.

Maybe it should be optional as dot is really cleaner and using [] will only be useful if you are going to use it with HTML5 history (not using #) and really decode the URLs on the server side.

Ryan Eastridge

@ricardovf, I'm arguing for using brackets rather than the dot syntax.

Ricardo Fritsche

@syntacticx my comment was confusing

Im arguing to make it optional to use dot or brackets since i see the point of using both depending on the project/objective. Dunno if its gonna be a hard thing thought.

Joe Hudson

I previously implemented the a[b] syntax and got feedback requesting the a.b syntax. I ended up realizing that I can't satisfy everyone and went with what I prefer which is the a.b syntax. There are hooks to easily change the behavior to suit your needs.

_setParamValue - for decoding the parameters
_toQueryParamName - for encoding the parameters (specifically, creating the name for the parameter)

Jeremy Ashkenas
Owner

So -- this is a fantastic feature for a Backbone plugin, and I highly encourage you to wrap it up and publish it as such on the Wiki.

But I don't think that query parameters in routes make sense for Backbone core. A big part of having a Backbone.Router, instead of using something like jQuery BBQ, is so that you have clean, pretty URL fragments like:

/search/madoff/p10

Instead of the nastier:

?action=search&query=madoff&page=10

The vast majority of Backbone apps will have a limited set of client-side URLs that can be easily expressed in the former notation. There may be some that have such a wide variety of URL possibilities that query params are a better choice, but they're going to be in the minority, and it's not worth adding this amount of code to core Backbone to help 'em out, when a plugin will work equally well.

Finally, whereas query parameters are the only way to communicate data to the server side in a GET request ... this is the client-side were talking about here, and a large number of your query params are probably better passed around in pure JavaScript, and not jammed into the URL, unless they're something you truly intend to make bookmarkable.

Jeremy Ashkenas jashkenas closed this
Joe Hudson

:(

I understand - thanks for taking the time to evaluate.

I would like to mention that it was never intended as a mutually exclusive deal... #/foo/bar?a=b&c=d

The problem is that the pretty URLs become much less pretty when you have optional parameters. The other problem is that there is also a fragment decoding issue (fragment is decoded before splitting it into parts) which makes parameters that contain special characters ('/') have to be encoded more than once.

Jeremy Ashkenas
Owner

Just out of curiosity, in your actual app -- what are some examples of real client-side URLs that take advantage of these query params?

Joe Hudson

Jeremy, will get details for you - I'm going to make sure it's ok to tell you who I work for (I think it's fine but just in case)...

In the meantime: https://github.com/documentcloud/backbone/wiki/Adding-Query-Parameter-support

I assume this is what you mean by a plugin - please let me know if there is a better way. Thanks.

Ricardo Fritsche

@jashkenas github uses it:

https://github.com/documentcloud/backbone/issues?labels=enhancement&sort=updated&direction=desc&state=open&page=1

The route could be: /documentcloud/backbone/issues

And everytime the query changes the route would be called with diferente query strings.

As you can note for exemple in the Labels selector (bottom left) the href of the links is changed in real time with updated url everytime i use a diferente filter.

The advantage of jhudson is that is really simplier to deal with an object (from query string) then from custom uri, like: /search/madoff/p10

Joe Hudson

Also, if this functionality is to remain as a plugin, I would like to request that the fragment uri decoding be pushed to _extractParameters - out of getFragment. Doing this would all this type of plugin with minimal risk to backbone core changes. Thoughts?

Jeremy Ashkenas
Owner

Yep -- extracting the decoding to an overridable function sounds like a fine idea.

Joe Hudson

Cool, then this really works just fine as a plugin - and you only have to get it if you want it. Thanks Jeremy.

Jeremy Ashkenas
Owner

@jhudson8 -- no, a massive Wiki paste is not what I mean by a plugin. ;)

Instead, make a github project that includes a script that provides the override functionality. So if I load backbone.js, and then backbone.queryparams.js ... I'll have a working version.

Joe Hudson

:) thanks

Matt Perpick

For what it's worth, my team has added hash string query parameters as well. URL fragments don't really scale or handle optional parameters. Also, returning a hash of unordered params from routes is nice, because adding or removing parameters doesn't require a change to the routing code.

Chris Mountford

A case for adding this functionality to core:

Whenever parameters can have a "/" in them, those values cannot be made to work in path params if tomcat or apache are involved. %2F is the URL encoded form of "/" but that value will not hide the "/" in the path param.

This means query params for any parameters whose values could possibly contain a "/", such as search queries. So search queries must go in the query params and therefore the backbone routes break down.

Slosh "\" is also impossible to work with in this way.

There is one case in which a "/" can work with path params, that is if it is the last path param and the slash is NOT URI encoded.

The relevant spec reference: http://labs.apache.org/webarch/uri/rfc/rfc3986.html#percent-encoding

"The purpose of reserved characters is to provide a set of delimiting characters that are distinguishable from other data within a URI. URIs that differ in the replacement of a reserved character with its corresponding percent-encoded octet are not equivalent. Percent-encoding a reserved character, or decoding a percent-encoded octet that corresponds to a reserved character, will change how the URI is interpreted by most applications. Thus, characters in the reserved set are protected from normalization and are therefore safe to be used by scheme-specific and producer-specific algorithms for delimiting data subcomponents within a URI."

Russell Davis

@jhudson8, any plans to make this into a plugin like @jashkenas suggested?

Joe Hudson

russelldavis,

Thanks for reminding me. I'll do it soon.

naid

thank you jhudson8, that helped us a lot =)

Joe Hudson

No problem, I'm glad it helped.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Oct 14, 2011
  1. Joe Hudson

    - changed hash decoding to individual parameters (so parameter data d…

    jhudson8 authored
    …oes not have to be encoded twice if it contains route reserved characters)
    
    - added new route syntax routeName/?
      - will match route 'routeName/a=b&c=d'
      - can still use route parameters -> routeName/:foo/?
      - the last route controller function argument is a hash containing key/values matching the query string (with values URI decoded)
  2. Joe Hudson
  3. Joe Hudson
  4. Joe Hudson
Commits on Oct 25, 2011
  1. Joe Hudson

    change credit to Kevin Decker

    jhudson8 authored
    This allows for routes that do not have a / separating the param section and the path section, i.e.
    
            "ip/:name/:id?": "item",
            "ip/:id?": "item",
  2. Joe Hudson

    added test case and fixed bug

    jhudson8 authored
Commits on Oct 27, 2011
  1. Joe Hudson

    no longer need to explicitely define the ? in the route definition fo…

    jhudson8 authored
    …r query parameters.
    
    any route ending with ?... will get an aditional hash with the parameters
  2. Joe Hudson
  3. Joe Hudson
Commits on Nov 2, 2011
  1. Joe Hudson

    added complex key structure support

    jhudson8 authored
    ex: #foo?foo[a]=b
    
      {
        'foo': {
          'a': 'b'
        }
      }
  2. Joe Hudson

    changed hash notation to a.b.c

    jhudson8 authored
    added value array notation using a|b|c
Commits on Nov 15, 2011
  1. Joe Hudson
Commits on Nov 28, 2011
  1. Joe Hudson

    added includeQueryPath parameter to getFragment

    jhudson8 authored
    removed query string content from getFragment by default
    added getQueryString method
  2. Joe Hudson
  3. Joe Hudson
Commits on Nov 29, 2011
  1. Joe Hudson

    getFragment does not default to removing the query string (but is sti…

    jhudson8 authored
    …ll available as a method parameter) - so it works just like it used to
  2. Joe Hudson

    added router toFragment method to serialize a hash/array structure in…

    jhudson8 authored
    …to the appropriate route with query string parameters
Commits on Nov 30, 2011
  1. Joe Hudson
This page is out of date. Refresh to see the latest.
Showing with 263 additions and 14 deletions.
  1. +152 −7 backbone.js
  2. +111 −7 test/router.js
159 backbone.js
View
@@ -664,6 +664,7 @@
// Cached regular expressions for matching named param parts and splatted
// parts of route strings.
+ var queryStringParam = /^\?(.*)/;
var namedParam = /:([\w\d]+)/g;
var splatParam = /\*([\w\d]+)/g;
var escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g;
@@ -714,17 +715,149 @@
// against the current location hash.
_routeToRegExp : function(route) {
route = route.replace(escapeRegExp, "\\$&")
- .replace(namedParam, "([^\/]*)")
- .replace(splatParam, "(.*?)");
+ .replace(namedParam, "([^\/?]*)")
+ .replace(splatParam, "([^\?]*)");
+ route += '([\?]{1}.*)?';
return new RegExp('^' + route + '$');
},
// Given a route, and a URL fragment that it matches, return the array of
// extracted parameters.
_extractParameters : function(route, fragment) {
- return route.exec(fragment).slice(1);
- }
+ var params = route.exec(fragment).slice(1);
+
+ // do we have an additional query string?
+ var match = params.length && params[params.length-1] && params[params.length-1].match(queryStringParam);
+ if (match) {
+ var queryString = match[1];
+ var data = {};
+ if (queryString) {
+ var keyValues = queryString.split('&');
+ var self = this;
+ _.each(keyValues, function(keyValue) {
+ var arr = keyValue.split('=');
+ if (arr.length > 1 && arr[1]) {
+ self._setParamValue(arr[0], arr[1], data);
+ }
+ });
+ }
+ params[params.length-1] = data;
+ }
+
+ // decode params
+ for (var i=1; i<params.length; i++) {
+ if (_.isString(params[i])) {
+ params[i] = decodeURIComponent(params[i]);
+ }
+ }
+
+ return params;
+ },
+
+ _setParamValue : function(key, value, data) {
+ // use '.' to define hash separators
+ var parts = key.split('.');
+ var _data = data;
+ for (var i=0; i<parts.length; i++) {
+ var part = parts[i];
+ if (i === parts.length-1) {
+ // set the value
+ _data[part] = this._decodeParamValue(value, _data[part]);
+ } else {
+ _data = _data[part] = _data[part] || {};
+ }
+ }
+ },
+
+ _decodeParamValue : function(value, currentValue) {
+ // '|' will indicate an array. Array with 1 value is a=|b - multiple values can be a=b|c
+ if (value.indexOf('|') >= 0) {
+ var values = value.split('|');
+ // clean it up
+ for (var i=values.length-1; i>=0; i--) {
+ if (!values[i]) {
+ values.splice(i, 1);
+ } else {
+ values[i] = decodeURIComponent(values[i])
+ }
+ }
+ return values;
+ }
+ if (!currentValue) {
+ return decodeURIComponent(value);
+ } else if (_.isArray(currentValue)) {
+ currentValue.push(value);
+ return currentValue;
+ } else {
+ return [currentValue, value];
+ }
+ },
+ // Return the route fragment with queryParameters serialized to query parameter string
+ toFragment: function(route, queryParameters) {
+ if (queryParameters) {
+ if (!_.isString(queryParameters)) {
+ queryParameters = this._toQueryString(queryParameters);
+ }
+ route += '?' + queryParameters;
+ }
+ return route;
+ },
+
+ // Serialize the val hash to query parameters and return it. Use the namePrefix to prefix all param names (for recursion)
+ _toQueryString: function(val, namePrefix) {
+ if (!val) return '';
+ namePrefix = namePrefix || '';
+ var rtn = '';
+ for (var name in val) {
+ var _val = val[name];
+ if (_.isString(_val) || _.isNumber(_val) || _.isBoolean(_val) || _.isDate(_val)) {
+ // primitave type
+ _val = this._stringifyQueryParam(_val);
+ if (_.isBoolean(_val) || _val) {
+ rtn += (rtn ? '&' : '') + this._toQueryParamName(name, namePrefix) + '=' + encodeURIComponent(_val).replace('|', '%7C');
+ }
+ } else if (_.isArray(_val)) {
+ // arrrays use | separator
+ var str = '';
+ for (var i in _val) {
+ var param = this._stringifyQueryParam(_val[i]);
+ if (_.isBoolean(param) || param) {
+ str += '|' + encodeURIComponent(param).replace('|', '%7C');
+ }
+ }
+ if (str) {
+ rtn += (rtn ? '&' : '') + this._toQueryParamName(name, namePrefix) + '=' + str;
+ }
+ } else {
+ // dig into hash
+ var result = this._toQueryString(_val, this._toQueryParamName(name, namePrefix, true));
+ if (result) {
+ rtn += (rtn ? '&' : '') + result;
+ }
+ }
+ }
+ return rtn;
+ },
+
+ // return the actual parameter name
+ // name: the parameter name
+ // namePrefix: the prefix to the name
+ // createPrefix: true if we're creating a name prefix, false if we're creating the name
+ _toQueryParamName: function(name, prefix, isPrefix) {
+ return (prefix + name + (isPrefix ? '.' : ''));
+ },
+
+ // Return the string representation of the param used for the query string
+ _stringifyQueryParam: function (param) {
+ if (_.isNull(param) || _.isUndefined(param)) {
+ return null;
+ }
+ if (_.isDate(param)) {
+ return param.getDate().getTime();
+ }
+ return param;
+ }
});
// Backbone.History
@@ -739,6 +872,7 @@
// Cached regex for cleaning hashes.
var hashStrip = /^#*/;
+ var queryStrip = /\?(.*)/;
// Cached regex for detecting MSIE.
var isExplorer = /msie [\w.]+/;
@@ -755,7 +889,7 @@
// Get the cross-browser normalized URL fragment, either from the URL,
// the hash, or the override.
- getFragment : function(fragment, forcePushState) {
+ getFragment : function(fragment, forcePushState, excludeQueryString) {
if (fragment == null) {
if (this._hasPushState || forcePushState) {
fragment = window.location.pathname;
@@ -765,11 +899,20 @@
fragment = window.location.hash;
}
}
- fragment = decodeURIComponent(fragment.replace(hashStrip, ''));
+ fragment = fragment.replace(hashStrip, '');
+ if (excludeQueryString) {
+ fragment = fragment.replace(queryStrip, '');
+ }
if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
return fragment;
},
+ getQueryString : function(fragment) {
+ fragment = this.getFragment(fragment);
+ var match = fragment.match(queryStrip);
+ return match && match[1];
+ },
+
// Start the hash change handling, returning `true` if the current URL matches
// an existing route, and `false` otherwise.
start : function(options) {
@@ -868,7 +1011,9 @@
}
}
if (triggerRoute) this.loadUrl(fragment);
- }
+ },
+
+
});
118 test/router.js
View
@@ -17,28 +17,35 @@ $(document).ready(function() {
this.testing = options.testing;
},
- search : function(query, page) {
+ search : function(query, page, queryParams) {
this.query = query;
this.page = page;
+ this.queryParams = queryParams;
+ this.queryString = Backbone.history.getQueryString();
+ this.fragment = Backbone.history.getFragment(null, false, true);
},
- splat : function(args) {
+ splat : function(args, queryParams) {
this.args = args;
+ this.queryParams = queryParams;
},
- complex : function(first, part, rest) {
+ complex : function(first, part, rest, queryParams) {
this.first = first;
this.part = part;
this.rest = rest;
+ this.queryParams = queryParams;
},
- query : function(entity, args) {
+ query : function(entity, args, queryParams) {
this.entity = entity;
this.queryArgs = args;
+ this.queryParams = queryParams;
},
- anything : function(whatever) {
+ anything : function(whatever, queryParams) {
this.anything = whatever;
+ this.queryParams = queryParams;
}
});
@@ -71,10 +78,87 @@ $(document).ready(function() {
}, 10);
});
- test("Router: routes via navigate", 2, function() {
+ asyncTest("Router: routes (two part - encoded reserved char)", 2, function() {
+ window.location.hash = 'search/nyc/pa%2Fb';
+ setTimeout(function() {
+ equals(router.query, 'nyc');
+ equals(router.page, 'a/b');
+ start();
+ }, 10);
+ });
+
+ asyncTest("Router: routes (two part - query params)", 5, function() {
+ window.location.hash = 'search/nyc/p10?a=b';
+ setTimeout(function() {
+ equals(router.query, 'nyc');
+ equals(router.page, '10');
+ equals(router.fragment, 'search/nyc/p10');
+ equals(router.queryString, 'a=b');
+ equals(router.queryParams.a, 'b');
+ start();
+ }, 10);
+ });
+
+ asyncTest("Router: routes (two part - query params - hash and list - location)", 23, function() {
+ window.location.hash = 'search/nyc/p10?a=b&a2=x&a2=y&a3=x&a3=y&a3=z&b.c=d&b.d=e&b.e.f=g&array1=|a&array2=a|b&array3=|c|d&array4=|e%7C';
+ setTimeout(function() {
+ equals(router.query, 'nyc');
+ equals(router.page, '10');
+ equals(router.queryParams.a, 'b');
+ equals(router.queryParams.a2.length, 2);
+ equals(router.queryParams.a2[0], 'x');
+ equals(router.queryParams.a2[1], 'y');
+ equals(router.queryParams.a3.length, 3);
+ equals(router.queryParams.a3[0], 'x');
+ equals(router.queryParams.a3[1], 'y');
+ equals(router.queryParams.a3[2], 'z');
+ equals(router.queryParams.b.c, 'd');
+ equals(router.queryParams.b.d, 'e');
+ equals(router.queryParams.b.e.f, 'g');
+ equals(router.queryParams.array1.length, 1);
+ equals(router.queryParams.array1[0], 'a');
+ equals(router.queryParams.array2.length, 2);
+ equals(router.queryParams.array2[0], 'a');
+ equals(router.queryParams.array2[1], 'b');
+ equals(router.queryParams.array3.length, 2);
+ equals(router.queryParams.array3[0], 'c');
+ equals(router.queryParams.array3[1], 'd');
+ equals(router.queryParams.array4.length, 1);
+ equals(router.queryParams.array4[0], 'e|');
+ start();
+ }, 10);
+ });
+
+ asyncTest("Router: routes (two part - query params - hash and list - navigate)", 15, function() {
+ window.location.hash = router.toFragment('search/nyc/p10', {
+ a:'l', b:{c: 'n', d:'m', e:{f: 'o'}}, array1:['p'], array2:['q', 'r'], array3:['s','t','|']
+ });
+ setTimeout(function() {
+ equals(router.query, 'nyc');
+ equals(router.page, '10');
+ equals(router.queryParams.a, 'l');
+ equals(router.queryParams.b.c, 'n');
+ equals(router.queryParams.b.d, 'm');
+ equals(router.queryParams.b.e.f, 'o');
+ equals(router.queryParams.array1.length, 1);
+ equals(router.queryParams.array1[0], 'p');
+ equals(router.queryParams.array2.length, 2);
+ equals(router.queryParams.array2[0], 'q');
+ equals(router.queryParams.array2[1], 'r');
+ equals(router.queryParams.array3.length, 3);
+ equals(router.queryParams.array3[0], 's');
+ equals(router.queryParams.array3[1], 't');
+ equals(router.queryParams.array3[2], '|');
+ start();
+ }, 10);
+ });
+
+ test("Router: routes via navigate", 4, function() {
Backbone.history.navigate('search/manhattan/p20', true);
equals(router.query, 'manhattan');
equals(router.page, '20');
+ equals(router.queryString, undefined);
+ equals(router.fragment, 'search/manhattan/p20');
});
asyncTest("Router: routes (splats)", function() {
@@ -85,6 +169,15 @@ $(document).ready(function() {
}, 10);
});
+ asyncTest("Router: routes (splats - query params)", 2, function() {
+ window.location.hash = 'splat/long-list/of/splatted_99args/end?c=d';
+ setTimeout(function() {
+ equals(router.args, 'long-list/of/splatted_99args');
+ equals(router.queryParams.c, 'd');
+ start();
+ }, 10);
+ });
+
asyncTest("Router: routes (complex)", 3, function() {
window.location.hash = 'one/two/three/complex-part/four/five/six/seven';
setTimeout(function() {
@@ -95,8 +188,19 @@ $(document).ready(function() {
}, 10);
});
+ asyncTest("Router: routes (complex - query params)", 4, function() {
+ window.location.hash = 'one/two/three/complex-part/four/five/six/seven?e=f';
+ setTimeout(function() {
+ equals(router.first, 'one/two/three');
+ equals(router.part, 'part');
+ equals(router.rest, 'four/five/six/seven');
+ equals(router.queryParams.e, 'f');
+ start();
+ }, 10);
+ });
+
asyncTest("Router: routes (query)", 2, function() {
- window.location.hash = 'mandel?a=b&c=d';
+ window.location.hash = router.toFragment('mandel', {a:'b', c:'d'});
setTimeout(function() {
equals(router.entity, 'mandel');
equals(router.queryArgs, 'a=b&c=d');
Something went wrong with that request. Please try again.