25

I'm working on a site that serves content via AJAX.

If you click an item in the menu, a content div gets updated with $.get response, nothing fancy.

I'm implementing history.pushState to allow navigation with the browser's back/forward button.

I have the following to load content on history navigation:

$(function() {
    $(window).bind("popstate", function() {
        $.getScript(location.href); 
    });
});

The problem is, when a page is loaded for the first time, this function does $.getScript so the page is loaded immediately again. The first time the page is loaded it renders the initial HTML view, then on the second load it renders the JS view, since its a JS request.

How could I prevent this event from firing on pages with HTML requests?

Simon East
  • 55,742
  • 17
  • 139
  • 133
jassa
  • 20,051
  • 4
  • 26
  • 24
  • Note: history.popstate is called once - immediately on first page load : and this is by design. (checked in Chrome) – Ujjwal Singh Dec 11 '12 at 09:44

5 Answers5

38

Using the native HTML5 History API you're going to run into some problems, every HTML5 browser handles the API a little bit differently so you can't just code it once and expect it to work without implementing workarounds. History.js provides a cross-browser API for the HTML5 History API and a optional hashchange fallback for HTML4 browsers if you want to go down that route.

For upgrading your website into a RIA/Ajax-Application you can use this code snippet: https://github.com/browserstate/ajaxify

Which is part of the longer article Intelligent State Handling which goes into explanations about hashbang, hashes and the html5 history api.

Let me know if you need any further help :) Cheers.

balupton
  • 47,113
  • 32
  • 131
  • 182
  • 1
    Thanks for mentioning History.js. I was about to implement pushState into our framework and would have probably ended up tearing my hair out. – andrew Feb 21 '12 at 19:51
  • 4
    Shameless advertising of your own plugin... tsk tsk... Just kidding History.js has saved my life (and a ton of hours of work) on many projects now! – xyhhx Nov 08 '12 at 20:28
  • 1
    Even hasherjs does the job fine. Its running perfectly good in our current production system. – sandeep talabathula Apr 17 '14 at 04:41
  • 1
    "every HTML5 browser handles the API a little bit differently": I'm so glad HTML5 unified the web dev experience and finally got us to Standardsville in one piece. /s – JoeBrockhaus Jan 13 '15 at 21:41
23

You need to get the event.originalEvent

// Somewhere in your previous code
if (history && history.pushState) {
  history.pushState({module:"leave"}, document.title, this.href);
}


$(window).bind("popstate", function(evt) {
  var state = evt.originalEvent.state;
  if (state && state.module === "leave") {
    $.getScript(location.href);
  }
});
mech
  • 674
  • 1
  • 6
  • 14
  • Doesn't testing for the existence of `history.pushState` imply the existence of `history`? Can anyone say why testing for `history.pushState` would not be enough (use case)? – Oliver Schafeld Apr 14 '16 at 13:46
  • @OliverSchafeld just seeing this. If you try to reference a property of `history`, but `history` is not an object, it will throw an error. So you have to to verify that `history` exists (and is an object, if you're not sure) before attempting to use (or check for) its properties. – Brendan Gannon Sep 13 '17 at 20:59
8

When the popstate event is fired on page load it will not have a state property in the event object. This allows you to check if the event is fired for a page load or not.

window.onpopstate = function (event) {
  if (event.state) {
    // do your thing
  } else {
    // triggered by a page load
  }
}
Oliver Nightingale
  • 1,805
  • 1
  • 17
  • 22
4

When the browser first loads, it always fires a popstate event. So you need to determine if this popstate is yours or not.

When you do your pushState, make sure you have a state object. That way you can check it later.

Then on the popstate handler, check the state object :)

$(function() {
    $(window).bind("popstate", function(data) {
        if (data.state.isMine)
            $.getScript(location.href); 
    });
});

// then add the state object
history.pushState({isMine:true},title,url);

Let me know if you need any more help :)

sydlawrence
  • 1,882
  • 2
  • 17
  • 21
1

I use the below approach to change the address bar and save the current state including the current HTML body, and I reload it on the back button click without any other AJAX call. All gets saved in your browser:

<script type="text/javascript">
   $(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;
   };
</script>
Mime
  • 1,142
  • 1
  • 9
  • 20
ghooz
  • 61
  • 1
  • 2