I'm using devise, rails with backbone. All my backbone routes are working just fine. But non-backbone routes like /accounts/login that are supposed to rendered by rails are being globbed with backbone router.
SS.Routers.ApplicationRouter = Backbone.Router.extend({
initialize: function() {
this.el = $("#content");
},
routes: {
"": "home"
},
"home": function () {
console.debug("Got home request");
var view = new SS.Views.Home();
this.el.empty().append(view.render());
}
});
The actual request/response to the /accounts/login is happening from rails logs. But Backbone home root gets triggered afterwards and my home page is rendered.
My layout has
$(function () {
SS.init();
});
from
window.SS = {
Models: {},
Collections: {},
Views: {Providers: {}},
Routers: {},
init: function (data) {
console.debug("Initializing Backbone Components");
new SS.Routers.ApplicationRouter();
new SS.Routers.ProvidersRouter();
if (!Backbone.history.started) {
Backbone.history.start();
Backbone.history.started = true;
};
}
};
Which is triggering my home route again.
"" route in backbone is not supposed to be globbing /accounts/login but it is.
A little bit of debugging is showing me that /accounts/login is being gobbled by "" since the fragment is an empty string.
And the fragment is an empty string in the all the cases where there is no match for backbone routes.
Code from backbone 0.9.2
loadUrl: function(fragmentOverride) {
var fragment = this.fragment = this.getFragment(fragmentOverride);
var matched = _.any(this.handlers, function(handler) {
if (handler.route.test(fragment)) {
console.debug(handler);
console.debug(fragment);
handler.callback(fragment);
return true;
}
});
return matched;
},
Any suggestions?
Add a class ('passThrough') or 'data-passThrough=true' attribute to the link. Catch this class/attribute in your router and return true so Backbone stops handling it and the browser treats it as a regular link.
Related
This is how I do my routes in backbonejs where the routing and its params are obtained first before deciding which external template to call. I find this is quite flexible.
var Router = Backbone.Router.extend({
routes: {
//'': 'renderBasic',
':module/:method/': 'renderDynamicViewPageBasic',
':module/:branch/:method/': 'renderDynamicViewPageBranch',
':module/:branch/:method/set:setnumber/page:pagenumber/': 'renderDynamicViewPagePager',
':module/:branch/:method?set=:setnumber&page=:pagenumber': 'renderDynamicViewPagePager'
},
renderDynamicViewPageBasic: function (module,method) {
$(el).html(Handlebars.getTemplate('template1')(data));
},
renderDynamicViewPageBranch: function (module,branch,method) {
$(el).html(Handlebars.getTemplate('template2')(data));
},
renderDynamicViewPagePager: function (module,branch,method,setnumber,pagenumber) {
$(el).html(Handlebars.getTemplate('template3')(data));
}
});
How about in emberjs, can I do the same - do the rout and get its params afirst before deciding which external template to call?
I read the documentation and tested it. It seems to be less flexible - for instance,
App.Router.map(function() {
this.route("about", { path: "/about" });
this.route("favorites", { path: "/favs" });
});
Is it possible to get the route and params and then the controller before getting the template?
if not, it seems to be the same as case using Angularjs which I finally decided not to use it because it gets the template first before sorting out the params.
You can define the template "post params" in EmberJs using the renderTemplate hook, where you can customize which template you'd like to use.
http://emberjs.jsbin.com/oXUqUJAh/1/edit
App.Router.map(function() {
this.route('apple', {path: 'apple/:id'});
});
App.AppleRoute = Ember.Route.extend({
model: function(params) {
return {coolProperty: params.id};
},
renderTemplate: function(controller, model) {
// send in the template name
this.render(model.coolProperty);
}
});
You can pass a function together with $route params to get customized result in angularjs actually.
template: function($params) {
return app.$templateCache.get($params); // or make template yourself from another source
}
If I do a manual page refresh or set a redirect like this
}).then(function() {
document.location = "/";
}, function() {
in a function in my Ember app, an Ember controller property is becoming unset. I'm not sure if it matters which property is becoming unset, but in case it does, here's the code. In my application template, I check for whether a user is authenticated
{{#if isAuthenticated}}
blah blah
{{else}}
blah blah
{{/if}}
The property is set in the AuthController
App.AuthController = Ember.ObjectController.extend({
currentUser: null,
isAuthenticated: Em.computed.notEmpty("currentUser.email"),
login: function(route) {
var me;
me = this;
return $.ajax({
url: "/users/sign_in.json",
type: "POST",
data: {
"user[email]": route.currentModel.email,
"user[password]": route.currentModel.password
},
success: function(data) {
me.set('currentUser', data.user);
So, after I do a page refresh, my currentUser is (according to Ember) no longer showing as authenticated. However, on the (Ruby on Rails) server side the user is still authenticated. I put this code in the layout to check and after Ember thinks that I'm signed out (with a page refresh), my server code is still telling me I'm signed in (which in fact I am).
<% if current_user %>
there is a current user
<% end %>
Can you explain why this might be happening?
Your best is to set up is probably to make isAuthenticated a computed property that does an ajax GET to /users/current if currentUser is null. /users/current should return {} if the user is not authenticated, and the current user data if they are.
App.AuthController = Ember.ObjectController.extend({
currentUser: null,
isAuthenticated: function() {
var currentUser = this.get('currentUser');
if(Ember.isEmpty(currentUser)) {
$.ajax({
type: "GET",
url: "/users/current.json",
async: true
}).done( function( data ) {
me.set('currentUser', data.user);
});
return false;
} else {
return true;
}
}.property('currentUser'),
});
Why are you doing a manual page refresh? This way you are starting the browser completely from scratch and therefore the Ember App initializes again and therefore the property currentUser is not set on your Controller.
Instead of doing this:
}).then(function() {
document.location = "/";
}, function() {
I assume that you want to go to the start page of your app (since you want to go to "/"). Instead you should use the Ember feature of transitioning between routes. I do not know, where you execute the code above, but you can do this either from one of your controllers or in a route.
In a controller you could use transitionToRoute:
// index is a Route which is implicitly generated by Ember for you
this.transitionToRoute('index');
In a router you could use transitionTo:
this.transitionTo('index');
I'm using the following router in my app:
app.Router = Backbone.Router.extend({
// define the route and function maps for this router
routes : {
"" : "showLogin",
// Sample usage: http://ServerManager/
"login" : "login",
// Sample usage: http://ServerManager/#login
"logout" : "logout",
// Sample usage: http://ServerManager/#logout
"folders" : "listFolders",
// Sample usage: http://ServerManager/#folders
"folders/:name" : "showFolder",
// Sample usage: http://ServerManager/#folders/System
"*other" : "defaultRoute"
// Sample usage: http://ServerManager/#badHash
},
showLogin : function() {
console.log("Home page loaded. Redirecting to #login page.");
this.navigate("login", {trigger: true, replace: true});
},
login : function() {
//console.log("login route fired");
// delete sessionStorage items and load a new loginview
this.changePage(new app.LoginView());
},
logout : function() {
//console.log("logout route fired");
// Clear the token and other session items from sessionStorage
sessionStorage.clear();
this.navigate("login", {trigger: true, replace: true});
},
listFolders : function() {
//console.log("listFolders route fired.")
if (app.folderView === undefined) {
console.log("creating new app.folderView");
app.folderView = new app.FolderView();
}
else {
console.log("using existing app.folderView");
}
this.changePage(app.folderView);
},
showFolder : function(name) {
console.log( app.router.routes[Backbone.history.fragment] );
//console.log("showFolder route fired.")
this.changePage(new app.ServiceView({folder : name}));
console.log( app.router.routes[Backbone.history.fragment] );
//app.router.navigate("folder/" + name, {trigger: false});
},
defaultRoute : function() {
alert("Error. Page doesn\'t exist.");
},
changePage : function(page) {
$(page.el).attr("data-role", "page");
page.render();
$("body").append($(page.el));
var transition = $.mobile.defaultPageTransition;
$.mobile.changePage($(page.el), {changeHash : true, transition : transition});
}
});
If I use
app.router.navigate("folders", {trigger: true, replace: true});
it successfully sends the app to server/#folders, but when I pass
app.router.navigate("folders/" + id, {trigger: true, replace: true});
it triggers the root of the app and loads my login page. I can see the correct URL (e.g. server/folders/folder1) temporarily flash before the redirect happens. Any ideas as to what's happening here?
Backbone routers expect more generic routes last.
Set-up the folders/:id route followed by the folders route.
UPDATE
Actually, after thinking about it, this may not be the case. Can you post console output? Is it calling the correct function?
Perhaps the $.mobile ... changeHash: true is conflicting. You should probably only have one of the frameworks change the url hash for you.
If I loop the collection in the view, it's seems empty, alert dialog don't show up. When I use console.log(this.collection) in this view, it's look ok (16 element in this collection).
My router: (collection url: '/api/employees', this is a rails json output)
Office.Routers.Employees = Backbone.Router.extend({
routes: {
"": "index"
},
initialize: function() {
this.collection = new Office.Collections.Employees();
this.collection.fetch();
},
index: function() {
var view = new Office.Views.EmployeesIndex({ collection: this.collection });
view.render();
}
});
and my index.js view:
Office.Views.EmployeesIndex = Backbone.View.extend({
render: function() {
this.collection.each( function( obj ){ alert(obj); } );
}
});
Edit:
This is the output of the console.log(this.collection) in view : http://i.stack.imgur.com/ZQBUD.png
Edit2:
I thing Rails is the guilty. When I work whit static collection, everything works fine
var collection = new Backbone.Collection([
{name: "Tim", age: 5},
{name: "Ida", age: 26},
{name: "Rob", age: 55}
]);
collection.fetch() makes an asynchronous request to the server. The index callback doesn't wait for the fetch to return. So your render function is rendering an empty collection.
You need to use the success callback of the fetch method:
index: function() {
this.collection.fetch({
success: function(data) {
var view = new Office.Views.EmployeesIndex({ collection: data });
view.render();
}
});
}
Note that the Backbone documentation recommends bootstrapping any initial data you need by including the data in the document itself:
When your app first loads, it's common to have a set of initial models
that you know you're going to need, in order to render the page.
Instead of firing an extra AJAX request to fetch them, a nicer pattern
is to have their data already bootstrapped into the page.
The fetch has probably not completed by the time your view renders. Try the following:
index: function() {
var p, collection, view;
collection = new Office.Collections.Employees();
p = collection.fetch();
view = new Office.Views.EmployeesIndex({ collection: collection });
p.done(view.render);
}
I'm trying to add controller to my Todo list app. Here's the code.
$(function(){
alert(Backbone); // => [object]
alert(Backbone.Controller); // => undefined
TodoList.Controllers.Todos = Backbone.Controller.extend({
routes: {
"documents/:id": "edit",
"": "index",
"new": "newDoc"
},
edit: function(id){
var todo = new Todo({id:id});
todo.fetch({
success:function(model,resp){
new App.Views.Edit({model:todo});
},
error: function(){
new Error({message: "Couldn't find the todo item."});
window.location.hash = '#';
}
});
},
index: function(){
window.App = new TodoList.Views.AppView
}
});
});
As mentioned in the comment, when I alert(Backbone), [object] is returned, while Backbone.Controller returns undefined and I can't figure out why. This is stopping the whole app to work.
It's been replaced with Backbone Router: http://backbonetutorials.com/what-is-a-router/
which backbone version do you have?
Try Backbone.Router instead of Backbone.Controller.
Quote from http://backbonejs.org : as of v5.0
Controller was renamed to Router, for clarity
If that's the case, you can make a reference copy of the Router in order to not alter the existing code :
if(Backbone.Router)
Backbone.controller = Backbone.Router;