Styling autocompletes

A complete autocomplete widget has three parts you can style individually:

  • the autocomplete widget, rendered on the form,
  • the autocomplete box, fetched by ajax,
  • choices presented by both the autocomplete box and widget deck.

Note that a choice HTML element is copied from the autocomplete box into the deck uppon selection. It is then appended a “remove” element, that will remove the choice uppon click.

Styling choices

By default, choices are rendered by the choice_html() method. The result of this method will be used in the autocomplete box as well as in the widget deck. There are three easy ways to customize it:

Overriding AutocompleteBase.choice_html_format

The easiest and most limited way to change how a choice is rendered is to override the AutocompleteBase.choice_html_format attribute.

For example:

class OsAutocomplete(autocomplete_light.AutocompleteListBase):
    choices = ['Linux', 'BSD', 'Minix']
    choice_html_format = u'<span class="block os" data-value="%s">%s</span>'

This will add the class os to choices.

Overriding AutocompleteBase.choice_html()

Overriding AutocompleteBase.choice_html() enables changing the way choices are rendered.

For example:

class PersonAutocomplete(autocomplete_light.AutocompleteModelBase):
    choice_html_format = u'''
        <span class="block" data-value="%s"><img src="%s" /> %s</span>
    '''

    def choice_html(self, choice):
        return self.choice_html_format % (self.choice_value(choice),
            choice.profile_image.url, self.choice_label(choice))

Overriding AutocompleteTemplate.choice_template

Perhaps the coolest way to style choices is to use a template. Just set AutocompleteTemplate.choice_template. It is used by AutocompleteTemplate.choice_html:

class PersonAutocomplete(autocomplete_light.AutocompleteModelTemplate):
    choice_template = 'person_choice.html'

Now, all you have to do is create a person_choice.html template. Consider this elaborated example with image and links to the detail page and admin change form:

{% load i18n %}
{% load thumbnail %}

<span class="block person" data-value="{{ choice.pk }}">
    <img src="{% thumbnail choice.profile_image.url 50x50 crop %}" />

    <a href="{{ choice.get_absolute_url }}">
        {{ choice.first_name }} {{ choice.last_name }}
    </a>

    <a href="{% url 'admin:persons_person_change' choice.pk %}">
        {% trans 'Edit person' %}
    </a>

    {% if choice.company %}
    <a href="{{ choice.get_absolute_url }}">
        {{ choice.company }}
    </a>
    {% endif %}
</span>

First, the template loads the i18n template tags library which enables the {% trans %} template tag, useful for internationalization.

Then, it defines the <span> tag, this element is valid anywhere even if your autocomplete widget is rendered in a <table>. However, this <span> element has the block class which makes it display: block for space. Also, it adds the person class to enable specific CSS stylings. Finally it defines the data-value attribute. Note that the ``data-value`` is critical because it is what tells autocomplete.js that this element is a choice, and it also tells widget.js that the value is {{ choice.pk }} (which will be rendered before widget.js gets its hands on it of course).

Styling autocomplete boxes

By default, the autocomplete box is rendered by the autocomplete_html() method. The result of this method will be used to render the autocomplete box. There are many ways to customize it:

Overriding AutocompleteBase.autocomplete_html_format

The easiest and most limited way to change how a autocomplete is rendered is to override the AutocompleteBase.autocomplete_html_format attribute.

For example:

class OsAutocomplete(autocomplete_light.AutocompleteListBase):
    autocompletes = ['Linux', 'BSD', 'Minix']
    autocomplete_html_format = u'<span class="autocomplete-os">%s</span>'

This will add the autocomplete-os class to the autocomplete box.

Overriding AutocompleteBase.autocomplete_html

Overriding AutocompleteBase.autocomplete_html() enables changing the way autocompletes are rendered.

For example:

class PersonAutocomplete(autocomplete_light.AutocompleteModelBase):
    autocomplete_html_format = u'''
        <span class="autocomplete-os">
            <span class="count">%s Persons matching your query</span>
            %s
        </span>
    '''

    def autocomplete_html(self):
        html = ''.join(
            [self.choice_html(c) for c in self.choices_for_request()])

        if not html:
            html = self.empty_html_format % _('no matches found').capitalize()

        count = len(self.choices_for_request())
        return self.autocomplete_html_format % (count, html)

This will add a choice counter at the top of the autocomplete.

Overriding AutocompleteTemplate.autocomplete_template

Perhaps the coolest way to style an autocomplete box is to use a template. Just set AutocompleteTemplate.autocomplete_template. It is used by AutocompleteTemplate.autocomplete_html:

class PersonAutocomplete(autocomplete_light.AutocompleteModelTemplate):
    autocomplete_template = 'person_autocomplete.html'

Now, all you have to do is create a person_autocomplete.html template. Consider this elaborated example with user-friendly translated messages:

{% load i18n %}
{% load autocomplete_light_tags %}

{% if choices %}
    <h2>{% trans 'Please select a person' %}</h2>
    {% for choice in choices %}
        {{ choice|autocomplete_light_choice_html:autocomplete }}
    {% endfor %}
{% else %}
    <h2>{% trans 'No matching person found' %}</h2>
    <p>
        {% blocktrans %}Sometimes, persons have not filled their name,
        maybe try to search based on email addresses ?{% endblocktrans %}
    </p>
{% endif %}

First, it loads Django’s i18n template tags for translation. Then, it loads autocomplete-light’s tags.

If there are any choices, it will display the list of choices, rendered by choice_html() through the autocomplete_light_choice_html template filter as such: {{ choice|autocomplete_light_choice_html:autocomplete }}.

If no choice is found, then it will display a user friendly suggestion.

Styling widgets

Widgets are rendered by the render() method. By default, it renders autocomplete_light/widget.html. While you can override the widget template globally, there are two ways to override the widget template name on a per-case basis:

Using another template instead of a global override allows to extend the default widget template and override only the parts you need.

If you’re not sure what is in a widget template, please review part 2 of reference documentation about widget templates.

Also, note that the widget is styled with CSS, you can override or extend any definition of autocomplete_light/style.css.

AutocompleteModelTemplate

By default, AutocompleteModelTemplate sets choice_template to autocomplete_light/model_template/choice.html. It adds a “view absolute url” link as well as an “update form url” link based on YourModel.get_absolute_url() and YourModel.get_absolute_update_url() with such a template:

{% load i18n l10n %}
{% load static %}

{% spaceless %}
<span class="block" data-value="{{ choice.pk|unlocalize }}">
    {{ choice }}
    {% with choice.get_absolute_url as url %}
        {% if url %}
        <a href="{{ url }}" target="_blank" class="choice-detail">&rarr;</a>
        {% endif %}
    {% endwith %}

    {% with choice.get_absolute_update_url  as url %}
        {% if url %}
        <a href="{{ url }}" target="_blank" class="choice-update">
            <img src="{% static 'admin/img/icon_changelink.gif' %}" />
        </a>
        {% endif %}
    {% endwith %}
</span>
{% endspaceless %}

It does not play well in all projects, so it was not set as default. But you can inherit from it:

class YourAutocomplete(autocomplete_light.AutocompleteModelTemplate):
    model = YourModel
autocomplete_light.register(YourAutocomplete)

Or let the register() shortcut use it:

autocomplete_light.register(YourModel,
    autocomplete_light.AutocompleteModelTemplate)

Or set it as default with AutocompleteRegistry.autocomplete_model_base and used it as such:

autocomplete_light.register(YourModel)