Using History.js back button doesn't work? - javascript

I'm using History.js from here https://github.com/browserstate/history.js/
This is what I currently have:
var History = window.History;
if (History.enabled) {
// set initial state for first page that is loaded
var State = History.getState();
History.pushState({urlPath: window.location.pathname}, $("title").text(), State.urlPath);
}
else {
return false;
}
// content update and back/forward button handler
History.Adapter.bind(window, 'statechange', function(){
var s = History.getState();
History.log(s.data, s.title, s.url);
// update view based on load type
OnLoadPageByURL(s.data.urlPath);
});
// navigation link handler(s)
$('body').on('click', 'a', function(e){
e.preventDefault();
var load = $(e.currentTarget).data('load');
var urlPath = $(this).attr('href');
var title = $(this).text();
History.pushState({ load: load, urlPath: urlPath }, title, urlPath);
});
Everything seems to work as it should however the back button doesn't seem to work correctly? What am I doing wrong?

Related

JS: Erroneous popstate event when trying to navigate back past the initial page load

I'm trying to implement JS history using pushState/popState. Navigating back and forward works just fine, but I have trouble navigating before the initial page load using the browser's back button. It needs 1 extra hit on the browser's back button to leave the page. Why is that?
function action(text) {
history.pushState({"text":text}, text);
doAction(text);
}
function doAction(text) {
$('span').text(text);
}
var $button = $('button');
var $p = $('p');
$p.hide();
action("foo");
$button.on('click', function(){
action("bar");
$button.hide();
$p.show();
})
window.addEventListener("popstate", function(e) {
if (e.state !== null) {
$button.show();
$p.text("Next back should navigate away from this page");
} else {
$p.text("Still here? Why is that? Next back will really navigate away");
}
});
https://jsfiddle.net/lilalinux/p8ewyjr9/20/
Edit: Tested with Chrome OS/X
The initial page load shouldn't use history.pushState because it would add another history entry. There is alredy an implicit first history item with state null.
Using history.replaceState for the initial page load, sets a state for that item but doesn't add another one.
var initialPageLoad = true;
function action(text) {
if (initialPageLoad) {
// replace the state of the first (implicit) item
history.replaceState({"text":text}, text);
} else {
// add new history item
history.pushState({"text":text}, text);
}
initialPageLoad = false;
doAction(text);
}
function doAction(text) {
$('span').text(text);
}
var $button = $('button');
var $p = $('p');
$p.hide();
action("foo");
$button.on('click', function(){
action("bar");
$button.hide();
$p.show();
})
window.addEventListener("popstate", function(e) {
if (e.state !== null) {
$button.show();
$p.text("Next back should navigate away from this page");
// } else {
// won't happen anymore, as the first item has state now
// $p.text("Still here? Why is that? Next back will really navigate away");
}
});

History.pushState creates large amout of entries

I'm trying to create something like a dynamic page, but I have this problem. When I fire history.pushState it creates large amount of history entries, even though the action that fires it is run only once. My code is as follows:
var url = 'http://localhost:8888/depeche-mode/violator'; // example url
var plainUrl = url + '/?plain',
startUrl = 'http://localhost:8888/',
newUrl = url.replace(startUrl, '#/');
$('#content').animate({
opacity: 0
}, 250, function() {
$('#content').load(plainUrl +' #content > *', function(response) {
$('#content').animate({opacity:1}, 250, function() {
document.title = pageTitle;
Posts.historyHash(newUrl);
});
});
});
edit:
var Posts = {
historyHash: function(newUrl) {
window.location.hash = newUrl;
$(window).bind('hashchange', function() {
var url = window.location.hash,
nohash = url.replace('#',''),
properUrl = 'http://localhost:8888/'+nohash;
history.pushState('','',newUrl);
});
}
}
The problem is very serious when I want to use Back button in my browser - I need to click it couple of times before I actually get to change the url. What can I do?
You're calling historyHash when you load content, and historyHash registers an event handler on window — every time you call it. So you end up with a bunch of event handlers for the hashchange event.
You presumably only want one. Either just register one, or unregister the previous ones when registering a new one.
I can't quite tell which of those you want, but as the handler uses the newUrl, it may well be that you want to unregister previous handlers. If so, probably best to use an event namespace so you only unregister your own handlers:
var Posts = {
historyHash: function(newUrl) {
window.location.hash = newUrl;
$(window)
.unbind('hashchange.historyhash') // Out with the old
.bind('hashchange.historyhash', function() { // In with the new
var url = window.location.hash,
nohash = url.replace('#',''),
properUrl = 'http://localhost:8888/'+nohash;
history.pushState('','',newUrl);
});
}
}
Although looking at it, you could just use a single handler and remember newUrl on Posts:
var Posts = {
historyHash: function(newUrl) {
window.location.hash = newUrl;
this.newUrl = newUrl;
}
};
$(window).bind('hashchange', function() {
var url, nohash, properUrl;
if (Posts.newUrl) {
url = window.location.hash,
nohash = url.replace('#',''),
properUrl = 'http://localhost:8888/'+nohash;
history.pushState('','',Posts.newUrl);
}
});

