the singularity of being and nothingness
Ext JS 5: Routing Part II
In my last post, we explored the new routing functionality that has been incorporated in Ext JS 5. I had a lot of great feedback and discussions come from that (thanks!), so I thought I’d add a few more notes about some of the routing functionality that I didn’t discuss in the last post. So without further jabbering, let’s dive in.
Route Conditions
One nice feature of routing in Ext JS 5 is that not only can you add a before handler to the route execution, but you can also filter routes which will be matched based on a regex string.
For example, let’s consider our users/:id route from the last post:
routes: { ... 'users/:id': { action: 'onUserDetail', before: 'onBeforeUserDetail' } }
As written, this route will match any hash in this form, regardless of what actual value is passed for our “id” placeholder. What this means is that if someone gets clever and starts to try to manually hack our hash with some value other than what comes naturally from our application, our before handler is still going to fire on the hacked value, and whatever code is in that handler will execute.
Obviously, we could put some error/hack handling in our before handler, but we can also use the conditions config to automatically weed out obviously incorrect patters.
In this example, let’s assume that all of our user ids are numeric, so we want to automatically ignore any hashes that match our route pattern, but have an “id” value that’s not numeric.
routes: { ... 'users/:id': { action: 'onUserDetail', before: 'onBeforeUserDetail', conditions : { ':id' : '([0-9]+)' } } }
This is pretty straightforward. Using the conditions config, we supply a regex string for our id placeholder that will instruct the Router to not process this route if the hash does not match the route + condition, but instead trigger the unmatchedroute event (more below).
Now we come to an important point of consideration. Obviously, you could use the before handler to accomplish the same thing. That is, you could inspect the incoming parameters from the route, run any kinds of checks against them that you want to, and then choose to resume or stop the route action based on whatever your application’s logic determines is needed to be done.
With conditions, you have no such opportunity. If the condition fails the incoming hash, the route’s action simply doesn’t get executed. All that will happen is that the Router’s generic unmatchedroute event will get fired, passing only the hash that was unsuccessfully matched.
What would be nice is if each route could define something like a conditionfail handler. This would be a handler that could be fired when a route’s base pattern is matched (e.g., users/:id) but is proactively failed because of the route’s configured conditions. I think this would be useful because it would allow for much more precise handling of unmatched routes. Instead of having to try to handle all unmatched routes in a single handler (which is currently how it works), the per-route configuration of unmatched route handlers would give much more granular context for dealing with the unmatched route however the particular instance dictates. Just a thought! 🙂
Unmatched Routes
I’ve mentioned it above, but the new Router also includes the ability to deal with any unmatched routes. Whenever an unmatched route is detected (e.g., a hash matching a pattern that simply isn’t defined, or a hash that fails an existing route’s condition), the unmatchedroute event is fired on base application instance. You can easily set up a listener for this like so:
In Application.js
listen: { controller: { '#': { unmatchedroute: 'onUnmatchedRoute' } } }, onUnmatchedRoute: function( hash ) { alert( 'Sorry, the ' + hash + ' route is not correct' ); }
Multiple Routes in a Single Hash
If you have a need within your application, the new Ext JS 5 Router also supports the execution of routes from the same hash. You can specify multiple routes in a single hash simply by concatenating each desired route with a pipe (|). This can be overriden by setting the multipleToken property of Ext.app.route.Router.
So this:
users/1234 | info (no spaces)
Would match and execute the actions for the following routes:
'users/:id' 'info'
The important thing to keep in mind about multiple routes is that they are completely segregated from each other. They do fire in order, but their execution does not depend on the resumption or cancellation of each other. So if my first route fails or is programmatically stopped, it will have no effect on subsequent routes.
In my mind, multiple routes would typically have some relationship to one another, so I’ve not been able to come up with a good use case for this. Also, the API for dealing with multiple routes feels a bit incomplete. The idea is that the multiple routes *should* be independent from one another, but there is not really a simple way (that I know of) to append or remove routes from the hash. So even though you *should* be treating multiple routes as independent things, they ultimately have to be created together as part of whatever action is actually creating the hash.
I would propose the following additions to controllers to help deal with this:
addTokenPart (part, override): Preserves current hash and appends new “part” using multipleToken setting. If override is true, does search for “part” in existing token, removes it from existing location before appending to the end; if false, does not append if part already exists in token
removeTokenPart(part): Removes token part from existing token
Conclusion
There’s more that could be said for Routing in Ext JS 5, but I think it’s time to move on. I’m going to be looking at View Controllers next, hooray! 🙂
Print article | This entry was posted by existdissolve on August 23, 2014 at 10:46 am, and is filed under Cool Stuff, Ext JS 5, Uncategorized. Follow any responses to this post through RSS 2.0. You can leave a response or trackback from your own site. |
about 9 years ago
Thank you for sharing.
You are the best supplement to the docs!
Playing with routes I notice this scenario:
Say we have a viewport with a west menu.
URL: myapp/#home
Route to home processed normally.
Home tab is loaded into center panel of viewport.
Now user closes the home tab.
Current state is not “#home”, but “#home” is still in the URL
User clicks west menu to open the home tab again.
Nothing happens.
Because the current token is already #home.
“There may be times that the current hash is the same as the hash that is trying to be updated to. In this case, redirectTo will return false and the hash will not get updated which will then not execute any configured routes. redirectTo also accepts a second parameter to force the Router to react to this hash by passing true”
How to sew those pieces together?
How can “#home” be removed from the url without reloading the page?
Where do the smarts go? In the router always, or do we need new logic in every component that can affect routing?
Maybe instead of just allowing the tab the be closed, I should be redirecting after close.
To close a tab is to change the routing? That could make sense.
Then some tab has to find it’s controller and issue a redirect.
It just doesn’t seem right.
If several tabs are open I don’t want to redirect anywhere, just let focus move to the next tab.
If user closes the home tab (and it was the only tab open), he should be looking at a blank center area.
IE The default route goes nowhere.
So back to redirect. If it fails you call it again with force?
Is it as simple as this:
this.redirectTo(item, (window.location.hash.indexOf(‘#’+item) >= 0) );
I dunno.