Scripts: the javascript side of autocompletes

Note

This chapter assumes that you have been through Quick start: adding simple autocompletes and Autocomplete classes and Form, fields and widgets.

Design documentation

Before installing your own autocomplete scripts, you should know about the humble provided scripts:

  • autocomplete.js provides yourlabs.Autocomplete via the $.yourlabsAutocomplete() jQuery extension: add an autocomplete box to a text input, it can be used on its own to create a navigation autocomplete like facebook and all the cool kids out there.
  • widget.js provides yourlabs.Widget via the $.yourlabsWidget() jQuery extension: combine an text input with an autocomplete box with a django form field which is represented by a hidden <select>.
  • addanother.js enables adding options to a <select> via a popup from outside the admin, code mostly comes from Django admin BTW,
  • remote.js provides yourlabs.RemoteAutocompleteWidget, which extends yourlabs.Widget and overrides yourlabs.Widget.getValue to create choices on-the-fly.
  • text_widget.js provides yourlabs.TextWidget, used to manage the value of a text input that has an autocomplete box.

Why a new autocomplete script you might ask ? What makes this script unique is that it relies on the server to render the contents of the autocomplete-box. This means that you can fully design it like you want, including new HTML tags like <img>, using template tags like {% url %}, and so on.

If you want to change something on the javascript side, chances are that you will be better off overriding a method like yourlabs.RemoteAutocompleteWidget instead of installing your own script right away.

What you need to know is that:

  • widgets don’t render any inline javascript: the have HTML attributes that will tell the scripts how to instanciate objects with $.yourlabsWidget(), $.yourlabsTextWidget() and so on. This allows to support dynamically inserted widgets ie. with a dynamic formsets inside or outside of django admin.
  • the particular attribute that is watched for is data-bootstrap. If an HTML element with class .autocomplete-light-widget is found or created with data-bootstrap="normal" then widget.js will call $.yourlabsWidget.
  • if you customize data-bootstrap, widget.js will not do anything and you are free to implement your script, either by extending a provided a class, either using a third-party script, either completely from scratch.

Examples

django-autocomplete-light provides consistent JS plugins. A concept that you understand for one plugin is likely to be appliable for others.

Using $.yourlabsAutocomplete to create a navigation autocomplete

If your website has a lot of data, it might be useful to add a search input somewhere in the design. For example, there is a search input in Facebook’s header. You will also notice that the search input in Facebook provides an autocomplete which allows to directly navigate to a particular object’s detail page. This allows a visitor to jump to a particular page with very few effort.

Our autocomplete script is designed to support this kind of autocomplete. It can be enabled on an input field and query the server for a rendered autocomplete with anything like images and nifty design. Just create a view that renders just a list of links based on request.GET.q.

Then you can use it to make a global navigation autocomplete using autocomplete.js directly. It can look like this:

// Make a javascript Autocomplete object and set it up
var autocomplete = $('#yourInput').yourlabsAutocomplete({
    url: '{% url "your_autocomplete_url" %}',
});

So when the user clicks on a link of the autocomplete box which is generated by your view: it is like if he clicked on a normal link.

You’ve learned that you can have a fully functional navigation autocomplete like on Facebook with just this:

$('#yourInput').yourlabsAutocomplete({
    url: '{% url "your_autocomplete_url" %}',
    choiceSelector: 'a',
}).input.bind('selectChoice', function(e, choice, autocomplete) {
    window.location.href = choice.attr('href');
});

autocomplete.js doesn’t do anything but trigger selectChoice on the input when a choice is selected either with mouse or keyboard, let’s enable some action:

Because the script doesn’t know what HTML the server returns, it is nice to tell it how to recognize choices in the autocomplete box HTML:: This will allow to use the keyboard arrows up/down to navigate between choices.

Refer to Making a global navigation autocomplete for complete help on making a navigation autocomplete.

Overriding a JS option in Python

