WTForms ChosenSelect
Recently I often had to build huge selects or even multiple selects and as you might know, especially multi selects can look quite ugly.
But they do not only look ugly, they are also very unnatural to handle, if not for pros at least for casual users, as you have to use shift+alt to select multiple entries, which is not clear to every user. Of course you can add a small description to explain that, but that does not really improve the usability itself.
So what is the alternative? The guys at harvest wrote a very nice javascript plugin that is very much downward compatible and will make your selects and multiple select much more beautiful: chosen. At some point I realized I did not want to manually add a script tag after every field in my templates and decided to write a custom widget to take care of that for me:
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
import json
from wtforms.widgets import Select, HTMLString
class ChosenSelect(Select):
def __init__(self, multiple=False, renderer=None, options={}):
"""
Initiate the widget. This offers you two general options.
First off it allows you to configure the ChosenSelect to
allow multiple options and it allows you to pass options
to the chosen select (this will produce a json object)
that chosen will get passed as configuration.
:param multiple: whether this is a multiple select
(default to `False`)
:param renderer: If you do not want to use the default
select renderer, you can pass a function that will
get the field and options as arguments so that
you can customize the rendering.
:param options: a dictionary of options that will
influence the chosen behavior. If no options are
given `width: 100%` will be set.
"""
super(ChosenSelect, self).__init__(multiple=multiple)
self.renderer = renderer
options.setdefault('width', '100%')
self.options = options
def __call__(self, field, **kwargs):
"""
Render the actual select.
:param field: the field to render
:param **kwargs: options to pass to the rendering
(i.e. class, data-* and so on)
This will render the select as is and attach a chosen
initiator script for the given id afterwards considering
the options set up in the beginning.
"""
kwargs.setdefault('id', field.id)
# currently chosen does not reflect the readonly attribute
# we compensate for that by automatically setting disabled,
# if readonly if given
# https://github.com/harvesthq/chosen/issues/67
if kwargs.get("readonly"):
kwargs['disabled'] = 'disabled'
html = []
# render the select
if self.renderer:
html.append(self.renderer(self, field, **kwargs))
else:
html.append(super(ChosenSelect, self).__call__(field, **kwargs))
# attach the chosen initiation with options
html.append(
'<script>$("#%s").chosen(%s);</script>\n'
% (kwargs['id'], json.dumps(self.options))
)
# return the HTML (as safe markup)
return HTMLString('\n'.join(html))
1
2
3
4
5
6
from wtforms import Form
from wtforms.fields import Select
class ExampleForm(Form):
example = Select("Example", choices=[("1", "1"), ("2", "2")], widget=ChosenSelect())
Now to use that you can simply pass widget=ChosenSelect()
or if you want a multi select widget=ChosenSelect(multiple=True)
to the field setup. As long as you include the chosen.js in your template your select will automatically be converted to a chosen select when your add {{ form.example }}
to your template.
And then it might just look like this: