Put the empty option on top in DropDownMultiple.
[matthijs/projects/xerxes.git] / tools / widgets / dropdownmultiple.py
1 # Code taken from http://www.djangosnippets.org/snippets/747/
2 # -*- coding: utf-8 -*-
3 from django.forms import widgets
4 from django.utils.safestring import mark_safe
5 from django.utils.datastructures import MultiValueDict
6 from django.forms.util import flatatt
7
8 TPL_OPTION = """<option value="%(value)s" %(selected)s>%(desc)s</option>"""
9
10 TPL_SELECT = """
11 <select class="dropdown_select" %(attrs)s disabled="disabled">
12 %(opts)s
13 </select>
14 """
15
16 TPL_SCRIPT = """
17 <script>
18     $('span#%(id)s_multiple>select.dropdown_select').change(function(){
19         var pattern = 'span#%(id)s_multiple>select.dropdown_select';
20         var last_item = $(pattern+':last');
21
22         if (last_item.val()) {
23             last_item.clone(true).appendTo($('span#%(id)s_multiple'));
24             $('span#%(id)s_multiple').append(' ');
25         };
26
27         var values = [];
28
29         for (var i=$(pattern).length-1; i>=0; i--) {
30             if (values.indexOf($($(pattern).get(i)).val()) >= 0) {
31                 $($(pattern).get(i)).remove();
32             } else {
33                 values.push($($(pattern).get(i)).val());
34             }
35         };
36     });
37     $(document).ready(function(){
38         // Since we do graceful fallback, the JScript driven interface
39         // is hidden and disabled by default and the plain HTML
40         // interface is shown. Here we swap them around.
41         $('select#%(id)s').hide();
42         $('select#%(id)s').attr('disabled', true);
43         $('span#%(id)s_multiple').show();
44         $('span#%(id)s_multiple>select.dropdown_select').attr('disabled', false);
45     });
46 </script>
47 """
48
49 TPL_FULL = """
50 <span class="dropdown_multiple" id="%(id)s_multiple" style="display:none">
51 %(values)s
52 %(script)s
53 </span>
54 """
55
56 class DropDownMultiple(widgets.SelectMultiple):
57     def __init__(self, attrs=None, choices=()):
58         super(DropDownMultiple, self).__init__(attrs=attrs, choices=choices)
59     
60     def render(self, name, value, attrs=None, choices=()):
61         # Always render both the default SelectMultiple and our
62         # javascript assisted version. This allows for graceful
63         # degradation when javascript is not available or enabled
64         # (together with the javascript code above).
65         nonjs_output = super(DropDownMultiple, self).render(name, value, attrs=attrs, choices=choices)
66         js_output    = self.render_js(name, value, attrs=attrs, choices=choices)
67
68         return nonjs_output + js_output
69
70     def render_js(self, name, value, attrs=None, choices=()):
71         if value is None: value = []
72         final_attrs = self.build_attrs(attrs, name=name)
73
74         # Pop id
75         id = final_attrs['id']
76         del final_attrs['id']
77
78         # Insert blank value. We insert this in self.choices, because
79         # render_options merges self.choices with its choices argument
80         # (in that order) and we want to have the empty option at the
81         # top.
82         old_choices = self.choices
83         self.choices = [('','---')] + list(self.choices)
84
85         # Build values
86         items = []
87         for val in value:
88             opts = self.render_options(choices, [val])
89             
90             items.append(TPL_SELECT %{'attrs': flatatt(final_attrs), 'opts': opts})
91
92         # Build blank value
93         opts = self.render_options(choices, [''])
94         items.append(TPL_SELECT %{'attrs': flatatt(final_attrs), 'opts': opts})
95
96         script = TPL_SCRIPT %{'id': id}
97         output = TPL_FULL %{'id': id, 'values': '\n'.join(items), 'script': script}
98         
99         # Restore the original choices
100         self.choices = old_choices
101
102         return mark_safe(output)
103
104     def value_from_datadict(self, *args, **kwargs):
105         # Let our parent take care of this, but filter out the empty
106         # value (which is usually present from the last unused
107         # dropdown).
108         values = super(DropDownMultiple, self).value_from_datadict(*args, **kwargs)
109         return [i for i in values if i]