Add a CSS class to an option in a WTForms SelectField
Asked Answered
P

2

4

Can anyone please tell me how to assign css class to choices values. I would like to change the background of each choices with small image, so how can I do it with wtforms and css?

class RegisterForm(Form):
    username = TextField('username', [validators.Length(min=3, max=50), validators.Required()])
    img_url = SelectField('avatar', 
            choices=[('static/images/avatars/1.jpg', '1'), 
                ('static/images/avatars/2.jpg', '2'),
                ('static/images/avatars/3.jpg', '3'), 
                ('static/images/avatars/4.jpg', '4'),
                ('static/images/avatars/5.jpg', '5'), 
                ('static/images/avatars/6.jpg', '6'),
                ('static/images/avatars/7.jpg', '7'), 
                ('static/images/avatars/8.jpg', '8'),
                ('static/images/avatars/9.jpg', '9'), 
                ('static/images/avatars/10.jpg','10')])
Perceptible answered 11/4, 2014 at 22:45 Comment(0)
F
6

If you look deep in the bowels of WTForms you'll find a widget class called SelectField

this is the method called build the html string:

@classmethod
def render_option(cls, value, label, selected, **kwargs):
    options = dict(kwargs, value=value)
    if selected:
        options['selected'] = True
    return HTMLString('<option %s>%s</option>' % (html_params(**options), escape(text_type(label))))

this is __call__ method that invokes the render_options function defined above.

def __call__(self, field, **kwargs):
    kwargs.setdefault('id', field.id)
    if self.multiple:
        kwargs['multiple'] = True
    html = ['<select %s>' % html_params(name=field.name, **kwargs)]
    for val, label, selected in field.iter_choices():
        html.append(self.render_option(val, label, selected))
    html.append('</select>')
    return HTMLString(''.join(html))

You are not going to be able to add the class attribute by simply instantiating a SelectField. When you do this it creates the Option instances implicitly. At render time the render_options methods of these implicit instances are only invoked with val, selected, and label arguments.

You can access the implicit Option instances after the fact. This is not without issue. If you look at @Johnston's example:

>>> i = 44
>>> form = F()
>>> for subchoice in form.a:
...     print subchoice(**{'data-id': i})
...     i += 1

He is doing exactly this. But you have to provide the attributes to the class at render time. The invocation subchoice(**{'data-id': i}) actually spits out the expected HTML. This poses quite the problem if you are integrating WTForms with a template engine. Because the something like jinja is calling these render functions for you.

If you want this type of behavior I would recommend writing your own implementation of SelectField that allows you to pass attributes into the implicit Option instances. This way the template engine can just go about the business of invoking render and you can keep your definitions of the form consolidated in your forms.py file(s)

Fanfaronade answered 1/5, 2014 at 0:40 Comment(0)
R
4

I just wanted to let you know that this is possible without monkey patching or rewriting wtforms. The library code does support it although not very straightforwardly. I found this out because I had the same issue as you and I attempted to write a fix for WTForms and submitted a PR myself and found out afterwards that you can just do this (I've spent days trying to figure this out):

>>> from wtforms import SelectField, Form
>>> class F(Form):
...    a = SelectField(choices=[('a', 'Apple'), ('b', 'Banana')])
... 
>>> i = 44
>>> form = F()
>>> for subchoice in form.a:
...     print subchoice(**{'data-id': i})
...     i += 1
... 
<option data-id="44" value="a">Apple</option>
<option data-id="45" value="b">Banana</option>

See the convo here:
https://github.com/wtforms/wtforms/pull/81

Resoluble answered 6/5, 2014 at 18:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.