history.js
history.js copied to clipboard
Pushing a state with an anchor in the url fails
@markjaquith found a bug with History.js which prevents the statechange event from being fired if the pushed state contains a anchor in the url. It is known to affect Chrome.
As pushing a URL with a anchor won't work in a HTML4 browser, as they will already have a hash (the state!) we have to handle this specially. Here is the proposed solution:
Lets say I do: History.pushState(null,null,'?myState#myAnchor');
HTML5 Browser will get: mysite.com/?myState#myAnchor
HTML4 Browser will get: mysite.com/#?myState&_anchor=myAnchor
In statechange, both will get:
History.getState().anchor === 'myAnchor'
Which they can then choose to scroll to if they wish in the own code using something like:
if ( History.getState().anchor && $.fn.ScrollTo ) $('#'+History.getState().anchor).ScrollTo();
This is the solution adopted by jQuery Ajaxy, as seen by the implementation on the balupton.com website: http://balupton.com/#/welcome?anchor=home-featured
This is actually going to be a really big update. It will require the elimination of the anchorchange
event and also require the change in issue #49 to be completed.
The statechange
handler would look something like this:
$(window).bind('statechange',function(){
var
State = History.getState(),
anchor = State.anchor,
same = State.same,
url = State.url;
// Load In Content
if ( same ) {
if ( anchor && $.fn.ScrollTo ) {
$('#'+anchor).ScrollTo();
}
return;
}
else {
$.ajax({
url: url,
success: function(data){
$content.html(data);
if ( $.fn.ScrollTo ) {
if ( anchor ) {
$('#'+anchor).ScrollTo();
}
else {
$content.ScrollTo();
}
}
},
error: function(){
document.location.href = url;
}
});
}
});
To add onto this.
The new flags are completed for HTML5 browsers - though in persistance mode I am having some issues.
For HTML4 browsers there will be a counter-intuitive but necessary change:
- Say if I'm on the state: http://mysite.com/#/page/one
- And I say I do:
document.location.hash = 'top';
which will go here: http://mysite.com/#top - That will be transformed into: http://mysite.com/#/page/one?_anchor=top
- And the http://mysite.com/#top state will be discarded
This sound good to me and I am in need of it.
I'm thinking of writing a function to recursively strip #s from the state object prior to a push (or replace). I have some high-priority HTML4 devices that are causing the "states / fragments with hashes are unsupported" message, and unfortunately have a deadline.
For anyone interested, here's how I've temporarily fixed this issue: ` function trimHistoryState(state) { var index, array, object, type = $.type(state); /* END: var */ if (type === 'string' && state.length > 0) { index = state.indexOf('#'); if (index !== -1) { state = state.substring(0, index); } } else if (type === 'array' && state.length > 0) { array = []; $.each(state, function(index, value) { array.push(trimHistoryState(value)); }); state = array; } else if (type === 'object') { object = {}; $.each(state, function(key, value) { object[key] = trimHistoryState(value); }); state = object; } return state; }
_pushState = History.pushState;
History.pushState = function() {
arguments = $.makeArray(arguments);
if (arguments.length >= 1) {
arguments[0] = trimHistoryState(arguments[0]);
}
if (arguments.length >= 3) {
arguments[2] = trimHistoryState(arguments[2]);
}
_pushState.apply(History, arguments);
}
_replaceState = History.replaceState;
History.replaceState = function() {
arguments = $.makeArray(arguments);
if (arguments.length >= 1) {
arguments[0] = trimHistoryState(arguments[0]);
}
if (arguments.length >= 3) {
arguments[2] = trimHistoryState(arguments[2]);
}
_replaceState.apply(History, arguments);
}
`
This requires jQuery (hence the $
s) and it seems to be working okay. I put this in my own code, and run it after the HistoryJS library is loaded but before I start using it.
This is definitely not a long term solution, and probably breaks other things, but it'll do me until the 1.8 release that solves all the world's hunger problems with a piece of string. :)
I tried this solution, but I need the page to scroll to my anchor, not just strip them out. In the end I did it this way:
$(window).bind('anchorchange',function (event) {
var state = History.getState().hash,
stateWithoutHash,
index = state.indexOf('#');
if (index === -1) {
window.location.reload(); //there is no hash in the current state but there is in the window, so this should fire a reload
} else {
stateWithoutHash = state.substring(0, index);
}
if(window.location.pathname !== stateWithoutHash) {
window.location.reload(); //the url does have a hash, but it's not on this page, it's on a different page, so we need to reload
}
});
This fires when the anchor changes (which is what happens when you try and go to a url with an anchor) and then (unless this anchor is on the current page/state, it forces a reload of the page. It's not pretty but it works!
I use this code to fix this issue :
History.Adapter.bind(window, "anchorchange", function(){
var State = History.getLastStoredState();
History.saveState(State);
History.Adapter.trigger(window, "statechange");
});
History.Adapter.bind(window, "statechange", function(){
var State = History.getState(),
url = State.url,
hashIndex = url.indexOf('#'),
hash = '';
// ....
// and in the ajax load function
....
if (hashIndex > 0) {
hash = url.substr(hashIndex + 1);
win.scrollIntoView(hash); // Use your scroll function
}
....
});
Is this way accepted?
Looks better than what I did! I need to look in more detail....
I'm having the same issue. I couldn't get kiddo13's code to work.
Hi, any progress on this? Still experiencing the issue on Chrome