Router in backbone not handling route after initialization - backbone.js

I have multiple routers in my app, in general way it looks like this:
// Start backbone.js
if (!Backbone.History.started) {
Backbone.history.start({pushState: true, hashChange: false});
}
// Perform some RPC requests ...
// Depending on user role, received from the server should be created suitable router:
var router;
if (typeof app.user.role === 'manager') {
router = new routers.manager();
} else {
router = new routers.guest();
}
Problem is that after page is loaded and script is executed routers do not do. anything. They do not load route for current url automatically. So, i had to fix it this way (i am not sure that it is a right way):
routers.guest.initialize = routers.manager.initialize = function() {
var defaultRoute = 'default';
if (typeof this.routes[Backbone.history.fragment] !== 'undefined') {
this[this.routes[Backbone.history.fragment]]();
} else {
this.navigate(defaultRoute, true);
}
};
It is working fine, except one bug: when i use route with params, for example /reset-password-confirm/:code - it is unable to find in in routes property. I could write some more code to fix it, but i suppose that i am doing something wrong, if i have to write such things - as i understand router should handle routes just after it was created.
So, questions:
Why my router(s) does not handle routes for current url after it is being created? Perhaps i need to start backbone history later? (but this bug will happen again later then)
How it is possible to make routes with params like /user/:id work there?
Perhaps it is bad idea to re-create routers? Perhaps it is better to create all of them one time?
P.S. I've tried to create both routers and keep them, also i've trie to call backbone history start method after all routers were created.. but this didn't help :/

Assuming you route is declared as the following:
routes : {
'/user/:id' : 'user'
}
Your initialize code is not working because when you initialize your router with a url such as: /user/1234. Backbone.history.fragment will be /user/1234 (not /user/:id). Since the this.routes object doesn't have a key of /user/1234, your else clause calls the default route.
If you first instantiate your router then call Backbone.history.start(), you will be able to remove your router initialize code. When you navigate to a url as /user/1234 your router will match the /user/:id route and call the user function.
The following should work for you without adding your initialize code:
var router = (app.user.role === 'manager') ? new routers.manager()
: new routers.guest();
Backbone.history.start({pushState: true, hashChange: false});

Looking at the code, seems like you're starting the backbone history before initializing any routes. That's most likely not goning to work.
The correct way of doing this type of seperation is by creating all the routes based on the role received from the server and then start the backbone history. Here's an SO thread that talks about it with code samples as well : How to protect routes for different user groups

Related

React Router: How to properly append to route/url without replacing anything?

How do I append to the route with react router?
Let's say the current route is /page1/page2 and I want to route to /page1/page2/next-page
first I get the router
const router = useHistory();
When I use push like this
router.push("next-page");
It routes to /page1/next-page.
When I use add a / like this
router.push("/next-page");
It routes to /next-page
I also tried something like this
router.push(`${router.location.pathname}/next-page`)
But the problem this way is, when I'm currently at /page1/page2/, I end up at /page1/page2//next-page with two //.
Is there a good way to solve this without having to write the complete route like router.push("/page1/page2/next-page")?
What I ended up doing is defining a function that removes an / form the url if there
export const removeSlashSuffix = (input) => {
if (input.charAt(input.length - 1) === "/") {
return input.substr(0, input.length - 1)
} else {
return input;
}
}
and then route like this
router.push(`${removeSlashSuffix(router.location.pathname)}/nest-page`)
This way I get no issues with routes that resolve in url with // in it.
This is somewhat of a hacky solution and I wish react-router-dom would support this out of the box but it doesn't seem like it does
If you do router.push("/page1/page2/next-page"), it should work. I think you need to specify the full path, I don't see anything wrong in doing so
if you want to append something to an existing path, you should be able to do something like
const location = useLocation()
const currentPath = location.pathname // to get current route
router.push(`${currentPath}/next-page`)
THis is a sudo-code, so you may need to tweak it a bit for proper syntax

Disabling routes in angular js + laravel project

I have a laravel application which uses angular js as the front end. There I need to disable this route. appo.dev/ which means the root path of the application. How can I disable that path only. Here I need to access other routes such as appo.dev/progess. I tried the following way in routes.php file. It's better if I can find a solution with this code.
Route::any('{path?}', function () {
return view("appo_app");
})->where("path", ".+")
->whereNotIn("path", "appo.dev/");
Above without whereNotIn clause it will work for all the routes. So I am thinking a way to disable only that particular route via wherenotin clause. Or is there a better wild card character? Anyone knows how to solve this issue.
Maybe without what you want is like the root route did not exist, you can try this.
And it will return an error 404. Maybe that's what you're looking for?
I assume that appo.dev is the domain.
Route::any('/', function () {
abort(404);
});
Route::any('{path?}', function () {
return view("appo_app");
})->where("path", ".+");
If it were not the domain, maybe this will help you.
Route::any('appo.dev', function () {
abort(404);
});
Route::any('appo.dev/{path?}', function () {
return view("appo_app");
})->where("path", ".+");
I hope it helps you. A cordial greeting.

