110

This question is similar to Track when user hits back button on the browser, but not the same... I have a solution and am posting it here for reference and feedback. If anyone has any better options, I'm all ears!

The situation is that I have a page with an "in place edit", a la flickr. I.e. there is a "click here to add a description" DIV, which when clicked turns into a TEXTAREA with Save and Cancel buttons. Clicking Save posts the data to the server to update the database and puts the new description in the DIV in place of the TEXTAREA. If the page is refreshed, the new description is displayed from the database with a "click to edit" option. Fairly standard web 2.0 stuff these days.

The issue is that if:

  1. the page is loaded without the description
  2. a description is added by the user
  3. the page is navigated away from by clicking a link
  4. the user clicks the back button

Then what is displayed (from the browser's cache) is the version of the page without the dynamically modified DIV containing the new description.

This is a fairly big problem as the user assumes that their update has been lost and won't necessarily understand that they need to refresh the page to see the changes.

So, the question is: How can you flag a page as being modified after it has loaded, and then detect when the user "goes back to it" and force a refresh in that situation?

Community
  • 1
  • 1
Tom
  • 42,844
  • 35
  • 95
  • 101
  • How is this different from the question you quoted? – innaM May 06 '09 at 11:57
  • the question is similar, but i think the environment and hence the answer is different, i could be wrong. my interpretation of what an issue may be that would be solved with the other solution is: user clicks a tab on a page that is loaded by ajax, then another tab and so on. clicking the back button would take you back to a different page, not the previous tab. they want to cycle back through the "ajax history" within the "page history". at least that's my impression of what the Yahoo Browser History Manager is supposed to do. i was after something a little more basic. – Tom May 06 '09 at 12:15
  • The accepted answer features your iframe trick. – innaM May 06 '09 at 12:59

12 Answers12

79

Use a hidden form. Form data is preserved (typically) in browsers when you reload or hit the back button to return to a page. The following goes in your page (probably near the bottom):

<form name="ignore_me">
    <input type="hidden" id="page_is_dirty" name="page_is_dirty" value="0" />
</form>

In your javascript, you will need the following:

var dirty_bit = document.getElementById('page_is_dirty');
if (dirty_bit.value == '1') window.location.reload();
function mark_page_dirty() {
    dirty_bit.value = '1';
}

The js that sniffs the form has to execute after the html is fully parsed, but you could put both the form and the js inline at the top of the page (js second) if user latency is a serious concern.

Nick White
  • 2,063
  • 16
  • 10
  • 7
    "Form data is preserved (typically) in browsers" -- are you able to expand on that? is it not preserved in some browsers? or in some situations? – Tom Apr 18 '11 at 06:54
  • 4
    Actually, let me list all the ways I can think of this to go wrong. (1) Opera keeps the full state of the page and won't re-execute the js. (2) If your cache settings are tweaked to force the page to load from the server on back-button, the form data might get lost, which is fine for the purposes of this question. (3) Phone browsers have a much smaller cache and the browser may have already dropped the page from cache (not a problem for these purposes). – Nick White Apr 19 '11 at 20:55
  • 14
    In some browsers hidden tags are not preserved. So you could use hidden text tag instead of using hidden input tag. – SajithK Jan 18 '12 at 12:12
  • 2
    This worked great for me. I'm sure it has situations when it breaks down, but every solution will for this problem. – Joren Apr 11 '12 at 23:44
  • It is not working on iPhone with Safari browser, try it, when you click back button, js is not called again. – woheras Apr 30 '15 at 10:56
  • Note, this doesn't seem to work without the `name` attribute being set. – Chris Schmitz Oct 26 '15 at 18:00
  • AFAIK this doesn't work if you're using HTTPS. There is a hack with using 'autocomplete' on the form - but this seems like a poor approach to me. – Ian Grainger Oct 13 '16 at 15:01
  • 1
    Beware, this script cause infinite loop! At least in Firefox. Because after reload, value of hidden input is still equal to 1. Bassem solution, seems to be the only one actually working. – instead Mar 07 '18 at 15:32
  • Not working on modern browsers. Hidden inputs not preserved in some browsers – Sam Jan 31 '21 at 10:36
72

Here is a very easy modern solution to this old problem.

if (window.performance && window.performance.navigation.type === window.performance.navigation.TYPE_BACK_FORWARD) {
    alert('Got here using the browser "Back" or "Forward" button.');
}

window.performance is currently supported by all major browsers.

Bassem
  • 2,736
  • 31
  • 13
  • I had not heard of this. I just found this documentation (https://developer.mozilla.org/en-US/docs/Web/API/Performance/navigation) and would be interested to hear you elaborate (e.g. why the need for `window.performance && window.performance.navigation.type` instead of just `window.performance.navigation.TYPE_BACK_FORWARD`?). – Ryan Nov 07 '17 at 22:48
  • 6
    `window.performance.navigation.TYPE_BACK_FORWARD` is a constant that is always equal to 2. So it is just used for comparison. – Bassem Nov 09 '17 at 00:35
  • Performance.navigation supported at all devices except andorid & opera mini but no problem its very nice (https://developer.mozilla.org/en-US/docs/Web/API/Performance/navigation) – Albaz Mar 03 '18 at 09:12
  • 5
    Looks like `performance.navigation` is deprecated and it's suggested to instead use https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigationTiming/type – RubberDuckRabbit Feb 27 '19 at 20:45
  • 3
    This will crash your script on Safari - both Mac and mobile – Chris Moschini Apr 27 '19 at 18:54
  • 1
    Firefox v70 will support performance.navigation = `2` for back button hits. Up until v70, it incorrectly returned `1`. – Kelderic Jul 31 '19 at 12:16
  • 1
    This is unfortunately now deprecated: https://developer.mozilla.org/en-US/docs/Web/API/Performance/navigation – Protector one Jan 13 '21 at 16:09
  • there are other answer here improve this deprecation – Nurkartiko Sep 09 '21 at 07:17
  • performance.getEntriesByType("navigation")[0].type – Mike Weston Nov 03 '22 at 15:31
  • Is not working in Chrome – MeSo2 Jan 22 '23 at 16:50
  • Working on chrome, but not on Safari – otidh May 11 '23 at 07:24
16

This article explains it. See the code below: http://www.webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/

<html>
    <head>
        <script>

            function pageShown(evt){
                if (evt.persisted) {
                    alert("pageshow event handler called.  The page was just restored from the Page Cache (eg. From the Back button.");
                } else {
                    alert("pageshow event handler called for the initial load.  This is the same as the load event.");
                }
            }

            function pageHidden(evt){
                if (evt.persisted) {
                    alert("pagehide event handler called.  The page was suspended and placed into the Page Cache.");
                } else {
                    alert("pagehide event handler called for page destruction.  This is the same as the unload event.");
                }
            }

            window.addEventListener("pageshow", pageShown, false);
            window.addEventListener("pagehide", pageHidden, false);

        </script>
    </head>
    <body>
        <a href="http://www.webkit.org/">Click for WebKit</a>
    </body>
</html>
Drew Baker
  • 14,154
  • 15
  • 58
  • 97
  • 1
    This is a good answer, works in latest Chrome 40 / Firefox 35. IE11 doesn't support those events, though. – Slava Abakumov Feb 04 '15 at 15:27
  • According to http://caniuse.com/#feat=page-transition-events page transition events are actually supported in IE11+ As we speak there's 88% global coverage of the feature – Cristian May 24 '16 at 08:23
  • FYI, Chrome returns `event.persisted` = `false`, even when hitting from back button. You must use `window.performance.navigation` or PerformanceNavigationTiming for Chrome. – Kelderic Jul 31 '19 at 12:23
10

For modern browsers, this seems to be the right way now:

const perfEntries = performance.getEntriesByType('navigation');
if (perfEntries.length && perfEntries[0].type === 'back_forward') {
  console.log('User got here from Back or Forward button.');
}

This is still "Experimental" as of Jan 2020, but seems to be supported well.

https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigationTiming

getup8
  • 6,949
  • 1
  • 27
  • 31
7

You can solve it using the onbeforeunload event:

window.onbeforeunload = function () { }

Having an onbeforeunload empty event manager function means the page will be re-built every single time it is accessed. Javascripts will re-run, server-side scripts will be re-run, the page will be built as if the user was hitting it for the very first time, even if the user got to the page just by hitting the back or forward button.

Here is the full code of an example:

<html>
<head>
<title>onbeforeunload.html</title>
<script>
window.onbeforeunload = function () { }

function myfun()
{
   alert("The page has been refreshed.");
}
</script>

<body onload="myfun()">

Hello World!<br>

</body>
</html>

Try it, navigate away this page and then get back using "Back" or "Forward" buttons in your browser.

It works fine in IE, FF and Chrome.

Of course you can do whatever you want inside myfun function.

More info: http://www.hunlock.com/blogs/Mastering_The_Back_Button_With_Javascript

Nes
  • 79
  • 1
  • 1
3

You can use localStorage or sessionStorage (http://www.w3schools.com/html/html5_webstorage.asp) to set a flag (instead of using a hidden form).

rerezz
  • 4,080
  • 1
  • 11
  • 4
  • 1
    its nice workaround but how do you it if he want to detect this every time , when i opened the site and closed it , i need to detect just when i opened and go back before closing – Albaz Mar 03 '18 at 09:21
2

Here's a jQuery version. I've run into needing to use it a few times due to the way Safari desktop/mobile handles the cache when a user presses the back button.

$(window).bind("pageshow", function(event) {
    if (event.originalEvent.persisted) {
        // Loading from cache
    }
});
Wes
  • 866
  • 10
  • 19
  • 1
    Doesn't appear to work in Chrome as far as I can tell. A console.log only gets triggered in the else statement (added for testing) regardless of how I access the page. – Luke Aug 22 '18 at 06:02
2

After hours down this rabbit hole, this is the solution that has finally worked best for me.

window.addEventListener('pageshow', function (event) {
    if (event.persisted || performance.getEntriesByType("navigation")[0].type === 'back_forward') {
        // DO STUFF HERE
    }
});

performance.getEntriesByType() does seem to be the correct way to do it, and for me it was working fine in both Edge and Firefox, but I was getting inconsistent results in Chrome. In some cases I would get a type of 'back_forward' as expected, but other times I would get whatever the original navigation type to that page was before navigating away and then coming back (i.e. 'navigate' or 'reload' instead of 'back_forward', even though the page had been reached through the back button).

event.persisted was working great for me in Chrome, but didn't seem to work at all in Edge.

Combining both has so far worked consistently for me in Chrome, Edge, and Firefox.

I was also able to get it to work in TypeScript by replacing the bit after the || with (performance.getEntriesByType("navigation")[0] as PerformanceNavigationTiming).type === 'back_forward'

(Only tested in Chrome 108, Edge 108, and Firefox 107)

GlenVaughan
  • 143
  • 1
  • 3
  • 11
2

As mentioned above, I had found a solution and am posting it here for reference and feedback.

The first stage of the solution is to add the following to the page:

<!-- at the top of the content page -->
<IFRAME id="page_is_fresh" src="fresh.html" style="display:none;"></IFRAME>
<SCRIPT style="text/javascript">
  function reload_stale_page() { location.reload(); }
</SCRIPT>

The contents of fresh.html are not important, so the following should suffice:

<!-- fresh.html -->
<HTML><BODY></BODY></HTML>

When client side code updates the page, it needs to flag the modification as follows:

function trigger_reload_if_user_clicks_back_button()
{
  // "dis-arm" the reload stale page function so it doesn't fire  
  // until the page is reloaded from the browser's cache
  window.reload_stale_page = function(){};

  // change the IFRAME to point to a page that will reload the 
  // page when it loads
  document.getElementById("page_is_fresh").src = "stale.html";
}

stale.html does all the work: When it is loaded it will call the reload_stale_page function which will refresh the page if necessary. The first time it is loaded (i.e. after the modification is made, the reload_stale_page function won't do anything.)

<!-- stale.html -->
<HTML><BODY>
<SCRIPT type="text/javascript">window.parent.reload_stale_page();</SCRIPT>
</BODY></HTML>

From my (minimal) testing at this stage, this seems to work as desired. Have I overlooked anything?

Tom
  • 42,844
  • 35
  • 95
  • 101
  • Not that I know of, but I haven't tested it fully. getElementById isn't supported in some older browsers, but that's easy to swap out for jquery or something if required. – Tom May 06 '09 at 12:19
1

Using this page, especially incorporating the comment by @sajith about the answer by @Nick White and this page: http://www.mrc-productivity.com/techblog/?p=1235

<form name="ignore_me" style="display:none">
    <input type="text" id="reloadValue" name="reloadValue" value="" />
</form>

$(function ()
{
    var date = new Date();
    var time = date.getTime();

    if ($("#reloadValue").val().length === 0)
    {
        $("#reloadValue").val(time);
    }
    else
    {
        $("#reloadValue").val("");
        window.location.reload();
    }
});
nmit026
  • 3,024
  • 2
  • 27
  • 53
0

I've run into a similar problem today and I solved it with localStorage (here with a bit of jQuery):

$(function() {

    if ($('#page1').length) {
        // this code must only run on page 1

        var checkSteps = function () {
            if (localStorage.getItem('steps') == 'step2') {
                // if we are here, we know that:
                // 1. the user is on page 1
                // 2. he has been on page 2
                // 3. this function is running, which means the user has submitted the form
                // 4. but steps == step2, which is impossible if the user has *just* submitted the form
                // therefore we know that he has come back, probably using the back button of his browser
                alert('oh hey, welcome back!');
            } else {
                setTimeout(checkSteps, 100);
            }
        };

        $('#form').on('submit', function (e) {
            e.preventDefault();
            localStorage.setItem('steps', 'step1'); // if "step1", then we know the user has submitted the form
            checkOrderSteps();
            // ... then do what you need to submit the form and load page 2
        });
    }

    if ($('#page2').length) {
        // this code must only run on page 2
        localStorage.setItem('steps', 'step2');
    }

});

Si basically:

On page 1, when the user submits the form, we set a value "steps" in localStorage to indicate what step the user has taken. At the same time, we launch a function with timeout that will check if this value has been changed (e.g. 10 times/second).

On page 2, we immediately change said value.

So if the user uses the back button and the browser restores page 1 in the exact state it was when we left it, the checkSteps function is still running, and able to detect that the value in localStorage has been changed (and can take appropriate action). Once this check has filled its purpose, there's no need to continue running it, so we simply don't use setTimeout anymore.

s427
  • 1,454
  • 5
  • 17
  • 33
0

Found this in a comment from someone else (thanks Mladen) - seems to work very well in Chrome 88.

if(String(window.performance.getEntriesByType("navigation")[0].type) === "back_forward"){
// Actions here, such as refresh page, etc.
}
Ben in CA
  • 688
  • 8
  • 22
  • 1
    Its deprecated. https://developer.mozilla.org/en-US/docs/Web/API/Performance/navigation – Dr. DS Jun 10 '21 at 15:45
  • @Dr.DS don't seems to me the answer was edited, and also seems that what you point out is not what the answer refer to. The deprecated method is another one which return number (0,1,2,99) this one that retunr string seems to me still supported – Skary Jul 07 '22 at 15:34
  • @Skary, window.performance.getEntriesByType("navigation")[0].type returns PerformanceNavigation.type (0, 1, 2, 255) and it is deprecated. Please refer https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigation/type – Dr. DS Jul 08 '22 at 18:07
  • @Dr.DS probably a problem of my browser, because i have just tested it (again right now) and window.performance.getEntriesByType("navigation")[0] will return 'PerformanceNavigationTiming ' object (i tested it in Chrome, Firefox and Opera). Then the property 'type' will get you a string representation of the object. None of those things are deprecated. In which browser did you have the bheaviour you mention? – Skary Jul 11 '22 at 11:00