AJAX and setInterval for window.location.hash

//Gather AJAX links
var ajaxLink = $("#logo, .navLink, .tableLink, .footerLink");
//Mark the recent state as null (because there is none yet)
var recentState = null;
//Initialize the page state based on the URL (bookmarking compatibility)
window.onload = function() {
//If no page state exists, assume the user is at index.html
if (window.location.hash == "") {
window.location.hash = "page=index";
}
//Load the page state based on the URL
loadStateFromURL();
//Keep the page state synchronized (back/forward button compatibility)
setInterval(loadStateFromURL, 500);
//Exit
return;
}
//Use AJAX for certain links
ajaxLink.click(function() {
//Update the URL
window.location.hash = "page=" + $(this).attr("id");
//Load the page state based on the URL
loadStateFromURL();
//Return false or else page will refresh
return false;
});
//Load the page state based on the URL
function loadStateFromURL() {
//If nothing has changed, exit
if (window.location.hash == recentState) {
return;
}
//Mark the recent state
recentState = window.location.hash;
//Go through an array of all AJAX links and check their IDs
for (var i = 0; i < ajaxLink.length; i++) {
//If we find a link's ID that matches the current state, load the relevant content
if ("#page=" + ajaxLink[i].id == window.location.hash) {
//Load contents into article.main
$("article.main").fadeOut(0).load(ajaxLink[i].href, function(response, status, xhr) {
//Show an error if the request fails
if (status == "error") {
$("article.main").load("./404.html");
window.location.hash = "page=404";
}
}).fadeIn(500);
//Update the page title
document.title = "\u2622 My Website Name \u2622 " + ajaxLink[i].text;
document.getElementById("headH2").textContent = ajaxLink[i].text;
//State has been fixed, exit
return;
}
}
}
This code works flawlessly when I run it locally!!!
But when I throw it on the web server my AJAX'd links will refresh the page when I first visit. However, if I use the back button then try the link again (or I'm assuming if the page is already in the browser cache), it will work properly.
I cannot allow this, because when people first visit my page the first link they click on will not operate as intended.
One of things I've also been testing is I'll bookmark my own site with a breadcrumb bookmark (example.com/#page=14) and see if it updates without my page already being in the browser cache. Again, it works on my local machine but not on my web server.
use event.preventDefault()
ajaxLink.click(function(e) {
e.preventDefault();
//Update the URL
window.location.hash = "page=" + $(this).attr("id");
//Load the page state based on the URL
loadStateFromURL();
//Return false or else page will refresh
return false;
});
The issue maybe is that when you are applying your click event to these links, they may not be loaded to the DOM. So the possible solution is to put ajaxLink.click(function() { ... }); part inside window.load event or document.ready event. Since you have used window.load event, you can do something like this.
//Initialize the page state based on the URL (bookmarking compatibility)
window.onload = function() {
//If no page state exists, assume the user is at index.html
if (window.location.hash == "") {
window.location.hash = "page=index";
}
//Load the page state based on the URL
loadStateFromURL();
//Keep the page state synchronized (back/forward button compatibility)
setInterval(loadStateFromURL, 500);
//Use AJAX for certain links
ajaxLink.click(function() {
//Update the URL
window.location.hash = "page=" + $(this).attr("id");
//Load the page state based on the URL
loadStateFromURL();
//Return false or else page will refresh
return false;
});
//Exit
return;
}
Solved my own question, had to continuously parse the AJAX links to stay updated with the DOM as it changes.
First I put the ajaxLink declaration into a function:
//Gather AJAX links
function parseAjaxLinks() {
var ajaxLink = $("#logo, .navLink, .tableLink, .footerLink");
return ajaxLink;
}
Then I had to put the ajaxLink click events into a function:
//Load the page state from an AJAX link click event
function loadStateFromClick() {
//Update the AJAX links
var ajaxLink = parseAjaxLinks();
ajaxLink.click(function() {
//Update the URL
window.location.hash = "page=" + $(this).attr("id");
//Load the page state based on the URL
loadStateFromURL();
//Return false or else page will refresh
return false;
});
}
Then I added a line in my window.onload event to keep my AJAX click events synchronized with the DOM (this adds overhead, but oh well):
//Initialize the page state based on the URL (bookmarking compatibility)
window.onload = function() {
//If no page state exists, assume the user is at index.html
if (window.location.hash == "") {
window.location.hash = "page=index";
recentState = window.location.hash;
}
//Load the page state based on the URL
loadStateFromURL();
//Keep the page state synchronized (back/forward button compatibility)
setInterval(loadStateFromURL, 250);
//Keep AJAX links synchronized (with DOM)
setInterval(loadStateFromClick, 250);
//Exit
return;
}
If you have a keen eye, you saw I had called the new parseAjaxLinks in my new loadStateFromClick function, so I added a line to the top of my loadStateFromURL function to keep the links updated in there as well:
//Load the page state based on the URL
function loadStateFromURL() {
//Update the AJAX links
var ajaxLink = parseAjaxLinks();
...
What I learned from this is the variables which are dependent on the DOM need to be continuously updated. While the DOM is loading, things are unpredictable and kind of sucks. **Drinks beer**

load history.js via ajax conditionally when browser does not support the HTML5 history API

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");
}
}