Avoid traliing slash for default routes in Backbone.js

I'm looking for something to accomplish in Backbone. Here is the explanation.
I have backbone routes like
{
'': 'defaultRoute',
'test': 'test',
'testing': 'testing'
}
Assuming my root url is '/routes'
Now when I say 'router.navigate('test', {trigger: true});' url changes to /routes/test.
Similar way when I call 'router.navigate('testing', {trigger: true});' url changes to /routes/testing.
But when I call 'router.navigate('', {trigger: true});' url changes to /routes/.
You know i didn't expect that / at the end. I never passed that. It should have been back to root url i.e. '/routes'.
Adding / at the end makes lot of difference/meaning. Checkout 'http://googlewebmastercentral.blogspot.in/2010/04/to-slash-or-not-to-slash.html'
Any fix for that (i.e. not having / at the end for default route)?
Try to override Backbone.History.prototype.navigate.
Copy default function and change this line :
var url = this.root + fragment;
to :
if(this.root !== '/' && fragment === ''){
var url = this.root.replace(trailingSlash, '');
}else{
var url = this.root + fragment;
}
First things first:
If your application is not being served from the root url / of your
domain, be sure to tell History where the root really is, as an
option: Backbone.history.start({pushState: true, root:
"/public/search/"})
Backbone#History
In your case, it is most likely to be something like root: "/routes". Something you need to keep in mind is that your website is seen in the browser as either a resource or a remote folder, therefore the default route without a trailing slash may not fully work by just leaving it as "" because that means usually "the current folder". You could try to set your root url as root: "/" instead (just as it should be by default) and create a "routes" resource as your default route, something like the following:
{
'test': 'test',
'testing': 'testing',
'routes': 'defaultRoute',
'*': 'catchAll'
}
Another recommendation that I am doing to you (as you can see it above) is to set your a catch all URL at the end in case someone enters a non-existent one, and you can also use it to redirect your users to your default route.
// your code above ...
defaultRoute: function() {
// your default route code...
},
catchAll: function() {
this.navigate('routes', {trigger: true});
}
// your code below ...
Finally, by any reason mess up with Backbone URLs manually, you are highly risking yourself to break the whole thing AND if in the future the API changes it should be easier to update if you just follow the intended use.
Update:-
This has been already fixed in Backbone's latest version. I asked a question on there forum and found the answer. I'm adding it here as an answer if it helps anyone who is looking for the same thing.
https://github.com/jashkenas/backbone/issues/2871

Backbone.history.navigate disregard trigger:false

