PDA

View Full Version : Problem with passing multiple parameters in url with Sencha-touch MVC routing



jartem
20 Jan 2011, 5:45 AM
Hi,

I stuck in a place where I need to pass several parameters in url while using Ext.Router & built-in MVC in Sencha-touch. Having initialized routing as below:

Ext.Router.draw(function (map) {
map.connect(':controller/:action/:parameter');
}
entering url "localhost/#users/show/3" executes 'show' action in 'users' controller with "3" as passed parameter.
works just fine, until several parameters need to be passed to an action for example "localhost/#users/search/type=3&quote=john", I would expect that 'search' action in 'users' controller would be executed with string parameter as "type=3&quote=john", but routing does not call any action if any spacial char is passed as parameter (':', '=',' &').
I assume this is a restricted by routing, but I dont get it why.
Other option is to register routes as following:

map.connect(':controller/:action/:parameter1/:parameter2/:parameter3/:parameter4');but I dont find it the best practice (for example when the number of parameters is dynamic)

Could anyone explain this to me how multiple parameters should be passed to action using routing, assuming that each parameter should be recognizable by its name.

cheers

szwejkc
21 Jan 2011, 1:20 AM
Sorry my friend. I don't have an answer. I really want to know the solution for this problem. Very good question!

suzzer99
25 Jan 2011, 3:00 PM
Does anyone know why I can't find Router in either the EXT JS or Sencha Touch API docs? Very strange.

Ion Tichy
26 Jan 2011, 7:04 AM
The docs have a few small holes in them. 'Viewport' is another missing class.

parseroo
1 Feb 2011, 8:36 PM
I can't say what the purpose is, but probably mostly to be simpler and more integrated with the basic 'app' model they have.

You can get the '&foo=bar' part to come through the Router into the controller by changing 'Ext.util.Route::createMatcherRegex' to allow the characters in a trailing (potentially ':rest' ) attribute and use that with a route like:


map.connect(':controller/:action/:rest');

for 'localhost/#users/search/type=3&quote=john' or change the last slash to a '&' and you could use 'localhost/#users/search&type=3&quote=john' which might be a bit more standard.




createMatcherRegex: function(url) {

var paramsInMatchString = this.paramsInMatchString,
length = paramsInMatchString.length,
i, cond, matcher;

for (i = 0; i < length; i++) {
cond = this.conditions[paramsInMatchString[i]];
if (i == length - 1) {
matcher = Ext.util.Format.format("({0})", cond || "[%a-zA-Z0-9\\_\\s,\\&\\=]+");
} else {
matcher = Ext.util.Format.format("({0})", cond || "[%a-zA-Z0-9\\_\\s,]+");
}

url = url.replace(new RegExp(paramsInMatchString[i]), matcher);
}


return new RegExp("^" + url + "$");
}

jartem
3 Feb 2011, 8:16 AM
thanks a million, that helps a lot,

gcallaghan
3 Feb 2011, 8:15 PM
I've typically seen frameworks with "clean" urls like django and apache mod rewrite have a route for every parameter, ie

route/param1
route/param1/param2
route/param1/param2/param3
etc

Server side languages typically give you access to the get or post arguments in a global. for javascript you could use a technique like...

http://www.netlobo.com/url_query_string_javascript.html


I try and avoid directly hacking on the source code when I can :)

parseroo
3 Feb 2011, 9:38 PM
The format '?foo=value1&bar=value2' is a standard clean url and is far more reliable, tolerant to changes, and more flexible than a full path like /value1/value2. Especially when you get to 'value5'! The link you reference even has exactly that format '?bob=123&frank=321...' in its example. Usually routing is done off everything up to the '?' where the rest are just passed in parameters. Effectively my change to the source adds that feature (using the first '&' to identify the rest) as a recommended improvement to Sencha routing.

gcallaghan
3 Feb 2011, 11:36 PM
Hey no worries parseroo, just suggesting another way and providing another point of view.


'?foo=value1&bar=value2'

is definitely a valid url, though not clean as in user friendly.
http://en.wikipedia.org/wiki/Clean_URL

The problem with GET arguments is that they are essentially arbitrary. Routes and explicit parameter matching increase the security/stability of the code.
http://en.wikipedia.org/wiki/Query_string#Flexibility_vs._security


reliable, tolerant to changes, and more flexible than a full path like /value1/value2.
I don't necessarily think that the flexibility is such a virtue. For secure code you have to check the parameter somewhere, whether its in the route or the controller. Adding a check to the controller or another route seem to be the same amount of work. Its never a good Idea to run arbitrary input.

I don't see how routes are unreliable. They seem to be common practice nowadays.
http://guides.rubyonrails.org/routing.html
http://docs.djangoproject.com/en/1.2/topics/http/urls/
http://drupal.org/getting-started/clean-urls
http://codex.wordpress.org/Using_Permalinks

While these methods don't disable get arguments, they do discourage their use.


But you are right, having the router break on a query string seems like a bug. If nothing else, it could ignore the query string and log a message.

Until it gets fixed you could use the Ext.override method to temporarily patch in the required functionality without hacking the source which may hinder any upgrade process.

