I have a backbone.js router set up with some routes as follows :
routes : {
'a-route' : 'goToRoute',
'a-route/*splat' : 'goToRoute'
}
goToRoute : function(splat){
if(!splat) {
// do this
} else {
// do that with splat
}
When I do a
router.navigate('a-route', {trigger : true});
everything works just fine. But when I do
router.navigate('a-route/more', {trigger : true});
the router is firing twice : first with the splat equal to 'undefined', and then a second time with the splat equal to 'more'.
If I comment out the route 'a-route' : 'goToRoute', then everything works as it should with router.navigate('a-route/more') ... but I need both routes - with and without the splat.
According to the docs I think I have this set up correctly, any ideas?
You do not need to use *, you must use : in your case.
routes : {
'a-route' : 'goToRoute',
'a-route/:splat' : 'goToRoute'
}
Related
How to use Ext.ComponentQuery.query with nested attributes in Sencha Touch?
e.g
var myHardtoGetObj = topLevelView.down('someview[config.categoryCfg.id=1]')[0];
This gets me "uncaught error"
given :
Ext.define('SomeView', {
xtype : 'someview',
config : {
categoryCfg : {
id : 5,
name : 'someName'
}
}
});
Is this possible?
Thanks.
The canonical way of doing things like that is adding a custom pseudo class matcher:
Ext.ComponentQuery.pseudos.hasCategoryId = function(components, selector) {
var result = [],
c, i, len;
for (i = 0, len = components.length; i < len; i++) {
c = components[i];
if (c.config.categoryCfg && c.config.categoryCfg.id == selector) {
result.push(c);
}
}
return result;
}
Then you can use this pseudo class both globally with Ext.ComponentQuery.query, and locally with methods like query, down, etc.:
var allMatched, someComponent;
allMatched = Ext.ComponentQuery.query(':hasCategoryId(1)');
someComponent = myPanel.down(':hasCategoryId(42)');
See more ways to skin the cat in ComponentQuery doc.
This really is an interesting question. There doesn't seem to be an absolutely straightforward solution, however there is a rather quick workaround. You can modify your view code to:
Ext.define('SomeView', {
xtype : 'someview',
config : {
categoryCfg : {
id : 5,
name : 'someName'
}
},
hasCategoryId: function (id) {
return this.getCategoryCfg().id == id;
}
});
Then you can make a query like this:
Ext.ComponentQuery.query('someview{hasCategoryId(1)}');
or
topLevelView.down('someview{hasCategoryId(1)}');
Note: The syntax of the selector is xtype{memberMethod()} without a space in between. This way both selectors must match (the same way as .class1.class2 in CSS). Also the selectors must be in this order, because the result set is filtered by each selector in order and if some of the components don't have the hasCategoryId method it will break with just '{hasCategoryId(1)}'
Although not exactly answering the question but you can do a little work around to get it to work.
you can add update method to your nestedConfig like so
Ext.define('myCoolClass', {
config : {
nestedConfig : {
nestedId : 5
},
nestedId : null
},
updateNestedConfig: function (nestedConfig) {
if (nestedConfig.nestedId) {
this.setNestedId(nestedConfig.nestedId);
}
}
});
By doing that you now have access to normal component query attribute
Ext.ComponentQuery.query('[nestedId=555]')
As an example. If you take a look at Sencha source code they use this quite a lot like in NavigationView and TabPanels
Having an issue with using Backbone history / push state - but only with browsers that don't support it (old IE)
The issue is this. When I visit /en_gb/dashboard for the first time - everything works, in all browsers. However, in IE<=9, it's appending #dashboard to the address bar, forming /en_gb/dashboard#dashboard. Now, when I hit refresh, my router is not triggering.
Not all my site is under Backbone control - so the router is working off:
routes: {
'dashboard': 'showDashboard'
}
My bootstrap looks like this:
if (Backbone.history) {
var pushStateSupported = _.isFunction(history.pushState);
var urlRoot = '/en_gb/';
var enableSilent = !pushStateSupported;
Backbone.history.start({
pushState: pushStateSupported,
root: urlRoot,
silent: enableSilent
});
if (!pushStateSupported) {
Backbone.history.navigate(window.location.pathname.substring(urlRoot.length), { trigger: true });
}
}
Adding debug, I can see Backbone.history.navigate() always being called but it seems the trigger: true is not being picked up when that hash is present.
Hmm - I seem to have fixed it - while not an elegant solution, this does solve it for me:
if (!pushStateSupported) {
var route = window.location.pathname.substring(urlRoot.length);
Backbone.history.navigate('/#' + route, { trigger: true });
}
It's not elegant in that the URL in the address bar appears as /en_gb/dashboard##dashboard - but it is now getting through the Backbone.navigate() method. Previously it was failing on
if (this.fragment === fragment) return;
I'm trying to pass a directory location and the file path as part of the hashmaps in backbone routes. This is the url with hashmaps:
localhost/index.html#directory-http://localhost/foobar-html/foo.html
and this is what my route that maps the above url:
routes: {
'directory-*directoryPath-*filePath': 'render'
},
render: function (directoryPath, filePath) {
// I get the entire url in directoryPath variable
// http://localhost/foobar-html/foo.html
// and filePath is empty.
}
What would be the right way to map such type of hash URL? Thanks!
From the fine manual:
Routes can contain parameter parts, :param, which match a single URL component between slashes; and splat parts *splat, which can match any number of URL components.
Your problem is that a splat eats up everything so having two splats in one route is pointless; you can't use a parameter part, :x, because that stops at a slash.
There are a few things you can do.
You could URI encode the slashes in link and use parameter parts. The URL would look like this:
#directory-http:%2f%2flocalhost%2ffoobar-html%2ffoo.html
and the router would be like this:
routes: {
'directory-:directoryPath-:filePath': 'render'
},
render: function(d, f) {
d = decodeURIComponent(d);
f = decodeURIComponent(f);
//...
}
Demo: http://jsfiddle.net/ambiguous/xBnaN/
You could add your route as a regex using route, that would give you more freedom in how you construct the pattern. For example, a fragment like this:
#directory-http://localhost/foobar-html/foo.html
could be handled with a router like this:
initialize: function() {
this.route(/directory-(.*?)-(.*)/, 'render');
},
render: function(d, f) {
//...
}
Demo: http://jsfiddle.net/ambiguous/r8MBb/
The second option will run into problems with you inevitably get a - inside your directoryPath or filePath; you could URI encode embedded -s to get them through the first option though.
Similar to Django's {{ url }}, is there a method or way to reverse a particular route by passing it a name and variables.
// example Router
var router = Backbone.Router.extend({
routes: {
'!/user/:user_id': 'editUserAction',
'!/': 'homeAction'
},
editUserAction(user_id) {
// edit user view
},
homeAction() {
// home view
}
});
Some method like
router.reverse('editUserAction', '5');
Would return the hash: !/user/5
router.reverse('homeAction');
Would return the hash: !/
A discussion about reverse routing. https://github.com/documentcloud/backbone/issues/951
a simple hack
var personRoutes = {
list: "/persons",
show: "/persons/:id",
edit: "/persons/:id/edit"
}
var getRoute = function(obj, route, routeDefinitions){
return routeDefinitions[route].replace(":id", obj.id);
}
var person = new Model({id: 1});
var route = getRoute(person, "edit", personRoutes); // => "/persons/1/edit"
Unfortunately no, there isn't anything like this built in to backbone. I've wanted something similar and there has been discussion of this on the list once or twice - maybe even a pull request (don't remember for sure at the moment). But it has not yet been done.
The best that I've come up with is to write my own methods that produce the correct route string:
function userEditPath(userId){
return "/user/" + userId + "/edit";
}
Using Backbone, is it possible for me to get the name of the current route? I know how to bind to route change events, but I'd like to be able to determine the current route at other times, in between changes.
If you have instantiated a Router in your application, the following line returns the current fragment:
Backbone.history.getFragment();
From the Backbone.js documentation:
"
[...]
History serves as a global router (per frame) to handle hashchange events or pushState, match the appropriate route, and trigger callbacks. You shouldn't ever have to create one of these yourself — you should use the reference to Backbone.history that will be created for you automatically if you make use of Routers with routes.
[...]"
If you need the name of the function bound to that fragment, you can make something like this inside the scope of your Router:
alert( this.routes[Backbone.history.getFragment()] );
Or like this from outside your router:
alert( myRouter.routes[Backbone.history.getFragment()] );
Robert's answer is interesting, but sadly it will only work if the hash is exactly as defined in the route. If you for example have a route for user(/:uid) it won't be matched if the Backbone.history.fragment is either "user" or "user/1" (both which are the two most obvious use cases for such route). In other words, it'll only find the appropriate callback name if the hash is exactly "user(/:uid)" (highly unlikely).
Since i needed this functionality i extended the Backbone.Router with a current-function that reuses some of the code the History and Router object use to match the current fragment against the defined Routes for triggering the appropriate callback.
For my use case, it takes the optional parameter route, which if set to anything truthful will return the corresponding function name defined for the route. Otherwise it'll return the current hash-fragment from Backbone.History.fragment.
You can add the code to your existing Extend where you initialize and setup the Backbone router.
var Router = new Backbone.Router.extend({
// Pretty basic stuff
routes : {
"home" : "home",
"user(:/uid)" : "user",
"test" : "completelyDifferent"
},
home : function() {
// Home route
},
user : function(uid) {
// User route
},
// Gets the current route callback function name
// or current hash fragment
current : function(route){
if(route && Backbone.History.started) {
var Router = this,
// Get current fragment from Backbone.History
fragment = Backbone.history.fragment,
// Get current object of routes and convert to array-pairs
routes = _.pairs(Router.routes);
// Loop through array pairs and return
// array on first truthful match.
var matched = _.find(routes, function(handler) {
var route = handler[0];
// Convert the route to RegExp using the
// Backbone Router's internal convert
// function (if it already isn't a RegExp)
route = _.isRegExp(route) ? route : Router._routeToRegExp(route);
// Test the regexp against the current fragment
return route.test(fragment);
});
// Returns callback name or false if
// no matches are found
return matched ? matched[1] : false;
} else {
// Just return current hash fragment in History
return Backbone.history.fragment
}
}
});
// Example uses:
// Location: /home
// console.log(Router.current()) // Outputs 'home'
// Location: /user/1
// console.log(Router.current(true)) // Outputs 'user'
// Location: /user/2
// console.log(Router.current()) // Outputs 'user/2'
// Location: /test
// console.log(Router.current(true)) // Outputs 'completelyDifferent'
I'm sure some improvements could be made, but this is a good way to get you started. Also, it's easy to create this functionality without extending the Route-object. I did this because it was the most convenient way for my set-up.
I haven't tested this fully yet, so please let me know if anything goes south.
UPDATE 04/25/2013
I did some changes to the function, so instead of returning either the hash or route callback name, i return an object with fragment, params and route so you can access all the data from the current route, much like you would from the route-event.
You can see the changes below:
current : function() {
var Router = this,
fragment = Backbone.history.fragment,
routes = _.pairs(Router.routes),
route = null, params = null, matched;
matched = _.find(routes, function(handler) {
route = _.isRegExp(handler[0]) ? handler[0] : Router._routeToRegExp(handler[0]);
return route.test(fragment);
});
if(matched) {
// NEW: Extracts the params using the internal
// function _extractParameters
params = Router._extractParameters(route, fragment);
route = matched[1];
}
return {
route : route,
fragment : fragment,
params : params
};
}
See previous code for further comments and explanations, they look mostly the same.
To get the calling route (or url) from the called route handler, you can get it by checking
Backbone.history.location.href ... the full url
Backbone.history.location.search ... query string starting from ?
I got here in the search of this answer so I guess I should leave what I have found.
If you use the root setting for the Router, you can also include it to get the 'real' fragment.
(Backbone.history.options.root || "") + "/" + Backbone.history.fragment
Here's a tad more verbose (or, depending on your taste, more readable) version of Simon's answer:
current: function () {
var fragment = Backbone.history.fragment,
routes = _.pairs(this.routes),
route,
name,
found;
found = _.find(routes, function (namedRoute) {
route = namedRoute[0];
name = namedRoute[1];
if (!_.isRegExp(route)) {
route = this._routeToRegExp(route);
}
return route.test(fragment);
}, this);
if (found) {
return {
name: name,
params: this._extractParameters(route, fragment),
fragment: fragment
};
}
}
If you look at the source for the Router, you'll see that when the router triggers the event saying that something changes, it passes the name with it as "route:name".
http://documentcloud.github.com/backbone/docs/backbone.html#section-84
You can always hook the "route" event on the router and store it to get the current route.
router = new Backbone.Router.extend({
routes: { "stuff/:id" : "stuff" },
stuff: function(id) {}
});
router.on('route', function(route) {
this.current = route;
});
Now if you navigate to /stuff/1, router.current will be "stuff"