pushState() and popState(): manipulating browsers' history

52.8k views Asked by At

I am working on a small project in which I want to create an Ajax-style website. Content is loaded with jQuery's load(). As some of you know, the down side of this is that the URL does not change accordingly to the content that is displayed. To make this work you can use pushState(). Because this is not cross-browser supported, I used the plug-in history.js.

This is working quite nicely, but the problem is that my back and forward buttons are not working as they should and I have no idea what I am doing wrong. The URL is changing correctly and the content is as well, but the title is not changing at all. With title I mean what is shown as title of the window (or tab) of the browser window. I.e. the <title> of the page that is loaded OR the title attribute of the link that refers to that page (they are the same). I think it has to do with the fact that I call only a section of the page whose title I need (i.e. by adding #content to the load()). I still want the title of that page though. Here is a test case of what I have up to now. (Not available any more since 28 Dec. 2013.) jQuery file:

$(document).ready(function () {
    var firstLink = $("ul > li:first-child > a");
    var menuLink = $("ul > li > a");
    var firstLoadedHtml = firstLink.attr("href") + " #content";

    $("#sectionContainer").hide().load(firstLoadedHtml).fadeIn("fast");
    firstLink.addClass("active");   

    menuLink.click(function(e) {
        history.pushState(null, null, this.href);

        e.preventDefault();
        e.stopImmediatePropagation();

        var CurLink = $(this);
        var newLoadedHtml = CurLink.attr("href") + " #content";
        var oldSection = $(".contentPage");

        $("#sectionContainer").hide().load(newLoadedHtml).fadeIn("fast");
        menuLink.removeClass("active");
        CurLink.addClass("active");
    });

    $(window).bind('popstate', function() {
            var returnLocation = history.location || document.location;

            $('#sectionContainer').load(returnLocation + "#content");
    });
});

I also noted that when going back to the basic page (i.e. /sectionLoaderTest/) it goes blank for a while and only after a while the content of the first page is loaded. Is there a way to optimize this?

Additionally, I am using jQuery to add an active-class to the link that has been clicked. How can I make sure that this changes accordingly to the history? So that the correct link is highlighted when a user navigates back?

So the three questions are:

  1. Get the title working when going forward and backward
  2. Optimize the load time of the mainpage
  3. Fix the active-class when going forward and backward

All help welcome!

NOTE 1: I would like to build on history.js and my own script and as such keep support for < HTML5 browsers. I would prefer this because 1. I know this has to work, there is just something that's going wrong. 2. It would save time if I could see one's solution and implement it in the script I already wrote rather than figuring out a whole new script.

NOTE 2: The bounty will only be given to he or she who can answer all of my questions (3)!

2

There are 2 answers

7
noob On BEST ANSWER

Answer 1 - Get the title working when going forward and backward

As far as I understood you want to change the document title html from the link is loaded in a div. When you're loading a file in a div via jQuery .load you're just inserting the complete response text after an ajax request in it. So with all the stuff which shouldn't be in a div like the title or meta tag. However the title of the current document (http://bramvanroy.be/sectionLoaderTest/index.html) is defined in the title which is inside the head tag and not in the title tag inside a div so that's why the document title doesn't change.

So how can I fix it?

Good question, because the title element inside a div element is invalid, most browsers will remove it so you can't just use this:

document.title = $("#sectionContainer title").text();

because the title element may not exist.

So what we need is the direct response from the jQuery .load function. I can get it by adding the callback argument to the function:

$("#sectionContainer")
    .hide()
    .load(newLoadedHtml, function(responseText) {
        document.title = $(responseText).filter("title").text();
    })
    .fadeIn("fast");

What you may not understand is why I use the .filter and not the .find function. This is because jQuery is kind of parsing html, head and body tags out of the html but not removing there child elements.

Answer 2 - Optimize the load time of the mainpage

A document is getting loaded from to top to the bottom.

So first of all the css, JavaScript etc. should be loaded and then the the main document. Because jQuery is always waiting for the document before it's executing it wouldn't be a very bad idea to put all your script elements right before the end of the body, so that your HTML can be loaded before.

BtW I just had this problem at the first time after that everything was cached and the document was loading very fast.

Answer 3 - Fix the active-class when going forward and backward

I would say loop trough the href attributes of the a elements and compare it with the data from History.getState(). Should be easy.

You did some mistakes in your code - Your fixed code:

You appended the hash #content to all URLs.. it doesn't make sense and it will be removed by jQuery again: https://github.com/jquery/jquery/blob/master/src/ajax.js#L617 (Comes from lines: 158, 190, 337, 617 - rhash is on line 6)

$(document).ready(function () {
    var firstLink = $("ul > li:first-child > a");
    var menuLink = $("ul > li > a");
    var firstLoadedHtml = firstLink.attr("href");

    $("#sectionContainer").hide().load(firstLoadedHtml).fadeIn("fast");
    firstLink.addClass("active");   

    menuLink.click(function(e) {

        e.preventDefault();
        e.stopImmediatePropagation();

        var newLoadedHtml = $(this).attr("href");

        History.pushState(null, newLoadedHtml, newLoadedHtml);

        $("#sectionContainer")
            .hide()
            .load(newLoadedHtml, function(responseText) {
                document.title = $(responseText).filter("title").text();
            })
            .fadeIn("fast");
    });

    History.Adapter.bind(window, "statechange", function() {
        menuLink.removeClass("active");
        $("a[href='" + History.getState().title + "']").addClass("active");
        $('#sectionContainer').load(document.location.href, function(responseText) {
            document.title = $(responseText).filter("title").text();
        }); 
    });
});
0
Deepak Thomas On

You could also try available routing plugins like davis.js or jQuery Pusher https://github.com/salvan13/jquery-pusher