Lose Those Hashbangs - A Simple Introduction to the HTML5 History API

Rate article down Rate article up 0 subscribe to cyberstream photos rss feed
Download the Opera: a fast, efficient, feature-packed, secure, personalizable web browser. The History API is one of my favorite parts of HTML5. It is a perfect solution to the problem of losing accessibility in JS-driven web applications. There were many heated debates about what role JS should play in web applications. Some developers were creating applications that were only functional in a browser with JS enabled. There are certain situations where I would agree that this is permissible. For example, there's no way a user with JS disabled can play JS-powered game. But to prevent a user without JS from using a normal web application like a social networking website is preposterous. Sure, they might not be able to enjoy the same seamless, animated functionality with JS disabled, but they deserve the website to work for them just as much as anyone else.

Some Practical Examples...

I'll give a practical illustration now to show you how the HTML5 History API is a real game changer. Up to this point, if you wanted to mark a virtual “page” in a Java-Script-driven application, you had to use a technique called fragment URIs. This technique capitalizes upon using the URL fragment to simulate changing the page URL without actually reloaded the page. Since the fragment of the URL refers to a location within the page, it can be modified without reloading the page. Some pages use the “hashbang”. Usually the fragment is ignored by search engines, but Googlebot will actually treat “hashbang URLs” as unique pages and index them as such. For example, website.com/page#!will/be/indexed, whereas website.com/page#will/not/be/indexed will not be indexed by Googlebot because it only has a number symbol, not followed by an exclamation mark.

The Problem

s I'll explain one of the biggest problems with fragment URIs with an hypothetical situation. Suppose you are browsing an “ajaxified” website that uses hashbang URLs. You see something on the page you really like, and you want to share it with your friend. You post the link to the page (e.g. sitename.com/#!/page_i_like) on Facebook, and one of your friends sees the page, and clicks on the link. The problem is, JS is off in his browser. So he loads the page, and he doesn't see what the other person saw because the site relies on JS to redirect to the page specified in the fragment.

The Solution

The HTML5 History API solves this predicament. In my opinion, the best way to build an accessible website is to build it so that it works in browsers with JS disabled. Then progressively enhance it with JS to make the functionality as seamless and animated as you want. This is possible with the HTML5 History API, which is why it is so cool!

Get to the Code Already!

Alright, enough talking. Let's do something now. There are two basic things you need to know. With the HTML5 History API, each “page” is represented by a history state. That history state holds any data you attach to it, a page title, and a URL. You can add an entry to the history state list with the history.pushState() function. Here's the syntax:
history.pushState(data, title, url) // the URL parameter is optional
When you push a new state into the history list, it will update the page title and URL (if specified) shown in the browser (Note: Opera Browser doesn't do anything with the title parameter in its implementation of the API yet). An important thing to notice is that the URL in the browser's address field will be updated regardless of whether the URL specified in pushState() is an actual page. Let's get started with our project:
<?php
// check that a GET parameter specifying the page to be displayed is defined
if ( isset($_GET['page']) && !empty($_GET['page']) ) {

    // display the appropriate page if it is “index”, “about”, or “contact”
    switch( strtolower(trim($_GET['page'])) ) {
        case 'index' :
            $title = 'Welcome to this Super-Cool HTML5-Powered Website!';
            $body = 'Hey friend, thanks for stopping by and checking out my little HTML5-powered website. Enjoy your visit!';
        break;
        case 'about' :
            $title = 'About Me...';
            $body = '<em>My autobiography</em>';
        break;
        case 'contact' :
            $title = 'Drop me a note!';
            $body = 'Use your visualization skills to see a contact form on this page.';
            break;
        default:
            exit ('Page not found!');
    }
} else header('Location: ?page=index'); // redirect to the index page by default
?>

<!DOCTYPE html>
<html>
    <head>
        <title>HTML5 History API | cyberstream.us</title>      
        
        <!--basic styles-->
        <style type="text/css">
            body { font-family: sans-serif; }            
            #container { padding: 10px 50px; }
            
            #navigation {
                padding: 5px;
                margin: 0;
                text-align: center;
            }
            
            
