Routing with slashes in Backbone - backbone.js

I am trying to use pushState routing in Backbone.js
The problem is that when I try to use routes which contain slashes in between the links, the router function wouldn't get called and the console would show an error:
var Router = Backbone.Router.extend({
routes: {
"work/mobileapps": "showPortfolio"
},
showPortfolio: function(){
alert('Show Portfolio');
}
The showPortfolio function never gets called in this case. I've also tried to do it with parameters, like this:
var Router = Backbone.Router.extend({
routes: {
"work/:section": "showPortfolio"
},
showPortfolio: function(){
alert('Show Portfolio');
}
but the problem still remains the same.
However, if I change the route to something like this:
var Router = Backbone.Router.extend({
routes: {
"work-mobileapps": "showPortfolio"
},
showPortfolio: function(){
alert('Show Portfolio');
}
it starts to work.
So it seems like there's some problem with the slashes. Please help and let me know what mistake I am making.
Thanks!
Here's the full code:
//Set variables according to production/development environment
var production = 0;
var rootpath = (production) ? "/" : "Websitev2/code";
//--------------------------------------------------------------
var Router = Backbone.Router.extend({
routes: {
"work/mobileapps": "showPortfolio",
"about": "showAbout",
"work": "showWork",
"": "showHome"
},
showPortfolio: function(){
alert('Show Portfolio');
//$("#work-content").empty();
},
showAbout: function () {
alert('Show About');
},
showWork: function () {
alert('Show Work');
},
});
$(document).ready(function () {
window.router = new Router();
Backbone.history.start({
pushState: true,
root: rootpath
});
});

In my very limited experience, have you tried:
var Router = Backbone.Router.extend({
routes: {
'work(/:section)': 'showPortfolio'
},
showPortfolio: function(section){
alert('Show Portfolio ' + section);
}
});
As noted in the comments, you need to reference routes with a hash:
http://localhost/Websitev2/code/#work/mobileapps

Related

Backbone views events firing repeatedly in routes

I have a backbonejs application that contains a router file and some views , and also i'm using requirejs to add views to routes and add templates to views. here is my codes :
routes.js
var AppRouter = Backbone.Router.extend({
routes: {
"": "getLogin",
"login": "getLogin",
"register": "getRegister",
"forget-password": "getForgetPassword"
},
getLogin: function() {
require(['views/auth/loginView'], function(view) {
view = new this.LoginView();
});
},
getRegister: function() {
require(['views/auth/registerView'], function() {
view = new this.RegisterView();
});
},
getForgetPassword: function() {
require(['views/auth/forgetPasswordView'], function() {
view = new this.ForgetPasswordView();
});
},
});
var route = new AppRouter();
Backbone.history.start();
loginView.js
var LoginView = Backbone.View.extend({
el: '#wrapper',
initialize: function() {
NProgress.start();
this.render();
},
render: function() {
require(['text!partials/auth/login.html'], function(t) {
var json = { title: 'title', formName: 'frmLogin' };
var template = _.template(t);
$('#wrapper').html(template(json));
});
NProgress.done();
},
events: {
"click #btnLogin": "login"
},
login: function(e) {
e.preventDefault();
alert('some message');
}
});
also registerView.js and forgetPasswordView.js are similar to loginView.js.
now! when i change routes multiple times and hit #btnLogn it fires alert('some message'); function multiple times...!
Have you tried un-delegating the events in the view, on route change?
You could override the route method (annotated source) in your AppRouter and run it before each route is rendered.
route: function(route, name, callback) {
view.undelegateEvents();
return Backbone.Router.prototype.route.apply(this, arguments);
}
Note: Just an idea, not tested with your code

BackboneJS - Subviews navigation

I have a pretty simple mainmenu with 4 anchors and the relevant views to them. Anyhow, on one of those views I want to add a little submenu with 3 tabs, which after clicking them they show a different view. I figured out how to do it with pushState:false but what I want is a clean URL. Right now, my URL would look like http://localhost/myproject/#secondpage/subview1 or http://localhost/myproject/#secondpage/subview2 etc etc. So does anyone know how I do achieve http://localhost/secondpage no matter which subview/tab is triggered?
Im using RequireJS and HandlebarsJS (for HTML-templating)
So right now my code (snippets) look like this:
Router.js
routes: {
'': 'index',
'firstpage' : 'firstpage',
'secondpage' : 'secondpage',
'secondpage/sub1' : 'sub1',
'secondpage/sub2' : 'sub2',
'secondpage/sub3' : 'sub3',
'thirdpage' : 'thirdpage'
},
Backbone.history.start({
pushState: false
});
My HTML with the anchors:
<ul>
<li>
<a class="sub1" href="#secondpage/sub1">Bands</a>
</li>
<li>
<a class="sub2" href="#secondpage/sub2">Koncert</a>
</li>
<li>
<a class="sub3" href="#secondpage/sub3">Locations</a>
</li>
</ul>
and my View looks like
define(['backbone','handlebars', 'text!templates/SubMenu.html'],
function(Backbone,Handlebars, Template) {
'use strict';
var SubMenuView = Backbone.View.extend({
template: Handlebars.compile(Template),
initialize: function () {
_.bindAll(this);
},
render: function() {
$(this.el).html(this.template());
return this;
}
});
return SubMenuView;
}
);
Another thing is: should I move the actions to the View by setting events? I kind of tried that but it didnt work since the Views are defined in the router...
What I tried is to set pushState:true, then I removed the secondpage/sub1 thingies in my router, then in my View I wrote:
events: {
'click a.sub1': 'sub1',
},
sub1: function(event) {
event.preventDefault();
var sub1Router = new Backbone.Router();
var route = '/secondpage/';
sub1Router.navigate(route, {trigger: true});
},
but that didnt work, that gave me URL not found so...
Any help is welcome! Thanks in advance...
[UPDATE]
OK, so by request, here is my (new) router:
var Router = Backbone.Router.extend({
routes: {
'': 'index',
'firstpage' : 'firstpage',
'secondpage' : 'secondpage',
'thirdpage' : 'thirdpage'
},
initialize: function () {
var self = this;
//Views
this.mainMenuView = new MainMenuView({el:'#mainMenu'}).render();
this.subMenuView = new SubMenuView();
Backbone.history.start({
pushState: true
});
},
index: function () {
var self = this;
},
firstpage: function() {
this.firstpageView = new FirstpageView({el:'#topContent'}).render();
},
secondpage: function() {
this.secondpageView = new SecondpageView({el:'#topContent'}).render();
this.subMenuView = new SubMenuView({el:'#subMenu'}).render();
},
thirdpage: function() {
var thirdpageView = new ThirdpageView({ el:'#topContent', collection:this.categoryCollection}).render();
},
sub1: function() {
this.sub1View = new Sub1View({el:'#subContent_2'}).render();
},
sub2: function() {
this.sub2View = new Sub2View({el:'#subContent_2'}).render();
},
sub3: function() {
this.sub3View = new Sub3View({el:'#subContent_2'}).render();
}
});
return Router;
}
And my (new) View looks like:
var SubMenuView = Backbone.View.extend({
template: Handlebars.compile(Template),
events: {
'click .sub1': 'sub1',
'click .sub2': 'sub2',
'click .sub3': 'sub3',
},
sub1: function(event) {
var sub1Router = new Backbone.Router();
var route = '/secondpage';
sub1Router.navigate(route, {trigger: true});
},
sub2: function(event) {
event.preventDefault();
var sub2Router = new Backbone.Router();
var route = '/secondpage';
sub2Router.navigate(route, {trigger: true});
},
sub3: function(event) {
event.preventDefault();
var sub3Router = new Backbone.Router();
var route = '/secondpage';
sub3Router.navigate(route, {trigger: true});
},
initialize: function () {
_.bindAll(this);
},
render: function() {
$(this.el).html(this.template());
return this;
}
});
return SubMenuView;
And my (new) HTML template:
<ul>
<li>
<a class="sub1" href="/secondpage/">Sub1</a>
</li>
<li>
<a class="sub2" href="/secondpage/">Sub2</a>
</li>
<li>
<a class="sub3" href="/secondpage/">Sub3</a>
</li>
</ul>
Hope this can contribute to more input/suggestions... This is really driving me nuts which make me consider using .show() and .hide() jquery method even if i dont really want...
What you're describing is how backbone routing works, either you use '/secondpage/sub1' and use server routes, or use '#secondpage/sub1' and hit backbone routing. Either way the address bar is going to update with your URL.
One alternative option is to use a events inside the view, handling a click event and updating the view's template accordingly.
However, if you're intent on using routes then maybe have a look at clickify.js. I haven't used it myself yet, although I have it bookmarked for potential future use... sounds like it might do what you want.
try to use this in your router :
Backbone.history.navigate('secondpage');
after all the work is done (the models are fetched the views are rendered)

After integrating requirejs in backbone app, (view) not working

I am developing my backbone application using require.js. all were fine. up to I integrate the routers. after I integrate my router I'm getting errors saying:
TypeError: appView is undefined
[Break On This Error]
that.$el.append(new appView.getView({model:data}).render());
and in the view.js I'm unable to route using this line(i intentionally commented)
listTrigger:function(){
myApp.navigate("/student");
}
and i post my all codes here... anyone can suggest or correct my code. or give me the reasons what i am doing wrong here?
main.js
require.config({
baseUrl: 'js/',
paths:{
'jquery' :"lib/jquery.min",
'underscore' :"lib/underscore-min",
'backbone' :"lib/backbone-min",
'appModel' :"app/model/model",
'appView' :"app/views/view",
'appViews' :"app/views/views",
'appRoute' :"app/router/router"
},
shim:{
underscore:{
exports:"_"
},
backbone:{
exports:"Backbone",
deps:["jquery","underscore"]
}
}
});
require(["appRoute"], function(appRoute) {
var myApp = new appRoute.getRoute();
Backbone.history.start();
});
model.js
define("appModel", ['backbone'], function(Backbone){
"use strict"
var appModel = Backbone.Model.extend({});
var appCollection = Backbone.Collection.extend({
model:appModel,
initialize:function(){
// console.log("initialized from collection");
}
});
return {
model: appModel,
collect:appCollection
}
});
view.js
define("appView", ["backbone","appViews"], function(Backbone,appViews){
"use strict"
var appView = Backbone.View.extend({
tagName:"li",
template:_.template($("#listTemp").html()),
events:{
"click" : "listTrigger"
},
initialize:function(){
this.render();
},
render:function(){
return this.$el.html(this.template(this.model.toJSON()));
},
listTrigger:function(){
// myApp.navigate("/student");
}
});
return{
getView: appView
}
})
views.js with some json data:
define('appViews', ["backbone","appModel","appView"], function(Backbone,appModel,appView){
"use strict";
var students = [
{"name":"student1"},{"name":"student2"},
{"name":"student3"},{"name":"student4"},
{"name":"student5"},{"name":"student6"},
{"name":"student6"},{"name":"student8"},
{"name":"student9"},{"name":"student0"}]
var appViews = Backbone.View.extend({
el:$("#app").find('ul'),
initialize:function(){
this.collection = new appModel.collect(students);
this.collection.on('reset', this.renderAll);
this.renderAll();
},
render:function(){
console.log("render called from views");
},
renderOne:function(){
console.log("render one")
},
renderAll:function(){
var that = this;
this.collection.each(function(data,i){
that.$el.append(new appView.getView({model:data}).render());
})
}
});
return {
appViews : appViews
}
})
router.js
define('appRoute', ["backbone","appModel","appView","appViews"], function(Backbone,appModel,appView,appViews){
var appRouter = Backbone.Router.extend({
routes:{
"":"initiate"
},
initialize:function(){
// console.log("called from routersssss");
},
initiate:function(){
new appViews.appViews();
}
})
return {
getRoute : appRouter
}
})
across this all are working correct up to using routers. after I'm not getting the result. Am I using routers incorrectly?
#TomasKirda: you can new-up without the trailing parentheses, but I wouldn't recommend it! Also, see here.
Having said that, you have identified the problem in this instance!
This code:
new appView.getView(...);
Is trying to create a new appView.getView which isn't a reference to a constructor (it would be if the appView constructor had a property getView).
So in this case, you are quite right that the parentheses are required:
new appView().getView(...);

How can I run a "middleware" function before a route method is executed?

Say I have a backbone router like:
routes:
"" : "homepage"
"catalog/:id" : "catalogPage"
"catalog/:id/products/:id2" : "productPage"
homepage : -> doStuff()
catalogPage: (id) -> doOtherStuff()
productPage: (id, id2) -> doEvenMoreStuff()
and a function:
executeBefore = -> console.log("hello")
If I want executeBefore to be called and executed each time a route is called and before the corresponding route method, is there a simple way to do it apart from inserting a call to executeBefore at the beginning of every route method ?
You can override the route function in your router class to intercept the route calls :
var Router = Backbone.Router.extend({
routes: {
"" : "homepage",
"catalog/:id" : "catalogPage"
},
route: function(route, name, callback) {
var router = this;
if (!callback) callback = this[name];
var f = function() {
console.log('route before', route);
callback.apply(router, arguments);
console.log('route after', route);
};
return Backbone.Router.prototype.route.call(this, route, name, f);
},
homepage: function() {
console.log("homepage");
},
catalogPage: function(id) {
console.log("catalogPage "+id);
}
});
var r = new Router();
Backbone.history.start();
And a demo http://jsfiddle.net/nikoshr/EdLzh/
Since Feb, 13, 2014, you can use router.execute(callback, args, name) (http://backbonejs.org/#Router-execute) for this.
So your interceptor will be looked something like this
routes: {
'': 'homepage',
'catalog/:id': 'catalogPage',
'catalog/:id/products/:id2': 'productPage'
},
execute: function(callback, args, name) {
// Do your stuff here
executeBefore();
if (callback) callback.apply(this, args);
}
https://github.com/jashkenas/backbone/commit/791033dc1aab53e9e5c9366f64a854224f851231
you can also use backbone.routefilter plugin.
you'll be able to set a filter for all routes
var Router = Backbone.Router.extend({
routes: {
"": "index",
"page/:id": "page"
},
before: function( route, params ) { ... },
after: function( route, params ) { ... },
index: function(){ ... },
page: function( route ){ ... }
});
Or select some routes
var Router = Backbone.Router.extend({
routes: {
"": "index",
"page/:id": "page"
},
before: {
"": function( route ) { ... },
"page/:id": function( route ) { ... }
},
after: function( route ) { ... },
index: function(){ ... },
page: function( route ){ ... }
});
You can play with the Events :
Backbone.history.on('route', function () {
// Do your stuff here.
executeBefore();
});
visiting #help/uploading will fire a route:help event from the router.
Apparently, this would be triggered before the redirection :
https://github.com/documentcloud/backbone/blob/master/backbone.js#L1243
Backbone.history.route(route, function(fragment) {
var args = router._extractParameters(route, fragment);
callback && callback.apply(router, args);
router.trigger.apply(router, ['route:' + name].concat(args));
router.trigger('route', name, args);
Backbone.history.trigger('route', router, name, args);
});

Backbone.js routes don't fire correctly

My code is as follow:
var AppRouter = Backbone.Router.extend({
_data: null,
_length: 0,
_index: null,
_todos: null,
routes: {
"*action": "index",
"category/:name": "hashcategory"
},
initialize: function(options){
this._data = options.data;
this._todos = new TodosCollection(options.data);
this._length = this._todos.length;
this._index = new CategoriesView({collection: this._todos});
},
index: function(){
this._index.render();
},
hashcategory: function(name){
console.log('started');
}
});
initializeRouter = function (router) {
Backbone.history.start({ pushState: true });
$(document).on('click', 'a:not([data-bypass])', function (evt) {
var href = $(this).attr('href');
var protocol = this.protocol + '//';
if (href.slice(protocol.length) !== protocol) {
evt.preventDefault();
router.navigate(href, true);
}
});
return router;
};
var start = function(){
p = $.ajax({
url: 'data/todolist.json',
dataType: 'json',
data: {},
success: function(data) {
var approuter = initializeRouter(new AppRouter({data: data}));
}
});
};
I have a <a> link in my html which has a href = "category/num1" attibute. But every time I click the link, it always shows a security error in firebug. Actually I just have one index.html page, what I want to do is append a string to it to make a fake html page like folder/index.html/category/num1 and all of the things will still be rendered in current page. But the url shown to me when the link is hovered is folder/category/num1. Because this path actually doesn't exist in my folder, I think that's why it shows a security error.
So how should I fix it? Should I create another html page and the corresponding folder? Or can I make all of the routing in one index.html page?
Try putting a # in the href, like
href = "#category/num1"

Resources