EzDevInfo.com

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

History.js hash fallbacks in Internet Explorer and pushState issue

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)

History.js not working in Internet Explorer

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)

Advertisements

statechange load is causing multiple loads of the same page

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?

enter image description here

(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)

Is there an alternative to History.js?

I'm looking for a polyfill for HTML5 History API. I don't understand why https://github.com/browserstate/History.js/ is widely popular since there are so many issues (as of June 27, 2012, there are 109 open issues reported on Github). What really annoys me is "statechage is triggered everytime pushState is called". I'd like to know, if there is a History API implementation for IEs (since all other browsers have implemented it already…).


Source: (StackOverflow)

Statechange is firing whenever i do a push-state

I am using history.js to handle back button. In history.js statechange is firing whenever i do a pushstate. Why?


Source: (StackOverflow)

Is there a way to tell what direction the state is going with history.js?

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)

HTML5 pushState using History.js. Trouble retrieving data from State.data

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)

Using the back and forward buttons with the History API

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)

How to change state without triggering statechange in history.js?

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)

AngularJS ng-include do HistoryJS stop working

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)

HTML5 History.pushState mangles URL's containing percent encoded non-Ascii (Unicode) chars

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)

How can I detect the back/forwards buttons being clicked when using History.js?

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)

Does Ember routing fall back to using a hash if browser doesn't support the History API?

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)

Differentiate Back/Forward click and History.pushState/replaceState in statechange event

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)

JQuery History.js plugin not replacing state of one page in both HTML4 and HTML5 browsers

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('<', '&lt;').replace('>', '&gt;').replace(' & ', ' &amp; ');
    //}
    //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)