history.js
History.js gracefully supports the HTML5 History/State APIs (pushState, replaceState, onPopState) in all browsers. Including continued support for data, titles, replaceState. Supports jQuery, MooTools and Prototype. For HTML5 browsers this means that you can modify the URL directly, without needing to use hashes anymore. For HTML4 browsers it wi…
History.js
I'm implementing a web site which sets its urls dynamically using History.js when new sections are loaded to the front page via ajax.
This seems to work well but there is a problem with the hash section in the url that History.js creates as a fallback in Internet Explorer.
Here are examples of links on the page, created using jquery:
function connect_browse_buttons(){
$('.browselink').each(function(){
$(this).click(function(){
var action = $(this).attr('name');
action = action.substring( ('action_browse').length );
browsetype = action;
if (isIE){
// remove data object and title to avoid use of SUIDs by History.js in IE
History.pushState(null, null, '/public/' + action);
} else {
History.pushState({oldurl: History.getState()['url']}, "MySite " + action, config.wwwroot + "public/" + action);
}
return false;
});
});
}
The .htaccess file redirects any urls such as http://mysite.com/public/category_a to http://mysite.com, where the javascript parses the url and loads the appropriate section via ajax requests in the changeState handler.
The javascript checks for urls such as
http://mysite.com/public/category_a
AND for equivalent fallback URLs created in Internet Explorer, i.e.
http://mysite.com/#public/category_a
That all works OK - So:
In Firefox, if I open the site at the root of the site, http://mysite.com, and click on a link as per above, the content loads (in the changeState handler), and the url is set by History.pushState as:
http://mysite.com/public/category_a
If I then click on another link the url is set as, for example:
http://mysite.com/public/category_b
In IE, if I open the site at the root of the site, and click on a link, as per above, the content loads, and the url is set with a hash as:
http://mysite.com/#public/category_a
If I then click on the next link, the url is set as:
http://mysite.com/#public/category_b
The problem arises when I open a page in IE that was bookmarked in Firefox, and doesn't have the hash in the url. Let's take our usual example:
http://mysite.com/public/category_a
If I open this url directly in IE, via a bookmark or by pasting the url in the browser address bar, the .htaccess redirects successfully, the url is parsed OK by the js file and the content loads. However, now if I click on the category_b link, the url is set by History.pushState to:
http://mysite.com/public/category_a#./category_b
What I really wanted was to set the url as:
http://mysite.com/#public/category_b
However, History.js seems to take the whole of the previous url as the base url for subsequent pushStates. I have tried setting absolute urls in History.pushState but without success. As you can see in the code block above, I have an IE-specific pushState statement. I have tried configuring this in various ways. How can I get History pushState to recognize:
http://mysite.com
as the base part of the url, which the hash section should be appended to?
Or is there a better way to approach this than the way I describe above?
Thanks in advance.
Source: (StackOverflow)
I am trying to get history.js to work in Internet Explorer because I need history.pushState() to work. I have read over the instructions on GitHub (https://github.com/browserstate/History.js/) and have tried implementing it, but havent had any success.
Here's what I have
<!DOCTYPE html>
<html>
<head>
<!-- jQuery -->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
<!-- History.js -->
<script defer src="http://balupton.github.com/history.js/scripts/bundled/html4+html5/jquery.history.js"></script>
<script type="text/javascript">
function addHistory(){
// Prepare
var History = window.History; // Note: We are using a capital H instead of a lower h
// Change our States
History.pushState(null, null, "mylink.html");
}
</script>
</head>
<body>
<a rel='nofollow' href="mylink.html">My Link</a>
<a rel='nofollow' href="otherlink.html">Other Link</a>
<button onclick="addHistory()" type="button">Add History</button>
</body>
Not sure what I'm doing wrong, but it's definitely not working in IE8 or IE9. It does work in Firefox, but that may be because Firefox actually supports history.pushstate to begin with. Any help is appreciated
Source: (StackOverflow)
It appears that this line in the statechange
event:
$('#content').load(State.data.home_page);
...is causing the same page to load multiple times which is resulting in glitches on my site. What can I do to fix this?
(function($) {
function loadHome() {
$('#content').load(site.url + '/ #primary', function() {
$(this).fadeIn(50);
});
}
// User event
$('.site-title a').on('click', function(e) {
e.preventDefault();
var newData = { home_page: site.url + '/ #primary' };
History.pushState(newData, site.title, site.url);
loadHome();
});
// History event
History.Adapter.bind(window, 'statechange', function(){
var State = History.getState();
$('#content').load(State.data.home_page);
});
})(jQuery);
Update
Ok, so I learned that .pushState
actually calls statechange
.
So basically, I'm thinking this is what's happening:
- User clicks on
.site-title a
pushState()
is called which calls statechange
which loads the part of the page.
- At the same time,
loadHome()
is also called which loads the same.
I can see that is why I am getting multiple instances of the same page. I thought statechange
is only called when the user clicks the back button on the browser. That kind of clears things up, but I'm still at a loss as to how to move forward with this. I'm not sure what to replace that .load
line with in the statechange
event, assuming that's the culprit.
Source: (StackOverflow)
I am using history.js to handle back button. In history.js statechange is firing whenever i do a pushstate. Why?
Source: (StackOverflow)
Like the title says, I'd like to be able to perform a different onstatechange
event if the pushState
function is called, instead of the back
function. Or, if the go
function is negative or positive.
Example:
if History.pushState()
or History.go(1)
are called, i want the statechange
event's callback to be forwardPushState
if History.back()
or History.go(-1)
are called, i want the statechange
event's callback to be backwardsPushState
Source: (StackOverflow)
I can set data into the State.data of History.js, like this:
var pushStateData = {};
function RetrieveSearchResults(type, url, searchData) {//, showResetButton,
controlToFocus, navDirection) {
pushStateData = {
SearchType : type,
SearchData : searchData,
};
RetrievePageResults(true, url, pushStateData);
}
function RetrievePageResults(pushNewUrl, url, pushStateData) {
navigationInProgress = true;
if (pushNewUrl) {
if (window.History) {
window.History.pushState(pushStateData, null, url);
}
$.get(url, pushStateData.SearchData, function (reply) {
$("#search-results").html(reply);
navigationInProgress = false;
});
}
If I set a breakpoint on the window.History.pushState statement, in Chrome, I can clearly see pushStateData has the desired values.
However, when I try to retrieve the data:
$(window).bind("statechange", function (e) {
if (!navigationInProgress) {
var State = window.History.getState();
if (window.console && window.console.log) {
console.log("popstate", State, window.location.href);
}
RetrievePageResults(false, State.cleanUrl, State.data);
}
});
When I set a breakpoint on the RetrievePageResults statement,
The State.data object no longer has any of the values I set. State.data is defined, and is not null, but it is an empty object without any apparent values.
Thanks,
Scott
Source: (StackOverflow)
I have the following test application: http://dev.driz.co.uk/ajax/
You can click the links to load in other pages using jQuery AJAX and there are two types, globalTabs and localTabs which load in content into different panels. NOTE: That each page is actually the full page but we only grab the content we want using jQuery find method.
To enhance this I am using the HTML5 History API (History.js for cross-browser).
The titles and urls change fine and the history stack is being pushed to successfully and when I use the back and forward buttons the url and title does revert back.
Now the complicated part is loading back in the content from the previous state and more complicated loading the correct type e.g. global or local ajax. To achieve this I am thinking I need to pass BOTH the data and the type to the push state so that it can be reused again for the popstate...
Can anyone help point me in the right direction? I have all the first parts working, just a case of getting this passing of the ajax type to the pushState and then reusing it with the popstate change.
To improve on this I rewriting it as follows to show my plan for passing the type and data:
NOTE: the uppercase History is because I'm using History.js
var App = {
init: function() {
$(document).ready(function() {
$('.globalTabs li a').live('click', function (e) {
e.preventDefault();
App.globalLoad( $(this).attr('href') );
});
$('.localTabs li a').live('click', function (e) {
e.preventDefault();
App.localLoad( $(this).attr('href') );
});
});
window.addEventListener('popstate', function(event) {
// Load correct content and call correct ajax request...
});
},
localLoad: function ( url ) {
$.ajax({
url: url,
success: function (responseHtml) {
$('.localContent').html($(responseHtml).find('#localHtml'));
$data = { $(responseHtml).find('#localHtml'), 'local'};
History.pushState($data, $(responseHtml).filter('title').text(), url);
}
});
},
globalLoad: function ( url ) {
$.ajax({
url: url,
success: function (responseHtml) {
$('.mainContent').html($(responseHtml).find('#globalHtml'));
$data = { $(responseHtml).find('#globalHtml'), 'global'};
History.pushState($data, $(responseHtml).filter('title').text(), url);
}
});
}
};
App.init();
UPDATE: To clarify this question, they're are two problems I seek help with.
1.) Get the back and forward buttons working with the ajax requests so they load the correct data back into the content area.
2.) Loading the content into the correct area by also passing the content with the pushState method so that it knows if it's a global div or local div request.
Source: (StackOverflow)
I am using history.js and I have an event handler
$(window).bind('statechange', function(){
// Loads the content representing the state via ajax
});
Most content changes are triggered by History.pushState([...])
when the user clicks a link or a button.
But in some cases the content change is managed by javascript directly (i.e. by changing, adding or removing a DOM element) and there is no need to load the content representing the new state via ajax.
Still, I need to push a state and change the url to reflect the new state, should the user hit reload or later want to use the back button etc.
So my question is: How do I push a state but avoid loading the content in some cases? Is there a kind of flag that can be passed to the statechange event handler or can I circumvent it altogether?
Source: (StackOverflow)
My code was working until I started using ng-include
. Now, everytime I'll go to a page that uses this directive, I'm getting stuck on the page. The back button has stopped working and you stay forever in a loop of the same page.
My website is running with Asp.NET MVC 5.
Some bits of my code:
HTML "host"
<div id="place">
<div data-ng-include="'/app/angular/views/place.html'"></div>
</div>
HTML place.html
<div>
<h1>{{place.PlaceName}}</h1>
<ul class="unstyled-list">
<li data-ng-repeat="hint in place.Hints">
<!-- more code -->
</li>
</ul>
</div>
JAVASCRIPT
$scope.place = { };
// 'maps' is a google maps plugin
maps.autoComplete({ success: function(result) {
// gets the result of the user search
History.pushState({ name: result.name, id: result.id }, result.name, '/place/' + result.id);
});
History.Adapter.bind(window, 'statechange', function () {
var state = History.getState();
// go to the server and get more info about the location
$mapService.getMetaInfo({id: state.data.id}).then(function (d) {
// get place info
$scope.place = d;
});
});
When I remove the ng-include
and replace it with the "raw" html, it works fine. This "infinite loop" happens only when ng-include
is added.
Source: (StackOverflow)
In an OSS web app, we have JS code that performs some Ajax update (uses jQuery, not relevant). After the page update, a call is made to the html5 history interface History.pushState
, in the following code:
var updateHistory = function(url) {
var context = { state:1, rand:Math.random() };
/* -----> bedfore the problem call <------- */
History.pushState( context, "Questions", url );
/* -----> after the problem call <------- */
setTimeout(function (){
/* HACK: For some weird reson, sometimes something overrides the above pushState so we re-aplly it
This might be caused by some other JS plugin.
The delay of 10msec allows the other plugin to override the URL.
*/
History.replaceState( context, "Questions", url );
}, 10);
};
[Please note: the full code segment is provided for context, the HACK part is not the issue of this question]
The app is i18n'ed and is using URL encoded Unicode segments in the URL's, so just before the marked problem call in the above code, the URL argument contains (as inspected in Firebug):
"/%D8%A7%D9%84%D8%A3%D8%B3%D8%A6%D9%84%D8%A9/scope:all/sort:activity-desc/page:1/"
The encoded segment is utf-8 in percent encoding. The URL in the browser window is: (just for completeness, doesn't really matter)
http://<base-url>/%D8%A7%D9%84%D8%A3%D8%B3%D8%A6%D9%84%D8%A9/
Just after the call, the URL displayed in the browser window changes to:
http://<base-url>/%C3%98%C2%A7%C3%99%C2%84%C3%98%C2%A3%C3%98%C2%B3%C3%98%C2%A6%C3%99%C2%84%C3%98%C2%A9/scope:all/sort:activity-desc/page:1/
The URL encoded segment is just mojibake, the result of using the wrong encoding at some level. The correct URL would've been:
http://<base-url>/%D8%A7%D9%84%D8%A3%D8%B3%D8%A6%D9%84%D8%A9/scope:all/sort:activity-desc/page:1/
This behavior has been tested on both FF and Chrome.
The history interface specs don't mention anything about encoded URL's, but I assume the default standard for URL formation (utf-8 and percent encoding etc) would apply when using URL's in function calls for the interface.
Any idea on what's going on here.
Edit:
I wasn't paying attention to the uppercase H in History - this code is actually using the History.js wrapper for the history interface. I replaced with a direct call to history.pushState
(notice the lowercase h) without going through the wrapper, and the code is working as expected as far as I can tell. The issue with the original code still stands - so an issue with the History.js library it seems.
Source: (StackOverflow)
Im using History.js (click) with jQuery in my code.
When loading new content via ajax i use:
History.pushState({my:stateobject},"newtitle","supernewurl");
This seems to work since the URL is updated in the browser.
However, I don't get it where I can hook up my code whenever a back or forward button is pressed. Which method/event is used for this?
Source: (StackOverflow)
Ember documentation states that it can be set to use the History API for routing rather than hash-based fragments by using:
App.Router.reopen({
location: 'history'
});
But I can find no mention of what will happen if a browser doesn't support the History API. Will it fall back to using a hash like History.js?
If not, should I check for History API support and switch history implementation to hash if it isn't supported?
Source: (StackOverflow)
I am using History.js plugin which support HTML 5 pushState and replaceState. The statechange is triggered when a user click back/forward button and when pushState/replaceState is used. I need to check whether statechange event is triggered from back/forward button or by using pushState/replaceState methods.
Source: (StackOverflow)
I am using JQuery History.js plugin to enable History API in HTML5 browsers and emulate in HTML4 browsers. I am using Ajaxify script to implement this plugin. I changed this script a little as shown:
var History, $, document;
function PrepareVariables() {
History = window.History,
$ = window.jQuery,
document = window.document;
}
function InitHistory() {
// Prepare Variables
var
/* Application Specific Variables */
//contentSelector = '#content,article:first,.article:first,.post:first',
contentSelector = '#navcontent';
$content = $(contentSelector), //.filter(':first'),
//contentNode = $content.get(0),
$menu = $('#menu,#nav,nav:first,.nav:first').filter(':first'),
activeClass = 'active selected current youarehere',
activeSelector = '.active,.selected,.current,.youarehere',
menuChildrenSelector = '> li,> ul > li',
completedEventName = 'statechangecomplete',
/* Application Generic Variables */
$window = $(window),
$body = $(document.body),
rootUrl = History.getRootUrl(),
scrollOptions = {
duration: 800,
easing: 'swing'
};
// Ensure Content
if ($content.length === 0) {
$content = $body;
}
// Internal Helper
$.expr[':'].internal = function (obj, index, meta, stack) {
// Prepare
var
$this = $(obj),
url = $this.attr('href') || '',
isInternalLink;
// Check link
isInternalLink = url.substring(0, rootUrl.length) === rootUrl || url.indexOf(':') === -1;
// Ignore or Keep
return isInternalLink;
};
// HTML Helper
var documentHtml = function (html) {
// Prepare
var result = String(html)
.replace(/<\!DOCTYPE[^>]*>/i, '')
.replace(/<(html|head|body|title|meta|script)([\s\>])/gi, '<div class="document-$1"$2')
.replace(/<\/(html|head|body|title|meta|script)\>/gi, '</div>');
// Return
return $.trim(result);
};
// Ajaxify Helper
$.fn.ajaxify = function () {
// Prepare
var $this = $(this);
// Ajaxify
//$this.find('a:internal:not(.no-ajaxy)').click(function (event) {
$this.find("a[data-isnav='0']").click(function (event) {
// Prepare
var
$this = $(this),
url = $this.attr('href'),
title = ($this.attr('title') || null);
// Continue as normal for cmd clicks etc
if (event.which == 2 || event.metaKey) {
return true;
}
// Ajaxify this link
History.pushState(null, title, url);
event.preventDefault();
return false;
});
// Chain
return $this;
};
// Ajaxify our Internal Links
$body.ajaxify();
// Hook into State Changes
$window.bind('statechange', function () {
// Prepare Variables
var
State = History.getState(),
url = State.url,
relativeUrl = url.replace(rootUrl, '');
// Start Fade Out
// Animating to opacity to 0 still keeps the element's height intact
// Which prevents that annoying pop bang issue when loading in new content
$content.animate({
opacity: 0
}, 800);
// Ajax Request the Traditional Page
callAjax("GetContent", {
URL: url /*typeOfHeader: contentType, argsdata: argdata*/
},
false,
function () {
var ops = $('#ops');
if (ops != null) ops.html('');
ShowProgress('');
//var now = (new Date()).getTime(); //Caching
//if (headerCache.exist(url)) {
// tDiff = now - headerCacheTime;
// if (tDiff < 3000) {
// setContentData(headerCache.get(url));
// return true;
// }
//}
},
function (d) {
//headerCache.set(url, d, null);
//cacheName = url;
HideProgress();
setContentData(d);
}, null);
// end ajax
}); // end onStateChange
}
(function (window, undefined) {
// Prepare our Variables
PrepareVariables();
// Check to see if History.js is enabled for our Browser
if (!History.enabled) {
return false;
}
// Wait for Document
$(function () {
InitHistory();
});
// end onDomLoad
})(window); // end closure
function UpdateHistory() {
var title = (document.title.trim().length > 0 ? document.title : null);
var url = window.location.href.replace(/^.*\/\/[^\/]+/, '');
var History = window.History;
History.replaceState(null, title, url);
$('a[data-isnav="0"').click(function () {
// Prepare
var
$this = $(this),
url = $this.attr('href'),
title = ($this.attr('title') || null);
// Continue as normal for cmd clicks etc
if (event.which == 2 || event.metaKey) {
return true;
}
// Ajaxify this link
History.pushState(null, title, url);
event.preventDefault();
return false;
});
}
function setContentData(d) {
var data = d.data;
// Fetch the scripts
//$scripts = $dataContent.find('.document-script');
//if ($scripts.length) {
// $scripts.detach();
//}
// Fetch the content
contentHtml = data;
if (!contentHtml) {
document.location.href = url;
return false;
}
// Update the menu
//$menuChildren = $menu.find(menuChildrenSelector);
//$menuChildren.filter(activeSelector).removeClass(activeClass);
//$menuChildren = $menuChildren.has('a[href^="' + relativeUrl + '"],a[href^="/' + relativeUrl + '"],a[href^="' + url + '"]');
//if ($menuChildren.length === 1) { $menuChildren.addClass(activeClass); }
// Update the content
$content.stop(true, true);
$content.html(contentHtml).ajaxify().css('opacity', 100).show(); /* you could fade in here if you'd like */
//Intialize other content
initContent();
// Update the title
//document.title = $data.find('.document-title:first').text();
//try {
// document.getElementsByTagName('title')[0].innerHTML = document.title.replace('<', '<').replace('>', '>').replace(' & ', ' & ');
//}
//catch (Exception) { }
// Add the scripts
//$scripts.each(function () {
// var $script = $(this), scriptText = $script.text(), scriptNode = document.createElement('script');
// if ($script.attr('src')) {
// if (!$script[0].async) { scriptNode.async = false; }
// scriptNode.src = $script.attr('src');
// }
// scriptNode.appendChild(document.createTextNode(scriptText));
// contentNode.appendChild(scriptNode);
//});
// Complete the change
if ($body.ScrollTo || false) {
$body.ScrollTo(scrollOptions);
} /* http://balupton.com/projects/jquery-scrollto */
$window.trigger(completedEventName);
// Inform Google Analytics of the change
if (typeof window._gaq !== 'undefined') {
window._gaq.push(['_trackPageview', relativeUrl]);
}
// Inform ReInvigorate of a state change
if (typeof window.reinvigorate !== 'undefined' && typeof window.reinvigorate.ajax_track !== 'undefined') {
reinvigorate.ajax_track(url);
// ^ we use the full url here as that is what reinvigorate supports
}
}
It is working fine and the content added on page using Ajax is added to previous state using UpdateHistory()
function. On some pages the state is updated successfully but on one page it is not updating the content when the page is accessed for the second time. I searched SO for all the similar questions but unable to get any solution. First I thought the problem is with Internet Explorer but then I tried it on Firefox but it didn't work. Please tell me what can be the reason?
UPDATE
It's working for URLs like:
http://localhost:13956/AppStore/App/2012/Install
But not for:
http://localhost:13956/AppStore
Source: (StackOverflow)