BackboneJS Turbolinks History Issue - javascript

I have a Rails 4 App that uses Turbolinks and Backbone.js.
Basically, my router looks like:
var BookRouter = Backbone.Router.extend({
routes: {
'': 'index'
},
index: function(){
var bookView = new BookView({
el: ".wrapper"
});
}
});
$(function() {
if (Backbone.History.started == false) {
var bookRouter = new BookRouter();
Backbone.history.start({ pushState: true, hashChange: false });
}
});
$(document).on('page:load', function (){
Backbone.history.stop();
Backbone.history.start({ pushState: true, hashChange: false });
var bookRouter = new BookRouter();
});
Here's the scenario:
A user lands on http://website.com/, which takes the user to the index route, which loads a template into the .wrapper, which is shown in my routing code above. This works fine.
A user clicks on one of my Rails generated pages (for example I have a http://website.com/books route which shows all the books that users have created.) This also works fine.
The user is still on the http://website/books route. If you click on my logo, it takes you to the index route (the one generated by the backbone code above). This works fine.
NOW THE PROBLEM: If you go back to Step 3, and instead of clicking the logo, decide to hit the back button, and go back from the books route to the index route, the contents that get put inside the $el .wrapper are repeated TWICE. So for example, in my template, I have the words "Please choose a book to continue". If you went to Step 1, you'd just see the words once. But now, you see n set of words each time you go to /books and then hit the back button n times.

The problem here is the hack used to make Backbone.js play together with Turbolinks. I guess the source of this hack is this blog post which recommends restarting the history whenever a link is handled by Turbolinks. However, stopping the history doesn't clear existing routes. So what you get after handling the page:load event is two router objects handling the same routes. And Backbone.History will trigger every callback for every matching route when you go back, even when they are registered for the same route - meaning that the index callback will run twice, hence the content duplication.
One way to deal with it would be simply removing the duplicate router:
$(document).on('page:load', function (){
Backbone.history.stop();
Backbone.history.start({ pushState: true, hashChange: false });
});
This is sufficient to trigger the router whenever Turbolinks is invoked. However, there seems to be a cleaner way to make Backbone.History trigger the router. From what I can tell from the source code, this should work correctly as well:
$(document).on('page:load', function (){
Backbone.history.checkUrl();
});

Related

How to keep the same Backbone template after reloading the browser

In my Backbone app (Rails backend), I am using [Handlebars] Javascript templates (JST's) for rendering my Backbone views. Whenever I do a browser refresh while on one of those URL templates, it jumps back to the root URL. I want it to stay on that current template after page-refresh. I want to do something like this...
getPage: function() {
$(window).on('beforeunload', function(){
var fragment = Backbone.history.fragment;
});
if (fragment === "this_current_template") {
return App.getCurrentTemplatePage(); // render it
} else if (fragment === "") {
return App.getFrontMainPage(); // render it
}
}
getPage is a method in my backbone App object. My fragment variable can't be recognized outside of the $(window) event handler. I can't seem to find a way to persist my previous Backbone.history.fragment in a variable, even if I make fragment global. It's all swept away after refresh. I know about Backbone.history.loadUrl(Backbone.history.fragment);, but I just can't get that to work either. It only seems to work prior to browser refresh.
Is it something I'll have to change in the root route of my backend code? or is there a way to persist the current page using Backbone / JS on page reload? Any help on this would be much appreciated!
First of all you need to create a Router. According to Backbone docs:
var Router = Backbone.Router.extend({
routes: {
"main": "main"
},
main: function() {
// your code here
}
});
After that you only need to init Backbone.history:
Backbone.history.start({});
After that Backbone automatically will try to find appropriate route for your hashbang and fire the appropriate action. In given example if your initial hashbang will be #main then Router.main action will be executed.

prevent users to navigate back to specific routes with backbone

I am new to backbone and I want to implement a very simple auth using backbone router.
I am actually using only the router from backbone in my app. When I start the app I render a login view and I also init the backbone router (Backbone.history.start();)
If login succeeded I call router.navigate('mainmenu', { trigger: true, replace: false }); to navigate to a new route where I render the main menu, but when I click on the browser's back button I navigate back to the login view.
Before navigating to the previous view (the login view) I want to ask the user if he wants to logout first, and if logout process goes well, then he is redirected to the login view.
How can I achieve that? I checked few other questions, but the answer is too complicated for my use case. I just want to prevent users to navigate back to specific views if they're logged in.
#Dethariel thanks for the answer. I successfully implemented some kind of session, using the built-in Backbone router. I started with their small example snippet from the Backbone.Router execute method backbone router execute snippet and did something similar to bellow:
var Router = Backbone.Router.extend({
// define routes and calkbacks
// ....
// define routes and calkbacks
execute: function(callback, args) {
// execute will be called before the callback for each specific route
// get the next route in here
var nextRoute = Backbone.history.fragment;
if(user.LoggedIn()){
// check if nextRoute is '#login*'. I could make other checks as well
if(nextRoute.indexOf('login')>-1)
prompt('Log out?');
// else continue routing
else if (callback) callback.apply(this, args);
}
else if (callback) callback.apply(this, args);
}
});
This is very minimal, and I don't think it's the best or secure way, but it's a good starting point for me.
You can add a backbone route which will handle the login page (if you haven't done that yet). Once this route is hit, you do (pseudo-code follows):
if (user.isLoggedIn()) {
if (showLogoutPrompt().decision === "logout") {
user.logout();
}
}
Hope this helps.

Backbone Routes - trigger route on page load

This is a simple question, but I am new to routing and haven't been able to find an answer to this.
I have a Marionette Router (if I intimidate you, its really the same thing as a Backbone Router. Very simple).
sys.routes = {
"app/:id": "onAppRoute",
};
sys.Router = new Marionette.AppRouter({
controller: {
onAppRoute: function(route) {
console.log('Called app route!');
},
},
appRoutes: sys.routes,
});
Backbone.history.start({pushState: true})
This works - if you hit the back button my browser, the url will change within my Single Page Application and will call the onAppRoute function.
However, let's say I open a new browser window and paste in my page url to a certain 'app':
http://localhost/app/myApplication
This doesn't call the onAppRoute function. It doesn't even seem like it should, though, but I really don't know.
I want it to.
I don't know if I am doing it wrong, or if I should just manually fire it by grabbing my page url on page load, parsing it, then 'navigating' to that route. Seems hacky.
Contrary to your intuition, backbone's default behaviour is to trigger matching routes on page load! cf. http://backbonejs.org/#Router - look for the option silent: true. You'd have to specify that for the router to IGNORE your matching routes on page load, i.e. not trigger the corresponding callbacks.
So your problem lies somewhere else: your routes do NOT match the url you have stated as an example. Clearly, you require an :id parameter, trailing http://localhost/app/myApplication. Therefore, http://localhost/app/myApplication/213 would cause your callback to be triggered on page load, given you didn't pass silent: true as an option to backbone.history.start().
If you want to match the 'root' url, i.e. no params, you would define the following route:
routes: {
'/': someFunction
}
The :id part is a parameter, which will be extracted by Backbone.Router and sent as an argument to onAppRoute.
But in your URL you don't have any parameters /localhost/app/myApplication

Directly calling url with Backbone

If I navigate to a view by clicking on a link such as 127.0.0.1/#/project/1, the correct view gets displayed. However, if I call this url directly in the browser (or hit refresh), the view won't be displayed. What could be the reason for this behaviour?
The way I set up the Router is as follows:
var AppRouter = Backbone.Router.extend({
routes: { },
initialize:function () { }
});
var app = new AppRouter();
and then in every module (I'm using require.js), a route and handler will be added
app.route("project/:id", "showProject");
Could it be that the routes aren't registered yet and thus the callbacks won't be called?
Make sure that you are calling Backbone.history.start() after all of your routers are loaded/instantiated and routes defined: http://backbonejs.org/#History-start
Alternatively, you could stop the history with Backbone.history.stop(), and start it again. Then the added route(s) will be picked up.
BTW, you can test if the history is currently started with the boolean Backbone.History.started (note the capital 'H' is necessary).

Return to index in backbone.js router

Just wanted to know, what is the proper way of returning to the index in backbone.js? So this is an outline of my code. I have a router, where it creates an instance of a view and passes the current instance of the router.
var AppRouter = Backbone.Router.extend({
callMasterViefw: function(){
MasterView.initialize(this);
},
})
Inside the MasterView, I create an instance of the MasterView and assign the router to it as well. I have a function, called reRouteToIndex, where basically i want to reroute to the index page.
MasterView= Backbone.View.extend({
render: function(){},
reRouteToIndex(){
this.router.navigate("", {trigger: true,replace: true});
},
});
All this works, but the problem is when I redirect to the index I return to something like www.test.com/index.html/#. Where it is important to note the # at the end. I was wondering if there was a way to route to the original path without the # like this www.test.com/index.html?
You could enable pushState in .navigate() (like .navigate({pushState: true, trigger: true}) but you'll need to configure your webserver to work with mod_rewrite (or equivalent).

Categories

Resources