Backbone route matching doesn't work - javascript

I have got three routes like this:
var appRouter = Backbone.Router.extend({
routes: {
"": "index",
"questionnaire/info/id/:id": "questionnaireInfo",
"questions/edit/*params": "questionEdit"
},
questionnaireInfo: function(id) {
$('#app-body').load('/dashboard/questionnaire/info/id/' + id);
},
questionEdit: function(questionnaireId) {
console.log(questionnaireId, params);
},
index: function() {
console.log('index');
}
});
And i initialize them like this:
var appRouting = new appRouter;
Backbone.history.start({
pushState: true,
silent: false,
root: '/dashboard/'
});
On first page load the route matches, it even console.log the proper messages. But i have got a link element like this:
Home Page
It doesn't match the "" route. And this href element doesn't match the "questionnaire/info/id/:id" route:
Load
How can i make this working? Thanks.

Maybe you're missing the '/' between '/dashboard' and each route

is index firing? in general the backbone docs recommend listing your routes from most specific to least specific. It's possible that the other roots aren't being called because "":"index" catches everything.

You need a trailing slash in your Backbone.history.start call for the root argument.
var appRouting = new appRouter;
Backbone.history.start({
pushState: true,
silent: false,
root: '/dashboard/'
});
You can see this in the example for Backbone.history in the docs.

I had the same problem. It seems that the links that call the routes have to be prepended by a "#" character. So if the root of the router is
/dashboard/
and your route is
"questionnaire/info/id/:id": "questionnaireInfo"
than the link triggering the route should be for example
Load
Hope this helps.

Related

Backbone Marionette is not firing routes

