Integration with forms

The purpose of this documentation is to describe every element in a chronological manner.

It is complementary with the quick documentation.

Django startup

Registry

Autocomplete basics

Examples

Simple model autocomplete:

import autocomplete_light

from cities_light.models import City

autocomplete_light.register(City, search_fields=('search_names',),
    autocomplete_js_attributes={'placeholder': 'city name ..'})

Slightly advanced autocomplete:

import autocomplete_light
from cities_light.models import Country, City
from django.contrib.auth.models import User, Group


class AutocompleteTaggableItems(autocomplete_light.AutocompleteGenericBase):
    choices = (
        User.objects.all(),
        Group.objects.all(),
        City.objects.all(),
        Country.objects.all(),
    )

    search_fields = (
        ('username', 'email'),
        ('name',),
        ('search_names',),
        ('name_ascii',),
    )


autocomplete_light.register(AutocompleteTaggableItems)

API

There are many autocompletes you can use, just to name a few:

  • AutocompleteRestModelBase,
  • AutocompleteGenericTemplate,
  • AutocompleteModelTemplate,
  • AutocompleteChoiceListBase ...

Each of them should have tests in autocomplete_light/tests and at least one example app in test_project/.

Forms

Note

Due to Django’s issue #9321, you may have to use autocomplete_light.FixedModelForm instead of django.forms.ModelForm. Otherwise, you might see help text like ‘Hold down “Control” key ...’ for MultipleChoiceWidgets.

API

A more high level API is also available:

Page rendering

It is important to load jQuery first, and then autocomplete_light and application specific javascript, it can look like this:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
{% include 'autocomplete_light/static.html' %}

However, autocomplete_light/static.html also includes “remote.js” which is required only by remote channels. If you don’t need it, you could either load the static dependencies directly in your template, or override autocomplete_light/static.html:

{% load static %}
<script type="text/javascript" src="{% static 'autocomplete_light/autocomplete.js' %}"></script>
<script type="text/javascript" src="{% static 'autocomplete_light/widget.js' %}"></script>
<script type="text/javascript" src="{% static 'autocomplete_light/addanother.js' %}"></script>
<script type="text/javascript" src="{% static 'autocomplete_light/text_widget.js' %}"></script>
<script type="text/javascript" src="{% static 'autocomplete_light/remote.js' %}"></script>
<link rel="stylesheet" type="text/css" href="{% static 'autocomplete_light/style.css' %}" />

Or, if you only want to make a global navigation autocomplete, you only need:

<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>

To enable autocomplete form widgets, you need to load:

  • jQuery
  • autocomplete_light/autocomplete.js
  • autocomplete_light/widget.js

Optionally:

  • autocomplete_light/style.css
  • autocomplete_light/remote.js

A quick way to enable all this in the admin, is to replace template admin/base_site.html, ie.:

{% extends "admin/base.html" %}

{% block extrahead %}
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript"></script>
    {% include 'autocomplete_light/static.html' %}
{% endblock %}

Widget in action

Widget definition

The first thing that happens is the definition of an AutocompleteWidget in a form.

Example:

from django import forms
from django.contrib.auth.models import User
from cities_light.models import City
import autocomplete_light

from models import Profile


class ProfileForm(forms.ModelForm):
    user = forms.ModelChoiceField(User.objects.all(),
        widget=autocomplete_light.ChoiceWidget('UserAutocomplete'))

    cities = forms.ModelMultipleChoiceField(City.objects.all(),
        widget=autocomplete_light.MultipleChoiceWidget('CityAutocomplete'))

    class Meta:
        model = Profile

Widget rendering

This is what the default widget template looks like:

{% load i18n %}
{% load staticfiles %}
{% load autocomplete_light_tags %}
{% load url from future %}

