Custom back button based on history - javascript

In my one-page web app, I would like to have a back button on some of the pages that takes the user back to a specific point in their browser history: not necessarily the previous page.
Note that I am not talking about the browser's back button, which should work as usual.
Use Case
For an example, consider the gmail web app. There is a folder view, potentially with search filters or other parameters, and then a message view that shows a single message. In the messages view, there is a back button that does not take you to the previous page: even if you have clicked around reading several messages, the back button will take you back to the folder view from which you originally came before clicking on any of the messages.
In the case of gmail, this is achieved by simply encoding the previous state at the end of the URL. E.g., if you got to this message by searching, it will be:
#search/stuff/<MESSAGE_ID>
From this, the app knows that the back button should point to the page with:
#search/stuff
In my application, however, there is too much state to encode it all in the URL. Rather than two views (folder + message), there are three views, let's call them A, B, and details, with both A and B having a wide array of possible filters and options encoded in the URL that I would like to preserve when e.g. returning from B to A or from details to B. Encoding all the parameters for A, B and details in the URL of the details page would make the URL extremely unwieldy.
Implementation
I thought this could be easily achieved using the html5 history API. However, as far as I can see the history API does not provide support for reading the URLs in the history: you can only blindly go back or forward.
history.js provides access to past states, as discussed in a related question:
History API: Javascript Pushstate, get previous URL
However, I am using angularjs which does not work well with history.js, because it talks directly to the history api instead of going through history.js, so the states from transitions caused by the angular $location service do not show up in history.js' History object.
It seems that what I would need to do one of the following:
get history.js to work well with angular
re-implement a subset of history.js' functionality in my own code
I also considered using document.referrer to peek at the previous value in history, but that does not work as it does not get set when navigating within a one-page app.

Answering my own question, I chose to go for the simpler solution suggested by #MaxArt in his comment.
Generate a random ID for a page
Since I use angularjs, I do this $on('$routeChangeSuccess') or $on('$routeUpdate')
without angularjs, I suppose I would do this onpopstate
Store a mapping from this random ID to all the URL information that I need (in my case, search and path) in sessionStorage
Include a search parameter from=the ID of the current page in outgoing links that go forward in the conceptual hierarchy of my app
When the custom back button is clicked, look up the state I come from in sessionStorage using the from seach parameter
if found, go back to that URL
if not found (user navigated to this page directlry), go back to the default URL for the previous view in the app's hierarchy
Rationale for taking this approach over the more general approach of building a history of past URLs:
As mentioned, the pushState API does NOT provide access to past URLs
Integrating History.js, which does provide that information, into angularjs does not seem trivial
angularjs uses the history API internally: it would need to be changed to instead use History
Implementing custom code to record the URL history in sessionStorage is also not trivial. The main problem is that, lacking integration with the browser's history, there does not seem to be a general way to know if a visited page is new or it was reached by going back one or more steps in the browser history. I'd be happy to be corrected on this if someone can suggest a solution (1)
Using History.js or any equivalent solution that wraps all history.pushState with additional code, requires pretty much all links on the page to be wrapped in History.pushState, otherwise URL changes do not show up in the History.
(1) How do I retrieve if the popstate event comes from back or forward actions with the HTML5 pushstate?

I've never worked with angular.js, but presumably window.history.pushState is what you're looking for if you want something guaranteed to work. Have a read-over of http://spoiledmilk.com/blog/html5-changing-the-browser-url-without-refreshing-page/ for the detailed information on what this baby can do.

Related

Mean Stack Application: Page does not rerender on refresh, but returns only JSON

