Make the DropDownMultiple widget degrade properly.
[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
79         choices = [('','---')] + list(choices)
80
81         # Build values
82         items = []
83         for val in value:
84             opts = self.render_options(choices, [val])
85             
86             items.append(TPL_SELECT %{'attrs': flatatt(final_attrs), 'opts': opts})
87
88         # Build blank value
89         opts = self.render_options(choices, [''])
90         items.append(TPL_SELECT %{'attrs': flatatt(final_attrs), 'opts': opts})
91
92         script = TPL_SCRIPT %{'id': id}
93         output = TPL_FULL %{'id': id, 'values': '\n'.join(items), 'script': script}
94
95         return mark_safe(output)
96
97     def value_from_datadict(self, *args, **kwargs):
98         # Let our parent take care of this, but filter out the empty
99         # value (which is usually present from the last unused
100         # dropdown).
101         values = super(DropDownMultiple, self).value_from_datadict(*args, **kwargs)
102         return [i for i in values if i]