Creating backbone routes dynamically based on flags - backbone.js

I am trying to create backbone routes for a user dynamically , only if the logged in user has the permission to view that particular route.In my current implementation i have created the routes and call the corresponding routing function and in that function i will check , if the user has the required privilege, if not rerouted to a default page. Can i create the routes itself based on the condition so that i don't have to check every time whether user has the appropriate privileges.
var Workspace = Backbone.Router.extend({
routes: {
"help": "help",
"search/:query": "search",
"search/:query/p:page": "search",
"default": "default"
},
help: function() {
if(!permission1){
router.navigate('default', true);
}
//write logic
},
search: function(query, page) {
if(!permission2){
router.navigate('default', true);
}
//write logic
},
//write logic for other routes
});

The solution to the problem i came up with after reading the backbone js documentation is as follows:
var GlobalRouter = Backbone.Router.extend({
initialize: function () {
this.route('*path','default',showDefault);
if (permission1) {
this.route('menu1', 'menu1', showMenu1);
}
if (permission2) {
this.route('menu2', 'menu2', renderMenu2);
}
if (permission3) {
this.route('menu3', 'menu3', renderMenu3);
}
}
});
Creating the routes likes this means that if a user does not have permission to view a certain route , there is no need for checking permission again as the routing will not happen as it is never created.

Related

Which one should I use? Backbone.js Router.navigate and window.location.hash

I began learning Backbonejs recently, by reading a book. and I feel a little bit confuse about this issue.Here is a Router:
define(['views/index', 'views/login'], function(indexView, loginView) {
var SelinkRouter = Backbone.Router.extend({
currentView: null,
routes: {
'home': 'home',
'login': 'login'
},
changeView: function(view) {
if(null != this.currentView)
this.currentView.undelegateEvents();
this.currentView = view;
this.currentView.render();
},
home: function() {
this.changeView(indexView);
},
login: function() {
this.changeView(loginView);
}
});
return new SelinkRouter();
});
and this is the boot method of a application:
define(['router'], function(router) {
var initialize = function() {
// Require home page from server
$.ajax({
url: '/home', // page url
type: 'GET', // method is get
dataType: 'json', // use json format
success: function() { // success handler
runApplicaton(true);
},
error: function() { // error handler
runApplicaton(false);
}
});
};
var runApplicaton = function(authenticated) {
// Authenticated user move to home page
if(authenticated) window.location.hash='home';
//router.navigate('home', true); -> not work
// Unauthed user move to login page
else window.location.hash='login';
//router.navigate('login', true); -> not work
// Start history
Backbone.history.start();
}
return {
initialize: initialize
};
});
My question is about the runApplication part. The example of the book that I read passed router into module just like this, but it used window.location.hash = "XXX", and the router wasn't touched at all.
I thought the "navigate" method would make browser move to the page I specified, but nothing happened. Why?
And for the best practice sake, what is the best way to achieve movement between pages(or views)?
thanks for any ideas.
You could also use the static method to avoid router dependency (while using requirejs for instance).
Backbone.history.navigate(fragment, options)
This way, you just need :
// Start history
Backbone.history.start();
// Authenticated user move to home page
if(authenticated)
Backbone.history.navigate('home', true);
// Unauthed user move to login page
else
Backbone.history.navigate('login', true);
According to the documentation, if you also want to call the function belonging to a specific route you need to pass the option trigger: true:
Whenever you reach a point in your application that you'd like to save
as a URL, call navigate in order to update the URL. If you wish to
also call the route function, set the trigger option to true. To
update the URL without creating an entry in the browser's history, set
the replace option to true.
your code should look like:
if(authenticated)
router.navigate('home', {trigger: true});
Once your router is created, you also have to call
Backbone.history.start();
Backbone.history.start([options])
When all of your Routers have
been created, and all of the routes are set up properly, call
Backbone.history.start() to begin monitoring hashchange events, and
dispatching routes.
Finally the runApplication logic will be something similar to this:
var runApplicaton = function(authenticated) {
var router = new SelinkRouter();
// Start history
Backbone.history.start();
// Authenticated user move to home page
if(authenticated)
router.navigate('home', true);
// Unauthed user move to login page
else
router.navigate('login', true);
}

Roles of backbone views, model, and router

