backbone route cycling between routes before getting to the right one - javascript

What is the difference between these two methods of handling routes?
var Router = Backbone.Router.extend({
routes: {'home': 'showHome},
showHome: function() {//do whatever}
});
and
var Router = Backbone.Router.extend({
routes: {'home': 'showHome},
initialize: function() {
var router = new Router();
router.on('route:showHome', function(){//do something});
}
});
Currently I have it the second way. It works fine and I get to the right places..
My router is now set up properly(I think) but my routes are still acting weird(as follows).
The only problem I am having is that when it goes to change a route the address bar flickers between multiple different routes before it lands on the right page (always gets to the right place).
I am changing pages be using window.location = '#/route'; inside of jQuery listeners inside of my views.
$('#right_arrow').live('click', {view: that}, this.rightArrow);
...
rightArrow: function(e){
var that = e.data.view;
if(typeof window.easyUserData.fbResponse.authResponse === 'undefined') {
// Not logged in
window.location = '#/login';
} else {
// Logged in
window.location = '#/notes/right';
}
return false;
}
I have been trying to debug and what i have found when i set the breakpoint at window.location = '#/notes/right';:
url changes properly ('#/notes/right')
return false executes
right arrow function ends and debugger goes into jquery code
after five or six 'step over's in debugger (still in jquery code) url changes to '#/news/right', which is another route I use but not sure why it is coming up here
after a few more 'step over' we get back to the original breakpoint where the url changes to the correct '#/notes/right'
the new view loads
Why is it behaving like this? Is it related to how I have my router set up?

Your code is instantiating a new Router in the initialize function, which is what is called when that particular object is instantiated to begin with. So essentially, you're creating an unnecessary router.
The code in the first example is correct. Then you simply do:
var myRouter = new Router();

Related

Director.js and Turbolinks - changing the window hash and using then back

I'm using Director.js in our application along with Turbolinks.
It works perfectly fine when changing the hash, but when using the browser back button (which should just change the hash back to the previous hash, and trigger the relevant Director.js route), Turbolinks takes over and replaces the body causing everything to break. Is there any way to opt out of Turbolinks when changing the window hash with window.location.hash?
EDIT: After more Googling, I've found Turbolinks causes issues with other JS routers as well (eg. Backbones's router). I've not found a working solution yet.
My JS looks as follows:
var new_item_js = function(){
if (signup_routes_defined == false) {
var step1 = function() {
console.log('step one functions go here.');
};
var step2 = function() {
console.log('step two functions go here.');
};
var routes = {
'new_item_step_1': step1,
'new_item_step_2': step2
};
var signup_router = Router(routes);
signup_router.init();
signup_routes_defined = true;
}
$(document).on('page:load ready', new_item_js);
I ended up dropping Turbolinks, which is a bummer. Would be great to actually have this working .

Views not rendered after transition event

I'm currently trying to build an app using backbone, require.js & jqm. I'm new to jquery mobile an I'm having strange rendering issues and I'm not able to track them down or fix them. So my question remains on a rather nebulous and phenomenal level - hopefully someone can help me out here. I've the feeling that I understood something wrong here.
So here's what I do:
Basically I'm using the router facilities of backbone.js and disabling all routing capabilities of jqm. Using (in main.js):
$.mobile.ajaxEnabled = false;
$.mobile.linkBindingEnabled = false;
$.mobile.hashListeningEnabled = false;
$.mobile.pushStateEnabled = false;
After that I use the routing technique proposed by Christophe Coenraets on his blog. Which basically looks like this:
var AppRouter = Backbone.Router.extend({
// Router constructor
initialize: function(){
this.loginView = new LoginView(this);
this.registerView = new RegisterView(this);
this.user = new User(this);
// Tell Backbone to listen for hashchange events
Backbone.history.start();
},
// Define routes
routes: {
'': 'home',
'login' : 'login',
'registration' : 'registration',
},
/// Define route actions ///
// Home route
home: function() {
this.user.isUserLoggedIn();
},
// Login route
login: function() {
console.log("Welcome to the router - login route.");
this.changePage(this.loginView);
},
registration:function() {
console.log("Welcome to the router - registration route.");
this.changePage(this.registerView);
},
changePage:function (page) {
$("body").empty();
$(page.el).attr('data-role', 'page');
$("body").append($(page.el));
page.render();
$(":mobile-pagecontainer").pagecontainer("change", $(page.el), {transition: "pop", changeHash: false, reverse: false});
}});
Basically I've to views: The Login & RegisterView at the moment. When I naviagte to the RegisterView it works fine, but navigating backwards to login I can see the transition ("pop") - but after the transition the content is not shown in the browser. It is present in the DOM but I figures out that certain css classes are missing for example "ui-page-active" on the data-role="page". When I apply that class manually I can see the LoginView but all events are lost (a click on the registration tab does not trigger anything).
I've the feeling that there is a conceptual misunderstanding on my side and I'm not able to figure out where the problem resides. I tried things like .trigger("create") but that looked rather like a helpless tryout than anything else.
I've set up a GitHub-Repository. I'm thankful for any help - thanks in advance and sorry for the confused question.
EDIT: Yeah... and here also the link to the repo.
I figured out, that I placed the configuration to prevent jqm in the wrong place. It has to reside in main.js before the actual app is loaded:
require(["jquery"], function( $ ){
$( document ).one( "mobileinit", function() {
//Set your configuration and event binding
$.mobile.ajaxEnabled = false;
$.mobile.linkBindingEnabled = false;
$.mobile.hashListeningEnabled = false;
$.mobile.pushStateEnabled = false;
});
require([
// Load our app module and pass it to our definition function
'app',
], function(App){
// The "app" dependency is passed in as "App"
// Again, the other dependencies passed in are not "AMD" therefore don't pass a parameter to this function
App.initialize();
});
});
Thanks for the help anyways.

Understanding click events in Backbone and Express

I am trying to learn some javascript and I've gone through several tutorials, now I'm trying to understand a real-life system. Here is a demo site that has been pretty well put together:
http://nodecellar.coenraets.org/
https://github.com/ccoenraets/nodecellar
I think I understand the basics of how events can be assigned to elements on the page but then when I look through his source code I can't figure out how even the first click works. When you click "Start Browsing" it should be caught by javascript somehow which fires off an asynchronous request and triggers the view to change with the data received. But in his / public/js/views/ nowhere is there event catching plugged in (except in the itemdetail view but that's a different view entirely).
I also tried using chrome developer tools to catch the click and find out what script caught it.
Under sources I tried setting an event breakpoint for DOM mutation and then clicked.... but no breakpoint (how is that possible? There's definitely a DOM mutation happening)
I then went under elements and checked under the "click" event listener and didn't see anything revealing there either.
Obviously I don't know what I'm doing here. Can anyone help point me in the right direction?
This app is using backbones routing capabilities to switch contexts.
It is basically using hash tags and listening for location change events to trigger updates to the page.
The routing configuration is in main.js:
See: Backbone.Router for more information.
Code Reference: http://nodecellar.coenraets.org/#wines
var AppRouter = Backbone.Router.extend({
routes: {
"" : "home",
"wines" : "list",
"wines/page/:page" : "list",
"wines/add" : "addWine",
"wines/:id" : "wineDetails",
"about" : "about"
},
initialize: function () {
this.headerView = new HeaderView();
$('.header').html(this.headerView.el);
},
home: function (id) {
if (!this.homeView) {
this.homeView = new HomeView();
}
$('#content').html(this.homeView.el);
this.headerView.selectMenuItem('home-menu');
},
list: function(page) {
var p = page ? parseInt(page, 10) : 1;
var wineList = new WineCollection();
wineList.fetch({success: function(){
$("#content").html(new WineListView({model: wineList, page: p}).el);
}});
this.headerView.selectMenuItem('home-menu');
},
// etc...
});
utils.loadTemplate(['HomeView', 'HeaderView', 'WineView', 'WineListItemView', 'AboutView'], function() {
app = new AppRouter();
Backbone.history.start();
});

Jasmine Testing Fancybox in Backbone - Spied Methods incorrectly passing through

I have a modal login view that I'm testing with Jasmine. I have a nasty problem testing that the login button redirects. I've put the redirect in a method so that I can mock it and test the call but when I trigger the click event on the sign in button the spy seems to be ignored.
Here's the view I'm testing...
define(['ministry', 'models/m-auth', 'helpers/cookie-manager', 'text!templates/modals/login.html', 'jquery.custom'],
function (Ministry, Authentication, CookieManager, TemplateSource) {
var loginView = Ministry.SimpleView.extend({
name: 'Login',
el: "#popupLoginWrapper",
template: Handlebars.compile(TemplateSource),
events: {
'submit #loginForm': 'signIn'
},
redirectToOrigin: function () {
// TODO: Change location to member home when implemented
location.href = '/#/jqm';
},
signIn: function (e) {
var that = this;
e.preventDefault();
// As we have no input yet log in manually using the auth model...
var authData = new Authentication.Request({
requestSource: $('#requestSource').text(),
apiKey: $('#apiKey').text(),
username: '*****',
password: '*****'
});
// This saves the login details, generating a session if necessary, then creates a cookie that lasts for 1 hour
authData.save(authData.attributes, {
success: function () {
if (authData.generatedHash !== undefined && authData.generatedHash !== null && authData.generatedHash !== '')
CookieManager.CreateSession(authData.generatedHash);
that.redirectToOrigin();
}
});
},
});
return loginView;
});
(It currently logs in with a hard coded account - wiring up the actual controls is the next job). And here's the test in question (I've combined all the before and afters into the test for simplicity here)...
var buildAndRenderView = function (viewObject) {
viewObject.render();
$('#jasmineSpecTestArea').append(viewObject.el);
return viewObject;
};
it("signs in when the 'Sign In' button is clicked", function () {
spyOn(objUt, 'redirectToOrigin').andCallFake(Helper.DoNothing);
spyOn(Backbone.Model.prototype, 'save').andCallFake(function (attributes, params) {
params.success();
});
$('body').append('<div id="jasmineSpecTestArea"></div>');
$('#jasmineSpecTestArea').append('<section id="popupLoginWrapper"></section>');
objUt = new View();
buildAndRenderView(objUt);
objUt.$('#loginForm button').click();
expect(objUt.redirectToOrigin).toHaveBeenCalled();
$('#jasmineSpecTestArea').remove();
});
Some brief justification: I do the vast majority of my views as independent components that I then render in a custom view variant called a Region (modeled on Marionette) - I generally find this to be a nice tidy practice that helps me keep track of what sits where in the app separately from what is held in a given 'space'. This strategy doesn't seem to work with fancybox modals, such as this, so the render takes place in a hidden existing DOM element and is then moved into the region, hence the need for the code to append 'popupLoginWrapper' into the test area.
The same test above will also fail if I call the sign in method directly with empty event arguments. Jasmine gives me the following error 'Error: Expected a spy, but got Function.' (Which makes sense, as it's calling the actual implementation of redirectToOrigin rather than the spy.
I have similar tests which pass, and the issue seems to be around the triggering of params.success(), but this is necessary to the test.
ADDITIONAL: After disabling the test, I found this issue affecting most of the tests for this modal. By stepping through, I can see that the spy is applied and the spy code is executed in some cases, but then the real success call seems to then be triggered afterwards.
I seem to have fixed this issue - I'm not strictly sure how; I just approached it completely fresh a few days later - The test now looks like this...
var buildAndRenderView = function (viewObject) {
viewObject.render();
$('#jasmineSpecTestArea').append(viewObject.el);
viewObject.postRender();
return viewObject;
};
it("signs in when the 'Sign In' button is clicked", function () {
$('body').append('<div id="jasmineSpecTestArea"></div>');
$('#jasmineSpecTestArea').append('<section id="popupLoginWrapper"></section>');
objUt = new View();
spyOn(objUt, 'redirectToOrigin').andCallFake(Helper.DoNothing);
spyOn(objUt, 'displayError').andCallFake(Helper.DoNothing);
spyOn(Backbone.Model.prototype, 'save').andCallFake(function (attributes, params) {
params.success();
});
objUt = buildAndRenderView(objUt);
objUt.$('#loginForm button').click();
expect(objUt.redirectToOrigin).toHaveBeenCalled();
expect(objUt.displayError).not.toHaveBeenCalled();
$('#jasmineSpecTestArea').remove();
});
The displayError call was added as I'd wrapped the error alert mechanism since the initial issue, so isn't particularly relevant. I split some variable sets into a postRender after fixing the test, so that is also not relevant.
The problem seemed to be calling the buildAndRenderView method without the return value being set to the object. By changing this...
buildAndRenderView(objUt);
to...
objUt = buildAndRenderView(objUt);
the issue resolved itself.

Event triggered multiple times after using back button in Backbone.js

I'm building a Backbone app and I came across this weird issue. In the state A (route: ""), I've got a view like that:
var view = Backbone.View.extend({
events : {
"click a.continue" : "next"
},
next : function(e) {
//Some stuff
Backbone.history.navigate("/page2");
}
});
and once I click on the anchor with "continue" class, I am redirected to a state B (route: "/page2"). If I click on the back button of my browser, and then I click on the anchor, debugging I've noticed that the next function is triggered twice. Actually if I keep going back and forth the number of times the event is triggered keeps increasing.
Any clue?
You've got a zombie view hanging around.
The gist of it is that when you are instantiating and displaying the second view ("state B"), you are not disposing of the first view. If you have any events bound to the view's HTML or the view's model, you need to clean those up when you close the form.
I wrote a detailed blog post about this, here: http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
Be sure to read the comments as "Johnny O" provides an alternative implementation which I think is quite brilliant.
I Have the same problem, the solution is...
App.Router = Backbone.Router.extend({
routes: {
"fn1": "fn1",
"fn2": "fn2"
},
stopZombies: function(objView){
if(typeof objView === "object"){
objView.undelegateEvents();
$(objView.el).empty();
}
},
fn1: function(){
this.stopZombies(this.lastView);
var view1 = new App.v1();
this.lastView = view1;
},
fn2: function(){
this.stopZombies(this.lastView);
var view2 = new App.v2();
this.lastView = view2;
}
});
Store the last execute view in this.lastView, then stopZoombies() remove the events from this view.

Categories