Using the back and forward buttons with the History API
Asked Answered
B

2

8

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.

Baa answered 17/8, 2012 at 9:36 Comment(0)
E
12

the data object is wrong, that is why you get that problem. An object is defined by key,values pair:

object = {key:'value'};
$data = { text:$(responseHtml).find('#globalHtml'), type:'global', url:'the_url'};

not

$data = { $(responseHtml).find('#globalHtml'), 'global'};

Using the object definition you will get your data back using History.getState()

History.Adapter.bind(window,'statechange',function(){
    var data = History.getState().data;
    if(data){
        var html = data.text;
        var type = data.type;
        var type = data.url;
        if(html && type){
            if(type == 'global'){
                // just change html
                $('.mainContent').html(html);
                // call the App object's function
                if(url) App.localLoad(url);
            }else{
                $('.localContent').html(html);
            }
        }
    }
})

NOTE:

  • when statechange event get's fired if any data is present it will fire the ajax calls
  • to simulate the ajax call without making it just change the title also, I think that will cover it

EDIT

  • the data object is actually taken from History.getState().data not History.getState()
  • added another param to the stored object to preserve url
  • using the url and the type you can make a call to the same function as in the onClick event

PROBLEM:

Uncaught TypeError: Converting circular structure to JSON

An object is referencing itself somewhere; hence the message "circular structure.". That means you are trying to stringify a object that has a reference to itself, this usualy happens with DOM elements. You should really consider changing

$data = { $(responseHtml).find('#localHtml'), 'local'};

to:

$data = { html:$(responseHtml).find('#localHtml').html(), type:'local'};

Notice the keys for the object (html and type) and the html function call (.html()), this way you wont send unnecessary data, just the html. If you need to use the html data when loading it as a DOM object just use this:

var DOM_Element_loaded = $('<div>').html(html_loaded);

now you can use DOM_Element_loaded to search/manipulate data in the html loaded without needing to attach it to the body.

Einkorn answered 25/8, 2012 at 7:45 Comment(5)
And how do I fire that off then? You don't mention popstate? As what I need to do is change the content and then title (effectively replicating EXACTLY what the ajax call did) when a user uses the back and forward buttons, but instead of ajax it will be using the history data as no point reloading it back in obviously.Baa
I also get the error: Uncaught TypeError: Converting circular structure to JSON Any ideas what's wrong? dev.driz.co.uk/ajaxBaa
edited my response, about the json error, just check that you pass simple objects, with no functionsEinkorn
One last question. The statechange event happens when you do pushState so effectively you do the ajax call or content change TWICE! Once in the click event and then again as the state has changed. Is it possible to separate this? As obviously this causes issues. Thanks.Baa
I am thinking to add a attr to the master div "page-title='title'" and check if the attr matches the curent title, if yes, then don't load itEinkorn
D
0

NOTE: this is really more of a comment than an answer, but you get better syntax highlighting with answer responses.

This is what I did when I ran into this problem. If I remember correctly, this prevented the double-request problem.

window.onpopstate = function(event) {
  if (event.state && event.state.initialHref) {
    // make an AJAX request
    // URL for AJAX request is event.state.initialHref
  }
}

When I use history.pushState() / history.replaceState(), I am defining an initialHref property in the object for the 1st argument. I am using an absolute URL for this value, not a relative one.

Dig answered 31/8, 2012 at 19:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.