Im make some ajax calls in my website and im trying to implement the history API to make it so I can navigate it with the browser history. This is the code for the "back button":
$(document).ready(function(){
window.addEventListener("popstate", function(e) {
//Flag I use to not fire it on the first page load
if (!ajaxhistory)
return;
do_my_ajaxs_things();
}, false);
});
But I have 2 problems
1) When I type the URL in the address bar to load the page for the first time. This also fires the ajax call, which is undesired obviously. Theres no need to ajax, since I have to load the entire page. This one, I have managed to solve it with a flag, but I wonder if theres a more elegant way to do it
2) Lets say I was in "google.com" and the I type my URL "www.mysite.com", then I make an ajax call and I go to "www.mysite.com/contacts". If I press BACK button once, i will go to "www.mysite.com" allright, but if I press BACK again, I will still be in "www.mysite.com", and I find myself I cant go back to google. How could I solve this issue?
All help appreciated. Thanks.
I think that your approach is wrong. You shouldn't need to do AJAX requests each time the user goes back - that's what the state is for, you should have all the relevant data already there. IMHO the logic should be the following:
If the window loads and window.history.state is already set - just apply this state.
Otherwise trigger an AJAX request to retrieve the default state and replace the current state with it.
Whenever the user navigates to a new "page", trigger an AJAX request to retrieve the new state and push it.
In your popstate handler you should simply apply the state you got in event.state, without doing any new AJAX requests.
Here is some example code with a fake loadPage function, normally you would put your AJAX call there. The applyState function and state data are also absolutely minimal.
<script type="text/javascript">
window.onload = function()
{
if (window.history.state)
{
applyState(window.history.state);
}
else
{
loadPage(0, function(state, title, loc)
{
window.history.replaceState(state, title, loc);
});
}
window.addEventListener("popstate", function(event)
{
applyState(event.state);
});
}
function goToPage(pageId)
{
loadPage(pageId, function(state, title, loc)
{
window.history.pushState(state, title, loc);
});
}
function loadPage(pageId, callback)
{
window.setTimeout(function()
{
var state = {text: "text" + pageId};
applyState(state);
callback(state, "page " + pageId, "#" + pageId);
}, 100);
}
function applyState(state)
{
document.getElementById("content").textContent = state.text;
}
</script>
<div id="content">
???
</div>
<button onclick="goToPage(1);">1</button>
<button onclick="goToPage(2);">2</button>
<button onclick="goToPage(3);">3</button>
For your first problem, this is really a browser issue. A browser should (in my opinion) never fire the popstate event when the page is loaded initially. I think the best thing you can do is only register the event after the page has been loaded.
History.js is a good library which smoothes out the history API quite a bit. Even if you don't use it they have good documentation about the API here:
https://github.com/browserstate/history.js/wiki/The-State-of-the-HTML5-History-API
About your second issue, the browser wil just go to google instead of firing the popstate event for your website. You don't have to worry about that, it's the browsers responsibility
Related
I have a form with a slider with which somebody can rate a picture. The form should be submitted when the slider is dragged, so "onmousup". This works fine. However, the page should be refreshed so that the rating that the user did is already submitted. This all happens on the index page so that location where the user scrolled to, should not be lost.
I tried to make the slider like this:
<%= f.range_field :score, class:"form-control-range slider", id:"formControlRange", onMouseUp:"submit(); reload(); " %>
...
<script>
function reload() {
console.log("hi");
location.reload;
}
</script>
This way I hoped that when the onMouseUp event happens, the page is first submitted and the page then reloaded, so that the rating the user did is displayed. If I have the slider without the "reload()" function then the form is submitted, but I have to reload manually so that the changes can be displayed.
Somehow, calling having the slider like this doesnt make both functions happen. Only the function that is called first is executed.
I have seen in other threads that having the slider like onMouseUp:"submit() && reload()", but this also doesnt work for me...
Do you have a way to make this work? Or do you have an idea of how the reload thing would be done better.
Thank you so much for your help!
Vincent
UPDATE:
I have looked up a different way, and it actually works when I set a timer for the reload function like this:
function reload() {
console.log("hi");
setTimeout(function()
{
location.reload(); //Refresh page
}, 50);
}
Funny thing now is that it works 50 ms, but not with 5 ms. This points to that it is somehow related with the order. Does anybody know how to tell the code to only reload the page AFTER the form has been submitted?
location.reload is a function, you need to call it as a function:
function reload() {
console.log("hi");
location.reload();
}
But this will not fix the problem by itself, because you are doing a submit and then reload. What if the reload is faster than the operation performed on submit? Then your old state is reloaded. What if the submit operation fails?
If we assume that you keep your current approach, then you need to do the submit and synchronize in some way before you reload (or rerender). If there is an error, display an error.
You can do this with AJAX. Or, if you prefer post and submit, then your server could resend the output as a response to post, which is pretty well handled by the browsers.
I personally prefer to do an AJAX request, handle the response and refresh the parts of the UI that are to be changed.
EDIT
This is how AJAX request can be sent:
function sendRequest(type, url, callback, async, params) {
if (async !== false) async = true;
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = callback;
xhttp.open(type, url, async);
xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhttp.send(params);
}
call this function, like:
sendRequest("POST", "yoururl", reload, true, yourparams);
Where reload is your function modified as above and yourparams is a set of parameters, like "firstparam=firstvalue&secondparam=secondvalue".
You've typed location.reload, missing () to call a method.
Please post the code of submit() function.
Page A:
$(document).ready(function () {
bindData();
});
function bindData() {
$('#searchbtn').bind('click', function () { SearchResult(); });
}
function SearchResult() {
ajax call...
}
Page A HTML:
<input type="button" id="searchbtn" />
Page B Details---> this page comes after selecting a specific search result from page A search list
Back<br />
Now when I go back to the Page A I can see my search criteria's as they were selected but the result Div is gone. What I am trying to do is I want the search list to stay when the Page comes back.
I think what I can do here is some how call the searchbtn click event again when the page comes back so the list will come-up again. Can anyone tell me how to fire the searchbtn click event only when the page comes back from Page B. or point me in the right way of doing this..
Thanks
The Browser Back button has long been problematic with AJAX. There are scripts, workarounds, and techniques out there (depending on the framework that you want to use).
Since it appears that you are using jQuery (based on your posted JavaScript syntax), here is a link to another Stackoverflow post regarding back button jQuery plugins.
history.back() will return you to the last URL visited, meaning that any ajax calls made during the user's visit will not be automatically repeated. Your browser may automatically restore your form selections, but the SearchResults() function is only called by a click event, not a selection event.
You can bind URLs to ajax states using a framework like sammy.js. That way, history.back() would take you to a URL associated with SearchResults().
function bindData() {
var chkinput1 = $("input:checkbox[name=x]:checked").length;
var chkinput2 = $("input:checkbox[name=y]:checked").length;
if (chkinput1 > 0 && chkinput2 > 0) {
SearchResult();
}
$('#searchbtn').bind('click', function () { SearchResult(); });
}
I know this is the worst way to achieve this result but I think instead of using any other plugins to add complexity we will go with this for now. If anyone else is looking for the same question let me tell you again this is not the best practice as on returning back to the history we are calling the search result again depending upon the cached input selection of checkboxes and generating the whole ajax call again to display the list. On the first request I am caching the list and setting sliding expiration so its not taking anytime to comeback and so everyone lives happily.
I have a page where user needs to enter some data and click save to validate the changes, but my problem is if the user is trying to close the browser window or click on a different link to navigate to a different page..I need to delete all the entries the user has saved so far..
I am doing it the following way
window.onbeforeunload = function()
{
if(confirm('Are you sure you want to navigate'))
{
//Invoke `enter code here`server side method
}
else
{
// return false;
}
}
Everything works fine if he click on Yes, the problem comes when he click on "No"..Even if he click on No..the page unload method is getting called and it is redirected to a different page..but I want it to stay in the same page in same state...can you please help me in achieving this.
Thanks and appreciate your response....
You cannot stop the user from leaving the page. What you can do is alert a message to them, asking if they want to leave or not.
The window.onbeforeunload event should return a string (and only a string). This string will be printed on the alert box made by the browser.
You cannot use your own alert box, or block the user from leaving (or redirect them).
window.onbeforeunload = function(){
return 'Are you sure you want to leave?';
};
Or with jQuery
$(window).on('beforeunload', function(){
return 'Are you sure you want to leave?';
});
When a user leaves the page, you can use the onunload event to make an AJAX call (you may need to use async: false here).
Example:
$(window).unload(function(){
$.ajax({
url: '/path/to/page/',
async: false, // this may be needed to make sure the browser doesn't
// unload before this is done
success: function(){
// Do something
}
});
});
NOTE: Instead of doing this, why don't you just save everything when the user is completed? Instead of saving it and then removing it if the user doesn't finish?
First of all: you can't! It's impossible. onbeforeunload only accepts a string as return value and will then close if the user wants that.
But then think about what happens if the computer is being without energy and shuts down? Or the browser will closed by the Task Manager? Or even more "realistic": The internet connection get lost! => Then you got invalid data states too!
You are trying to solve a false problem! Your problem isn't this function, your problem is the state of your formular!
Why do you need some kind of function? Do you saving the data before he clicks on save? Then don't! Or make sure to have another query which detects unfinished data in your database and delete it after a timeout!
onbeforeunload only accepts a string as return value. That string will be displayed by the browser with the option to stay on the page or leave it. But that's ll you can do.
You can use something like this, just call the following function on your page
function noBack() {
window.onbeforeunload = function(){window.history.forward()}
}
this disables Back button if window.history is clean, otherwise it works only first time.
I want to alert() when browser's back or forward button is clicked or hash is changed in javascript. I have tried this solution and it is working but it is causing problems on other links in webpage and submit each request twice on any link click event.
Is there any solution to capture it without using setInterval() function? So I need to capture hash change or back/forward button click event? I need a simple javascript code/function/property that should work in all modern browsers.
Any solution ?
Thanks
Not a good idea
Can you rather explain the reasoning behind this? We've all been down this road of preventing backs/forwards and similar and mangling with browser functionality.
It turns out though it's better to obey to browser and write your application in that way so these things become irrelevant. And it's also true that browsers are locking more and more things to client javascript apps so it's highly likely your app is going to fail after (few) browser upgrades.
Go with HTML5
HTML5 History spec may be exactly what you're after. It's the way things should work and be done in regard to Ajax applications and browser0s back/forward functionality. I suggest you check it out. See a working demo that does this rather nicely.
I believe this is the answer Robert Koritnik was looking for, I found it here: https://developers.google.com/tv/web/articles/location-hash-navigation
There is an event (window.onhashchange) that fires whenever the location hash has been updated or changed so all you have to do is set up an event handler using JavaScript to listen for this event and execute code based on the hash. This is basically how it is done:
function getLocationHash() {
return window.location.hash.substring(1);
}
window.onhashchange = function(e) {
switch(getLocationHash()) {
case 'state1':
execute code block 1;
break;
case 'state2':
execute code block 2;
break;
default:
code to be executed if different from case 1 and 2;
}
}
I have it working on my site:
http://www.designhandler.com
It is all dynamically changing content. No ajax yet but when I am finished it will be. I still use the window.location.hash to keep track of the site states. If you navigate through the site and then begin to use the back forward buttons to navigate once the site is in the history it will change the states dynamically like if the user was actually clicking through the nav, rather than needing to reload the page afterward.
It's this for hash or for redirection? What are you trying to do? This kind of action is usually highly intrusive.
You may want to try "onbeforeunload" event for this javascript before leaving the page
Edited
Actually, the link you provide is quite accurate.
var hash = location.hash;
setInterval(function()
{
if (location.hash != hash)
{
hashUpdatedEvent(hash);
}
}, 100);
function hashUpdatedEvent(hash)
{
switch(...);
}
Your link duplicate action problem would be corrected if you change
Go for it
function someFuncion()
{
doWhatever();
location.hash = 'somethingwasdone';
}
function hashUpdatedEvent(hash)
{
if(hash == 'somethingwasdone')
{
doWhatever();
}
}
By just (update the hash and let the "event" handle the action) :
Go for it
function someFuncion()
{
location.hash = 'somethingwasdone';
}
function hashUpdatedEvent(hash)
{
if(hash == 'somethingwasdone')
{
doWhatever();
}
}
Javascript provide the event popstate to capture browser's back/forward button click event -
window.addEventListener("popstate", function(e) {
// if a back or forward button is clicked, do whatever, like alert or anything
console.log('href => ', e.path[0].location.href);
// href => https://testdomain.com/demos/material/admin-app/#!/app/dashboard
console.log('hash => ', e.path[0].location.hash);
//hash => #!/app/dashboard
console.log('pathname => ', e.path[0].location.pathname);
//pathname => /demos/material/admin-app/
});
Read more on popstate
I'm pretty much a novice when it comes to js so I'm sorry if I'm missing something really simple.
Basically, I've done some research into using the history.pustate and popstate and I've made it so a query string is added to the end of the url (?v=images) or (?v=profile)...(v meaning 'view') by using this:
var url = "?v=profile"
var stateObj = { path: url };
history.pushState(stateObj, "page 2", url);
I want to make it so I can load content into a div but without reloading the page which I have done using the .load() function.
I then used this code:
$(window).bind('popstate', function(event) {
var state = event.originalEvent.state;
in $(document).ready() section and later tried within just <script> tags and neither worked.
I don't know how to make it so the content changes when I use the back button or at least makes it so I can trigger my own function to do so; and I'm assuming it has something to do with the state object?! I just can't seem to find anything online that explains the process clearly.
If someone could help me out it would be amazing and thank you in advance to anyone who does!
The popstate only contains a state when there is one.
When it goes like this:
initial page loaded
new page loaded, with state added via pushState
back button pressed
then there is no state, because the initial page was loaded regularly, not with pushState. As a result, the onpopstate event is fired with a state of null. So when it is null, it means the original page should be loaded.
You could implement it such that history.pushState will be called consistently and you only need to provide a state change function like this: Click here for jsFiddle link
function change(state) {
if(state === null) { // initial page
$("div").text("Original");
} else { // page added with pushState
$("div").text(state.url);
}
}
$(window).on("popstate", function(e) {
change(e.originalEvent.state);
});
$("a").click(function(e) {
e.preventDefault();
history.pushState({ url: "/page2" }, "/page2", "page 2");
});
(function(original) { // overwrite history.pushState so that it also calls
// the change function when called
history.pushState = function(state) {
change(state);
return original.apply(this, arguments);
};
})(history.pushState);
Maybe it's not best solution, and maybe it doesn't suit your needs. But for me it was best to just reload the page. So the page is consistent an it loads everything according to current querystring.
$(document).ready(function() {
$(window).on("popstate", function (e) {
location.reload();
});
});