I am developing a backbone application which is using require.js.
I want a user to enter in the 'id' for a model and then either be redirected to a view for that model if it exists, or display an error message if it does not. This sounds extremely simple, but I am having trouble figuring out the roles of each component.
In the application below, the user will come to an index page with an input (with id 'modelId') and a button (with class attribute 'lookup').
The following piece of code is the router.
define(['views/index', 'views/myModelView', 'models/myModel'],
function(IndexView, MyModelView, myModel) {
var MyRouter = Backbone.Router.extend({
currentView: null,
routes: {
"index": "index",
"view/:id": "view"
},
changeView: function(view) {
if(null != this.currentView) {
this.currentView.undelegateEvents();
}
this.currentView = view;
this.currentView.render();
},
index: function() {
this.changeView(new IndexView());
},
view: function(id) {
//OBTAIN MODEL HERE?
//var model
roter.changeView(new MyModelView(model))
}
});
return new MyRouter();
});
The following piece of code is the index view
define(['text!templates/index.html', 'models/myModel'],
function( indexTemplate, MyModel) {
var indexView = Backbone.View.extend({
el: $('#content'),
events: {
"click .lookup": "lookup"
},
render: function() {
this.$el.html(indexTemplate);
$("#error").hide();
},
lookup: function(){
var modelId = $("#modelId").val()
var model = new MyModel({id:modelId});
model.fetch({
success: function(){
window.location.hash = 'view/'+model.id;
},
error: function(){
$("#error").text('Cannot view model');
$("#error").slideDown();
}
});
},
});
return indexView
});
What I can't figure out is that it seems like the better option is for the index view to look up the model (so it can display an error message if the user asks for a model that doesn't exist, and also to keep the router cleaner). But the problem is that the router now has no reference to the model when the view/:id router is triggered. How is it supposed to get a hold of the model in the view() function?
I guess it could do another fetch, but that seems redundant and wrong. Or maybe there is supposed to be some global object that both the router and the view share (that the index view could put the model in), but that seems like tight coupling.
You can do something like this. You could do something similar with a collection instead of a model, but it seems like you don't want to fetch/show the whole collection?
With this type of solution (I think similar to what #mpm was suggesting), your app will handle browser refreshes, back/forward navigation properly. You basically have a MainView, which really acts more like a app controller. It handles events triggered either by the router, or by user interaction (clicking lookup or a back-to-index button on the item view).
Credit to Derick Bailey for a lot of these ideas.
In the Router. These are now only triggered if the user navigates by changing a URL or back/forward.
index: function() {
Backbone.trigger('show-lookup-view');
},
view: function(id) {
var model = new MyModel({id: id});
model.fetch({
success: function(){
Backbone.trigger('show-item-view', model);
},
error: function(){
// user could have typed in an invalid URL, do something here,
// or just make the ItemView handle an invalid model and show that view...
}
});
}
In new MainView, which you would create on app startup, not in router:
el: 'body',
initialize: function (options) {
this.router = options.router;
// listen for events, either from the router or some view.
this.listenTo(Backbone, 'show-lookup-view', this.showLookup);
this.listenTo(Backbone, 'show-item-view', this.showItem);
},
changeView: function(view) {
if(null != this.currentView) {
// remove() instead of undelegateEvents() here
this.currentView.remove();
}
this.currentView = view;
this.$el.html(view.render().el);
},
showLookup: function(){
var view = new IndexView();
this.changeView(view);
// note this does not trigger the route, only changes hash.
// this ensures your URL is right, and if it was already #index because
// this was triggered by the router, it has no effect.
this.router.navigate('index');
},
showItem: function(model){
var view = new ItemView({model: model});
this.changeView(view);
this.router.navigate('items/' + model.id);
}
Then in IndexView, you trigger the 'show-item-view' event with the already fetched model.
lookup: function(){
var modelId = $("#modelId").val()
var model = new MyModel({id:modelId});
model.fetch({
success: function(){
Backbone.trigger('show-item-view', model);
},
error: function(){
$("#error").text('Cannot view model');
$("#error").slideDown();
}
});
},
I don't think this is exactly perfect, but I hope it could point you in a good direction.

Selective history.back() using Backbone.js

I have a Backbone app. I'm using Backbone.history to enable use of the back button. We have a page (settings) that auto loads a popup requiring input from the user. If the user chooses cancel, I want to go back to the previous page. I can do this using window.history.back().
The problem is, if the user went directly to that page (app#settings) from another url (like google) by typing the url into the browser, I want to redirect the user to the home page (app/) rather than going back to google.
I haven't been able to figure out any way to do this. Backbone.history looks like it store information from the browser's back button, so it has a history even if they just arrived at the app. I also couldn't find a way to view the previous url.
Is this possible?
Wrap the back navigation logic in a method of your own. Perhaps on the router:
var AppRouter = Backbone.Router.extend({
initialize: function() {
this.routesHit = 0;
//keep count of number of routes handled by your application
Backbone.history.on('route', function() { this.routesHit++; }, this);
},
back: function() {
if(this.routesHit > 1) {
//more than one route hit -> user did not land to current page directly
window.history.back();
} else {
//otherwise go to the home page. Use replaceState if available so
//the navigation doesn't create an extra history entry
this.navigate('app/', {trigger:true, replace:true});
}
}
});
And use the router method to navigate back:
appRouter.back();
I used the same answer from jevakallio, but I had the same problem that commenter Jay Kumar had: The routesHit doesn't subtract so hitting appRouter.back() enough times will take the user out of the app, so I added 3 lines:
var AppRouter = Backbone.Router.extend({
initialize: function() {
this.routesHit = 0;
//keep count of number of routes handled by your application
Backbone.history.on('route', function() { this.routesHit++; }, this);
},
back: function() {
if(this.routesHit > 1) {
//more than one route hit -> user did not land to current page directly
this.routesHit = this.routesHit - 2; //Added line: read below
window.history.back();
} else {
//otherwise go to the home page. Use replaceState if available so
//the navigation doesn't create an extra history entry
if(Backbone.history.getFragment() != 'app/') //Added line: read below
this.routesHit = 0; //Added line: read below
this.navigate('app/', {trigger:true, replace:true});
}
}
});
And use the router method to navigate back:
appRouter.back();
Added lines:
1st one: Subtract 2 from routesHit, then when its redirected to the "back" page it'll gain 1 so it's actually like you did just a minus 1.
2nd one: if user is already at "home", there wont be a redirect so don't do anything to routesHit.
3rd one: If user is where he started and is being sent back to "home", set routesHit = 0, then when redirected to "home" routesHit will be 1 again.

What to be done to prevent the router URL being used directly on the address bar by user?

Have done some working samples using Backbone Router, but is there a way to protect the routes being used directly on the address bar? And also when the user press the back button on the browser, the routes doesn't get cleared and creates issues. What is the best solution for this?
I think I see what you're saying - you want to force the user to enter your site through a certain (home) page. Is that correct?
This is useful, for example, when you're building a mobile-optimized-web-app, and you always want users to enter through a splash screen. What I'll do is set a 'legitEntrance' property to my router, and check for it on every route, as so:
APP.Router = Backbone.Router.extend({
legitEntrance: false,
// Just a helper function
setLegitEntrance: function() {
this.legitEntrance = true;
},
// Send the user back to the home page
kickUser: function() {
this.navigate("home", {trigger:true});
},
routes : {
...
},
// Example router function: Home page
routeToHome: function() {
this.setLegitEntrance();
var homeView = APP.HomeView.extend({ ... });
homeView.render();
},
// Example router function: some other internal page
routeToSomeOtherInternalPage: function() {
if(!this.legitEntrance) {
this.kickUser();
return;
}
var someOtherInternalView = APP.SomeOtherInternalView.extend({
...
});
someOtherInternalView.render();
}
....
});
I'm sure this code could be cleaned up some, but you get the general idea. Hope it helps.

loading multiple views in init function breaks one or the other

I'm building my first backbone.js app, and I've run into a problem when trying to initialize my app and display both recipes and a shopping list, both of which are different (yet related) backbone objects.
My init function is
var MyApp= {
Models: {},
Views: {},
Routers: {},
Collections: {},
AppView: {},
Init: function() {
new MyApp.Views.ShoppingList;
new MyApp.Routers.Recipes;
Backbone.history.start();
}
};
Strangely, when I use
new MyApp.Routers.ShoppingList;
new MyApp.Routers.Recipes;
I don't get the shopping list View, I only get the recipes.
I also don't get any errors.
The shopping list router is fairly basic
MyApp.Routers.ShoppingList = Backbone.Router.extend({
routes: {
"": "index",
"shopping_list/:id": "show"
},
index: function(){
console.log('this');
new MyApp.Views.ShoppingList();
}
});
so from what I understand, the app should load the router, and display the view, but I'm not getting that or the console.log.
--------------as requested, here is my 'recipes router'---------------
MyApp.Routers.Recipes = Backbone.Router.extend({
routes: {
"": "index",
"recipes/:id": "show"
},
index: function(){
if(!MyApp.RecipeList){
MyApp.RecipeList = new MyApp.Collections.RecipeList;
MyApp.RecipeList.page = 1;
} else {
MyApp.RecipeList.page++;
}
MyApp.RecipeList.url='/recipes?page='+MyApp.RecipeList.page;
MyApp.RecipeList.fetch({
add: true,
success: function() {
new MyApp.Views.RecipeList({ collection: MyApp.RecipeList});
},
error: function() {
new Error({ message: "Error loading documents." });
}
});
},
show: function(id){
var recipe = MyApp.RecipeList.get(id);
new MyApp.Views.RecipeView({ model: recipe});
},
newRecipe: function(){
new App.Views.Edit({ model: new Recipe() });
},
edit: function(id){
var recipe = new Recipe({ id: id});
recipe.fetch({
success: function(model, resp){
new App.Views.Edit({ model: recipe});
},
error: function(){
new Error({message: "Hey!? Were'd it go? sorry I can't find your recipe"});
window.location.hash = '#';
}
});
}
});
----------------- some progress -----------------------------
I may be wrong, but in commenting out sections of the router, I find that the problem may be caused by my 'routes' as they both have index where the url is empty. Commenting out the 'routes' in one controller/router causes the other controller/router to display.
I've changed the routes so that they are more representative of their namespace
routes{
"recipes" : "recipes"
},
recipes: function()...
but I'm still not getting the right information to display. I'm now trying to figure out if I need an initialize function and what that would look like, or if I've even debugged this properly
--------------------- update, I was using backbone wrong ------------------------
It turns out I believe that I was mis-understanding Routers and was thinking of them more like controllers, so I was calling multiple routers on load, but the page was only loading the last one which pointed to an empty route as you can only request a single url route at a time.
Now I'm loading multiple Views on load and only one router.
After instantiating your view, you still need to render it and add it to the DOM.
index: function(){
console.log('this');
var view = new MyApp.Views.ShoppingList();
//you don't have to append to the whole body, but this is just an example
$('body').append(view.render().el);
}

Resources