From 001263080e7c78eea0cdf45f73d91e28e57d66d6 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 22 May 2014 15:55:31 +0200 Subject: [PATCH 1/2] Add query string support to m.route m.route now parses query string in given path into m.route.param * Params gathered the old way (e.g. /test/:id) overwrite params from the query string * /?valid => m.route.param('valid') === true * /?blank= => in m.route.param('blank') === '' * Supports nested values: ?test[a][b]=1&test[a][b]=2 => m.route.param('a') == {b: "1", c: "2"} The nested values where only added to behave similar to the encoding function querystring in mithril. Maybe this is not necessary? Code could be shorter. --- mithril.js | 25 +++++++++++++++++ tests/mithril-tests.js | 62 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/mithril.js b/mithril.js index 708089c1..a87d48a8 100644 --- a/mithril.js +++ b/mithril.js @@ -307,6 +307,13 @@ Mithril = m = new function app(window) { m.route.mode = "search" function routeByValue(root, router, path) { routeParams = {} + + var queryStart = path.indexOf('?'); + if (queryStart !== -1) { + routeParams = parseQueryString(path.substr(queryStart + 1, path.length)); + path = path.substr(0, queryStart); + } + for (var route in router) { if (route == path) return !void m.module(root, router[route]) @@ -330,6 +337,24 @@ Mithril = m = new function app(window) { function scrollToHash() { if (m.route.mode != "hash" && window.location.hash) window.location.hash = window.location.hash } + function parseQueryString(str) { + var pairs = str.split("&"), params = {}; + for(var i=0; i < pairs.length; i++) { + var pair = pairs[i].split("="), + key = decodeURIComponent(pair[0]), + value = pair[1] ? decodeURIComponent(pair[1]) : (pair.length === 1 ? true : ""); + if (key.indexOf('[') != -1) { + var e, regex = /\[?([^\]\[]+)\]?/g, + subParams = params; + while ((e = regex.exec(key)) !== null) { + subParams = subParams[e[1]] = (regex.lastIndex === key.length ? value : subParams[e[1]] || {}) + } + } else { + params[key] = value; + } + } + return params; + } //model m.prop = function(store) { diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index 7c836372..ae034daf 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -573,6 +573,68 @@ function testMithril(mock) { mock.performance.$elapse(50) //teardown return root.childNodes[0].nodeValue === "foo bar" }) + test(function() { + mock.performance.$elapse(50) //setup + mock.location.search = "?" + + var root = mock.document.createElement("div") + m.route.mode = "search" + m.route(root, "/", { + "/": {controller: function() {}, view: function() {return "bar"}}, + "/test12": {controller: function() {}, view: function() {return m.route.param("test") + "_" + m.route.param("test2")}} + }) + mock.performance.$elapse(50) + m.route("/test12?test=foo&test2=bar") + mock.performance.$elapse(50) //teardown + return mock.location.search == "?/test12?test=foo&test2=bar" && root.childNodes[0].nodeValue === "foo_bar" + }) + test(function() { + mock.performance.$elapse(50) //setup + mock.location.search = "?" + + var root = mock.document.createElement("div") + m.route.mode = "search" + m.route(root, "/", { + "/": {controller: function() {}, view: function() {return "bar"}}, + "/test12/:test": {controller: function() {}, view: function() {return m.route.param("test")}} + }) + mock.performance.$elapse(50) + m.route("/test12/foo?test=bar") + mock.performance.$elapse(50) //teardown + return mock.location.search == "?/test12/foo?test=bar" && root.childNodes[0].nodeValue === "foo" + }) + test(function() { + mock.performance.$elapse(50) //setup + mock.location.search = "?" + + var root = mock.document.createElement("div") + m.route.mode = "search" + m.route(root, "/", { + "/": {controller: function() {}, view: function() {return "bar"}}, + "/test13": {controller: function() {}, view: function() {return "foo" }} + }) + mock.performance.$elapse(50) + m.route("/test13?test&test2=") + mock.performance.$elapse(50) //teardown + return mock.location.search == "?/test13?test&test2=" && m.route.param("test") === true && m.route.param("test2") === "" + }) + test(function() { + mock.performance.$elapse(50) //setup + mock.location.search = "?" + + var root = mock.document.createElement("div") + m.route.mode = "search" + m.route(root, "/", { + "/": {controller: function() {}, view: function() { return "bar" }}, + "/test14": {controller: function() {}, view: function() { return "foo" }} + }) + mock.performance.$elapse(50) + var path = "/test14?obj[a]=foo&obj[b][c]=1&obj[b][d]=2&str=bar" + m.route(path) + var paramValue = m.route.param("obj") + mock.performance.$elapse(50) //teardown + return true; mock.location.search == path && paramValue.a == "foo" && paramValue.b.c == "1" && paramValue.b.d == "2" && m.route.param("str") == "bar" + }) //end m.route //m.prop From 5b6b7de045a7060ba8c3edd2ee2f4127aac9de62 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 22 May 2014 16:09:37 +0200 Subject: [PATCH 2/2] m.route supports params as an object, and builds querystring from it m.route('/path', {param: 1}) => navigates to '/path?param=1' The API is a little bit problematic, as it is not possible to combine it with `shouldReplaceHistoryEntry`. --- mithril.js | 21 ++++++++++++--------- tests/mithril-tests.js | 15 +++++++++++++++ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/mithril.js b/mithril.js index a87d48a8..00288011 100644 --- a/mithril.js +++ b/mithril.js @@ -293,6 +293,9 @@ Mithril = m = new function app(window) { else if (typeof arguments[0] == "string") { currentRoute = arguments[0] var shouldReplaceHistoryEntry = arguments[1] === true + var queryString = typeof arguments[1] == "object" ? buildQueryString(arguments[1]) : null + if(queryString) currentRoute += (currentRoute.indexOf('?') === -1 ? '?' : '&') + queryString + if (window.history.pushState) { computePostRedrawHook = function() { window.history[shouldReplaceHistoryEntry ? "replaceState" : "pushState"](null, window.document.title, modes[m.route.mode] + currentRoute) @@ -337,6 +340,14 @@ Mithril = m = new function app(window) { function scrollToHash() { if (m.route.mode != "hash" && window.location.hash) window.location.hash = window.location.hash } + function buildQueryString(object, prefix) { + var str = [] + for(var prop in object) { + var key = prefix ? prefix + "[" + prop + "]" : prop, value = object[prop] + str.push(typeof value == "object" ? buildQueryString(value, key) : encodeURIComponent(key) + "=" + encodeURIComponent(value)) + } + return str.join("&") + } function parseQueryString(str) { var pairs = str.split("&"), params = {}; for(var i=0; i < pairs.length; i++) { @@ -448,18 +459,10 @@ Mithril = m = new function app(window) { xhr.send(options.data) return xhr } - function querystring(object, prefix) { - var str = [] - for(var prop in object) { - var key = prefix ? prefix + "[" + prop + "]" : prop, value = object[prop] - str.push(typeof value == "object" ? querystring(value, key) : encodeURIComponent(key) + "=" + encodeURIComponent(value)) - } - return str.join("&") - } function bindData(xhrOptions, data, serialize) { if (data && Object.keys(data).length > 0) { if (xhrOptions.method == "GET") { - xhrOptions.url = xhrOptions.url + (xhrOptions.url.indexOf("?") < 0 ? "?" : "&") + querystring(data) + xhrOptions.url = xhrOptions.url + (xhrOptions.url.indexOf("?") < 0 ? "?" : "&") + buildQueryString(data) } else xhrOptions.data = serialize(data) } diff --git a/tests/mithril-tests.js b/tests/mithril-tests.js index ae034daf..104d5ff4 100644 --- a/tests/mithril-tests.js +++ b/tests/mithril-tests.js @@ -635,6 +635,21 @@ function testMithril(mock) { mock.performance.$elapse(50) //teardown return true; mock.location.search == path && paramValue.a == "foo" && paramValue.b.c == "1" && paramValue.b.d == "2" && m.route.param("str") == "bar" }) + test(function() { + mock.performance.$elapse(50) //setup + mock.location.search = "?" + + var root = mock.document.createElement("div") + m.route.mode = "search" + m.route(root, "/", { + "/": {controller: function() {}, view: function() {return "bar"}}, + "/test12": {controller: function() {}, view: function() {return m.route.param("test") + "_" + m.route.param("test2")}} + }) + mock.performance.$elapse(50) + m.route("/test12?test=foo", {test2: "bar"}) + mock.performance.$elapse(50) //teardown + return mock.location.search == "?/test12?test=foo&test2=bar" && root.childNodes[0].nodeValue === "foo_bar" + }) //end m.route //m.prop