Creating and using custom UI widgets

Extending existing widgets

Sometimes you need to use a custom widget for a field, or you simply want to use another UI library.

Here’s an example which use a dropdown menu instead of a switch button for a boolean field:

;(function($){

    var booleanDropDown = {
        _type: 'boolean',
        options: {
            choices: 'Yes|No'
        }
    };

    booleanDropDown._init = function() {
        var $self = this,
            label = $self.options.choices.split('|')

        $self.label_on = label[0];
        $self.label_off = label[1];
        $self.element.hide();
        $self.btn_group = $('<div class="btn-group" />').insertAfter($self.element);
        $self.btn_label = $('<button class="btn" />').appendTo($self.btn_group);
        $self.choices   = $('<ul class="dropdown-menu" />').appendTo($self.btn_group);
        $self.btn_toggle = $(['<button class="btn dropdown-toggle" data-toggle="dropdown">',
                              '<span class="caret"></span></button>'].join(''))
                              .insertAfter($self.btn_label);
        $self._populate();
    };

    booleanDropDown._populate = function() {
        var $self = this;
        if ($self.element.is(':checked')) {
            $('<li><a href="#off">'+ $self.label_off +'</a></li>')
                .appendTo($self.choices)
                .bind('click.editlive', function(){
                    $self.btn_group.removeClass('open');
                    $self.choices.find('li').remove();
                    $self.element.prop('checked', false);
                    $self.change();
                    $self._populate();
                    return false;
                });
        }
        else {
            $('<li><a href="#on">'+ $self.label_on +'</a></li>')
                .appendTo($self.choices)
                .bind('click.editlive', function(){
                    $self.btn_group.removeClass('open');
                    $self.choices.find('li').remove();
                    $self.element.prop('checked', true);
                    $self.change();
                    $self._populate();
                    return false;
                });
        }
        $self.btn_label.text(this.get_value_display());
    };

    booleanDropDown._display_errors = function(errors) {
        var $self = this;
        $.each(errors, function(k, v) {
            var el = $self.btn_group;
            el.tooltip({
                title: v.message,
                placement: $self.options.errorplacement
            }).tooltip('show');
        });
    };

    booleanDropDown.get_value_display = function() {
        var $self = this;
        if ($self.element.is(':checked')) {
            return $self.label_on;
        }
        else {
            return $self.label_off;
        }
    };

    booleanDropDown._get_value = function() {
        if (this.element.is(':checked')) return true;
        else return false;
    };

    $.widget('editliveWidgets.booleanDropDown', $.editliveWidgets.charField, booleanDropDown);

})(jQuery);

Create a widget from scratch

The JavaScript

Barebone example:

;(function($){

    $.widget('editliveWidgets.autocompleteField', $.editliveWidgets.charField, {
        _type: 'autocomplete',
        options: {}
    });

})(jQuery);

As is, this plugin will act exactly as a char field.

The charField is the base field widget, which means that looking at the source code of $.editliveWidgets.charField basically defines the editlive widget API.

For example, let’s say we want to activate a autocomplete plugin on our field.

We’d simply override the _create method like this:

;(function($){

    $.widget('editliveWidgets.autocompleteField', $.editliveWidgets.charField, {
        _type: 'autocomplete',
        options: {},

        _create: function() {
            var $self = this;
            $.editliveWidgets.charField.prototype._create.apply(this, arguments);
            $self.element.autocomplete({minKeys: 2});
        }
    });

})(jQuery);

For more examples please refer to the widgets source code.

The adaptor

In order to use your custom widget you will have to create a custom adaptor.

Fortunately, this is quite trivial. Here’s the date picker adaptor for example:

class DateAdaptor(BaseAdaptor):
    """DateField adaptor"""

    def __init__(self, *args, **kwargs):
        """
        The DateAdaptor override the __init__ method
        to add the data-format argument to the
        widget's attributes. This is what links Django internal
        date format with the UI.
        """
        super(DateAdaptor, self).__init__(*args, **kwargs)
        field = self.form.fields.get(self.field_name)
        if field:
            self.attributes.update({'data-format': '%s' % field.widget.format})
        if self.form_field:
            self.attributes.update({'data-type': 'dateField'})

    def render_value(self, value=None):
        """
        If no custom "date" template filter is passed as argument,
        we add one using the settings.DATE_FORMAT as value.
        """
        if self.template_filters is None:
            self.template_filters = []
        if not any(i.startswith('date:') for i in self.template_filters):
            self.template_filters.append(u"date:'%s'" % settings.DATE_FORMAT)
        return unicode(super(DateAdaptor, self).render_value(value=value))

You can browse the adaptors source code for more examples.

Using a custom adaptor

To use a custom adaptor, simply pass a widget argument to editlive:

{% editlive "object.date_start" widget="mymodule.adaptors.MyCustomDatePickerAdaptor" as field %}
{{ field }}