I try to study and use Backbone/Marionette in my project. Now I stuck with Router navigation which work not as I though it should.
class MyApp.Router extends Marionette.AppRouter
appRoutes :
'info/:place/(:what)' : 'places_page'
MyApp.Controller = ->
places_page: (place,what)->
console.log 'Triggered places_page'
MyApp.addInitializer( ->
controller = new MyApp.Controller()
new MyApp.Router
controller: controller
Backbone.history.start( pushState: false )
)
MyApp.vent.on('do:search', ->
console.log 'triggered do:search'
place = 'Moscow'
what = 'Пицца'
info_model.set place: place, item:what
new_url = 'info/'+where+'/'+what
if new_url != decodeURIComponent(Backbone.history.fragment)
Backbone.history.navigate(new_url, {trigger: false})
On initial load of site.com/#info/Budapest/Vine page or reload it, I get Triggered places_page message as I expect.
But when I fire do:search event which update url to site.com/#info/Moscow/Пицца, I get Triggered places_page again! So it reload all my views from scratch instead of just change url and re-render one model.
What I can do wrong here?
Update 2:
Found strange thing. If I use latin letters in new url, everything work like it should.
But if I use cyrillic in new url path, it will trigger route function.
Backbone: 1.0, Marionette:v1.0.3, jquery: 1.9.1
Mystery solved!
That happens because of non-latin symbols in url.
Correct code:
new_url = 'info/'+encodeURIComponent(where)+'/'+encodeURIComponent(what)
if new_url != Backbone.history.fragment
Backbone.history.navigate(new_url, {trigger: false})
Because Backboune.navigate don't execute navigate if url didn't change and trigger is false by default, I can write it simple like that:
new_url = 'info/'+encodeURIComponent(where)+'/'+encodeURIComponent(what)
Backbone.history.navigate(new_url)
Backbone.history takes an object when starting. Try this syntax in CoffeeScript:
Backbone.history.start
pushState: false
In addition, pushState is false by default, so you can just have Backbone.history.start()
Does this solve your issue?
Proper URL encoding is required. I couldn't find this in the docs of Backbone related to the router functionality.
I had the same issue, alas with spaces in the query part, i.e.:
#app/terms?filter=java ee
Together with the encodeURIComponent solution as described in your answer, I also found the following lines of comments in backbone.js (1.0.0), pertaining to the navigate function:
// Save a fragment into the hash history, or replace the URL state if the
// 'replace' option is passed. You are responsible for properly URL-encoding
// the fragment in advance.

Persisting data between routes without going to the server?

I am working on a log in for my backbone application and came on an issue I am not sure how to solve without making a call to the server. This brought up a discussion in my team about what the way other folks are handing this kind of thing in backbone because we think we will be running into a similar thing moving forward.
It's Friday and I'm probably just brain dead, but here goes...
We have a User Model. The login method of the View creates a new user Model and call's it's login method passing in the user's credentials and a callback function which has an object that contains the users information.
Here is the login method for our View:
login: function(event){
event.preventDefault();
var user = new App.User;
user.login($('#username').val(), $('#password').val(),
(function(msg) {
// success callback
if (msg.loggedIn) {
console.log("Authenticate successful: " + JSON.stringify(msg));
var data = { user : msg, bob : "bob", trigger:true };
console.log("Prepared data: " + JSON.stringify(data));
App.router.navigate('home',data);
} else {
console.log("Authenticate unsuccessful: " + JSON.stringify(msg));
}
}).bind(this),
function(msg) {
// failure callback
console.log("Authenticate communication failure: " + JSON.stringify(msg));
alert("Communication fail!");
App.router.navigate('login',{trigger:true});
});
}
What we are trying to figure out is how to best make this Model data available to another route (home) so we can use it in the View.
So I have this router:
routes: {
'': 'home',
'home': 'home',
'login': 'login'
},
home: function(data){
console.log(data);
}
Once we have logged the user in we need to update the route and have access to that users data, but don't want to have to make a trip back to the server to fetch it.
I am concerned because we are building a kind of "wizard" where the user may need to move forward and backward through some steps and I don't want to have to hit the server every time they navigate through the application, but it's seeming like we are going to either need to save stuff to a global variable (don't want to do this) or make a trip back to the server every time. I'm sure others have had to deal with similar issues. Just looking for some insight.
Thanks!
"it's seeming like we are going to either need to save stuff to a global variable (don't want to do this)"
To state the obvious: You're going to need to preserve state. Your options are either to transfer the state to the server and back, or hold state on the client. Since you've already identified that you don't want to pass the state via a server, you're left with preserving state between different pages (routes) on the client.
And that's what global variables are for. It sounds icky, I know, but it's also one of the main benefits that Single-Page Applications bring to the table. Statefulness. And that state will always be held by some global object.
There are better and worse ways of managing state. Having a global variable called data that you keep assigning and reassigning is obviously the worst way. You should figure out a pattern that makes sense for your requirement.
If I understood your code sample correctly, what you want to store is the information about the current user. It also seems that you already have a global variable App. It occurs to me that it would be a good idea to keep some kind of session info:
login: function(data){
doLogin({
success: function(userData) {
App.session.currentUser = userData;
App.router.navigate('home', { trigger:true });
}
});
},
home: function(data){
if(!App.session || !App.session.currentUser) {
App.router.navigate('login', { trigger:true });
return;
}
var user = App.session.currentUser;
//do something with user
}
State is not necessarily evil. What's evil is depending on global state throughout the application, which leads easily to untestable spaghetti code. But if you resolve the state dependency as "high up" in the chain as possible (e.g. in the Router), and pass the values down using constructors and method arguments, you can still keep the testability and side-effectlessness in the rest of the codebase.
Sorry that I don't have a silver bullet for you. There are some libraries, Backbone.StateManager among them, which can help in managing state, transitions and such, but essentially they don't do anything you can't do for yourself.
Use localStorage!
Modify your code to do the following:
// success callback
if (msg.loggedIn) {
console.log("Authenticate successful: " + JSON.stringify(msg));
var data = { user : msg, bob : "bob", trigger:true };
var dataString = JSON.stringify(data);
console.log("Prepared data: " + dataString;
window.localStorage.setItem("userdata",dataString);
App.router.navigate('home',data);
Now whenever you need to check if the user is logged in, do the following:
try {
var userData = window.localStorage.getItem ("userdata");
} catch (e) {
// Do something
}
The try-catch is necessary to make sure that your code doesn't barf if the authentication has never been successful.
Its too late to reply but there is another much better way to do this depending on the router engine you are using and with no local variables.
I would try to give a general example using backbone which applies to all.
Generally your router would be in one place where things are being handled. Assuming its backbone lets have the routes defined as follows.
var router = Backbone.Router.extend({
routingData: {}, // this will have the routing data
routes: {
'': 'home',
'home': 'home',
'login': 'login'
},
navigate: function(url, data) {
this.routingData["data"] = data; // whenever navigation is done, this will be reset
//This is the routing code whichever the f/w may be.
Backbone.Router.prototype.navigate(route, { trigger: true });
},
home: function(data) {
var params = this.routingData["data"]; //retreiving the routing params
console.log(params);
}
})
Now if you want to pass data, you can do
router.navigate(<URL>,<SOME DATA>)

Resources