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.