I try to use the window.history.replaceState method just to show the URL shown in the browser.
window.history.replaceState("","","newurl.php");
Well, this works well and changes the URL. But what is this "State" that I set to "" here? And how should I change my code if I simply want to be the State as before? (As I said... I just wanted to change the URL and this works well, not to change the state...)
.replaceState() a way to associate some data (any data that can be structured cloned) with the current location - without reloading the page. There's also pushState, that does the same thing, but you can also use the browser's back button to go back to the previous displayed url / state.
If you don't use these for something else, you don't have to care about the state you set (it's only visible to your website).
If you, on the other hand, do use states, and don't want to change it, you can set the same state as it was before. You can get the current state with history.state:
window.history.replaceState(window.history.state,"","newurl.php");
Related
I found a lot of questions about this on Stack Overflow, but they were all very specific about certain parts. I did find this question whose answers provide some nice references, but they don't actually explain how it all works, and their examples hardly do anything. I want to know more about how it all works together, and I want to use vanilla JavaScript.
(Also, many of the answers on other questions are years old.)
GETTING STARTED
First of all, you can remove the window part. Just history works fine. But before we get into how everything works together, we need to know what we can use.
Important Events
window.onload
This event fires whenever your webpage is loaded. There are two cases that will fire this event:
When your webpage is navigated to from another webpage. Note that I wrote webpage, not website. Moving between pages on the same site will trigger this event.
Just after your webpage is refreshed.
window.onpopstate
This event fires when you navigate between history states that you have set. Your browser automatically sets history states (to null) during normal browsing, but navigating to/from these states will not trigger this event.
window.onunload
This event fires whenever your webpage is unloaded. There are two cases that will fire this event:
When you navigate to another webpage from your webpage.
Just before your webpage is refreshed.
Important Objects
The history interface contains five functions (described below), two read-only objects (described here), and works a bit like a linked list. The two objects contained in each 'link' of the history object are:
length - This is the number of history states for the current browser window. It starts at 1.
state - This is a JavaScript object that can contain practically anything. It is null by default.
You can access them by calling history.length and history.state respectively, though history.state can only be used to get the current history state.
Important Functions
history.go(distance)
This function does the same thing as pressing the back or forward button in your browser, with the added functionality of being able to specify exactly how far you want to go. For example, history.go(3) has the same effect as would pushing your forward button three times, without actually loading the pages between your start and end locations. A negative value will likewise move you backwards through your history. history.go(0), history.go(), and even history.go(NaN) have the same effect as refreshing the page (this does not trigger the popstate event). If you cannot move forward/backward as far as specified, the function will do nothing.
history.back()
This function has the same functionality as the back button in your browser. It is equivalent to history.go(-1). If it cannot go back, the function will do nothing.
history.forward()
This function has the same functionality as the forward button in your browser. It is equivalent to history.go(1). If it cannot go forward, the function will do nothing.
history.replaceState(state, title[, location])
This function replaces the current history state. It takes three arguments, though the last one is optional. The arguments are:
state - This is the most important argument. The object you give to this argument will be saved to history.state for later retrieval. This is a deep copy, so if you later modify the original object it will not change the saved state. You could also set this to null, but if you aren't going to use it, there's not much point in using history at all.
title - The HTML Standard suggests that a string passed to this argument could be used by the browser in the user interface, but no browser currently does anything with it.
location - This argument allows you to change the URL relative to the current page. It cannot be used to change the URL to that of another website, but it can be used to change the URL to that of another page on your website. I would advise against this however, as the page is not actually reloaded even though the URL is of another page. Using back/forward will show the changed URL, but will not change the page, and will trigger popstate rather than load or unload. Refreshing the page after changing its URL will load the page specified by the URL rather than the page you were previously on. This functionality could be used to provide a link to your page in its current state, but I would recommend only changing the query string rather than the full URL. If this argument is not used, the URL will not change.
history.pushState(state, title[, location])
This function works the same as history.replaceState, except it puts the new state after the current state instead of replacing the current state. All history states that could have previously been accessed with forward are discarded, and the new state becomes the current state.
ASSEMBLING THE PIECES
The history interface is very useful for allowing your users to navigate through dynamically generated content from within their browser without having to reload the entire page, but you need to be mindful of all the possible things your users could do that could affect the history state.
First time navigating to your page
Should your users be greeted with a menu/list, some specific dynamically generated content, or maybe some random dynamically generated content?
Will your page display correctly without history, or even JavaScript?
Using back/forward to return to your page
Should your users see the same thing they saw their first time, or should they see the result of their visit reflected in the content? (A "Welcome Back" message might be a nice touch to some, but an unwanted distraction to others.)
Refreshing your page
Should you get a new page, return to the start page, or reload the same page? (Your users probably won't expect that last one if the URL hasn't changed.)
Using back/forward from a refreshed page
Should you get new content relative to the refreshed page, or reload the previously saved state?
Navigating away from your page
Do you need to save anything before leaving?
Returning to your page via a deep link
Do you have code in place to recognize and handle a deep link?
Note there is no way to delete a saved state (other than a specific case with pushState() mentioned above). You can only replace it with new content.
PUTTING IT ALL TOGETHER
Since this is starting to get a bit wordy, lets finish it off with some code.
// This function is called when the page is first loaded, when the page is refreshed,
// and when returning to the page from another page using back/forward.
// Navigating to a different page with history.pushState and then going back
// will not trigger this event as the page is not actually reloaded.
window.onload = function() {
// You can distinguish a page load from a reload by checking performance.navigation.type.
if (window.performance && window.PerformanceNavigation) {
let type = performance.navigation.type;
if (type == PerformanceNavigation.TYPE_NAVIGATE) {
// The page was loaded.
} else if (type == PerformanceNavigation.TYPE_RELOAD) {
// The page was reloaded.
} else if (type == PerformanceNavigation.TYPE_BACK_FORWARD) {
// The page was navigated to by going back or forward,
// though *not* from a history state you have set.
}
}
// Remember that the browser automatically sets the state to null on the
// first visit, so if you check for this and find it to be null, you know
// that the user hasn't been here yet.
if (history.state == null) {
// Do stuff on first load.
} else {
// Do stuff on refresh or on returning to this page from another page
// using back/forward. You may want to make the window.onpopstate function
// below a named function, and just call that function here.
}
// You can of course have code execute in all three cases. It would go here.
// You may also wish to set the history state at this time. This could go in the
// if..else statement above if you only want to replace the state in certain
// circumstances. One reason for setting the state right away would be if the user
// navigates to your page via a deep link.
let state = ...; // There might not be much to set at this point since the page was
// just loaded, but if your page gets random content, or time-
// dependent content, you may want to save something here so it can
// be retrieved again later.
let title = ...; // Since this isn't actually used by your browser yet, you can put
// anything you want here, though I would recommend setting it to
// null or to document.title for when browsers start actually doing
// something with it.
let URL = ...; // You probably don't want to change the URL just yet since the page
// has only just been loaded, in which case you shouldn't use this
// variable. One reason you might want to change the URL is if the
// user navigated to this page with a query string in the URL. After
// reading the query string, you can remove it by setting this
// variable to: location.origin + location.pathname
history.replaceState(state, title, URL); // Since the page has just been loaded, you
// don't want to push a new state; you should
// just replace the current state.
}
// This function is called when navigating between states that you have set.
// Since the purpose of `history` is to allow dynamic content changes without
// reloading the page (ie contacting the server), the code in this function
// should be fairly simple. Just things like replacing text content and images.
window.onpopstate = function() {
// Do things with history.state here.
}
// This function is called right before the page is refreshed, and right
// before leaving the page (not counting history.replaceState). This is
// your last chance to set the page's history state before leaving.
window.onunload = function() {
// Finalize the history state here.
}
Notice that I never called history.pushState anywhere. This is because history.pushState should not be called anywhere in these functions. It should be called by the function that actually changes the page in some way that you want your users to be able to use the back button to undo.
So in conclusion, a generic setup might work like this:
Check if (history.state == null) in the window.onload function.
If true, overwrite the history state with new information.
If false, use the history state to restore the page.
While the user is navigating the page, call history.pushState when important things happen that should be undoable with the back button.
If/When the user uses their back button and the popstate event is triggered, use the history state you set to return the page to its previous state.
Do likewise if/when the user then uses their forward button.
Use the unload event to finalize the history state before the user leaves the page.
I am using Firefox, and I have noticed something interesting while working on my website. One page has a table of information, but also maintains Javascript variables indicating the table's contents so it can look up information more easily.
When I navigate away from the page by typing in a URL, then hit the back button to return, I have noticed that the page seems to retain its state. The table still has the same contents as when I left, and further interactions with the page indicate that the Javascript variables have maintained their values as well.
I had assumed that my $(document).ready(); functions would run again when the back button was hit, but this is clearly not the case as I wrote those functions to ensure the page is its initial state, regardless of the page's current content.
I think it is perfectly acceptable for the page to be restored this way as long as I can depend on the page being restored properly. If instead, the page is restored to its initial state, that would be acceptable as well.
Is it safe to rely on this behavior? Would I be better off using something like onunload to ensure that the page's state is not saved? Should I just assume the browser will do things properly?
I would say that my website's state is defined by the HTML on the page and the contents of Javascript variables. It uses AJAX (or I guess technically AJAJ) to interact with PHP pages, but all interactions are discrete transactions. The only state/session needed by the server are login authentication cookies, which should be unaffected by this.
I am working on some javascript code, and using window.History.pushState to load new HTML pages, instead of using href tags. My code (which is working fine) looks like this.
window.History.pushState({urlPath:'/page1'},"",'/page1')
strangely, this fails, ie reloads the browser
window.History.pushState({urlPath:'/page2.php'},"",'/page2.php')
But this works, content is updated, browser not refreshed ! (notice the URL is absolute and not relative)
window.History.pushState({urlPath:'www.domain.com/page2.php'},"",'www.domain.com/page2.php')
The documentation for window.History.pushState says that the third parameter URL can be either absolute or relative -
URL — The new history entry's URL is given by this parameter. Note
that the browser won't attempt to load this URL after a call to
pushState(), but it might attempt to load the URL later, for instance
after the user restarts the browser. The new URL does not need to be
absolute; if it's relative, it's resolved relative to the current URL.
The new URL must be of the same origin as the current URL; otherwise,
pushState() will throw an exception. This parameter is optional; if
it isn't specified, it's set to the document's current URL.
Absolute URLs seem to be working but relative seem to be not. Why is this happening?
The short answer is that history.pushState (not History.pushState, which would throw an exception, the window part is optional) will never do what you suggest.
If pages are refreshing, then it is caused by other things that you are doing (for example, you might have code running that goes to a new location in the case of the address bar changing).
history.pushState({urlPath:'/page2.php'},"",'/page2.php') works exactly like it is supposed to in the latest versions of Chrome, IE and Firefox for me and my colleagues.
In fact you can put whatever you like into the function: history.pushState({}, '', 'So long and thanks for all the fish.not a real file').
If you post some more code (with special attention for code nearby the history.pushState and anywhere document.location is used), then we'll be more than happy to help you figure out where exactly this issue is coming from.
If you post more code, I'll update this answer (I have your question favourited) :).
As others have suggested, you are not clearly explaining your problem, what you are trying to do, or what your expectations are as to what this function is actually supposed to do.
If I have understood correctly, then you are expecting this function to refresh the page for you (you actually use the term "reloads the browser").
But this function is not intended to reload the browser.
All the function does, is to add (push) a new "state" onto the browser history, so that in future, the user will be able to return to this state that the web-page is now in.
Normally, this is used in conjunction with AJAX calls (which refresh only a part of the page).
For example, if a user does a search "CATS" in one of your search boxes, and the results of the search (presumably cute pictures of cats) are loaded back via AJAX, into the lower-right of your page -- then your page state will not be changed. In other words, in the near future, when the user decides that he wants to go back to his search for "CATS", he won't be able to, because the state doesn't exist in his history. He will only be able to click back to your blank search box.
Hence the need for the function
history.pushState({},"Results for `Cats`",'url.html?s=cats');
It is intended as a way to allow the programmer to specifically define his search into the user's history trail. That's all it is intended to do.
When the function is working properly, the only thing you should expect to see, is the address in your browser's address-bar change to whatever you specify in your URL.
If you already understand this, then sorry for this long preamble. But it sounds from the way you pose the question, that you have not.
As an aside, I have also found some contradictions between the way that the function is described in the documentation, and the way it works in reality. I find that it is not a good idea to use blank or empty values as parameters.
See my answer to this SO question. So I would recommend putting a description in your second parameter. From memory, this is the description that the user sees in the drop-down, when he clicks-and-holds his mouse over "back" button.
window.history.pushState({urlPath:'/page1'},"",'/page1')
Only works after page is loaded, and when you will click on refresh it doesn't mean that there is any real URL.
What you should do here is knowing to which URL you are getting redirected when you reload this page.
And on that page you can get the conditions by getting the current URL and making all of your conditions.
I'm trying to monitor my URL hash (a.k.a. fragment) using the onhashchange event so that I can make appropriate ajax calls based upon the parameters I'm storing in the hash. Unfortunately, there is something unexpected changing my hash. In all of my code, there is only one place that where I use window.location.hash and it is simply checking the value of the hash, not changing it. I know that the back and forward buttons can change the hash, but I'm not touching them. How do I find where the hash change is coming from?
Update
Ok... figured it out. And yes, I'm a dummy, but I leave my findings here for those as dumb as I. I was looking for something programmatic changing my hash, but what was really happening was that I was clicking on an anchor with href="#". There is an event handler hooked to these, and I set the return value to false and that prevented it from changing the URL.
Links that target internal anchors change the hash. For example:
Contact Us
Clicking that would change the hash to #contact.
Also, if you're using any third-party javascript libraries, it's possible that some code in there might be changing it.
What does the hash change to? From what? And When? If you can identify the exact circumstances that trigger the change, that should give you some idea what might be changing it.
When the browser clicks "back" button, I want to append "&page=3" to the URL.
How do I bind it, and then do this?
Edit: I want this very simple.
BIND the event{
window.location(that-url + "&page=3")
}
Can it be done that simply?
It sounds like you're trying to create a history plugin.
Have you tried using using the mikage history plugin?
I wouldn't recommend changing the URL when they navigate away from the current page (which is what the back button does), because you immediately erase the forward history (thus breaking the forward button). When trying to handle the back button with pagination and javascript/ajax it is more typical to use the browser hash to pass parameters. The JavaScript namespace doesn't get cleared when the forward and backward buttons are used and the hash is updated according to what navigation was used. These history plugins have a couple of methods to detect when navigation is used (as the doc load event doesn't fire).
So beware, writing a history plugin isn't straightforward because of the way browsers fail to consistently handle hash property of the location object (part of the window object). You will definitely want to look at what others have done.
We use the window.location.hash to handle the history in our app.
I guess it works well in single page apps and is very simple.
For multiple pages app, I don't think it's a good idea to try to control and change the natural page history of the browser.
When the user clicks "back" or "next", the hash key gets the previous or next value.
Because of IE7 you need to use a polling technique (but it is ok in all browsers), with a setInterval(...) and a fast function that checks for instance every 300ms if the hash has changed.
Then, if a change occurs, act accordingly.
ie: call the server and refresh some areas in the page.
It works very well, and does not kill at all the responsiveness of the application.