Question
Given navigation from /page.html#A to /page.html#B, is there a way to distinguish between a user:
Clicking the browsers's 'back' button, and
Manually changing the url back to /page.html#A ?
Background / Context
I'm building a web app, where a single page transitions between multiple slides of content, each identified by a specific location hash, eg '#A', '#B'.
For example, when the user is on slide 'A', and selects option 'B', the location changes from /page.html#A to /page.html#B.
After the transition, location.hash==#B, and back() (either via JS or browser button) would return the user to location.hash==#A.
However, there is nothing to prevent a user from manually changing the hash in the URL bar. In this case the browser would consider this a navigation forward, inserting /page.html#B in the back history. That is, navigation history would be #A > #B > #A and clicking back would now take a user to #B.
I need to distinguish between these two cases so that when I know the user has manually updated the url hash, I can trigger go(N) to synchronise the browser back/next state.
Attempts so far
1) HTML5 popstate event:
I had hoped that the html5 popstate event ( https://developer.mozilla.org/en-US/docs/Web/Events/popstate ) would only be fired for case#1, but I can confirm it fires in both cases.
2) Browser .onhashchange event
I can confirm that if present, the event is fired in both cases
3) jQuery mobile hashChange()
I can confirm is fired in both cases
4) Read browser navigation history
My next thought would be to maintain a JS array of hash history, and compare whether the new hash and browser history match the JS array, but JS can't read the browser location history for security reasons.
Thoughts
I know that if I call window.history.forward(), and no page exists, nothing happens. I'm thinking a JS array of hash history, calling forward(), checking the new location.hash (as security now allows it), comparing to JS array, then calling go(N) to synchronise the browser back/next state. But it's a bit messy.
As there is no back button event in javascript, the best I can recommend is creating your own back button on your page
Look at: How to Detect Browser Back Button event - Cross Browser
Yes, you can distinguish between:
Clicking the back()/forward() browser button, and
Manually editing the location.hash in the browser URL bar
It is also possible to use both alongside in-page HTML element navigation.
Issues:
Browser back(), forward() and go() calls do not immediately update location.hash. It is required to wait ~10ms via setTimeout() to let browser finish the navigation and update location.
Solution (pseudo-code):
Maintain an array of backward_history_hashes (note 'backwards' means logically, not temporally)
Maintain a value of current_location.hash
Maintain an array of forward_history_hashes
Maintain a boolean flag for in-page navigation, default to FALSE
Maintain a boolean flag whether to ignore_hash_change
Create a setTimeout() monitor to check for location.hash changes
In each case, the history arrays are simple string arrays of location.hashes
on_in_page_navigation()
set in_page_flag = true
trigger browser navigation via back(), forward() or go(N)
set in_page_flag = false
on_location_hash_change()
set ignore_hash_change = true
if( ! in_page_flag) rewrite_browser_history()
display content corresponding to new location.hash
set ignore_hash_change = false
rewrite_browser_history()
just assume that it was a manual URL edit, and use JS history arrays to trigger back() and forward() calls to generate the desired browser-history
execute go(N) to desired location.hash to synchronize browser-history with JS history arrays
Related
I have a 3 step signup process where each step is shown on the page using javascript without a page refresh. What I am trying to do now is add a back reference to what step the user was on so if they click the browser back button they will not lose all of their progress.
So for example, as the user navigates from Step 2 to Step 3 the URL stays at www.example.com. The user then clicks the browser back button. The URL should now be www.example.com?step-2.
I'm thinking that I will somehow need to use the History API to accomplish this but if I use window.history.pushState(null, null, 'www.example.com?step-2'), the current URL would be changed as well.
How would I accomplish adding to the history without changing the current URL?
If your objective is to not change the URL, but to still allow back and forth history state changes, your best bet would be to utilize the window's hashchange event listener. This would of course utilize hash references within the URL, but the base URL won't change:
function locationHashChanged() {
if (location.hash === '#step-2') {
// Do something here
}
}
window.onhashchange = locationHashChanged;
For further info on this, refer to official documentation:
https://developer.mozilla.org/en-US/docs/Web/API/Window/hashchange_event
On the footer of the website I'm working on, I have links to different pages, and in one case, 2 links to the same page with a different hash in the url ,like this :
<a href="http://example.com/mypage#test>Test</a>
<a href="http://example.com/mypage#test2>Test2</a>
These hashes are not true anchors, they reflect some actions the user takes (namely showing/hiding some content).
If I come from another page, I navigate without any problem. However, if I am already on "mypage", then the hash changes, but nothing happens. The browser detects and anchor change and thus tries to navigate to the anchor.
That's fair enough, but I want my user to be actually redirected to "http://example.com/mypage#test2", as if he copy-pasted it himself in the address bar. How can I achieve that ?
I could use the hashchange event but it would make it complicated to manage the rest of the javascript, so I wonder if there is a simpler way to do it.
You have a very simple solution if you are using jQuery:
$(window).on('hashchange', function() {
//code
});
Check out this: On - window.location.hash - Change?
Also this MDN documentation.
Use the location.hash property of the window object to find out what the anchor is. To detect a change in the hash, with plain javascript, just use the onhashchange event.
window.onhashchange = new function() {
window.location.replace("http://example.com/mypage" + window.location.hash);
});
I have a simple pop-up contact form script written:
$(document).ready(function(){
var popupButton = $("#contact-popup-button");
var popupBox = $("#pop-up-contact");
var popupBg = $("#pop-up-close-background");
popupButton.on("click", function(){
popupBox.addClass("slide-out");
popupBg.fadeIn(200);
});
popupBg.on("click", function(){
popupBox.removeClass("slide-out");
popupBg.fadeOut(100);
});
Basically when a button is clicked, a div appears and the space behind it gets foggy. If you press the space around the appeared div, it will dissapear.
Now for mobile devices, I'd like there also to be an option to make the div dissapear on clicking the back button. Unfortunately, I can not get it to work in practice at all.
I have tried these answers:
handling back button in android from jquery
Take control of hardware back button jquery mobile
But both seem to fail in this task, and the others use plugins, which I'd like to avoid.
Tested on LG G2 Mini and Sony Xperia Z1
One approach would be to use the HTML5 History API.
When opening the popup you can push a state to the history stack before opening the popup:
history.pushState({popupOpen: false}, "My title", "index.html");
This method automatically updates the page title (which is currently ignored in most browser implementations) and the last part of the url, that will be displayed in the browser bar. In most cases, you can enter your filename here. The first argument is an object containing the data you can access later when popping a state.
As soon as you have pushed a state to the history stack, when pressing the back key, the browser does not return to the last page as usual, but pops the last state on the stack. This applies for all browsers though, if you want the functionality for mobile browsers only, you have to do a browser check before calling history.pushState.
To correctly handle the back event, you need to subscribe to the popstate-Event. This can be done with the following code:
window.addEventListener("popstate", function(event) {
var data = event.state;
if(data.popupOpen === false) {
popupBg.trigger('click');
}
});
You register an event listener that fires as soon as the user navigates back. In the event.state variable the data you passed in when pushing the state can be accessed again.
Good luck!
I have an ajax controlled website, where I have two types of pages, displayed to the user. For the simplicity let's call them MAINPAGEs and SUBPAGEs. MAINPAGEs contain information, and SUBPAGEs are all forms, where the user can add or modify existing information of a MAINPAGE for example.
If my site is visited by a user with HTML5 compatible browser, I use HistoryJS to update the url when he/she navigates on my website. Let's pressume the following example:
The user entered my website and navigated to the following pages in the following order, and his history looks something like this:
MAINPAGE(1) --> MAINPAGE(2) --> SUBPAGE(1) --> SUBPAGE(2)
When the user completes the form on SUBPAGE(2), I want to redirect him immediatly to the last MAINPAGE he visited. So for example when the user completes the form, I would like that the users history to be this:
MAINPAGE(1) --> MAINPAGE(2)
Visually, I am able to achieve this, everything works correctly, but afterwards, in a HTML5 browser, if I press the native back key on the browser, the page tries to revert to SUBPAGE(1), the correct back state from the initial history.
Is it achievable, to delete some of the history states, and if yes, how can I do that?
Here's the code I use so far:
ConverserNavigation.prototype.getPreviousMainAction = function() {
// NOTE: the code that deals with non HTML5 compatible browsers,
// was removed because everything works fine there
var startFrom, found=false;
if (this.HistoryJS.status==true) startFrom=History.getState().data.id;
// if browser is HTML5 compatible, get the current state from History object,
// which is a HistoryJS object
while ((!found) && (startFrom>0)) // find the last MAINPAGE visited by user
{
startFrom--;
if (this.historyData[startFrom].pageData.page != 'quickactions') found=true;
}
if (this.HistoryJS.status==true) History.replaceState({id:startFrom}, this.historyData[startFrom].urlData.title, this.historyData[startFrom].urlData.url);
// replace the current history state, with the one to where we want to revert
this.currentNavigationId=startFrom;
this.back(); // render the ui to navigate back to the previous page, works as intended
for (var i=this.currentNavigationId;i<this.historyData.length;i++) delete this.historyData[i]; // delete the unused history data
}
I've managed to solve this issue by modifying my code the following way:
Replaced this line:
if (this.HistoryJS.status==true) History.replaceState({id:startFrom}, this.historyData[startFrom].urlData.title, this.historyData[startFrom].urlData.url);
with this:
if (this.HistoryJS.status==true) {
History.go(goBack); //goBack is the number of states I have to backtrack
this.HistoryJS.manualStateChange=false; // telling the browser to not fire my own UI updating functions
History.back(); // navigating one more state back in the History object
History.pushState({id:startFrom}, this.historyData[startFrom].urlData.title, this.historyData[startFrom].urlData.url); // recreating the original state in the History.
this.HistoryJS.manualStateChange=true; // restarting the UI update functions on pop or push events
}
In facebook, whenever you navigate to a different URL (in some situations), the URL changes but there is no feeling sensed as going to a different page.
For example: when we view pictures in facebook, and when we move to the next image the URL changes in the address bar
FROM >facebook.com/foo?bar=foobar&xxxx=
TO > >>facebook.com/foo?bar=boobar&xxxx=
and this is not hashed change also
like
FROM >facebook.com/xxxxx#xxx=xxxx
TO > >>facebook.com/xxxxx#xxx=yyyy
How is this possible seamlessly. I mean how is that only a container is modified on URL change. URL change is supposed to navigate to a different page which can contain cached information from previous page and THIS navigation by URL change can be seen obviously by browser's screen going blank for a moment.
If using an iFrame, how to implement this ?
I use somehting similar to this
try {
var stateObj = { foo: "bar" };
history.pushState(stateObj, "page", href);
loadPage(href);
}
catch(e) {
window.location.hash = "#!/"+href;
}
If it supports the HTML5 pushState them change URL, but if it doesn't then the fall back is the window hash.
wow. I just asked it few minutes ago ... use search next time ;)
Dynamic favicon when I'm proccessing ajax data
Modify the URL without reloading the page
There's a jQuery plugin called "address" that will watch for changes and call the function you give. I think it's just checking the URL every 100ms or so.
They issue an AJAX request for the data necessary to fulfil the "navigation", then tell the browser to "go to #xxx=yyy". Since such an anchor doesn't exist, the browser doesn't actually scroll down. However, it does record a new history entry, and also updates the URL so that if someone copy-pastes that URL, they will view the same object that the user is seeing, rather than just the original page.