Views not rendered after transition event - javascript

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.

Related

Not able to get Iron router to work on the first time browser back is clicked

I am using Iron Router to launch Bootstrap modals from their specific URL in a Meteor project and I want to be able to use the browser back and forward buttons to navigate through them.
The problem:
Iron Router doesn't fire on the first time the browser's back button is clicked, after that it works fine!
My code:
I simplified the code a little bit but this is how I set the URL when a modal opens or closes(this works).
Template.main.events({
// modal closed -> URL to "/"
'click.dismiss.bs.modal': function () {
if(window.location.pathname != "/"){
// error prevention:
// click.dismiss.bs.modal also fires when user opens a modal
window.history.pushState("", "", "/");
}
},
// modal opened -> URL to "/u/username"
'click *[data-target="#infoModal"]': function () {
window.history.pushState("", "", "/u/" + username);
}
});
This is my router.js code, also a bit simplified. The console logs do not return the first time the browser's back button is pressed.
Router.route('/u/:username', {
name: 'u.show',
waitOn: function () {
return Meteor.subscribe('users');
},
action: function () {
console.log("Iron Router is trying to open #infoModal");
$('#infoModal').modal('show');
}
});
Router.route('/', {
name: 'home.show',
waitOn: function () {
return Meteor.subscribe('users');
},
action: function () {
console.log("Iron Router is trying to close all modals");
$('.modal').modal('hide');
}
});
I never worked with Iron Router before so I don't really know what I am doing wrong here, any help is much appreciated!
I think you need to use Router.go rather than manipulating browser history yourself. That way the library can "do the right thing" for its internal bookkeeping.
Template.MyButton.events({
'click #clickme': function () {
Router.go('/one');
}
});
You should also implement your route check using iron-router's built in facilities to make your life a little easier:
if(Router.current().route.getName() != 'home.show') {
Router.go('home.show');
}
That way if you ever change the URL (like putting the whole app under a subpath) then this code doesn't also have to be changed.

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();
});

backbone route cycling between routes before getting to the right one

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();

BackboneJS Routing

My app's index page is at http://cms/admin (I'm on localhost). On the index page there is only one a element:
deneme
When i click on the link it goes to /cms/admin/test
I want to use BackboneJS's routing mechanism to convert my app to ajax friendly app but i can't do it until now. Here is my JS code:
$(function() {
var AppRouter = Backbone.Router.extend({
routes: {
"test": "defaultRoute"
},
defaultRoute: function() {
console.log('its here');
}
});
var appRouter = new AppRouter();
Backbone.history.start({
pushState: true,
slient: true,
root: '/admin/'
});
});
When i run the page and click the link, it doesn't log anything to console and browser follows the link. After page loads, it logs "its here" message.
I already tried it without the root param, "/admin/test" instead of "test". i tried every combination of: "test", "/test", "test/", "/admin/test", "admin/test" etc..
Thanks.
You have to override the default behavior of the link.
You have to move between pages explicitly calling appRouter.navigate("test", {trigger: true});. So try to capture the click event over your link within a View.events, prevent the default behavior of the link and call appRouter.navigate()
Update
Also you can do it this in a batch:
$("a.bb_link").each( function( index, link ){
$(link).click( function( event ){
event.preventDefault();
appRouter.navigate( $(this).attr( "href" ), {trigger: true} );
});
});​
Every link with class bb_link will be using the Backbone.Router.
Try setting your anchor tag as follows:
deneme
Note the hash (#)

Testing a trigger click from a Backbone.View which opens a new Backbone.View

I have two Backbone Views, MainView and PopupView.
MainView contains a help button. When the help button handler is fired it shows the Backbone.View.
My question is how should I test this behavior from the MainView module?
Here's my code about MainView:
var MainView = Backbone.View.extend({
events: {
'click #help' : 'showPopUp'
},
showPopUp: function() {
var popupView = new PopupView();
app.vent.trigger('showModal', popupView);
}
});
Here's my code about the mainView.spec:
describe("When help button handler fired", function() {
beforeEach(function() {
this.view.render();
this.view.$el.find('#help').trigger('click');
});
it("shows the popup", function() {
// what should I do?
});
});
Here's my code about the app:
var app = new Marionette.Application();
app.addRegions({
header: '#header',
sidebar: '#sidebar',
main: '#main',
modal: '#modal'
});
app.vent.on('showModal', function(view) {
var modal = app.modal;
modal.show(view);
modal.$el.modal({
show: true,
keyboard: true,
backdrop: 'static'
});
});
If you are using Sinon and Chai, you can try this:
describe("When help button handler fired", function() {
beforeEach(function() {
this.popupSpy = sinon.spy()
app.vent.on('showModal', this.popupSpy);
this.view.render();
this.view.$el.find('#help').trigger('click');
});
it("shows the popup", function() {
this.popupSpy.callCount.should.equal(1);
this.popupSpy.args[0][0].should.be.an.instanceOf(PopupView);
});
});
So your Main View shouldn't open the popup, it shouldn't not even know that something like this exist. It should just notify the other modules via the eventbus that an popup should open by firing the event.
As you use app.vent I assume you're using marionette. In my project I have a Marionette.Region to handle an overlay view. And this region should open/close the view.
Doing it this way, its much easier to test. In the main view you can spy on the app.vent function and test that it will be execute when the button is clicked. In your region you can fire the event on app.vent and spy on your view.render function.
Creating new instances in your object you wanna test, makes testing always harder as it should be. Sure its easier in JavaScript, as in Java for example, cause you can override existing function on runtime in JavaScript, but using some way of dependency injection makes it always easier to mock and spy the dependencies.
How about this:
describe("When help button handler fired", function() {
var popupShown;
beforeEach(function() {
popupShown = false;
this.view.render();
app.vent.on('showModal', function() {
popupShown = true;
});
this.view.$el.find('#help').trigger('click');
});
it("shows the popup", function() {
expect(popupShown).toBe(true);
});
});
That said, I would recommend a few things:
As was mentioned elsewhere, don't create the modal view in MainView. This couples the two too tightly.
In your tests, you may want to say something like it("triggers the help event") or something similar. This is particularly important since you're unit testing that object in isolation. For integration testing, the opposite would be true.
I'm not sure that you aren't doing too much in your beforeEach function. You may want to at least trigger the button click in the it scope, although based on what you're describeing, this may be OK.

Categories