Javascript widget and autocomplete objects options can be overidden via HTML data attributes:

  • yourlabs.Autocomplete will use any data-autocomplete-* attribute on the input tag,
  • yourlabs.Widget will use any data-widget-* attribute on the widget container.

Those can be set in Python either with register(), as Autocomplete class attributes or as widget attributes. See next examples for details.

Per registered Autocomplete

These options can be set with the register() shortcut:

autocomplete_light.register(Person,
    attrs={
        'placeholder': 'foo',
        'data-autocomplete-minimum-characters': 0
    },
    widget_attrs={'data-widget-maximum-values': 4}
)

Per Autocomplete class

Or equivalently on a Python Autocomplete class:

class YourAutocomplete(autocomplete_light.AutocompleteModelBase):
    model = Person
    attrs={
        'placeholder': 'foo',
        'data-autocomplete-minimum-characters': 0
    },
    widget_attrs={'data-widget-maximum-values': 4}

Per widget

Or via the Python widget class:

autocomplete_light.ChoiceWidget('FooAutocomplete',
    attrs={
        'placeholder': 'foo',
        'data-autocomplete-minimum-characters': 0
    },
    widget_attrs={'data-widget-maximum-values': 4}
)

NOTE the difference of the option name here. It is attrs to match django and not attrs. Note that Autocomplete.attrs might be renamed to Autocomplete.attrs before v2 hits RC.

Override autocomplete JS options in JS

The array passed to the plugin function will actually be used to $.extend the autocomplete instance, so you can override any option, ie:

$('#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,
});

Note

The pattern is the same for all plugins provided by django-autocomplete-light.

Override autocomplete JS methods

Overriding methods works the same, ie:

$('#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
    },
});

Note

The pattern is the same for all plugins provided by django-autocomplete-light.

Overload autocomplete JS methods

Use call to call a parent method. This example automatically selects the choice if there is only one:

$(document).ready(function() {
    var autocomplete = $('#id_city_text').yourlabsAutocomplete();
    autocomplete.show = function(html) {
        yourlabs.Autocomplete.prototype.show.call(this, html)
        var choices = this.box.find(this.choiceSelector);

        if (choices.length == 1) {
            this.input.trigger('selectChoice', [choices, this]);
        }
    }
});

Get an existing autocomplete object and chain autocompletes

You can use the jQuery plugin yourlabsAutocomplete() to get an existing autocomplete object. Which makes chaining autocompletes with other form fields as easy as:

$('#country').change(function() {
    $('#yourInput').yourlabsAutocomplete().data = {
        'country': $(this).val();
    }
});

Overriding widget JS methods

The widget js plugin will only bootstrap widgets which have data-bootstrap="normal". Which means that you should first name your new bootstrapping method to ensure that the default behaviour doesn’t get in the way.

autocomplete_light.register(City,
    widget_attrs={'data-widget-bootstrap': 'your-custom-bootstrap'})

Note

You could do this at various level, by setting the bootstrap argument on a widget instance, via register() or directly on an autocomplete class. See Overriding JS options in Python for details.

Now, you can instanciate the widget yourself like this:

$(document).bind('yourlabsWidgetReady', function() {
    $('.your.autocomplete-light-widget[data-bootstrap=your-custom-bootstrap]').live('initialize', function() {
        $(this).yourlabsWidget({
            // Override options passed to $.yourlabsAutocomplete() from here
            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:
            maxValues: 3,
            // or method:
            getValue: function(choice) {
                // This is the method that returns the value to use for the
                // hidden select option based on the HTML of the selected
                // choice.
                //
                // This is where you could make a non-async post request to
                // this.autocomplete.url for example. The default is:
                return choice.data('id')
            },
        })
    });
});

You can use the remote autocomplete as an example.

Note

You could of course call $.yourlabsWidget() directly, but using the yourlabsWidgetReady event takes advantage of the built-in DOMNodeInserted event: your widgets will also work with dynamically created widgets (ie. admin inlines).