Login

filter/search a newforms select widget

Author:
miracle2k
Posted:
September 18, 2007
Language:
Python
Version:
.96
Score:
0 (after 0 ratings)

Adds a filter input above a select widget that allows live-filtering on the client-side (no ajax) in Firefox.

Example:

make_fields_searchable(ModelItemForm, { 'publisher': {'set_size': 8}, 'developer': {'set_size': 8}, 'genre': {}, 'platform': {} })

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
"""
    Adds client-side Javascript filtering to the widget instance passed. The widget
    is expected to render as an HTML select, or the Javascript associated will fail.
    
    This only works in Gecko - functionality in IE and Opera is limited. The list is
    not filtered, but the first matching item will automatically be selected.
"""
make_searchable_js = \
"""
// filters a select by hiding all non-matching items - only works in FF so far.
//
// select          -  the id of the select control to filter
// filter_by_ctrl  -  a reference to an input object with the text to filter by
function filter_select(select, filter_by_ctrl)
{
    filter_text = filter_by_ctrl.value.replace(/^\s+|\s+$/g,"");  // trimmed
    if (filter_by_ctrl.value == filter_by_ctrl.old_value) return false;    
    select_ctrl = document.getElementById(select);

    sel_found = false;
    for(i=0;i<select_ctrl.options.length;i++)
    {
        do_show = (!filter_text ||
                   select_ctrl.options[i].value=="" ||
                   select_ctrl.options[i].text.search(new RegExp(filter_text, "i"))!=-1)
        select_ctrl.options[i].style.display = do_show ?'block':'none';
        // preselect the first item, and try to be smart about it
        if (!sel_found && do_show) {
            if (
                (select_ctrl.options[i].value=="" && !filter_text) ||                
                (select_ctrl.options[i].value!="")
               )
            {
                if (!select_ctrl.options[i].selected)
                {
                    select_ctrl.options[i].selected = true;
                    // we need to call a possible onchange manually, because it doesn't
                    // happen by itself. unfortunately, there is also an old but as-of-yet
                    // unfixed bug in firefox that requires the setTimeout() workaround,
                    // see:
                    //  * https://bugzilla.mozilla.org/show_bug.cgi?id=317600
                    //  * https://bugzilla.mozilla.org/show_bug.cgi?id=246518
                    if (select_ctrl.onchange) window.setTimeout(function(){select_ctrl.onchange()}, 0);
                }
                sel_found = true; 
            }
        }
    }
    // do some work of our own to determine one the value has changed, for
    // performance reaonns, but e.g. we also don't want to change the selection
    // unless the filter changed at least, and definitely not on control keys
    // such as arrow up or arrow down.
    filter_by_ctrl.old_value = filter_by_ctrl.value;
}
// helper function that helps redirect certain keys from the filter input
// to the select, to allow a certain amount of control without changing focus (
// e.g. key up, down, ...)
//
// select          -  the id of the select to control
// e               -  event object
function filter_select_ctrl(select, e)
{
    select_ctrl = document.getElementById(select);
    if (e.keyCode == 40) {
        for(i=select_ctrl.selectedIndex+1;i<select_ctrl.options.length;i++) {
            if (select_ctrl.options[i].style.display!='none') {
                select_ctrl.options[i].selected = true;
                return true;
            }
        }
    }
    else if (e.keyCode == 38) {
        for(i=select_ctrl.selectedIndex-1;i>=0;i--) {
            if (select_ctrl.options[i].style.display!='none') {
                select_ctrl.options[i].selected = true;
                return true;
            }
        };
    }
    // TODO: page up / page down (33/34)
}
"""
def make_searchable(widget, set_size=None):
    def render_hook(self, old_render, name, value, attrs, choices):
        result = '<input type="text" onkeyup="filter_select(\'%(id)s\', this)"'\
                 ' onkeypress="filter_select_ctrl(\'%(id)s\', (window.event)?window.event:event)" /><br />' % {'id': attrs['id']}        
        # add a search box
        return result+old_render(name, value, attrs, choices)
    
    old_render = widget.render
    import new
    widget.render = new.instancemethod(
        lambda self, name, value, attrs=None, choices=(): \
            render_hook(self, old_render, name, value, attrs, choices),
        widget, widget.__class__)
    widget.attrs['tabindex'] = -1
    if set_size: widget.attrs['size'] = set_size
    
"""
    Make multiple fields of a form searchable in one go.
    
    form can be either a form class, or a form instance.
    
    fields can be a list of valid field names for this form, or a dict with
    field names as keys, and options dicts as values. Example:
    
        {'foreignkey': {'set_size': 5}, ...}
    
    Valid options are whatever is allowed by make_searchable(), the dict
    is directly expandend to a parameter list on call.
"""
def make_fields_searchable(form, fields):
    # determine whether this is a form class or a form instance, and use
    # the correct field property accordingly.
    if hasattr(form, 'base_fields'): fields_array = form.base_fields
    else: fields_array = form.fields
    # get a list of field names
    if type(fields) == list: field_names = fields
    else: field_names = fields.keys()
    
    for field in field_names:
        params = {}
        if type(fields) == dict:
            params = fields[field]
        make_searchable(fields_array[field].widget, **params)

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 1 week ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 2 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 6 months ago

Comments

Please login first before commenting.