You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
86 lines
3.2 KiB
86 lines
3.2 KiB
import re |
|
from copy import deepcopy |
|
from typing import Any, Callable, Dict, List, Optional, Union |
|
|
|
from ..events import GenericEventArguments |
|
from .choice_element import ChoiceElement |
|
from .mixins.disableable_element import DisableableElement |
|
|
|
|
|
class Select(ChoiceElement, DisableableElement, component='select.js'): |
|
|
|
def __init__(self, |
|
options: Union[List, Dict], *, |
|
label: Optional[str] = None, |
|
value: Any = None, |
|
on_change: Optional[Callable[..., Any]] = None, |
|
with_input: bool = False, |
|
multiple: bool = False, |
|
clearable: bool = False, |
|
) -> None: |
|
"""Dropdown Selection |
|
|
|
The options can be specified as a list of values, or as a dictionary mapping values to labels. |
|
After manipulating the options, call `update()` to update the options in the UI. |
|
|
|
:param options: a list ['value1', ...] or dictionary `{'value1':'label1', ...}` specifying the options |
|
:param value: the initial value |
|
:param on_change: callback to execute when selection changes |
|
:param with_input: whether to show an input field to filter the options |
|
:param multiple: whether to allow multiple selections |
|
:param clearable: whether to add a button to clear the selection |
|
""" |
|
self.multiple = multiple |
|
if multiple: |
|
if value is None: |
|
value = [] |
|
elif not isinstance(value, list): |
|
value = [value] |
|
super().__init__(options=options, value=value, on_change=on_change) |
|
if label is not None: |
|
self._props['label'] = label |
|
if with_input: |
|
self.original_options = deepcopy(options) |
|
self._props['use-input'] = True |
|
self._props['hide-selected'] = not multiple |
|
self._props['fill-input'] = True |
|
self._props['input-debounce'] = 0 |
|
self._props['multiple'] = multiple |
|
self._props['clearable'] = clearable |
|
|
|
def on_filter(self, e: GenericEventArguments) -> None: |
|
self.options = [ |
|
option |
|
for option in self.original_options |
|
if not e.args or re.search(e.args, option, re.IGNORECASE) |
|
] |
|
self.update() |
|
|
|
def _event_args_to_value(self, e: GenericEventArguments) -> Any: |
|
# pylint: disable=no-else-return |
|
if self.multiple: |
|
if e.args is None: |
|
return [] |
|
return [self._values[arg['value']] for arg in e.args] |
|
else: |
|
if e.args is None: |
|
return None |
|
return self._values[e.args['value']] |
|
|
|
def _value_to_model_value(self, value: Any) -> Any: |
|
# pylint: disable=no-else-return |
|
if self.multiple: |
|
result = [] |
|
for item in value or []: |
|
try: |
|
index = self._values.index(item) |
|
result.append({'value': index, 'label': self._labels[index]}) |
|
except ValueError: |
|
pass |
|
return result |
|
else: |
|
try: |
|
index = self._values.index(value) |
|
return {'value': index, 'label': self._labels[index]} |
|
except ValueError: |
|
return None
|
|
|