It seems that nobody cares much for web2py, ajax and browser back buttons
:) Nevertheless, I made some progress and thought I'd post some findings
here If anyone searches the group with a similar goal.
Let me describe the problem once again. Imagine a scenario:
1) visitor is on Google, searches for a web2py powered site, finds it,
follows the link
2) site loads
3) visitor sees an internal link which loads a web2py component (ajax call)
with new content, follows the link, component loads
4) visitor reads the content, wants to go back, uses the browser back
button...
5) KABOOM, user is back on Google instead on the first page of the site
which is pretty bad as far as user experience goes.
Anyway, our site has a number of components that can work as regular pages
(.load and .html do the same thing) so what I really need is a way for a
component to tell the browser it has loaded. To solve this, i need to
manipulate the browser history manually.
Sadly, current state of things is pretty messy on the browser side. After
looking at what's available, I found several solutions but none of them
perfect. I decided to give up on Internet Explorer 8 and 9 completely.
Sorry folks, you're on your own. IE 10 *should* work as it comes out...
yes, I believe in Santa. :)
So, the first thing I tried was using the native browser's history API [1],
which all modern HTML5 browsers have (yes, forget IE9). It looks fine on
paper, but the implementation has some problems. Chromium / Chrome behaves
differently than Firefox/Opera/Safari in one crucial point (fires initial
onpopstate, I even managed to crash Chromium) [2], so I had to abandon
using this API directly. If it wasn't for this Chromium issue, it would be
the perfect solution.
Then I tried using the history.js [3]. This project has a long history of
successfully coping with ajax/browser history issues, so I was optimistic.
But sadly, history.js's master branch (version 1.7.1) also works in a way
that pushState and replaceState will immediately trigger onpopstate, which
is not what I wanted [4]. Luckily, the devel branch (1.8.0-dev) got an
additional State.internal [5] with this commit [6], so I can tell if
onpopstate is triggered by the browser's buttons or not.
Ok, long story is coming to an end. It's time for some code. This is what
I'm currently at:
- set the history entry in the component's controller:
response.js =
"History.pushState({isMyPageName:1},'','"+URL('some','thing',extension=False)+"')"
-at the end of layout.html:
<script>
(function(window,undefined){
var History = window.History;
if ( !History.enabled ) {
return false;
}
History.Adapter.bind(window,'statechange',function(){
var State = History.getState();
if (( State.data.isMyPageName == 1 ) && ( State.internal ==
false )) {
//alert("data: " + JSON.stringify(State.data) + "
title: " + State.title + " url: " + State.url + " internal: " +
State.internal);
window.location = State.url;
}
});
})(window);
</script>
Ok, that's about it... The solution is flaky at times and I still need to
manipulate document.title, haven't gotten to that yet. But it works, web2py
components can silently change the browser url as they load. And no &#&%/
URL hashes and hashbangs...
[1] https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history
[2]
https://github.com/balupton/history.js/wiki/The-State-of-the-HTML5-History-API
[3] https://github.com/balupton/history.js
[4] https://github.com/balupton/history.js/issues/47
[5] https://github.com/balupton/history.js/tree/dev
[6]
https://github.com/balupton/history.js/commit/c79c17f9801373f5218f144088c387f1935f3462