Hi have the following jQuery event:
listItems.on('click', function(ev) {
ev.preventDefault();
reload = true;
var url = jQuery(this).attr('data-url');
history.pushState({}, '', url);
...
}
Which load dynamic content via AJAX and pushes history state to modify URL in browser from www.domain.com
to www.domain.com/page-x
. The only reason im loading content via AJAX is that i need to animate page transitions. If you go to www.domain.com/page-x
you get normal HTML page.
The problem occurs if after clicking listItem user clicks back button on it's browser. The url changes back from www.domain.com/page-x
to www.domain.com
, but the page doesn't reload. Thats why i added this event:
window.onpopstate = function() {
if (reload == true) {
reload = false;
location.reload();
}
}
It reloads page after url has changed, if current page was loaded via AJAX. It's working fine, but now i have other problem:
Any ideas/solutions would be highly appreciated.
Rather than using reload=true
you should rely on event.state
so that when popstate
occurs you get a snapshot of what you recorded for that URL.
Manipulating the browser history (MDN)
For example:
listItems.on('click', function(ev) {
ev.preventDefault();
var url = jQuery(this).attr('data-url');
history.pushState({ action: 'list-item-focused' }, '', url);
...
}
And then:
window.onpopstate = function(e) {
/// this state object will have the action attribute 'list-item-focused'
/// when the user navigates forward to the list item. Meaning you can
/// do what you will at this point.
console.log(e.state.action);
}
You probably should avoid a full page reload when the user hits back, and instead animate your content back that used to be there, that way you aren't trashing the page. You can then tell the difference between the landing page and the list pages by checking for event.state.action
and code your responses accordingly.
window.onpopstate = function(e) {
if ( e.state.action == 'list-item-focused') {
/// a list item is focused by the history item
}
else {
/// a list item isn't focused, so therefore landing page
/// obviously this only remains true whilst you don't
/// make further pushed states. If you do, you will have to
/// extend your popstate to take those new actions into account.
}
}
I'm sure you are aware of this, but pushState
isn't fully cross-browser, so you should also anticipate what could happen for users if these methods aren't supported.
Also, as you are using jQuery, it makes it quite easy for your to store further useful information in the state object, that may help you enhance your reactions in popstate
:
history.pushState({
action: 'list-item-focused',
listindex: jQuery(this).index()
});
You have to bear in mind that any data you store is serialised, meaning that it will most likely be converted to some form of non-interactive string or binary data. This means you can't store references to elements or other "live" instances; hence the fact I'm storing the list items index instead.
With the above you now know what action was occurring and on what list item, which you can retrieve at the other end by using:
listItems.eq(event.state.listindex)
MDN has this to say about what you should do onload
.
Reading the current state
When your page loads, it might have a non-null state object. This can happen, for example, if the page sets a state object (using pushState() or replaceState()) and then the user restarts her browser. When your page reloads, the page will receive an onload event, but no popstate event. However, if you read the history.state property, you'll get back the state object you would have gotten if a popstate had fired.
You can read the state of the current history entry without waiting for a popstate event using the history.state property like this:
Put simply they are just recommending that you should listen out for the current history.state
when the page loads, and act accordingly based on what your state.action
describes. This way you support both events that are triggered within a page's life-time i.e. popstate
and when a user directly jumps to a history state which causes a page load.
So with the above in mind it would probably be best to structure things like so:
window.onpopstate = function(e) {
reactToState(e.state);
}
window.onload = function(){
history.state && reactToState(history.state);
}
var reactToState = function(state){
if ( state.action == 'list-item-focused') {
/// a list item is focused by the history item
}
else {
/// a list item isn't focused, so therefore landing page
/// obviously this only remains true whilst you don't
/// make further pushed states. If you do, you will have to
/// extend your popstate to take those new actions into account.
}
}
I've used inline event listeners for simplicity and because your examples do too, however it would be advised to use the addEventListener
and attachEvent
(IE only) methods instead... or better still, because you are using jQuery.
jQuery(window)
.on('popstate', function(e) {
reactToState(e.originalEvent.state);
}
.on('load', function(){
history.state && reactToState(history.state);
}
;
Obviously in order to move between two states, you need to know what both those states are; this means having some way to record your current state — so you can compare with the new state and act accordingly. As you only have two states this may not be imperative, but I'd urge you to always be thinking forward about the possibility of having more complexity than you currently have.
I do not know the layout of your code, so it makes it tricky recommending where you should place variables and other such items. However, no matter how you store the information, it doesn't change the fact that it will make your life and code easier if you do:
var reactToState = function(state){
var currentState = reactToState.currentState || {};
if ( !currentState.action ) { currentState.action = 'start'; }
if ( state.action == 'list-item-focused') {
if ( currentState.action == 'start' ) {
/// here we know we are shifting from the start page to list-item
}
}
else {
if ( currentState.action == 'list-item-focused' ) {
/// here we know we are shifting from the list-item to the start
}
}
/// as the state will be global for your application there is no harm
/// in storing the current state on this function as a "static" attribute.
reactToState.currentState = state;
}
Better yet, if you're not averse to switch statements, you can make the above more readable:
var reactToState = function(state){
/// current state with fallback for initial state
var currentState = reactToState.currentState || {action: 'start'};
/// current to new with fallback for empty action i.e. initial state
var a = (currentState.action||'start');
var b = (state.action||'start');
switch ( a + ' >> ' + b ) {
case 'start >> list-item-focused':
/// animate and update here
break;
case 'list-item-focused >> start':
/// animate and update here
break;
}
/// remember to store the current state again
reactToState.currentState = state;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With