I'm currently designing a MEAN.js web application, and for some reason, whenever I refresh the page on a route or window.reload, it does not rerender the page, but only returns the JSON file found at that current route.
For example, when I'm at localhost:8080/people:
If I click here from the main page, I get
But if I hit refresh or reload the page for whatever reason I get
Does anyone have any idea why this is happening and how to fix it?
Presumably you are using what Angular call's html5Mode routing.
This uses pushState and friends. These are browser features designed to allow you to build a web app which has what appear to be separate pages with unique, real URLs but when you change the page you actually modify the DOM of the current page (to State B) instead of loading a new one from scratch.
The design intention for pushState and friends is that if you request the unique, real URL that maps onto State B then the server will provide the browser with the HTML for State B directly.
This means that:
if you arrive on the site without going to the homepage first, then you
load the content you are trying to load directly (which is faster than loading the homepage and then modifying it with JavaScript).
if you arrive on the site without JavaScript working (which could be for many reasons, then everything still works. See also Progressive Enhancement and Unobtrusive JavaScript.
What you've done wrong is that your URLs are mapping onto your JSON based API instead of server side processes that generate the pages.
You need to write the server side processes. You could consider using the Accept header to allow them to share URLs with the API (so the server returns either JSON or HTML depending on what the client says it accepts).

Back button handling in Single Page Apps?

A similar question here has been noted!
I've got an SPA based exclusively on the DHTMLX toolkit (fabulous stuff incidentally). One serious UX problem is with back button handling: there is none.
Their forum recommended 'any js routing library to handle state of the app in the url hash and restore it back'.
I am confused by this as the SPA has only the simplest HTML, is exclusively Javascript and does most communicating via WebSockets ... does this mean I have to store state on each button click/keypress?
So, ...
Does the panel have any recommendations on best practices?
Is there an existing library that will do this?
If said library is light on examples, can anyone provide a basic how-to?
Many thanks
Dhtmlx is a great framework for building SPAs. Like all SPAs, the back button will simply take the user right out of app. Also the user cannot bookmark anything.
So what you want to do is use javascript's pushState() which will allow you to control the url.
For example, suppose you show a search screen to go to a record. The user enters the search criteria and presses search. You bring in the results via ajax and update a grid. Then the user selects the row and you go to a detail page (typical search functionality here).
At this point, you would want to use pushState() to change the url to something like:
http:/me.com/search/23432
This will allow the user to bookmark the page. Then when the user leaves the detail page, use pushState() and set the url to http:/me.com/search
So you have complete control over the url.
The next thing you need to learn is popState(). This function is called when the url changes. So suppose the user pushes the bookmark to go to "23432". popState() will be called and you'll react accordingly.
That's basically it in a nutshell: pushState() and popState().
Some older browsers do not react to pushState/popState. There are libraries floating around to handle older browsers using url hashing. I am not too familiar with them as I am only supporting html5 browsers.

Usage of Hash(#) in URL

I was wondering is there any use of hash other than as an anchor in URL. I read about it here
getting the full url including query string after hash .
What is state information for the client? Please help.
The hash can be used also for single page applications, so instead of using it to navigate to a point in a page you use the hash as a means for navigated from page to page. The advantage of this is that it does not require a page refresh.
There is also a method called hashbanging which is used for single page applications and is used for helping ajax applications more indexible.
There are a few good articles on the subject
https://github.com/browserstate/history.js/wiki/Intelligent-State-Handling
http://danwebb.net/2011/5/28/it-is-about-the-hashbangs
https://developers.google.com/webmasters/ajax-crawling/
Consider one page website, or website built fully on AJAX, without any page reloads.
#hash helps such applications to push state of the application to the client, this helps the application itself to be aware of the state and the client (and browser) to be aware of the state. This will also help the user to bookmark the application in its' current state and use back and forward buttons (browser history).

Backbone routers and compatibility with web server controller

How do I get compatibility between my web server's controllers and Backbone router?
I have it set it so when a user clicks on a link, a view is rendered, and the URL looks like this: /test/1, which is what I want. The problem comes in when the user tries to access test/1 by entering it into the address bar. My backend has controllers that is in charge of routing URLS.
How would I get it so it uses the Backbone routes rather than the backend routes?
One way that works is when I access the url #test/1. It is bookmarkable and can be entered into the address bar. The problem is that backbone stripes the # on load.
So, I see two solutions to my problem:
Get the backend controllers to interact with Backbone routes
Make it so the #'s aren't removed when they are entered inside of the address bar.
Which of the above solutions is recommended. And, how would I implement them. The second solution seems easier, but how would I make it so backbone doesn't strip the URLs of the hashes?
From what I understand when the user navigates to the root page and then test/1 via a link the logic is handled by backbone and a view is rendered. But when the user navigates directly to test/1 this is not handled correctly.
To handle this you need to setup a route on your server that points any URLs handled by backbone to the root page. The logic for this depends on your server which you have not specified. To do something like this in ASP you might setup a route like this:
RouteTable.Routes.MapWebPageRoute("test/{id}", "~/Default.cshtml", new {}, new { id = "\\d+" });
This would cause a URL such as test/1 to be handled by the default page which be the same handler as if the user navigated to /. Once the page has loaded on the client the Backbone router would fire for the test/1 route.
Well, really, there are many reasons why you want the URL to include the # all the time. It makes lots of things work better. For example, the correct controller is automatically selected by the web server and the correct route is automatically provided to Backbone whether you manually type in the URL, use a bookmark, use a link from another site, or use the back and forward buttons on the web browser. So choice 2 is definitely the one you want. This is also the standard behavior of Backbone.
So my question is "How did you get backbone to stop using the # in the first place?"
Edit: Thanks to Chris Herring for pointing us to a great article explaining why # is bad. With that, I will leave it as an exercise to the reader about which kind of pain they want to endure. I think # is still the way to go so long as all the Backbone route is changing is how the information on the page is displayed and not what information is on the page. If a web crawler that does not support JavaScript can scrape all the same information regardless of what comes after the #, then I still don't see a problem with it.

Using Javascript back function after POST form submit

In my web application, I'm providing a link, clicking on which goes to another page, shows some data,popups a page and goes back using javascript back() function.
It was working fine but it fails in certain cases. For example, this link appears after a form submission also. When the back() function is called here, the Webpage Expired message is shown because it was a POST operation.
How can I go back to the previous page using javascript in this situation?
Please provide your inputs...
You can't do anything about page expiration from JavaScript - that's how the browser opts to protect the user from re-submitting the form accidentally.
You could use the POST/Redirect/GET approach, depending on how your application works, to ensure users always land on a GET after submitting their form, so you always have a valid state to go back to.
To be honest, 99% of the time using the back() functionality in JavaScript is an indication something's wrong in the underlying approach. It's just too fraught with problems to be workable, as you're seeing here. You might get further if you start a new question to describe what you're trying to accomplish. There's a lot of really smart people around here who can all put their heads together and help you come up with something.
Can you explain why you need to navigate to the previous page using client-side script? I'm assuming it's because the user may be arriving at this page from various other pages and you want to direct them back to the page they came from. You could always store the user's previous page URL in a session or cache object and direct them back to that URL in server-side code. Or, if you know where they will always be coming from, direct them back to a static URL. I may be able to offer more assistance if you offer more details.

Categories

Resources