pushState and back button works but content doesn't change

It does load a new page and the url does update, but when I press the back button, only the url has changed without refreshing but the content doesn't.
$('button').click(function(){
window.history.pushState({}, '', 'page2.php');
$('body').html('<div class="loading.gif"></div>');
//Load Page
$.get('page2.php', function(data){
$('body').html(data);
};
//Edited
$(window).bind('popstate', function(){
//What should I code here??
});
});
I did something like that :
$(window).bind('popstate', function(){
window.location.href = window.location.href;
});
And it work wonderful.
The code is taking the location from the url and redirect to this url.
I use this to change bar adress and save current state, including current html body, and i reload it on back bouton click without any other ajax call. All is saved in your browser :
$(document).ajaxComplete(function(ev, jqXHR, settings) {
var stateObj = { url: settings.url, innerhtml: document.body.innerHTML };
window.history.pushState(stateObj, settings.url, settings.url);
});
window.onpopstate = function (event) {
var currentState = history.state;
document.body.innerHTML = currentState.innerhtml;
};
You need to implement the popstate event. When you click the back button after pushing a state, the page receives the popstate event. In it you need to replace the page contents with the correct page.
See an example from MDN
Updated code:
$('button').click(function(){
// Store some information in the state being pushed
window.history.pushState({url:'page2.php'}, '', 'page2.php');
$('body').html('<div class="loading.gif"></div>');
//Load Page
$.get('page2.php', function(data){
$('body').html(data);
};
//Edited
$(window).bind('popstate', function(event){
var url = null;
if (event.state && event.state.url) {
url = event.state.url;
} else {
url = 'index.html'; // or whatever your initial url was
}
// Update the contents of the page with whatever url was stored in the state.
$.get(url, function(data){
$('body').html(data);
};
});
});

Categories

Resources