I'm creating a simple application using Backbone and Marionette. It's just to fetch a list of Wordpress posts (using an API) and display it.
It's a very simple app so it's not modularized.
I have the following (it's all placed in the same file):
if ( Backbone.history )
Backbone.history.start({ pushState: false });
if ( Backbone.history.fragment === '' )
API.listAllPosts();
else
API.listSinglePost( Backbone.history.fragment );
// Is not firing anything from here...
MyBlog.Router = Marionette.AppRouter.extend({
appRoutes: {
'': 'listPosts',
':post_name': 'listSingle'
},
listPosts: function() {
console.log('router');
API.listAllPosts();
},
listSingle: function(model) {
console.log('router, single');
API.listSinglePost(model);
}
});
// ...to here
var API = {
listAllPosts: function() {
// Fetch all posts and display it. It's working
},
listSinglePost: function(model) {
// Fetch a single post and display it. It's working
}
}
MyBlog.addInitializer(function() {
console.log('initializer'); // It's firing
new MyBlog.Router({
controller: API
});
});
As Derick Bailey, Marionette's creator, said about using triggers on naviagate:
it encourages bad app design and it is strongly recommended you don’t
pass trigger:true to Backbone.history.navigate.
What I'm missing here?
You start the Backbone history before creating the router instance.
Just move that to after the router is created.
MyBlog.addInitializer(function() {
new MyBlog.Router({ controller: API });
// should be started after a router has been created
Backbone.history.start({ pushState: false });
});
The other thing is that the callbacks should be defined inside of a controller or you should change appRoutes to routes.
The major difference between appRoutes and routes is that we provide
callbacks on a controller instead of directly on the router itself.
[...]
As the AppRouter extends Backbone.Router, you can also define a routes
attribute whose callbacks must be present on the AppRouter.
Move this
if ( Backbone.history )
Backbone.history.start({ pushState: false });
if ( Backbone.history.fragment === '' )
API.listAllPosts();
else
API.listSinglePost( Backbone.history.fragment );
after the point where your App gets started or inside an initialize:after event handler.
Check this previous question: Marionette.js appRouter not firing on app start

iron-router render notFound template when route does not exist

If the data function returns a falsy value like null, the NotFound template will be rendered in my application. This works fine, but now I want also to render the NotFound template, if the route does not exist.
For instance:
this.route('settingsOverviewPage', {
path: '/settings',
data: function() { return Users.findOne(Meteor.userId()); },
waitOn: function() {
if (Meteor.userId()) {
return Meteor.subscribe('ownUser', Meteor.userId());
}
return null;
}
});
If I use this route: /settings12345 for instance, the browser reloads, but it renders the last route.
Any help would be greatly appreciated.
You have to define a "catch-all" route like this :
this.route("notFound",{
path:"*",
template:"notFoundTemplate"
});
It is important that you define this route as the LAST one, otherwise it will catch valid URLs.

Backbone routes are not working

I am creating a simple Backbone app and the routing is not working. Here is my router.
define(function(require) {
'use strict';
var Backbone = require('backbone');
var Header = require('views/header.view');
var MainBody = require('views/main.body.view');
var Router = Backbone.Router.extend({
routes: {
"": "main",
"about/": "about"
},
main: function() {
var header = new Header();
$('#header').html(header.render());
var body = new MainBody();
$('#app').html(body.render());
},
about: function() {
console.log("About");
}
});
return Router;
});
I hit the / route as expected, but when I go to /about, it never hits the about function. Am I supposed to have a hash in the url somewhere? What else could I be missing that would cause this issue?
It's because routes are explicitly matched, so "about/": "about" will match /about/ and "about": "about" will match /about
There is a way to match both /about and /about/ and it requires using optional matchers (matcher) so your routes hash key will look like this
"about(/)": "about"
For hashless routes to work you need to run Backbone.history.start({pushState: true}).
According to backbonejs documentation, routers should be defined and addressed as follows:
var Workspace = Backbone.Router.extend({
routes: {
"help": "help", // #help
"search/:query": "search", // #search/kiwis
"search/:query/p:page": "search" // #search/kiwis/p7
},
help: function() {
...
},
search: function(query, page) {
...
}
});
Therefore, when you type http://<URL>/backbonepage#help, the help function will be invoked

Loading data into root route of ember router when it's created outside of application.create()

When you want to use classes you created in Em.Application.create() in your router you need to specify the router outside of the application.create. But because the application is automatically initialized the router doesn't route to the / route.
You used to be able to defer the initialization by adding autoinit: false to the application.create. Now you are supposed to use App.deferReadiness() and App.advanceReadiness(). However this doesn't appear to work.
And I can't seem to escape the feeling that you are "supposed" to do it differently.
I added the minimal code to show the problem below. There is also a jsfiddle here
EDIT:
Apparently there is a new router in ember I kinda sorta overlooked that. I've changed the code to the new router, but guess what it still doesn't work :P
window.App = App = Em.Application.create({
ApplicationController: Em.Controller.extend({}),
ApplicationView: Em.View.extend({
template: Em.Handlebars.compile('{{outlet}}'),
}),
ExtendedPatientController: Em.ObjectController.extend({}),
ExtendedPatientView: Em.View.extend({
classNames: ['patient-view', 'extended'],
template: Em.Handlebars.compile('{{name}}')
}),
Patient: Em.Object.extend({
name: undefined,
}),
});
App.Router.map(function (match) {
match('/').to('application', function (match) {
match('/').to('extendedPatient');
})
});
App.deferReadiness();
App.ExtendedPatientRoute = Em.Route.extend({
setupController: function (controller) {
controller.set('', App.Patient.create({
name: "Bert"
}));
},
renderTemplates: function () {
this.render('extendedPatient', {
into: 'application'
});
}
});
App.advanceReadiness();
You're actually doing a lot more work than you need to here.
Here's all the code that you need to make your example work.
Template:
<script type="text/x-handlebars" data-template-name="index">
<div class="patient-view extended">
<p>Name: {{name}}</p>
</div>
</script>
App:
window.App = Em.Application.create();
App.Patient = Em.Object.extend({
name: null
});
App.IndexRoute = Em.Route.extend({
model: function() {
return App.Patient.create({
name: "Bert"
});
}
});
The working fiddle is at: http://jsfiddle.net/NXA2S/23/
Let me explain it a bit:
When you go to /, you are entering the automatic index route. All you need to do to show something on the screen for that route is to implement an index template. The easiest way to do that when you're getting up and running is to put your template in your index.html. Later, you will probably want to use build tools (see my answer here for more information).
You can control what model is displayed in a route's template by overriding the model hook in its route handler. In the case of index, the route handler is App.IndexRoute. In this case, the model is a brand new App.Patient.
You will probably want to implement controllers and events. You can learn more about the router on the Ember.js website
So the new router does solve this problem and does feel a bit shinier.
I finaly found out how to do this basic example this is what happens in the router:
App.Router.map(function (match) {
match('/').to('extendedPatient');
});
This what needs to happen in the views:
ExtendedPatientView: Em.View.extend({
classNames: ['patient-view', 'extended'],
//You need to specify the defaultTemplate because you extend the view class
//instead on initializing it.
defaultTemplate: Em.Handlebars.compile('{{name}}')
}),
You do not have to defer the readiness in the app the new router fixes that.
And in the route you do not need to specify the renderTemplates so the router now looks like:
App.ExtendedPatientRoute = Em.Route.extend({
setupController: function (controller) {
controller.set('content', App.Patient.create({
name: "Bert"
}));
},
});
http://jsfiddle.net/NXA2S/28/

Backbone.js PushStates: Fallback for Internet Explorer not working

My site has just implemented pushstates in Backbone.js and the entire site breaks for IE. How should I create a fallback for IE?
What I am trying to achieve
Main URL: http://mydomain.com/explore
Another URL: 'http://mydomain.com/explore/1234
The main page of the site is http://mydomain.com/explore which triggers the router function explore.
When a user visits http://mydomain.com/explore/1234, Backbone's router will trigger the function viewListing, which is the same as function explore, but also includes details for item id 1234.
Backbone.js Router
// Router
var AppRouter = Backbone.Router.extend({
routes: {
'explore': 'explore',
'explore/:id': 'viewListing',
},
explore: function() {
this.listingList = new ListingCollection();
// More code here
},
viewListing: function(listing_id) {
this.featuredListingId = listing_id; // Sent along with fetch() in this.explore()
this.explore();
}
});
App = new AppRouter();
// Enable pushState for compatible browsers
var enablePushState = true;
// Disable for older browsers (IE8, IE9 etc)
var pushState = !!(enablePushState && window.history && window.history.pushState);
if(pushState) {
Backbone.history.start({
pushState: true,
root: '/'
})
} else {
Backbone.history.start({
pushState: false,
root: '/'
})
}
Problem: As you can see in the above code, I tried to disable pushstates with pushState: false if it is an incompatible browser.
However for IE to access what would normally work for a normal browser (http://mydomain.com/explore), IE will need to go to http://mydomain.com/explore/#explore which is making things confusing! Further more to access http://mydomain.com/explore/1234 IE needs to go to http://mydomain.com/explore/#explore/1234
How should this be fixed?
If you don't want http://mydomain.com/explore/#explore url, then you have to redirect to
http://mydomain.com/#explore so Backbone will start with it instead.
if(!pushState && window.location.pathname != "/") {
window.location.replace("/#" + window.location.pathname)
}
UPD: you'll probably have to remove the leading slash when setting path as a hash window.location.pathname.substr(1)
UPD2: if you want /explore/ to be the root for your backbone routes then you have to exclude it from routes and set as a root in History.start({root: "/explore/"})
routes: {
'': 'explore',
':id': 'viewListing',
}

Categories

Resources