|
|
# backport of broken code for python 3.11 on webhelpers2
|
|
|
|
|
|
import collections
|
|
|
from webhelpers2.misc import NotGiven
|
|
|
from webhelpers2.html.tags import _set_id_attr, NL, _OptionsList, OptGroup, HTML
|
|
|
|
|
|
|
|
|
class Options(_OptionsList):
|
|
|
"""A list of options and/or optgroups for a select or datalist.
|
|
|
|
|
|
I'm a subclass of ``list``. My elements are ``Option`` and/or ``OptGroup``
|
|
|
instances. Do not add other element types or they may cause
|
|
|
``options.render`` or ``str(options)`` to fail.
|
|
|
"""
|
|
|
|
|
|
def __init__(self, options=None, prompt=None):
|
|
|
"""Construct an ``Options`` instance.
|
|
|
|
|
|
**options**: An iterable of ``Option`` instances, ``OptGroup``
|
|
|
instances and/or strings. If you pass strings, they will be converted
|
|
|
to simple ``Option`` instances (i.e., the label will be the string, and
|
|
|
the value will be ``None``).
|
|
|
|
|
|
**prompt**: If passed, this will be turned into an extra option before
|
|
|
the others. The string argument will become the option's label, and
|
|
|
the option's value will be the empty string.
|
|
|
"""
|
|
|
if prompt:
|
|
|
self.add_option(prompt, "")
|
|
|
if options:
|
|
|
self._parse_options(options)
|
|
|
|
|
|
def __repr__(self):
|
|
|
classname = self.__class__.__name__
|
|
|
return "{0}({1!r})".format(classname, list(self))
|
|
|
|
|
|
def add_optgroup(self, label, options=None):
|
|
|
"""Create an ``OptGroup``, append it, and return it.
|
|
|
|
|
|
The return value is the ``OptGroup`` instance. Call its
|
|
|
``.add_option`` method to add options to the group.
|
|
|
"""
|
|
|
group = OptGroup(label, options)
|
|
|
self.append(group)
|
|
|
return group
|
|
|
|
|
|
def render(self, selected_values=None):
|
|
|
"""
|
|
|
Render the options as a concatenated literal of <option> and/or
|
|
|
<optgroup> tags, with a newline after each.
|
|
|
|
|
|
**selected_values**: The value(s) that should be preselected. You
|
|
|
can pass a string, int, bool, or other scalar type, or a sequence of
|
|
|
these. If you pass a scalar it will be standardized to a one-element
|
|
|
tuple. If you don't pass anything, it will default to ``("",)`` (a tuple
|
|
|
containing the empty string); this will correctly preselect prompts.
|
|
|
|
|
|
Note that ``selected_values`` *does not do type conversions*. If you
|
|
|
pass an int, the corresponding ``Option.value`` must also be an int or
|
|
|
otherwise equal to it. (The actual comparision operator is ``in``.)
|
|
|
|
|
|
Calling ``str(options)`` or ``options.__html__()`` is the same as
|
|
|
calling ``options.render()`` without arguments. This is only useful if
|
|
|
you don't want to pass any selected values.
|
|
|
"""
|
|
|
|
|
|
selected_values = self._parse_selected_values(selected_values)
|
|
|
return self._render(self, selected_values)
|
|
|
|
|
|
__str__ = __html__ = render
|
|
|
|
|
|
def _render(self, options, selected_values):
|
|
|
tags = []
|
|
|
for opt in options:
|
|
|
if isinstance(opt, OptGroup):
|
|
|
content = self._render(opt, selected_values)
|
|
|
tag = HTML.tag("optgroup", NL, content, label=opt.label)
|
|
|
tags.append(tag)
|
|
|
else:
|
|
|
value = opt.value if opt.value is not None else opt.label
|
|
|
selected = value in selected_values
|
|
|
tag = HTML.tag("option", opt.label, value=opt.value, selected=selected)
|
|
|
tags.append(tag)
|
|
|
return HTML(*tags, nl=True)
|
|
|
|
|
|
@staticmethod
|
|
|
def _parse_selected_values(values):
|
|
|
if values is None:
|
|
|
return ("",)
|
|
|
is_string = isinstance(values, str)
|
|
|
is_seq = isinstance(values, collections.abc.Sequence)
|
|
|
if is_string or not is_seq:
|
|
|
return (values,)
|
|
|
else:
|
|
|
return values
|
|
|
|
|
|
|
|
|
def raw_select(name, selected_values, options, id=NotGiven, **attrs):
|
|
|
"""Create a dropdown selection box.
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
* **name**: the name of this control.
|
|
|
|
|
|
* **selected_values**: the value(s) that should be preselected.
|
|
|
A string or list of strings. Some other types are allowed; see the
|
|
|
``Options.render`` method for details.
|
|
|
|
|
|
* **options**: an ``Options`` instance, or an iterable to pass to the
|
|
|
its constructor. See the ``Options`` class for allowed element types.
|
|
|
|
|
|
* **id**: the HTML ID attribute. This should be a keyword argument if
|
|
|
passed. By default the ID is the same as the name. filtered through
|
|
|
``_make_safe_id_component()``. Pass None to suppress the
|
|
|
ID attribute entirely.
|
|
|
|
|
|
The following options may only be keyword arguments:
|
|
|
|
|
|
* **multiple**: if true, this control will allow multiple
|
|
|
selections.
|
|
|
|
|
|
* **prompt**: An extra option that will be prepended to the list.
|
|
|
The argument is the option label; e.g., "Please choose ...". The generated
|
|
|
option's value will be the empty string (""), which is equivalent to not
|
|
|
making a selection. If you specify this and also pass an ``Options``
|
|
|
instance in ``options``, it will combine the two into a new ``Options``
|
|
|
object rather than reusing the existing one.
|
|
|
|
|
|
Any other keyword args will become HTML attributes for the <select>.
|
|
|
"""
|
|
|
|
|
|
_set_id_attr(attrs, id, name)
|
|
|
attrs["name"] = name
|
|
|
prompt = attrs.pop("prompt", None)
|
|
|
if prompt or not isinstance(options, Options):
|
|
|
options = Options(options, prompt=prompt)
|
|
|
return HTML.tag("select", NL, options.render(selected_values), **attrs)
|