So currently in my windows 8 javascript app (based on the navigation template), upon navigation to the home page on app startup (home.html) from the default.js initialization point, in home.html's corresponding home.js file, I'm creating this gesture system to handle pinch to zoom and swiping back and forth (which works correctly):
(function () {
"use strict";
WinJS.UI.Pages.define("/pages/home/home.html", {
// This function is called whenever a user navigates to this page. It
// populates the page elements with the app's data.
processed: function (element, options) {
MyGlobals.loadBooks();
if (!MyGlobals.localSettings.values['firstRunCompleted']) {
MyGlobals.localSettings.values['firstRunCompleted'] = true;
MyGlobals.loadXmlBooksIntoDatabase();
//MyGlobals.integrityCheck();
};
WinJS.Resources.processAll();
},
ready: function (element, options) {
var myGesture = new MSGesture();
var chapterContainer = document.getElementById("main-content-area");
myGesture.target = chapterContainer;
WinJS.Namespace.define("MyGlobals", {
myGesture: myGesture,
});
chapterContainer.addEventListener("MSGestureStart", this.gestureListener, false);
chapterContainer.addEventListener("MSGestureEnd", this.gestureListener, false);
chapterContainer.addEventListener("MSGestureChange", this.gestureListener, false);
chapterContainer.addEventListener("MSInertiaStart", this.gestureListener, false);
chapterContainer.addEventListener("MSGestureTap", this.gestureListener, false);
chapterContainer.addEventListener("MSGestureHold", this.gestureListener, false);
chapterContainer.addEventListener("pointerdown", this.gestureListener, false);
},
gestureListener: function (evt) {
console.log("in gesturelistener now");
if (evt.type == "pointerdown") {
MyGlobals.myGesture.addPointer(evt.pointerId);
return;
}
if (evt.type == "MSGestureStart") {
if (evt.translationX < -3.5) {
MyGlobals.nextChapterHandler();
};
if (evt.translationX > 3.5) {
MyGlobals.previousChapterHandler();
}
};
if (evt.type == "MSGestureChange") {
if (evt.scale < 1) {
MyGlobals.fontSizeModify("decrease");
};
if (evt.scale > 1) {
MyGlobals.fontSizeModify("increase");
}
};
},
The problem though is that when I navigate to another page using WinJS.Navigation.navigate(), when I then navigate back to home.html using WinJS.Navigation.navigate(), all of the above event listeners don't work, and seem to not be added to the chapterContainer (from inspecting the live DOM). However, all other event listeners that are in the ready: function, do work perfectly (other listeners omitted from above code). Is there something wrong that I'm doing here?
Thanks a lot!
The page being navigated to also had a div with id "main-content-area", and the event listeners were going onto the old page instead of the page being navigated back to.
Hi I'm trying to use modernizer load (yepnope.js) to conditionally load history.js (via AJAX) only when the browser does not natively support the HTML5 history API....
However in my tests on IE9/IE8 modernizer appears to load the history.js file successfully (at least I can see the HTTP request in the IE9 developer tools) However i still get an error (unrecognised method) when I try to use history.pushState or History.pushState.... can anyone suggest why this might be?
Modernizr.load([{
//test
test : Modernizr.history,
//if yes then do nothing as nothing extra needs loading....
//if no then we need to load the history API via AJAX
nope : ['/js/asm/vendor/history.js'],
complete : function() {
Tabs.init();
}
}])
var Tabs = {
init: function() {
this.bindUIfunctions();
this.pageLoadCorrectTab();
},
bindUIfunctions: function() {
.......
},
changeTab: function(hash) {
var anchor = $("[href='" + hash + "']");
var div = $(hash);
function displayTab(anchortab) {
// activate correct anchor (visually)
........
}
displayTab(anchor);
// update history stack adding additional history entries.
if (typeof history.pushState !== "undefined") {
// pushState is supported!
window.history.pushState(null, null, hash);
} else {
//use history API instead
History.pushState(null, null, hash);
}
//We also need to handle the backstate by telling the brower to trigger the tab behaviour!
window.addEventListener("popstate", function(e) {
anchor = $('[href="' + document.location.hash + '"]');
if (anchor.length) {
displayTab(anchor);
} else {
defaultAnchor = $('.transformer-tabs li.active a');
displayTab(defaultAnchor);
}
});
// Close menu, in case mobile
},
// If the page has a hash on load, go to that tab
pageLoadCorrectTab: function() {
......
},
toggleMobileMenu: function(event, el) {
......
}
}
I found I got on much better with the following lib (although IE8 still does not allow me to use the back and forward browser button to go between tabs).... at least there are no JS errors and it works for me in IE9 https://github.com/devote/HTML5-History-API
Modernizr.load([{
//test
test : Modernizr.history,
//if yes then do nothing as nothing extra needs loading....
//if no then we need to load the history API via AJAX
nope : ['/js/asm/vendor/history.min.js'],
complete : function() {
var location = window.history.location || window.location;
Tabs.init();
}
}])
//responsive tabs API code.
var Tabs = {
init: function() {
this.bindUIfunctions();
this.pageLoadCorrectTab();
},
bindUIfunctions: function() {
// Delegation
$(document)
.on("click", ".transformer-tabs a[href^='#']:not('.active')", function(event) {
Tabs.changeTab(this.hash);
event.preventDefault();
})
.on("click", ".transformer-tabs a.active", function(event) {
Tabs.toggleMobileMenu(event, this);
event.preventDefault();
});
},
changeTab: function(hash) {
var anchor = $("[href='" + hash + "']");
function displayTab(anchortab) {
var url = anchortab.attr("href");
console.log("url" + url);
var div = $(url);
// activate correct anchor (visually)
anchortab.addClass("active").parent().siblings().find("a").removeClass("active");
// activate correct div (visually)
div.addClass("active").siblings().removeClass("active");
anchortab.closest("ul").removeClass("open");
}
displayTab(anchor);
// update history stack adding additional history entries.
// pushState is supported!
history.pushState(null, null, hash);
//We also need to handle the backstate by telling the brower to trigger the tab behaviour!
$(window).on('popstate', function(e) {
anchor = $('[href="' + document.location.hash + '"]');
if (anchor.length) {
displayTab(anchor);
} else {
defaultAnchor = $('.transformer-tabs li.active a');
displayTab(defaultAnchor);
}
});
// Close menu, in case mobile
},
// If the page has a hash on load, go to that tab
pageLoadCorrectTab: function() {
this.changeTab(document.location.hash);
},
toggleMobileMenu: function(event, el) {
$(el).closest("ul").toggleClass("open");
}
}
How do you definitively detect whether or not the user has pressed the back button in the browser?
How do you enforce the use of an in-page back button inside a single page web application using a #URL system?
Why on earth don't browser back buttons fire their own events!?
(Note: As per Sharky's feedback, I've included code to detect backspaces)
So, I've seen these questions frequently on SO, and have recently run into the issue of controlling back button functionality myself. After a few days of searching for the best solution for my application (Single-Page with Hash Navigation), I've come up with a simple, cross-browser, library-less system for detecting the back button.
Most people recommend using:
window.onhashchange = function() {
//blah blah blah
}
However, this function will also be called when a user uses on in-page element that changes the location hash. Not the best user experience when your user clicks and the page goes backwards or forwards.
To give you a general outline of my system, I'm filling up an array with previous hashes as my user moves through the interface. It looks something like this:
function updateHistory(curr) {
window.location.lasthash.push(window.location.hash);
window.location.hash = curr;
}
Pretty straight forward. I do this to ensure cross-browser support, as well as support for older browsers. Simply pass the new hash to the function, and it'll store it for you and then change the hash (which is then put into the browser's history).
I also utilise an in-page back button that moves the user between pages using the lasthash array. It looks like this:
function goBack() {
window.location.hash = window.location.lasthash[window.location.lasthash.length-1];
//blah blah blah
window.location.lasthash.pop();
}
So this will move the user back to the last hash, and remove that last hash from the array (I have no forward button right now).
So. How do I detect whether or not a user has used my in-page back button, or the browser button?
At first I looked at window.onbeforeunload, but to no avail - that is only called if the user is going to change pages. This does not happen in a single-page-application using hash navigation.
So, after some more digging, I saw recommendations for trying to set a flag variable. The issue with this in my case, is that I would try to set it, but as everything is asynchronous, it wouldn't always be set in time for the if statement in the hash change. .onMouseDown wasn't always called in click, and adding it to an onclick wouldn't ever trigger it fast enough.
This is when I started to look at the difference between document, and window. My final solution was to set the flag using document.onmouseover, and disable it using document.onmouseleave.
What happens is that while the user's mouse is inside the document area (read: the rendered page, but excluding the browser frame), my boolean is set to true. As soon as the mouse leaves the document area, the boolean flips to false.
This way, I can change my window.onhashchange to:
window.onhashchange = function() {
if (window.innerDocClick) {
window.innerDocClick = false;
} else {
if (window.location.hash != '#undefined') {
goBack();
} else {
history.pushState("", document.title, window.location.pathname);
location.reload();
}
}
}
You'll note the check for #undefined. This is because if there is no history available in my array, it returns undefined. I use this to ask the user if they want to leave using a window.onbeforeunload event.
So, in short, and for people that aren't necessarily using an in-page back button or an array to store the history:
document.onmouseover = function() {
//User's mouse is inside the page.
window.innerDocClick = true;
}
document.onmouseleave = function() {
//User's mouse has left the page.
window.innerDocClick = false;
}
window.onhashchange = function() {
if (window.innerDocClick) {
//Your own in-page mechanism triggered the hash change
} else {
//Browser back button was clicked
}
}
And there you have it. a simple, three-part way to detect back button usage vs in-page elements with regards to hash navigation.
EDIT:
To ensure that the user doesn't use backspace to trigger the back event, you can also include the following (Thanks to #thetoolman on this Question):
$(function(){
/*
* this swallows backspace keys on any non-input element.
* stops backspace -> back
*/
var rx = /INPUT|SELECT|TEXTAREA/i;
$(document).bind("keydown keypress", function(e){
if( e.which == 8 ){ // 8 == backspace
if(!rx.test(e.target.tagName) || e.target.disabled || e.target.readOnly ){
e.preventDefault();
}
}
});
});
You can try popstate event handler, e.g:
window.addEventListener('popstate', function(event) {
// The popstate event is fired each time when the current history entry changes.
var r = confirm("You pressed a Back button! Are you sure?!");
if (r == true) {
// Call Back button programmatically as per user confirmation.
history.back();
// Uncomment below line to redirect to the previous page instead.
// window.location = document.referrer // Note: IE11 is not supporting this.
} else {
// Stay on the current page.
history.pushState(null, null, window.location.pathname);
}
history.pushState(null, null, window.location.pathname);
}, false);
Note: For the best results, you should load this code only on specific pages where you want to implement the logic to avoid any other unexpected issues.
The popstate event is fired each time when the current history entry changes (user navigates to a new state). That happens when user clicks on browser's Back/Forward buttons or when history.back(), history.forward(), history.go() methods are programatically called.
The event.state is property of the event is equal to the history state object.
For jQuery syntax, wrap it around (to add even listener after document is ready):
(function($) {
// Above code here.
})(jQuery);
See also: window.onpopstate on page load
See also the examples on Single-Page Apps and HTML5 pushState page:
<script>
// jQuery
$(window).on('popstate', function (e) {
var state = e.originalEvent.state;
if (state !== null) {
//load content with ajax
}
});
// Vanilla javascript
window.addEventListener('popstate', function (e) {
var state = e.state;
if (state !== null) {
//load content with ajax
}
});
</script>
This should be compatible with Chrome 5+, Firefox 4+, IE 10+, Safari 6+, Opera 11.5+ and similar.
if (window.performance && window.performance.navigation.type == window.performance.navigation.TYPE_BACK_FORWARD) {
alert('hello world');
}
This is the only one solution that worked for me (it's not a onepage website).
It's working with Chrome, Firefox and Safari.
I had been struggling with this requirement for quite a while and took some of the solutions above to implement it. However, I stumbled upon an observation and it seems to work across Chrome, Firefox and Safari browsers + Android and iPhone
On page load:
window.history.pushState({page: 1}, "", "");
window.onpopstate = function(event) {
// "event" object seems to contain value only when the back button is clicked
// and if the pop state event fires due to clicks on a button
// or a link it comes up as "undefined"
if(event){
// Code to handle back button or prevent from navigation
}
else{
// Continue user action through link or button
}
}
Let me know if this helps. If am missing something, I will be happy to understand.
In javascript, navigation type 2 means browser's back or forward button clicked and the browser is actually taking content from cache.
if(performance.navigation.type == 2)
{
//Do your code here
}
Correct answer is already there to answer the question. I want to mention new JavaScript API PerformanceNavigationTiming, it's replacing deprecated performance.navigation.
Following code will log in console "back_forward" if user landed on your page using back or forward button. Take a look at compatibility table before using it in your project.
var perfEntries = performance.getEntriesByType("navigation");
for (var i = 0; i < perfEntries.length; i++) {
console.log(perfEntries[i].type);
}
This will definitely work (For detecting back button click)
$(window).on('popstate', function(event) {
alert("pop");
});
My variant:
const inFromBack = performance && performance.getEntriesByType( 'navigation' ).map( nav => nav.type ).includes( 'back_forward' )
Browser: https://jsfiddle.net/Limitlessisa/axt1Lqoz/
For mobile control: https://jsfiddle.net/Limitlessisa/axt1Lqoz/show/
$(document).ready(function() {
$('body').on('click touch', '#share', function(e) {
$('.share').fadeIn();
});
});
// geri butonunu yakalama
window.onhashchange = function(e) {
var oldURL = e.oldURL.split('#')[1];
var newURL = e.newURL.split('#')[1];
if (oldURL == 'share') {
$('.share').fadeOut();
e.preventDefault();
return false;
}
//console.log('old:'+oldURL+' new:'+newURL);
}
.share{position:fixed; display:none; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,.8); color:white; padding:20px;
<!DOCTYPE html>
<html>
<head>
<title>Back Button Example</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</head>
<body style="text-align:center; padding:0;">
Share
<div class="share" style="">
<h1>Test Page</h1>
<p> Back button press please for control.</p>
</div>
</body>
</html>
See this:
history.pushState(null, null, location.href);
window.onpopstate = function () {
history.go(1);
};
it works fine...
I was able to use some of the answers in this thread and others to get it working in IE and Chrome/Edge. history.pushState for me wasn't supported in IE11.
if (history.pushState) {
//Chrome and modern browsers
history.pushState(null, document.title, location.href);
window.addEventListener('popstate', function (event) {
history.pushState(null, document.title, location.href);
});
}
else {
//IE
history.forward();
}
A full-fledged component can be implemented only if you redefine the API (change the methods of object ' history ')
I will share the class just written.
Tested on Chrome and Mozilla
Support only HTML5 and ECMAScript5-6
class HistoryNavigation {
static init()
{
if(HistoryNavigation.is_init===true){
return;
}
HistoryNavigation.is_init=true;
let history_stack=[];
let n=0;
let current_state={timestamp:Date.now()+n};
n++;
let init_HNState;
if(history.state!==null){
current_state=history.state.HNState;
history_stack=history.state.HNState.history_stack;
init_HNState=history.state.HNState;
} else {
init_HNState={timestamp:current_state.timestamp,history_stack};
}
let listenerPushState=function(params){
params=Object.assign({state:null},params);
params.state=params.state!==null?Object.assign({},params.state):{};
let h_state={ timestamp:Date.now()+n};
n++;
let key = history_stack.indexOf(current_state.timestamp);
key=key+1;
history_stack.splice(key);
history_stack.push(h_state.timestamp);
h_state.history_stack=history_stack;
params.state.HNState=h_state;
current_state=h_state;
return params;
};
let listenerReplaceState=function(params){
params=Object.assign({state:null},params);
params.state=params.state!==null?Object.assign({},params.state):null;
let h_state=Object.assign({},current_state);
h_state.history_stack=history_stack;
params.state.HNState=h_state;
return params;
};
let desc=Object.getOwnPropertyDescriptors(History.prototype);
delete desc.constructor;
Object.defineProperties(History.prototype,{
replaceState:Object.assign({},desc.replaceState,{
value:function(state,title,url){
let params={state,title,url};
HistoryNavigation.dispatchEvent('history.state.replace',params);
params=Object.assign({state,title,url},params);
params=listenerReplaceState(params);
desc.replaceState.value.call(this,params.state,params.title,params.url);
}
}),
pushState:Object.assign({},desc.pushState,{
value:function(state,title,url){
let params={state,title,url};
HistoryNavigation.dispatchEvent('history.state.push',params);
params=Object.assign({state,title,url},params);
params=listenerPushState(params);
return desc.pushState.value.call(this, params.state, params.title, params.url);
}
})
});
HistoryNavigation.addEventListener('popstate',function(event){
let HNState;
if(event.state==null){
HNState=init_HNState;
} else {
HNState=event.state.HNState;
}
let key_prev=history_stack.indexOf(current_state.timestamp);
let key_state=history_stack.indexOf(HNState.timestamp);
let delta=key_state-key_prev;
let params={delta,event,state:Object.assign({},event.state)};
delete params.state.HNState;
HNState.history_stack=history_stack;
if(event.state!==null){
event.state.HNState=HNState;
}
current_state=HNState;
HistoryNavigation.dispatchEvent('history.go',params);
});
}
static addEventListener(...arg)
{
window.addEventListener(...arg);
}
static removeEventListener(...arg)
{
window.removeEventListener(...arg);
}
static dispatchEvent(event,params)
{
if(!(event instanceof Event)){
event=new Event(event,{cancelable:true});
}
event.params=params;
window.dispatchEvent(event);
};
}
HistoryNavigation.init();
// exemple
HistoryNavigation.addEventListener('popstate',function(event){
console.log('Will not start because they blocked the work');
});
HistoryNavigation.addEventListener('history.go',function(event){
event.params.event.stopImmediatePropagation();// blocked popstate listeners
console.log(event.params);
// back or forward - see event.params.delta
});
HistoryNavigation.addEventListener('history.state.push',function(event){
console.log(event);
});
HistoryNavigation.addEventListener('history.state.replace',function(event){
console.log(event);
});
history.pushState({h:'hello'},'','');
history.pushState({h:'hello2'},'','');
history.pushState({h:'hello3'},'','');
history.back();
```
Here's my take at it. The assumption is, when the URL changes but there has no click within the document detected, it's a browser back (yes, or forward). A users click is reset after 2 seconds to make this work on pages that load content via Ajax:
(function(window, $) {
var anyClick, consoleLog, debug, delay;
delay = function(sec, func) {
return setTimeout(func, sec * 1000);
};
debug = true;
anyClick = false;
consoleLog = function(type, message) {
if (debug) {
return console[type](message);
}
};
$(window.document).click(function() {
anyClick = true;
consoleLog("info", "clicked");
return delay(2, function() {
consoleLog("info", "reset click state");
return anyClick = false;
});
});
return window.addEventListener("popstate", function(e) {
if (anyClick !== true) {
consoleLog("info", "Back clicked");
return window.dataLayer.push({
event: 'analyticsEvent',
eventCategory: 'test',
eventAction: 'test'
});
}
});
})(window, jQuery);
The document.mouseover does not work for IE and FireFox.
However I have tried this :
$(document).ready(function () {
setInterval(function () {
var $sample = $("body");
if ($sample.is(":hover")) {
window.innerDocClick = true;
} else {
window.innerDocClick = false;
}
});
});
window.onhashchange = function () {
if (window.innerDocClick) {
//Your own in-page mechanism triggered the hash change
} else {
//Browser back or forward button was pressed
}
};
This works for Chrome and IE and not FireFox. Still working to get FireFox right. Any easy way on detecting Browser back/forward button click are welcome, not particularly in JQuery but also AngularJS or plain Javascript.
I solved it by keeping track of the original event that triggered the hashchange (be it a swipe, a click or a wheel), so that the event wouldn't be mistaken for a simple landing-on-page, and using an additional flag in each of my event bindings. The browser won't set the flag again to false when hitting the back button:
var evt = null,
canGoBackToThePast = true;
$('#next-slide').on('click touch', function(e) {
evt = e;
canGobackToThePast = false;
// your logic (remember to set the 'canGoBackToThePast' flag back to 'true' at the end of it)
}
<input style="display:none" id="__pageLoaded" value=""/>
$(document).ready(function () {
if ($("#__pageLoaded").val() != 1) {
$("#__pageLoaded").val(1);
} else {
shared.isBackLoad = true;
$("#__pageLoaded").val(1);
// Call any function that handles your back event
}
});
The above code worked for me. On mobile browsers, when the user clicked on the back button, we wanted to restore the page state as per his previous visit.
Solution for Kotlin/JS (React):
import org.w3c.dom.events.Event
import kotlin.browser.document
import kotlin.browser.window
...
override fun componentDidMount() {
window.history.pushState(null, document.title, window.location.href)
window.addEventListener("popstate", actionHandler)
}
...
val actionHandler: (Event?) -> Unit = {
window.history.pushState(
null,
document.title,
window.location.href
)
// add your actions here
}
Was looking for a solution for this issue and put together a simple skeleton test html based on a few answers here and the MDN Web Doc pages for History.pushState() and WindowEventHandlers.onpopstate.
The following HTML and JavaScript is easy enough to copy and paste and test.
Works with back and forward browser buttons, shortcut keys, adds a change to the URL (which is important in some cases).
Simply enough to add to existing code key points and should be expandable too.
<html>
<body>
<div id="p1">Option 1</div>
<div id="p2">Option 2</div>
<div id="p3">Option 3</div>
<div id="p4">Option 4</div>
<div id="c"></div>
<script>
var chg={
set:function(str){
var d=document.getElementById("c");
d.textContent=str;
},
go:function(e){
var s={"p":this.id};
chg.set(s.p);
hstry.add(s);
}
};
var hstry={
add:function(s){
var u=new URL(window.location);
u.searchParams.set("x",s.p);
window.history.pushState(s,"",u);
},
adjust:function(state){
if(state.p){
chg.set(state.p);
}
}
};
window.onpopstate=function(e){
console.log("popstate, e.state:["+ JSON.stringify(e.state) +"]");
hstry.adjust(e.state);
}
window.onload=function(){
var i,d,a=["p1","p2","p3","p4"];
for(i=0;i<a.length;i++){
d=document.getElementById(a[i]);
d.addEventListener("click",chg.go,false);
}
}
</script>
</body>
</html>
browser will emit popstate event if you navigate through your app with calling
window.history.pushState({},'','/to')
If you manually enter the addresses into the address bar and click on the back button, popstate event will NOT be fired.
If you navigate in your app with this simplified function
const navigate = (to) => {
window.history.pushState({}, ",", to);
};
then this will work
const handlePopstate = () => {
console.log("popped");
};
window.addEventListener("popstate", handlePopstate);
I tried the above options but none of them is working for me. Here is the solution
if(window.event)
{
if(window.event.clientX < 40 && window.event.clientY < 0)
{
alert("Browser back button is clicked...");
}
else
{
alert("Browser refresh button is clicked...");
}
}
Refer this link http://www.codeproject.com/Articles/696526/Solution-to-Browser-Back-Button-Click-Event-Handli for more details