/* display items in the navigation list in one line */
ul#navigation li { list-style: none; display: inline; padding: 5px; }
/* reset the link styles */
a { text-decoration: none; font-weight: bold; color: #999; }
/* underline links (other than the active one) that are hovered over */
li:not(.active) a:hover { border-bottom: 2px solid #ccc; color: #555; } li.active a { color: #444; } </style> </head> <body> <div id="container"> <!--Output the title and paragraph text for the specified page--> <h1><?php echo $title ?></h1> <p><?php echo $body ?></p> </div> <?php // output the navigation bar with PHP so that we can apply a class of “active” ... // ... to the link to the current page $navigation = array('index' => 'Home', 'about' => 'About me', 'contact' => 'Contact Me'); echo '<ul id="navigation">'; foreach ($navigation as $key => $value) { if ($key == $_GET['page']) $active = 'class="active"'; else $active = ''; echo "<li $active> <a href="?page=$key" id="$key">$value</a> </li>"; } echo '</ul>'; ?> <!--import the jQuery library--> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script> </body> </html>
With this PHP and HTML code, the page works without JS enabled. Now let's progressively enhance it with JS.
<script>
    $(function() {           
     
        // this function will load the page specified in the “page” parameter
        function loadPage(page) {

           // create an array of the title and main text content of each page
            var pages = {
                index: ['Welcome to this Super-Cool HTML5-Powered Website!', 
                    'Hey friend, thanks for stopping by and checking out my little HTML5-powered website. Enjoy your visit!'],
                about: ['About Me...', '<em>My autobiography</em>'],
                contact: [ 'Drop me a note!', 'Use your visualization skills to see a contact form on this page.']
            }               

            // fade out the main page content
            $('#container').fadeOut('slow', function() {

                // update the page title and content
                $('h1').html(pages[page][0]);
                $('p').html(pages[page][1]);
   
                // remove the class of the currently active link
                $('.active').removeClass('active');

                // apply a class of “active” to the link to the currently active page
                $('#' + page).parent('li').addClass('active');

                // now that we have updated the page, fade it in
                $(this).fadeIn('slow')
            });
        }

        // this will run whenever one of the links in the navigation bar is clicked
        $('#navigation a').click(function(e) {
            // prevent the page from loading normally
            e.preventDefault()     

            // get the name of the page that is supposed to be loaded
            page_to_load = $(this).attr('id');

            // pass the name of the page to loaded to the loadPage() function ...
            // … which will load the new content into the page
            loadPage(page_to_load);

            // add this state to the history cache and update the page URL
            history.pushState(page_to_load, page_to_load, '?page=' + page_to_load )
        });

        // make the back button work in the browser
        window.addEventListener('popstate', function() {
            // when a page is revisited, load it by passing its name to the loadPage function
            loadPage(history.state);
        });
    });            
</script>
This is demonstrates how simple the HTML5 history API is. The API doesn't actually load content into the page, but we can do that with JS. You will notice that we added an event listener to the “popstate” event. If the user has already clicked on some links that were intercepted by JS , the page was loaded with JS, and an entry is added to the history cache, and then he clicks the back button, then the “popstate” event is fired. You can fetch the data that you pushed to the history cache in the first parameter of the the “pushState()” function via the history.state property. This is useful for identifying the page.

Detecting Support for the HTML5 History API

It is very simple to detect if the HTML5 history API is supported. The “History” object has existed for a long time, but HTML5 just added several methods and properties to it for the History API. “pushState()” is one of those methods they added. We can check for support for the API like this:
if (typeof History != 'undefined' && 'pushState' in history) {
      // the browser supports the HTML5 History API
} else {
      // the browser does NOT support it
}

Conclusion

This was a very simple introduction to the HTML5 History API, and I hope you enjoyed it and found it useful. Dev.Opera has a fantastic overview of the API that goes into more detail than I did here. Check out a demo of this code here.

Posted March 14, 2012 at 11:43AM by Eli Mitchell in HTML5, Javascript, API with 2 responses

Post a comment!

Name:
Your website: (optional)
E-mail:

Your comment: