CodeIgniter + jQuery(ajax) + HTML5 pushstate: How can I make a clean navigation with real URLs? CodeIgniter + jQuery(ajax) + HTML5 pushstate: How can I make a clean navigation with real URLs? codeigniter codeigniter

CodeIgniter + jQuery(ajax) + HTML5 pushstate: How can I make a clean navigation with real URLs?


I've put up a successful minimal example of HTML5 history here: http://cairo140.github.com/html5-history-example/one.html

The easiest way to get into HTML5 pushstate in my opinion is to ignore the framework for a while and use the most simplistic state transition possible: a wholesale replacement of the <body> and <title> elements. Outside of those elements, the rest of the markup is probably just boilerplate, although if it varies (e.g., if you change the class on HTML in the backend), you can adapt that.

What a dynamic backend like CI does is essentially fake the existence of data at particular locations (identified by the URL) by generating it dynamically on the fly. We can abstract away from the effect of the framework by literally creating the resources and putting them in locations through which your web server (Apache, probably) will simply identify them and feed them on through. We'll have a very simple file system structure relative to the domain root:

/one.html/two.html/assets/application.js

Those are the only three files we're working with.

Here's the code for the two HTML files. If you're at the level when you're dealing with HTML5 features, you should be able to understand the markup, but if I didn't make something clear, just leave a comment, and I'll walk you through it:

one.html

<!doctype html><html>  <head>    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.js"></script>    <script src="assets/application.js"></script>    <title>One</title>  </head>  <body>    <div class="container">      <h1>One</h1>      <a href="two.html">Two</a>    </div>  </body></html>

two.html

<!doctype html><html>  <head>    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.js"></script>    <script src="assets/application.js"></script>    <title>Two</title>  </head>  <body>    <div class="container">      <h1>Two</h1>      <a href="one.html">One</a>    </div>  </body></html>

You'll notice that if you load one.html through your browser, you can click on the link to two.html, which will load and display a new page. And from two.html, you can do the same back to one.html. Cool.

Now, for the history part:

assets/application.js

$(function(){    var replacePage = function(url) {        $.ajax({            url: url,            type: 'get',            dataType: 'html',            success: function(data){                var dom = $(data);                var title = dom.filter('title').text();                var html = dom.filter('.container').html();                $('title').text(title);                $('.container').html(html);            }        });    }    $('a').live('click', function(e){        history.pushState(null, null, this.href);        replacePage(this.href);        e.preventDefault();    });    $(window).bind('popstate', function(){        replacePage(location.pathname);    });});

How it works

I define replacePage within the jQuery ready callback to do some straightforward loading of the URL in the argument and to replace the contents of the title and .container elements with those retrieved remotely.

The live call means that any link clicked on the page will trigger the callback, and the callback pushes the state to the href in the link and calls replacePage. It also uses e.preventDefault to prevent the link from being processed the normal way.

Finally, there's a popstate event that fires when a user uses browser-based page navigation (back, forward). We bind a simple callback to that event. Of note is that I couldn't get the version on the Dive Into HTML page to work for some reason in FF for Mac. No clue why.

How to extend it

This extremely basic example can more or less be transplanted onto any site because it does a very uncreative transition: HTML replacement. I suggest you can use this as a foundation and transition into more creative transitions. One example of what you could do would be to emulate what Github does with the directory navigation in its repositories. It's an intermediate manoever that requires floats and overflow management. You could start with a simpler transition like appending the .container in the loaded page to the DOM and then animating the old container to {height: 0}.

Addressing your specific "For example"

You're on the right track for using HTML5 history, but you need to clarify your idea of exactly what /foo and /bar will contain. Basically, you're going to have three pages: /, /foo, and /bar. / will have an empty container div. /foo will be identical to / except in that container div has some foo content in it. /bar will be identical to /foo except in that the container div has some bar content in it. Now, the question comes to how you would extract the contents of the container through Javascript. Assuming that your /foo body tag looked something like this:

<body>  <a href="/foo">foo</a>  <a href="/bar">bar</a>  <div class="container">foo</div></body>

Then you would extract it from the response data through var html = $(data).filter('.container').html() and then put it back into the parent page through $('.container').html(html). You use filter instead of the much more reasonable find because from some wacky reason, jQuery's DOM parser produces a jQuery object containing every child of the head and every child of the body elements instead of just a jQuery object wrapping the html element. I don't know why.

The rest is just adapting this back into the "vanilla" version above. If you are stuck at any particular stage, let me know, and I can guide you better though it.

Code

https://github.com/cairo140/html5-history-example


Try this in your controller:

if (!$this->input->is_ajax_request())    $this->load->view('header');$this->load->view('your_view', $data);if (!$this->input->is_ajax_request())    $this->load->view('footer');