Bucs
1 May 2011, 8:52 AM
@parseroo, I am having some issues trying to get what you are saying to work for me. Can you elaborate on how you are declaring the route to accept parameters, and then how are you referencing the params from inside the controller action?

Your route declaration is sort of a catchall it looks like whereas I have seen most of the ones in the examples provide a controller mapping config object like so:



map.connect("products/list", { controller: 'ProductController', action: 'list'});


How would i adjust the above to include params like you say that would get passed into the controller and be accessible? And how are you accessing them once inside the action, by parsing the historyUrl?

Also, adding ANYthing extra to the routes in the routes.js file seems to break the history connection for me, as it doesn't allow me to return to the list page and fire the "list" action in the controller if I had gone one level deeper (say a details page), and then tried to return with the browser back button. I assuming this is because the params tacked onto the end of the route break the connection...almost as if the ":param" is not distinguished from the route at all in the routing process.

Passing params in the Touch MVC pattern needs a LOT of work imo, and no one (including Sencha) seems to know much about how to achieve this.

parseroo
1 May 2011, 3:12 PM
I am pretty sure any pattern will work as well, although I do normally use generic routing for everything but the root route.

Any additional parameters are shoved into 'rest' as a string, so I used


jQuery.deparam.fragment(params.rest)

to parse the additional parameters. You could use something simpler [maybe parseuri.js] without pulling in jQuery, but I have jQuery around to fix DOM issues 'concisely' and for some useful widgets, so I just used a method available.

I ultimately ended up not caring about more complex URLs on the client side, so I definitely have not verified the extra parameters work with history. The code doesn't get in the way of history for me, but I could certainly imagine some other quirk in the code base that did when extra parameters were introduced... although that would be :(

If you care, I now just send the extra parameters to the server on first application hit (potentially even the deep link hit) and they stay active for the entire session. Not as nice as just turning a flag on/off, better sub-page control, or a deep-linked sort adjustment, but I don't need the extra right now.

Bucs
1 May 2011, 3:49 PM
Thanks @parseroo for the response.

Actually, I combined both your's and @gcallaghan's input into a very workable solution that handles parameter passing, deep linking, and history support in a very acceptable manner. I tend to agree more with @gcallaghan when it comes to the URL structure using a true URL pathing scheme for the parameters b/c in my opinion it is much more intuitive to the user (or client) to look at and work with. I also agree with him when it comes to using Ext.override when it comes to altering the core functionality as it keeps your original source code intact and lets you upgrade without having to search through all the code and rewrite it again in a new version.

With that said, your override is still needed to fix the oversight (bug) in the router functionality so that the parameters following the action don't get shaved off. I had to add "\\-" to account for dashes in the productId, but your code made that trival.

So, here's what I ended up with for my overrides that included your code:


Ext.override(Ext.util.Route, {

createMatcherRegex: function (url) {

var paramsInMatchString = this.paramsInMatchString,
length = paramsInMatchString.length,
i, cond, matcher;

for (i = 0; i < length; i++) {
cond = this.conditions[paramsInMatchString[i]];
if (i == length - 1) {
matcher = Ext.util.Format.format("({0})", cond || "[%a-zA-Z0-9\\_\\s,\\&\\=\\-]+");
} else {
matcher = Ext.util.Format.format("({0})", cond || "[%a-zA-Z0-9\\_\\s,]+");
}

url = url.replace(new RegExp(paramsInMatchString[i]), matcher);
}


return new RegExp("^" + url + "$");
}
});




My routes.js file looks like this:


Ext.Router.draw(function (map) {
// HomeController
map.connect("home/index", { controller: 'HomeController', action: 'index' });

// ProductsController
map.connect("products/list/:method/:category/:term", { controller: 'ProductController', action: 'list' });
map.connect("products/show/:productId", { controller: 'ProductController', action: 'show' });


});


My Dispatch commands do two things, they build the historyUrl in accordance with the above routes, and they also pass the parameters as properties of the options config object. Doing this seems to ensure that the necessary parameters are included in the options object whether I am using the dispatch command, directly pathing to a page (deep linking), or using the history object (by using the browser forward and back buttons). It also allows me to use history.go(-1) for any back button (like on the toolbar) to return to the previous page very easily.

Here's the dispatch code for the Product List action:


Ext.dispatch({
controller: 'ProductController',
action: 'list',
historyUrl: 'products/list/' + method + '/' + category + '/' + term,
parentView: 'Index',
method: method,
category: category,
term: term
});


As I stated, with the above code, I get parameter passing, deep linking, and history support in an easy to use, intuitive fashion...so thanks for your help!

mhdsherafat
12 Dec 2012, 3:22 AM
great to all,
I don't read all of the article but i wrote my controller like this:
Ext.define("app.controller.path.Co",{
extend:"Ext.app.Controller",
config:{
routes:{
'path/pathinner/:id/:dataid/:owner':'insertComment'
}
} ,
insertComment:function(id,dataid,owner){
console.log(id);
console.log(dataid);
console.log(owner);
}
});


and called it like this:
host/#path/pathinner/23/14/18

it works perfectly.