Using Backbone.history to go back without triggering route function - backbone.js

I have a modal popup which when opened changes the URL. When a user closes the popup I want to go back to the previous URL but I don't want to trigger the route associated with that URL because that will reload my collection and render the view etc. Is there a way to callwindow.history.back()without triggering the route, or is there a backbone equivalent of this?
The only solution I can think of would be to save the previous route, then when the modal is closed call
Backbone.history.navigate(route, {trigger: false, replace: true});
but this seems like a complex way to solve an easy problem.

Storing history in a router sounds like a good solution to me, I couldn't figure out better way to solve this problem.
A good solution of that is here:
Silently change url to previous using Backbone.js
I would do a minor tweak so it would look like this:
class MyRouter extends Backbone.Router
initialize: (options) ->
#on "all", #storeRoute
#history = []
storeRoute: ->
#history.push Backbone.history.fragment
previous: ->
if #history.length > 1
#navigate #history[#history.length-2], false
else
#navigate '', true
Then you can just call MyRouter.previous(), and if you came by direct request, it will bring you to your root.
I wish it were a default feature of the router, at least so that it keeps 5 last routes.

Related

React-router: Change URL and clear history

I have the following scenario:
A user opens an activation link; after the user has completed the activation process, the system will move them to another page.
I don't want to keep the activation link in the browser's history because when the the user goes back they will get to the activation step again.
How do I replace the history of a browser to remove certain requests from my application?
In ReactJs you should use browserHistory for this purpose. This takes care of your histories and you don't need to implement those functions on your own.
browserHistory has 2 methods push() and replace() which do the same functions as #fazal mentioned in his answer but in a better way.
So if you want to avoid user going back to previous state you would need to use browserHistory().replace
Start with importing it into your code:
import {browserHistory} from 'react-router'
After user has activated you do following:-
browserHistory.replace(//your new link)
HTML5 history API provide two methods to Adding and modifying history entries.
pushState() : back state preserved
replaceState() : no back state
Assuming you are using react-router.
So, on your Component use
this.props.history.replaceState('your new state')
read more: Manipulating the browser history
video: REACT JS TUTORIAL #6 - React Router & Intro to Single Page Apps with React JS
I know this is old but I had a similar issue and none of the other answers were 100% applicable with my version or React but this should work in more recent versions by clearing appended paths.
//replace(path, state)
this.props.history.replace("/home", "urlhistory");
Run this block where you want change route
history.entries = [];
history.index = -1;
history.push(`/route`);
This will clear the history and change for a new one.
window.location.href = 'Your path';
or
document.location.replace().

how to check base url before routing using router in backbone.js

I am new to backbone.js, pardon me if the question seems silly.
I am routing to other pages using :
this.options.app.navigate( myNewURL, true);
this myNewURL gets appended to the current URL in the window but before doing that i want to make some checks on it.How do i fetch the current url and then append the newUrl to it and redirect?
eg:
current url : abc.com/firstString/SecondString
on navigation i want : abc.com/SecongString/myNewUrl
this firstString might or might not exist, so i have to make a check for it and remove it before redirection.
How can that be done?
The way I prefer navigation in Backbone.
Backbone.history.navigate('myNewUrl',{trigger:true}); //This will fire the router
Make trigger false to just change the route, but not execute the router.
In order to get the current url, you can use
Backbone.history.fragment();
References :
http://backbonejs.org/docs/backbone.html

Why is it considered bad practice to call trigger: true in the navigate function of backbone.js?

I have read in several places that calling the Backbone.history.navigate function is considered bad practice.
For example Addy Osmani sais in his book "Developing Backbone.js Applications"
It is also possible for Router.navigate() to trigger the route along
with updating the URL fragment by passing the trigger:true option.
Note: This usage is discouraged...
http://addyosmani.github.io/backbone-fundamentals/#backbone.history
Or Derick Bailey in his blog post even sais:
You shouldn’t be executing the route’s handler from within your application, most of the time.
But I don't really understand the reasoning behind it and what would be a better solution.
In my opinion it is not really bad to call the navigate function with the trigger:true option. The route function could upon calling always check if the considered data is already loaded and show this loaded data instead of doing the whole work all over again...
There seems to be some confusion about what Router#navigate does exactly, I think.
Without any options set it will update the URL to the fragment provided.
E.g. router.navigate('todo/4/edit') will update the URL to #todo/4 AND will create a browser history entry for that URL. No route handlers are run.
However, setting trigger:true will update the URL, but it will also run the handler that was specified for that route (In Addy's example it will call the routers editTodo function) and create a browser history entry.
When passing replace:true the url will be updated, no handler will be called, but it will NOT create a browser history entry.
Then, what I think the answer is:
the reason why the usage of trigger:true is discouraged is simple, navigating from application state to application state to application state requires most of the time different code to be run than when navigating to a specific application state directly.
Let's say you have states A, B and C in your application. But state B builds upon state A and state C builds upon B.
In that case when you navigate from B to C only a specific part of code will need to be executed, while when hitting state C directly will probably execute some state checking and preparation:
has that data been loaded? If not, load it.
is the user logged in? If not redirect.
etc.
Let's take an example: State A (#list) shows a list of songs. State B (#login) is about user authentication and state C (#list/edit) allows for editing of the list of songs.
So, when the user lands on state A the list of songs is loaded and stored in a collection. He clicks on a login-button and is redirected to a login form. He successfully authenticates and is redirected back to the song list, but this time with delete-buttons next to the songs.
He bookmarks the last state (#list/edit).
Now, what needs to happen when the user clicks on the bookmark a few days later?
The application needs to load the songs, needs to verify the user is (still) logged in and react accordingly, stuff that in the state transition flow had already been done in the other states.
Now for a note of my own:
I'd never recommend the above approach in a real application as in the example. You should check whether the collection is loaded when going from B to C and not just assume it already is. Likewise you should check whether the user really is logged in. It's just an example.
IMO the router really is a special kind of view (think about it, it displays application state and translates user input into application state/events) and should always be treated as such. You should never ever rely on the router to transition between states, but rather let the router reflect the state transitions.
I have to disagree with #Stephen's answer here. And the main reason why is because the use of router.navigate({trigger : true}) gives the router responsibility to handle the application's state. It should only reflect application state, not control it.
Also, it is not a View's responsibility to change the hash of the window, this is the router's only job! Don't take it away from it! Good modularity and separation of concerns makes for a scalable and maintainable application.
Forwarding a person to a new section within your application
Backbone is an event driven framework, use events to communicate. There is absolutely no need to call router.navigate({ trigger : true }) since functionality should not be in the router. Here is an example of how I use the router and I think promotes good modularity and separation of concerns.
var Router = Backbone.Router.extend({
initialize: function(app) {
this.app = app;
},
routes: {
'videoLibrary' : function() { this.app.videoLibrary(); }
}
});
var Application = _.extend({}, Backbone.Events, {
initialize: function() {
this.router = new Router( this );
this.listenTo( Backbone, 'video:uploaded', function() {
this.router.navigate('/videoLibrary');
this.videoLibrary();
});
},
videoLibrary: function() {
//do useful stuff
}
});
var uploadView = Backbone.View.extend({
//...
uploadVideo: function() {
$.ajax({
//...
success: function() { Backbone.trigger('video:uploaded'); }
});
}
});
Your view does not need or want to know what to do when the user is done uploading, this is somebody else's responsibility. In this example, the router is just an entry point for the application's functionality, an event generated by the uploadView is another. The router always reflects the application state through hash changes and history but does not implement any functionality.
Testability
By separating concerns, you are enhancing the testability of your application. It's easy to have a spy on Backbone.trigger and make sure the view is working properly. It's less easy to mock a router.
Modules management
Also, if you use some module management like AMD or CommonJS, you will have to pass around the router's instance everywhere in the application in order to call it. Thus having close coupling in your application an this is not something you want.
In my opinion it's considered bad practice because you should imagine a Backbone application not like a Ruby On Rails application but rather like a Desktop application.
When I say RoR, I'm just saying a framework supporting routing in sense that a route brings you to a specific call to the controller to run a specific action (imagine a CRUD operation).
Backbone.history is intended just as a bookmark for the user so he can, for example, save a specific url, and run it again later. In this case he will find the same situation he left before.
When you say:
In my opinion it is not really bad to call the navigate function with
the trigger:true option. The route function could upon calling always
check if the considered data is already loaded and show this loaded
data instead of doing the whole work all over again...
That to me sounds smelly. If you are triggering a route and you are checking for the data to see if you have it, it means that you actually already had them so you should change your view accordingly without loading again the entire DOM with the same data.
That said trigger:true is there so do we have reason use it? In my opinion it is possible to use it if you are completely swapping a view.
Let's say I have an application with two tabs, one allows me to create a single resource, the other one let me see the list of the created resources. In the second tabs you are actually loading a Collection so data is different between the two. In this case I would use trigger:true.
That said I've been using Backbone for 2 weeks so I'm pretty new to this world but to me it sounds reasonable to discourage the use of this option.
It depends on your context.
If you have done something in your current view that might affect the view you are about to navigate to, for example creating for deleting a customer record, then setting trigger to true is the right thing to do.
Think about it. If you delete a customer record don't to want to refresh the list of customers to reflect that deletion?

Handing cold navigation to internal URL in Backbone?

I'm wondering how people handle the following case in Backbone: usually when a user navigates to the root of your app, a certain set of data is loaded from the backend, processed right away and then displayed in the DOM.
There are links in the app that will navigate you to different sub-sections of it. The router catches the navigation and replaces the current page with whatever page you navigated to, all based on the same data that's already been fetched.
The problem is that the user could bookmark that internal/secondary URL and navigate to it "cold", as in before the data has had a chance to be fetched, without going through the root URL. Is there an idiomatic/conventional way of handling that situation (in the router, I'm assuming)?
One way is, in the various router path-handling functions, to always call a method that will check if there's sufficient data to complete the operation, and if not, fetch it and then proceed?
Backbone won't hit the initial route in your router before you call Backbone.history.start, so you can delay it until you've done the necessary setup. I typically define a start method on my application's main router. Looks something like:
var AppRouter = Backbone.Router.extend({
start: function() {
//init code here
something.fetch({success: function() {
//only call history start after the necessary initial setup is done
Backbone.history.start();
}});
}
});
And then start the application using that method:
window.app = new AppRouter();
window.app.start();
It's good to remember that there is nothing confining you to build your application using only the predefined pieces provided by Backbone. If your startup code is heavy, it may not belong to the router. In such case you should define a helper function to encapsulate the startup logic, and leave the router out of it altogether:
//startup.js
function startup(onComplete) {
//do initialization stuff...
onComplete();
});
//main.js
startup(function() {
Backbone.history.start();
});

Converting existing web app to use hashtag URIs using Backbone.js

I'm attempting to use Backbone and it's Router to turn an app into an ajax app, however it currently uses several different methods (helpers) of generating links. Unfortunately, this means manually changing each and every link to use a hashtag is out of the question.
What would be the best method of ensuring every link, form post, redirect, etc. gets parsed as a hashtag URL that can be caught by Backbone's Router? Or, even better, is it possible for the Router to accept "true URL's" from a request? Example: a request to /app/mail/inbox.php is caught by a rule in the Router, and is turned into #/mail/inbox after firing the appropriate method to handle the request.
What would be the best method of ensuring every link, form post, redirect, etc. gets parsed as a hashtag URL that can be caught by Backbone's Router?
I don't think that Backbone.Router is supposed to handle, say, form posts. It's supposed to give your application view state—bookmark-friendly and refreshable URLs [1].
If you want to ‘ajaxify’ forms, then you probably should add a handler for form's submit event and do something like $.ajax() there, preventing the default action.
Regarding plain old links, History.pushState() support has been added to Backbone recently. It means that you can define your routes as /app/*, and don't need to replace old href attributes. However, you'll still need to catch link click events to prevent default action.
For example:
var handle_link_click = function(e) {
path = $(e.target).attr('href');
app.main_router.navigate(path, true); // This.
e.preventDefault();
};
$('a:internal').click(handle_link_click);
Router's navigate() method will do history.pushState() if it's available, falling back to old hashchange. And true as a second argument means that it will fire corresponding handler action.
[1] See also this presentation about Backbone

Resources