I have been trying to get my script working but apparently there is something wrong with it: when I try to go backwards with the browser back button, it stops at the first page backwards i.e. the second time I click the back button, does not work properly and instead updates the current page with itself.
Examples:
homepage -> second page -> third page -> second page -> second page -> second page (and so on)
homepage -> second page -> third page -> fourth page -> third page-> third page (and so on)
This instead works:
homepage -> second page -> homepage
Does anyone have a clue to what I am missing?
var domain = 'http://example.com/';
function updatePage(json){
var postData = JSON.parse(json);
// pushState
var url = domain + postData.url;
var title = postData.title;
document.title = title;
history.pushState({"page": url}, title, url);
// Clears some elements and fills them with the new content
// ...
// Creates an 'a' element that triggers AJAX for the next post
var a = document.createElement('a');
a.innerHTML = postData.next;
a.href = domain + postData.next;
document.getElementById('container').appendChild( a );
listenerAttacher( a );
// Creates another 'a' element that triggers AJAX for the previous post
a = document.createElement('a');
a.innerHTML = postData.previous;
a.href = domain + postData.previous;
document.getElementById('container').appendChild( a );
listenerAttacher( a );
}
function loadPost( resource ){
// Loads post data through AJAX using a custom function
loadHTML( resource, function(){
updatePage( this.responseText );
});
}
function listenerAttacher( element ){
// Adds a click listener to an element.
element.addEventListener('click', function(e){
e.preventDefault();
e.stopPropagation();
loadPost( this.href +'.json' );
return false;
},
false);
}
(function(){
history.replaceState({'page': window.location.href}, null, window.location.href);
// Adds the event listener to all elements that require it.
var titles = document.querySelectorAll('.post-title');
for (var i = 0; i < titles.length; i++){
listenerAttacher( titles[i] );
}
// Adds a popstate listener
window.addEventListener('popstate', function(e){
if ( e.state == null || e.state.page == domain ){
window.location = domain;
}
else {
loadPost( e.state.page + '.json' );
}
}, false);
})();
When you pressed back button, popstate event is fired and loadPost function is called. However in loadPost, history.pushState method is called again, which pushes the current page on the history stack again. Which explains why the first back button works and then it does not.
1) A quick fix is to check if the current state matches the state you are trying to push:
if (!history.state || history.state.page!=url)
history.pushState({ "page": url }, title, url);
2) Event better, you can add parameter to loadPost and updatePage functions to prevent unnecessary pushState calls:
function updatePage(json, disablePushState) {
...
// disablePushState is true when back button is pressed
// undefined otherwise
if (!disablePushState)
history.pushState({ "page": url }, title, url);
...
}
function loadPost(resource, disablePushState) {
// Loads post data through AJAX using a custom function
loadHTML(resource, function (responseText) {
updatePage(responseText, disablePushState);
});
}
...
window.addEventListener('popstate', function (e) {
if (e.state == null || e.state.page == domain) {
window.location = domain;
}
else {
loadPost(e.state.page + '.json', true);
}
return true;
});
Hope this help.
Related
Hash anchor links fail to find/target ID DOM element target (and therefore fail to trigger JS UI tab change)
Hi visit https://eurosurveillance.org/content/10.2807/1560-7917.ES.2019.24.40.1900157 and scroll down the HTML fulltext (shown in this tab) and try clicking on the link marked 'Supplement 1' or 'Supplement 2' note nothing happens.
There is nothing clever about the links they are normal html hash links....
Supplement 1
Now if you visit the same page but this time do a hard reload with the following URL in the browser address bar https://eurosurveillance.org/content/10.2807/1560-7917.ES.2019.24.40.1900157#html_fulltext (notice the only change is the hash in the URL). Now try following the same instructions as before and you will notice that for some reason the anchor link is now followed and the anchor tab JS is triggered this is behaviour that I am expecting
Please advise. thanks in advance.
Hi many thanks the issue was resolved by refactoring the responsive-tabs.js code:
$(document).ready(function() {
//responsive tabs API code.
var Tabs = {
init: function() {
this.bindUIfunctions();
this.pageLoadCorrectTab();
},
bindUIfunctions: function() {
// Delegation
$(document)
.on("click", ".js-transformer-tabs a[href^='#']:not('.active, .disabled')", function(event) {
Tabs.changeTab(this.hash);
event.preventDefault();
})
.on("click", ".js-transformer-tabs a.active, .js-transformer-tabs a.disabled", function(event) {
Tabs.toggleMobileMenu(event, this);
event.preventDefault();
})
.on("click", ".js-textoptionsFulltext a.html:not('.disabled')", function(event) {
//console.log("tab?" + this.hash);
event.preventBubble=true;
var anchorTab = Tabs.changeTab(this.hash);
anchorTab.trigger("click"); //trigger click event in this case it will be html fulltext.
//event.preventDefault();
});
//We also need to handle the back state by telling the browser to trigger the tab behaviour!
$(window).on('popstate', function(e) {
anchor = $('[href="' + document.location.hash + '"]');
if (anchor.length > 0) {
Tabs.displayTab(anchor);
} else {
var defaultAnchor = $('.js-transformer-tabs li.active a');
if (defaultAnchor) {
Tabs.displayTab(defaultAnchor)
}
}
});
},
changeTab: function(hash) {
var anchor = $(".js-transformer-tabs a[href='" + hash + "']");
var activeTab = anchor.find('span strong');
Tabs.displayTab(anchor);
$(".js-dropdown li").hide();
$(".js-mobile-tab").text($(activeTab).text());
// update history stack adding additional history entries.
// pushState is supported!
history.pushState(null, null, hash);
// Close menu, in case mobile
return anchor; // make property available outside the function
},
pageLoadCorrectTab: function() {
//console.log("document.location.hash: " + document.location.hash);
if (document.location.hash.length > 0) {
// If the page has a hash on load, go to that tab
var anchor = $(".js-transformer-tabs a[href='" + document.location.hash + "']");
if (!anchor.hasClass("disabled")) {
var anchorTab = this.changeTab(document.location.hash);
// this is a further amendment to allow the fulltext and
//(any future event if its attached) to load when bookmarking a page with a particular tab.
anchorTab.trigger("click");
}
} else if ($(".js-transformer-tabs .active.tabIcon .active").length > 0) {
// go to which ever tab is defined as active in the JSP page and trigger a click on it
// but, only when no hash in the url
var activeTab = $(".js-transformer-tabs .active.tabIcon .active");
activeTab.trigger("click"); //trigger click
}
},
toggleMobileMenu: function(event, el) {
$(el).closest("ul").toggleClass("open");
},
displayTab: function(anchortab) {
var url = anchortab.attr("href");
//console.log("url" + url);
var divtabContent = $(url);
// activate correct anchor (visually)
anchortab.addClass("active").parent().siblings().find("a").removeClass("active");
// activate correct div (visually)
divtabContent.addClass("active").siblings().removeClass("active");
}
}
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/' + ingentaCMSApp.instanceprefix + '/vendor/history.min.js'],
complete : function() {
var location = window.history.location || window.location;
Tabs.init();
}
}])
});
//end of responsive tabs API code.
$(".js-select").click(function(){
$(".js-dropdown li").slideToggle();
});
I have used history.pushState() and now if the user refreshes the page then it is refreshing current URL which is not an original URL.
I tried detecting refresh page with a cookie, hidden filed but it is not working.
window.onload = function() {
document.cookie="PR=0";
var read_cookies = document.cookie;
var s = read_cookies;
s = s.substring(0, s.indexOf(';'));
if( s.includes("1"))
{
window.location.href = "https://www.google.com";
}
else{
document.cookie="PR=1";
}
loadURL();
};
function loadURL()
{
document.cookie="PR=1";
document.getElementById("visited").value="1";
var str="abc/b cd";
str=str.replace(/ /g, "-");
history.pushState({},"",str);
}
when user is refreshing the page I need original URL on that time.
This might be helpful. But you need control over the pushed url.
// this goes to your 'original' url
window.addEventListener('beforeunload', function (event) {
sessionStorage.setItem('lastPage', window.location.href)
}
// page with your pushed url
if (sessionStorage.getItem('lastPage') === 'PAGE_YOU_DONT_WANT_TO_BE_REACHABLE_DIRECTLY') {
window.location = 'PAGE_YOU_WANT'
}
I'm interested what the use case for this is. As far as my knowledge goes you can't suppress the refresh event completely.
I've found how to fix the back button, but the forward button has remained unfix-able. The url will change but the page doesn't reload, this is what I'm using:
$('.anchor .wrapper').css({
'position': 'relative'
});
$('.slanted').css({
'top': 0
});
// Do something after 1 second
$('.original-page').html('');
var href = '/mission.html'
console.log(href);
// $('#content-div').html('');
$('#content-div').load(href + ' #content-div');
$('html').scrollTop(0);
// loads content into a div with the ID content_div
// HISTORY.PUSHSTATE
history.pushState('', 'New URL: ' + href, href);
window.onpopstate = function(event) {
location.reload();
};
// response.headers['Vary'] = 'Accept';
// window.onpopstate = function (event) {
// alert("location: " + document.location + ", state: " + JSON.stringify(event.state));
// location.reload();
// response.headers['Vary'] = 'Accept';
// };
// $(window).bind('popstate', function () {
// window.onpopstate = function (event) {
// window.location.href = window.location.href;
// location.reload();
// };
e.preventDefault();
As you can see, I've tried several different things, and the back button works just fine but not the forward button.
Keep in mind that history.pushState() sets a new state as the newest history state. And window.onpopstate is called when navigating (backward/forward) between states that you have set.
So do not pushState when the window.onpopstate is called, as this will set the new state as the last state and then there is nothing to go forward to.
Complete solution working with multiple clicks on back and forward navigation.
Register globally window.onpopstate event handler, as this gets reset on page reload (and then second and multiple navigation clicks don't work):
window.onpopstate = function() {
location.reload();
};
And, update function performing AJAX reload (and, for my use-case replacing query parameters):
function update() {
var currentURL = window.location.href;
var startURL = currentURL.split("?")[0];
var formParams = form.serialize();
var newURL = startURL + "?" + formParams;
var ajaxURL = startURL + "?ajax-update=1&" + formParams;
$.ajax({
url: ajaxURL,
data: {id: $(this).attr('id')},
type: 'GET',
success: function (dataRaw) {
var data = $(dataRaw);
// replace specific content(s) ....
window.history.pushState({path:newURL},'',newURL);
}
});
}
I suggest reading about navigating browser history and the pushState() method here. It explicitly notes that pushState() by itself will not cause the browser to load a page.
As far as the forward button not working, once you call pushState() the browser is (conceptually) at the last (latest) page of the history, so there is no further page to go "forward" to.
I’m working on an eshop where items are opened on top of a page in iframes. I’m using
history.pushState(stateObj, "page 2", http://localhost:8888/product-category/tyger/vara-tyger/?view=product&item=test-4);
in order to let customers copy the current url and use it to go to the current page with the item opened in an iframe. In addition, I’m using
window.addEventListener('popstate', manageHistory);
function manageHistory(event) {
if (!has_gone_back) {
var iframeOpen = false;
has_gone_back = true;
}
else {
var iframeOpen = true;
has_gone_back = false;
}
}
in order to let customers use their browser’s back and forward buttons for navigation (closing and opening the iframe).
However, when opening one product (calling history.pushState once), using the browser’s back button, and opening another product (calling history.pushState again), and going back again, manageHistory() is not called. The customer is taken to the first opened product but if pressing back again, manageHistory() is called.
I want manageHistory() to be called when pressing back on the product page opened second in order to add code to redirect customers to the category's start page when pressing back.
I’ve tried both adding Event Listeners for both opened products and also for only the first one. Any ideas what the problem may be?
From https://developer.mozilla.org/en-US/docs/Web/Events/popstate
Note that just calling history.pushState() or history.replaceState() won't trigger a popstate event. The popstate event is only triggered by doing a browser action such as a click on the back button (or calling history.back() in JavaScript).
You can overwrite popState and replaceState, but what is generally a better idea is to create a wrapper which sets the url and then triggers your handler function.
Something like this...
function urlChangeHandler() {
var url = window.location.href;
// Whatever you want to do...
}
// Handle initial url:
urlChangeHandler();
window.addEventListener('popstate', urlChangeHandler);
var urlState = {
push: function(url) {
window.history.pushState(null, null, url);
urlChangeHandler();
},
replace: function(url) {
window.history.replaceState(null, null, url);
urlChangeHandler();
}
}
I have a similar file in one of my projects which updates the datastore based on the #hash...
import tree from './state'
// No need for react-router for such a simple application.
function hashChangeHandler(commit) {
return () => {
const hash = window.location.hash.substr(1);
const cursor = tree.select('activeContactIndex');
const createCursor = tree.select('createNewContact');
cursor.set(null);
createCursor.set(false);
(() => {
if(!hash.length) {
// Clean up the url (remove the hash if there is nothing after it):
window.history.replaceState(null, null, window.location.pathname);
return;
}
if(hash === 'new') {
createCursor.set(true);
return;
}
const index = parseInt(hash, 10);
if(!isNaN(index)) {
cursor.set(index);
}
})();
commit && tree.commit();
}
}
// Handle initial url:
hashChangeHandler(true)();
// Handle manual changes of the hash in the url:
window.addEventListener('hashchange', hashChangeHandler(true));
function createHash(location) {
return (location !== null) ? `#${location}` : window.location.pathname;
}
module.exports = {
push: (location, commit=true) => {
window.history.pushState(null, null, createHash(location));
hashChangeHandler(commit)();
},
replace: (location, commit=true) => {
window.history.replaceState(null, null, createHash(location));
hashChangeHandler(commit)();
}
}
I want to toggle between two jQuery functions. It has to be done on page load - and each page load should only execute one of the scripts.
This is what I got so far:
HTML:
<button class=".click">Click me</button>
Script:
$(function() {
if (window.location.href.indexOf("addClass") > -1) {
$("body").addClass("test");
}
else {
$("body").addClass("secondtest");
}
$('.click').on('click', function() {
console.log("Clicked");
var url = window.location.href;
if (url.indexOf('?') > -1) {
url += '?param=addClass'
} else {
url += '?param=1'
}
window.location.href = url;
});
});
This Gets me a bit on the way, the first click adds ?param=1 on the first click - nothing happens - second click it adds the ?param=addClass and the body gets the class. If I click again it adds ?param=addClass every time.
I want one of the script to run as default - then I want the first button click to reload and run the other script instead. If I click once more I want it to reverse the url so the first script loads, like a toggle.
I now there is an easy way to just toggle classes, but I specifically need to run one of two scripts on a new page load.
Update:
$(function() {
if (window.location.href.indexOf("addClass") > -1) {
$("body").addClass("test");
}
else {
$("body").addClass("secondtest");
}
$('.click').on('click', function() {
console.log("Clicked");
var url = window.location.pathname;
var url = window.location.href;
if (url.indexOf('?param=1') > -1) {
url = url.replace("param=1", "")+'param=addClass'
} else {
url = url.replace("?param=addClass", "")+'?param=1'
}
window.location.href = url;
});
});
This set the body class on first page load - then first click ads ?param=1 but doesnt change the body class. Second click replaces ?param=1 with ?param=addClass and changes the body class - after that the toggles works. So How do I make it work from the first click?
This will be the default functionality, if no query string is present then add ?param=1:
var url = window.location.href;
if(url.indexOf('?param=1')==-1 )
{
window.location.href = url+"?param=1";
}
This will be the onclick functionality to toggle the urls as it is replacing the existing functionality.
$('.click').on('click', function() {
var url = window.location.href;
if (url.indexOf('?param=1') > -1) {
url = url.replace("param=1", "")+'param=addClass'
} else {
url = url.replace("?param=addClass", "")+'?param=1'
}
window.location.href = url;
});
If you want to toggle the classes as well you can use .toggleClass("test secondtest")
The issue you have is in this if:
var url = window.location.href;
if (url.indexOf('?') > -1) {
url += '?param=addClass'
} else {
url += '?param=1'
}
Scenario 1: /test.html
indexOf('?') will be negative. You will then redirect the user to /test.html?param=1
Scenario 2: /test.html?param=1
indexOf('?') will then be positive. You will then redirect the user to /test.html?param=1?param=addClass
Scenario 3: /test.html?param=addClass
indexOf('?') will then be positive. You will then redirect the user to /test.html?param=addClass?param=addClass
So... what is wrong?
You are using window.location.href. Excellent for setting the path but bad if you want to actually manage the query parameters.
Solution
var url = window.location.pathname;
var hasParams = window.location.href.indexOf('?') > -1;
if (hasParams) {
url += '?param=addClass'
} else {
url += '?param=1'
}
window.location.href = url;
Since you are redirecting on the same host (seen with your code), you only need the pathname. pathname doesn't include parameters (?key=value&...) and can be used to redirect a user on the same domain.