123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- from copy import deepcopy
- from typing import Any, Callable, Dict, List, Literal, 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,
- new_value_mode: Optional[Literal['add', 'add-unique', 'toggle']] = None,
- multiple: bool = False,
- clearable: bool = False,
- ) -> None:
- """Dropdown Selection
- This element is based on Quasar's `QSelect <https://quasar.dev/vue-components/select>`_ component.
- 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.
- If `with_input` is True, an input field is shown to filter the options.
- If `new_value_mode` is not None, it implies `with_input=True` and the user can enter new values in the input field.
- See `Quasar's documentation <https://quasar.dev/vue-components/select#the-new-value-mode-prop>`_ for details.
- :param options: a list ['value1', ...] or dictionary `{'value1':'label1', ...}` specifying the options
- :param label: the label to display above the selection
- :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 new_value_mode: handle new values from user input (default: None, i.e. no new values)
- :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 new_value_mode is not None:
- if isinstance(options, dict) and new_value_mode == 'add':
- raise ValueError('new_value_mode "add" is not supported for dict options')
- self._props['new-value-mode'] = new_value_mode
- with_input = True
- 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 _event_args_to_value(self, e: GenericEventArguments) -> Any:
- # pylint: disable=no-else-return
- if self.multiple:
- if e.args is None:
- return []
- else:
- args = [self._values[arg['value']] if isinstance(arg, dict) else arg for arg in e.args]
- for arg in e.args:
- if isinstance(arg, str):
- self._handle_new_value(arg)
- return [arg for arg in args if arg in self._values]
- else:
- if e.args is None:
- return None
- else:
- if isinstance(e.args, str):
- self._handle_new_value(e.args)
- return e.args if e.args in self._values else None
- else:
- 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
- def _handle_new_value(self, value: str) -> None:
- mode = self._props['new-value-mode']
- if isinstance(self.options, list):
- if mode == 'add':
- self.options.append(value)
- elif mode == 'add-unique':
- if value not in self.options:
- self.options.append(value)
- elif mode == 'toggle':
- if value in self.options:
- self.options.remove(value)
- else:
- self.options.append(value)
- # NOTE: self._labels and self._values are updated via self.options since they share the same references
- else:
- if mode in 'add-unique':
- if value not in self.options:
- self.options[value] = value
- elif mode == 'toggle':
- if value in self.options:
- self.options.pop(value)
- else:
- self.options.update({value: value})
- self._update_values_and_labels()
|