<span class="autocomplete-light-widget {{ name }}
    {% if widget.widget_js_attributes.max_values == 1 %}single{% else %}multiple{% endif %}"
    id="{{ widget.html_id }}-wrapper"
    {{ widget.widget_js_attributes|autocomplete_light_data_attributes }}
    {{ widget.autocomplete_js_attributes|autocomplete_light_data_attributes:'autocomplete-' }}
    >

    {# a deck that should contain the list of selected options #}
    {{ choices }}
    <div id="{{ widget.html_id }}-deck" class="deck" >
        {% for choice in autocomplete.choices_for_values %}
            {{ choice|autocomplete_light_choice_html:autocomplete }}
        {% endfor %}
    </div>

    {# a text input, that is the 'autocomplete input' #}
    <input type="text" class="autocomplete" name="{{ name }}-autocomplete" id="{{ widget.html_id }}_text" value="" {{ extra_attrs }} />

    {# A link to add a new choice using a popup #}
    {% if autocomplete.add_another_url_name %}
    <a href="{% url autocomplete.add_another_url_name %}?_popup=1" class="autocomplete-add-another" id="add_{{ widget.html_id }}" style="display:none;">
            <img src="{% static 'admin/img/icon_addlink.gif' %}" width="10" height="10" alt="{% trans 'Add another' %}" />
        </a>
    {% endif %}

    {# a hidden select, that contains the actual selected values #}
    <select style="display:none" class="value-select" name="{{ name }}" id="{{ widget.html_id }}" multiple="multiple">
        {% for value in values %}
            <option value="{{ value }}" selected="selected">{{ value }}</option>
        {% endfor %}
    </select>

    {# a hidden div that serves as template for the 'remove from deck' button #}
    <div style="display:none" class="remove">
        {# This will be appended to choices on the deck, it's the remove button #}
        X
    </div>

    <div style="display:none" class="choice-template">
        {% comment %}
        the contained element will be used to render options that are added to the select
        via javascript, for example in django admin with the + sign

        The text of the option will be inserted in the html of this tag
        {% endcomment %}
        <div class="choice">
        </div>
    </div>
</span>

Javascript initialization

widget.js initializes all widgets that have bootstrap=’normal’ (the default), as you can see:

$('.autocomplete_light_widget[data-bootstrap=normal]').each(function() {
    $(this).autocompleteWidget();
});

If you want to initialize the widget yourself, set the widget or channel bootstrap to something else, say ‘yourinit’. Then, add to yourapp/static/yourapp/autocomplete_light.js something like:

$('.autocomplete_light_widget[data-bootstrap=yourinit]').each(function() {
    $(this).yourlabs_widget({
        getValue: function(choice) {
            // your own logic to get the value from an html choice
            return ...;
        }
    });
});

Also, load yourapp/autocomplete_light.js in your override of autocomplete_light/static.html.

You should take a look at the docs of autocomplete.js and widget.js, as it lets you override everything.

One interresting note is that the plugins (yourlabsAutocomplete and autocompleteWidget) hold a registry. Which means that:

  • calling someElement.autocompleteWidget() will instanciate a widget with the passed overrides
  • calling someElement.autocompleteWidget() again will return the widget instance for someElement

This is exactly what you need to use to make autocompletes that depend on each other.

Javascript cron

widget.js includes a javascript function that is executed every two seconds. It checks each widget’s hidden select for a value that is not in the widget, and adds it to the widget if any.

This is useful for example, when an item was added to the hidden select via the ‘+’ button in django admin. But if you create items yourself in javascript and add them to the select it would work too.

The reason for that is that adding and selecting an option from a select doesn’t trigger the javascript change event, which is a hudge pity.

Javascript events

When the autocomplete input is focused, autocomplete.js checks if there are enought caracters in the input to display an autocomplete box. If minimumCharacters is 0, then it would open even if the input is empty, like a normal select box.

If the autocomplete box is empty, it will fetch the autocomplete view. That view delegates the rendering of the autocomplete box to the registered autocomplete.

Then, autocomplete.js recognizes options with a selector. By default, it is ‘[data-value]’. This means that any element with a data-value attribute in the autocomplete html is considered a selectable choice.

When an option is selected, widget.js calls it’s method getValue() and adds this value to the hidden select. Also, it will copy the choice html to the widget.

When an option is removed from the widget, widget.js also removes it from the hidden select.