2012-10-09

How to Set Hijax with Django and jQuery

The main principle of progressive enhancement is that the markup, styling, and Javascript are separated. The site should be functional and browsable without Javascript, but when Javascript is activated, it can add additional functionality to the elements of a page. For example, it can hijack the default behavior of normal links replacing it with Ajax loading of just a specific part of the page. This type of progressive enhancement was called Hijax by Jeremy Keit.

Hijax has the following benefits:

  • Visitors can browse the content on any browser and on any platform.
  • The content is indexable by search engines for each page.
  • Open graph lets you share each page on Facebook with specific title, description and image.

Let me show you an example of Hijax. Recently during my spare time I developed a simple collective storytelling game 1000 Words. Every participant at any point of a story has a chance to choose the continuation previously entered by someone else or to continue by his own words. It's like threaded comments where you see only one thread at a time.

All continuations are browsable without Javascript and each of them has Open Graph set, so Google can index all variations of the stories and any page can be shared on Facebook with custom title, description, and image. When Javascript is enabled (which is the usual case for human visitors), clicking on one of the given continuation choices doesn't refresh the full page, but rather loads the piece of the story and further possible continuations by Ajax and injects into a proper place. This way, I don't need to load all the history with each request and the visitor has more pleasant experience.

Django HttpRequest has a method to check if the call to a page was made by Ajax or by the normal browsing. It's the request.is_ajax() method. Depending on the call, the view of a page can provide different templates: one containing the full HTML with head and body, and another one just with the required snippet of HTML.

There are two ways to organize your templates for Hijax in Django without repeating yourself. One way is to include the Ajax snippet into the template of non-Ajax version.

# views.py
def my_view(request, ...):
    # ...
    template = "myapp/story.html"
    if request.is_ajax():
        template = "myapp/story_ajax.html"
    # ...   
    return render(request, template, context_dict)
{# story.html #}
{% extends "base.html" %}

{% block content %}
    {% include "myapp/story_ajax.html" %}
{% endblock %}
{# story_ajax.html #}
<div class="dynamic-content">
    ...
</div>

The other way to organize your templates for Hijax is extend different bases for normal and Ajax cases. The normal base would have all usual HTML and body, whereas the base Ajax template would just have empty block.

# views.py
def my_view(request, ...):
    # ...
    if request.is_ajax():
        context_dict['base_template'] = "base_ajax.html"
    # ...   
    return render(request, "myapp/story.html", context_dict)
{# story.html #}
{% extends base_template|default:"base.html" %}

{% block content %}
    <div class="dynamic-content">
        ...
    </div>
{% endblock %}
{# base_ajax.html #}
{% block content %}
{% endblock %}

Last but not least, it's time to add unobtrusive Javascript which replaces the default behavior of the links with Ajax calls. With jQuery it should be something like this:

// hijax.html
(function($, undefined) {
    $(document).ready(function() {
        // hijack all links with the css class "dynamic"
        $('a.dynamic').live('click', function() {
            $('#content').load($(this).attr('href'));
            return false;
        });
    });
})(jQuery);

This is not the exact code from the game, but it illustrates the concept of progressive enhancement very well.

2012-10-05

Quick Tip on Setuptools

You are probably used to the default usage of Setuptools, for example the following command installs the latest Django version into your python installation or currently active virtual environment:

easy_install django

Sometimes you might need to install a specific version of a module. This is an example of installing Django 1.3.3:

easy_install django==1.3.3
If you want to upgrade the existing installation to the latest version, you can do that with:
easy_install -U django