Voodoo black magic¶
This cookbook is a work in progress. Please report any error or things that could be explained better ! And make pull requests heh …
High level Basics¶
Various cooking recipes your_app/autocomplete_light_registry.py
:
# This actually creates a thread safe subclass of AutocompleteModelBase.
autocomplete_light.register(SomeModel)
# If NewModel.get_absolute_url or get_absolute_update_url is defined, this
# will look more fancy
autocomplete_light.register(NewModel,
autocomplete_light.AutocompleteModelTemplate)
# Extra **kwargs are used as class properties in the subclass.
autocomplete_light.register(SomeModel,
# SomeModel is already registered, re-register with custom name
name='AutocompleteSomeModelNew',
# Filter the queryset
choices=SomeModel.objects.filter(new=True))
# It is possible to override javascript options from Python.
autocomplete_light.register(OtherModel,
attrs={
# This will actually data-autocomplete-minimum-characters which
# will set widget.autocomplete.minimumCharacters.
'data-autocomplete-minimum-characters': 0,
'placeholder': 'Other model name ?',
}
)
# But you can make your subclass yourself and override methods.
class YourModelAutocomplete(autocomplete_light.AutocompleteModelTemplate):
template_name = 'your_app/your_special_choice_template.html'
attrs = {
'data-mininum-minimum-characters': 4,
'placeholder': 'choose your model',
}
widget_attrs = {
# That will set widget.maximumValues, naming conversion is done by
# jQuery.data()
'data-widget-maximum-values': 6,
'class': 'your-custom-class',
}
def choices_for_request(self):
""" Return choices for a particular request """
self.choices = self.choices.exclude(extra=self.request.GET['extra'])
return super(YourModelAutocomplete, self).choices_for_request()
# Just pass the class to register and it'll subclass it to be thread safe.
autocomplete_light.register(YourModel, YourModelAutocomplete)
# This will subclass the subclass, using extra kwargs as class attributes.
autocomplete_light.register(YourModel, YourModelAutocomplete,
# Again, registering another autocomplete for the same model, requires
# registration under a different name
name='YourModelOtherAutocomplete',
# Extra **kwargs passed to register have priority.
choice_template='your_app/other_template.html')
Various cooking recipes for your_app/forms.py
:
# Use as much registered autocompletes as possible.
SomeModelForm = autocomplete_light.modelform_factory(SomeModel,
exclude=('some_field'))
# Same with a custom autocomplete_light.ModelForm
class CustomModelForm(autocomplete_light.ModelForm):
# autocomplete_light.ModelForm will set up the fields for you
some_extra_field = forms.CharField()
class Meta:
model = SomeModel
# Using form fields directly in any kind of form
class NonModelForm(forms.Form):
user = autocomplete_light.ModelChoiceField('UserAutocomplete')
cities = autocomplete_light.ModelMultipleChoiceField('CityAutocomplete',
widget=autocomplete_light.MultipleChoiceWidget('CityAutocomplete',
# Those attributes have priority over the Autocomplete ones.
attrs={'data-autocomplete-minimum-characters': 0,
'placeholder': 'Choose 3 cities ...'},
widget_attrs={'data-widget-maximum-values': 3}))
tags = forms.TextField(widget=autocomplete_light.TextWidget('TagAutocomplete'))
Low level basics¶
This is something you probably won’t need in the mean time. But it can turn out to be useful so here it is.
Various cooking recipes for autocomplete.js
, useful if you want to use it
manually for example to make a navigation autocomplete like facebook:
// Use default options, element id attribute and url options are required:
var autocomplete = $('#yourInput').yourlabsAutocomplete({
url: '{% url "your_autocomplete_url" %}'
});
// Because the jQuery plugin uses a registry, you can get the autocomplete
// instance again by calling yourlabsAutocomplete() again, and patch it:
$('#country').change(function() {
$('#yourInput').yourlabsAutocomplete().data = {
'country': $(this).val();
}
});
// And that's actually how to do chained autocompletes.
// The array passed to the plugin will actually be used to $.extend the
// autocomplete instance, so you can override any option:
$('#yourInput').yourlabsAutocomplete({
url: '{% url "your_autocomplete_url" %}',
// Hide after 200ms of mouseout
hideAfter: 200,
// Choices are elements with data-url attribute in the autocomplete
choiceSelector: '[data-url]',
// Show the autocomplete after only 1 character in the input.
minimumCharacters: 1,
// Override the placeholder attribute in the input:
placeholder: '{% trans 'Type your search here ...' %}',
// Append the autocomplete HTML somewhere else:
appendAutocomplete: $('#yourElement'),
// Override zindex:
autocompleteZIndex: 1000,
});
// Or any method:
$('#yourInput').yourlabsAutocomplete({
url: '{% url "your_autocomplete_url" %}',
choiceSelector: '[data-url]',
getQuery: function() {
return this.input.val() + '&search_all=' + $('#searchAll').val();
},
hasChanged: function() {
return true; // disable cache
},
});
// autocomplete.js doesn't do anything but trigger selectChoice when
// an option is selected, let's enable some action:
$('#yourInput').bind('selectChoice', function(e, choice, autocomplete) {
window.location.href = choice.attr('href');
});
// For a simple navigation autocomplete, it could look like:
$('#yourInput').yourlabsAutocomplete({
url: '{% url "your_autocomplete_url" %}',
choiceSelector: 'a',
}).input.bind('selectChoice', function(e, choice, autocomplete) {
window.location.href = choice.attr('href');
});
Using widget.js is pretty much the same:
$('#yourWidget').yourlabsWidget({
autocompleteOptions: {
url: '{% url "your_autocomplete_url" %}',
// Override any autocomplete option in this array if you want
choiceSelector: '[data-id]',
},
// Override some widget options, allow 3 choices:
maximumValues: 3,
// or method:
getValue: function(choice) {
return choice.data('id'),
},
});
// Supporting dynamically added widgets (ie. inlines) is
// possible by using "solid initialization":
$(document).bind('yourlabsWidgetReady', function() {
$('.your.autocomplete-light-widget[data-bootstrap=your-custom-bootstrap]').live('initialize', function() {
$(this).yourlabsWidget({
// your options ...
})
});
});
// This method takes advantage of the default DOMNodeInserted observer
// served by widget.js
There are some differences with autocomplete.js:
- widget expect a certain HTML structure by default,
- widget options can be overridden from HTML too,
- widget can be instanciated automatically via the default bootstrap
Hence the widget.js HTML cookbook:
<!--
- class=autocomplete-light-widget: get picked up by widget.js defaults,
- any data-widget-* attribute will override yourlabs.Widget js option,
- data-widget-bootstrap=normal: Rely on automatic bootstrap because
if don't need to override any method, but you could change
that and make your own bootstrap, enabling you to make
chained autocomplete, create options, whatever ...
- data-widget-maximum-values: override a widget option maximumValues, note
that the naming conversion is done by jQuery.data().
-->
<span
class="autocomplete-light-widget"
data-widget-bootstrap="normal"
data-widget-maximum-values="3"
>
<!--
Expected structure: have an input, it can set override default
autocomplete options with data-autocomplete-* attributes, naming
conversion is done by jQuery.data().
-->
<input
type="text"
data-autocomplete-minimum-characters="0"
data-autocomplete-url="/foo"
/>
<!--
Default expected structure: have a .deck element to append selected
choices too:
-->
<span class="deck">
<!-- Suppose a choice was already selected: -->
<span class="choice" data-value="1234">Option #1234</span>
</span>
<!--
Default expected structure: have a multiple select.value-select:
-->
<select style="display:none" class="value-select" name="your_input" multiple="multiple">
<!-- If option 1234 was already selected: -->
<option value="1234">Option #1234</option>
</select>
<!--
Default expected structure: a .remove element that will be appended to
choices, and that will de-select them on click:
-->
<span style="display:none" class="remove">Remove this choice</span>
<!--
Finally, supporting new options to be created directly in the select in
javascript (ie. add another) is possible with a .choice-template. Of
course, you can't take this very far, since all you have is the new
option's value and html.
-->
<span style="display:none" class="choice-template">
<span class="choice">
</span>
</span>
</span>
Read everything about the registry and widgets.