Making a global navigation autocomplete

This guide demonstrates how to make a global navigation autocomplete like on http://betspire.com.

Create the view

The global navigation autocomplete is generated by a normal view, with a normal template.

Then, you can just test it by openning /your/autocomplete/url/?q=someString

Only two things matter:

  • you should be able to define a selector for your options. For example, your autocomplete template could contain a list of divs with class “option”, and your selector would be ‘.option’.
  • each option should contain an url of course, to redirect the user when he selects a option

Actually, it’s not totally true, you could do however you want, but that’s a simple way i’ve found.

Once this works, you can follow to the next step. For your inspiration, you may also read the following example.

Example

Personnaly, I like to have an app called ‘project_specific’ where I can put my project-specific, non-reusable, code. So in project_specific/autocomplete.py of a project I have this:

from django import shortcuts
from django.db.models import Q

from art.models import Artist, Artwork

def autocomplete(request,
    template_name='project_specific/autocomplete.html', extra_context=None):
    q = request.GET['q'] # crash if q is not in the url
    context = {
        'q': q,
    }

    queries = {}
    queries['artworks'] = Artwork.objects.filter(
        name__icontains=q).distinct()[:3]
    queries['artists'] = Artist.objects.filter(
        Q(first_name__icontains=q)|Q(last_name__icontains=q)|Q(name__icontains=q)
        ).distinct()[:3]
    # more ...

    # install queries into the context
    context.update(queries)

    # mix options
    options = 0
    for query in queries.values():
        options += len(query)
    context['options'] = options

    return shortcuts.render(request, template_name, context)

And in project_specific/autocomplete.html:

{% load i18n %}
{% load thumbnail %}
{% load url from future %}
{% load humanize %}

<ul>
{% if artworks %}
    <li><em>{% trans 'Artworks' %}</em></li>
    {% for artwork in artworks %}
        <li class="artwork">
            <a href="{{ artwork.get_absolute_url }}">
                {% if artwork.first_image %}
                    <img src="{% thumbnail artwork.first_image 16x16 %}" style="vertical-align: middle" />
                {% endif %}
                {{ artwork }}
            </a>
        </li>
    {% endfor %}
{% endif %}
{% if artists %}
    <li><em>{% trans 'Artists' %}</em></li>
    {% for artist in artists %}
        <li class="artist">
            <a href="{{ artist.get_absolute_url }}">
                {% if artist.image %}
                    <img src="{% thumbnail artist.image 16x16 %}" style="vertical-align: middle" />
                {% endif %}

                {{ artist }}
            </a>
        </li>
    {% endfor %}
{% endif %}
{# more ...}

{% if not options %}
    <li><em>{% trans 'No options' %}</em></li>
    <li><a href="{% url 'haystack_search' %}?q={{ q|urlencode }}">{% blocktrans %}Search for {{ q }}{% endblocktrans %}</a></li>
{% endif %}

</ul>

In this template, my option selector is simply ‘li:has(a)’. So every <a> tag that is in an li with an a tag will be considered as a valid option by the autocomplete.

As for the url, it looks like this:

url(
    r'^autocomplete/$',
    views.autocomplete,
    name='project_specific_autocomplete',
),

So, nothing really special here ... and that’s what I like with this autocomplete. You can use the presentation you want as long as you have a selector for your options.

Create the input

Nothing magical here, just add an HTML input to your base template, for example:

<input type="text" name="q" id="main_autocomplete" />

Of course, if you have haystack or any kind of search, you could use it as well, it doesn’t matter:

<form action="{% url haystack_search %}" method="get">
    {{ search_form.q }}
</form>

Loading the script

If you haven’t done it already, load jQuery and the yourlabs_autocomplete extension, for example:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
<script src="{{ STATIC_URL }}autocomplete_light/autocomplete.js" type="text/javascript"></script>

Script usage

The last thing we need to do is to connect the autocomplete script with the input and the autocomplete view. Something like this would work:

<script type="text/javascript">
$(document).ready(function() {
    $('input#main_autocomplete').yourlabs_autocomplete({
        url: '{% url project_specific_autocomplete %}',
        zindex: 99999,
        id: 'main_autocomplete',
        iterablesSelector: 'li:has(a)',
        defaultValue: "{% trans 'Search : an artwork, an artist, a user, a contact...' %}",
    });
});
</script>

There are other options. If these don’t work very well for you, you should read autocomplete.js. It’s not a fat bloated script like jQueryUi autocomplete with tons of dependencies, so it shouldn’t be that hard to figure it out.

The other thing you want to do, is bind an event to the event yourlabs_autocomplete.selectOption, that is fired when the user selects an option by clicking on it for example:

<script type="text/javascript">
$(document).ready(function() {
    $('#search_bloc input[name=q]').bind('yourlabs_autocomplete.selectOption', function(e, option) {
        var autocomplete = $(this).yourlabs_autocomplete();

        // hide the autocomplete
        autocomplete.hide();

        // change the input's value to 'loading page: some page'
        autocomplete.el.val('{% trans 'loading page' %}: ' + $.trim(option.text()));

        // find the url of the option
        link = $(option).find('a:first');

        // if the link looks good
        if (link.length && link.attr('href') != undefined) {
            // open the link
            window.location.href = link.attr('href');
            return false;
        } else {
            // that should only happen during development !!
            alert('sorry, i dunno what to do with your selection!!');
        }
    });
});
</script>

That’s all folks ! Enjoy your fine global navigation autocomplete. Personnaly I think there should be one in the header of every project, it is just so convenient for the user. And if nicely designed, it is very ‘web 2.0’ whatever it means hahah.