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
Related
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
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
}
I'm trying to achive the following:
On page A we have an access restricted Link to page B. The access restriction is handled on the server side in PHP.
When a user clicks on this link to page B we display a modal dialogue on page A (via javascript) with a form, having the link's href (B) as the action. (To give the user an immediate feedback. The fallback is to redirect him to a login form that redirects him to the site he wants to access.)
This system works quite well.
But now comes my question:
We have access restricted links that should be opened in a new window.
Now if I use target="_blank" on the form the user stays logged out on the page he came from (A), that is still open in the background.
Is there a way to reload the page (A, in the background) right after the form has been submitted to the new window (B)?
My first idea was to use window.location.reload(); in the submit handler on page A.
This didn't work in chrome and from what I understand could create a race condition.
Another idea would be to log the user in via an ajax call and open a new window through javascript. Is there a way to do this without having to deal with pop-up blockers?
I implemented the idea of lostsource (see below) with one slight addition.
As I need to reload only once, the timer of setInterval can be stopped if the cookie changed.
var ri=setInterval(function() {
if(oldCookie != document.cookie) {
// assuming a login happened, reload page
clearInterval(ri);
window.location.reload();
}
},1000); // check every second
I still love the idea. stackoverflow is awsome!
Assuming you're storing PHP session information inside a cookie, you might be able to monitor your document.cookie for changes.
Before submitting the form store the value of the current cookie and monitor it for changes with a timer:
form.onsubmit = function() {
var oldCookie = document.cookie;
var cookiePoll = setInterval(function() {
if(oldCookie != document.cookie) {
// stop polling
clearInterval(cookiePoll);
// assuming a login happened, reload page
window.location.reload();
}
},1000); // check every second
}
On the parent page, do you have any visual/functional changes because of the login? As in any new actions possible?
If not, then you dont have to do anything as you would be checking for login on every action from the parent page, you can check for permissions along with that.
If there are changes or additional functionalities, you can call a javascript function in the parent, say reloadMe, using window.opener.reloadMe()
Why not just a simple setTimeout
setTimeout(function(){ location.reload(); }, 1000);
It is a bit hacky, but seems appropriate for your situation.
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.
Is there a way to make the user's back button on their browser, call a javascript function instead of going back a page?
You can't override the behaviour that if a user follows a link to your page, clicking Back will take them off it again.
But you can make JavaScript actions on your page add entries into the history as though they were clicks to new pages, and control what happens with Back and Forward in the context of those clicks.
There are JavaScript libraries to help with this, with Really Simple History being a popular example.
yes, you can. Use this js:
(function(window, location) {
history.replaceState(null, document.title, location.pathname+"#!/stealingyourhistory");
history.pushState(null, document.title, location.pathname);
window.addEventListener("popstate", function() {
if(location.hash === "#!/stealingyourhistory") {
history.replaceState(null, document.title, location.pathname);
setTimeout(function(){
location.replace("http://www.programadoresweb.net/");
},0);
}
}, false);
}(window, location));
That will redirect your back button to the location.replace you specify
I think this will do the trick.
you can write your custom code to execute on browser back button click inside onpopstate function.
This works in HTML5.
window.onpopstate = function() {
alert("clicked back button");
}; history.pushState({}, '');
I assume you wish to create a one-page application that doesn't reload the website as the user navigates, and hence you want to negate the back button's native functionality and replace it with your own. This can also be useful in mobile web-apps where using the back button inside apps is common to close an in-app window for example. To achieve this without a library, you need to:
1st. Throughout your application modify the window's location.hash instead of the location.href (which is what tags will do by default). For example, your buttons could fire on click events that modify the location.hash like this:
button.addEventListener('click', function (event) {
// Prevent default behavior on <a> tags
event.preventDefault()
// Update how the application looks like
someFunction()
// Update the page's address without causing a reload
window.location.hash = '#page2'
})
Do this with every button or tag you have that would otherwise redirect to a different page and cause a reload.
2nd. Load this code so that you can run a function every time the page history changes (both back and forward). Instead of the switch that I used in this example, you can use an if and check for other states, even states and variables not related to location.hash. You can also replace any conditional altogether and just run a function every time the history changes.
window.onpopstate = function() {
switch(location.hash) {
case '#home':
backFromHome()
break
case '#login':
backFromLogin()
break
default:
defaultBackAnimation()
}
}
This will work until the user reaches the first page they opened from your website, then it will go back to new tab, or whatever website they were in before. This can't be prevented and the teams that develop browsers are patching hacks that allow this, if a user wants to exit your website by going back, they expect the browser to do that.
If you are creating a one-page web application, where your html body has different sections and you want to nevigate through back button to the previous section you were. This answer will help you.
Where your website sections are differentiated by #. Such as:
your-web-address.com/#section-name
Just follow a few steps:
Add a class and a id in every section in you html body. Here it is ".section"
<section class="section" id="section-name">...</section>
Add two CSS class in your linked css (e.g., style.css) file to your html (e.g., index.html) file such:
.section .hide {
display: none;
}
.section .active{
dislplay: block;
}
Add this JavaScript function in you linked .js (e.g., main.js) file to your html file.
window.onpopstate = function () {
if (location.hash !== "") {
const hash = location.hash;
// Deactivating existing active 'section'
document.querySelector(".section.active").classList.add("hide");
document.querySelector(".section.active").classList.remove("active");
// Activating new 'section'
document.querySelector(hash).classList.add("active");
document.querySelector(hash).classList.remove("hide");
}
}