diff --git a/examples/reference/widgets/MultiChoice.ipynb b/examples/reference/widgets/MultiChoice.ipynb new file mode 100644 index 0000000000..bbeeb3066a --- /dev/null +++ b/examples/reference/widgets/MultiChoice.ipynb @@ -0,0 +1,127 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import panel as pn\n", + "pn.extension()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The ``MultiChoice`` widget allows selecting multiple values from a list of options. It falls into the broad category of multi-value, option-selection widgets that provide a compatible API and include the [``MultiSelect``](MultiSelect.ipynb), [``CrossSelector``](CrossSelector.ipynb), [``CheckBoxGroup``](CheckBoxGroup.ipynb) and [``CheckButtonGroup``](CheckButtonGroup.ipynb) widgets. The ``MultiChoice`` widget provides a much more compact UI than [``MultiSelect``](MultiSelect.ipynb).\n", + "\n", + "For more information about listening to widget events and laying out widgets refer to the [widgets user guide](../../user_guide/Widgets.ipynb). Alternatively you can learn how to build GUIs by declaring parameters independently of any specific widgets in the [param user guide](../../user_guide/Param.ipynb). To express interactivity entirely using Javascript without the need for a Python server take a look at the [links user guide](../../user_guide/Param.ipynb).\n", + "\n", + "#### Parameters:\n", + "\n", + "For layout and styling related parameters see the [customization user guide](../../user_guide/Customization.ipynb).\n", + "\n", + "##### Core\n", + "\n", + "* **``options``** (list or dict): List or dictionary of options\n", + "* **``max_items``** (int): Maximum number of options that can be selected\n", + "* **``value``** (list): Currently selected option values\n", + "\n", + "##### Display\n", + "\n", + "* **``delete_button``** (boolean): Whether to display a button to delete a selected option\n", + "* **``disabled``** (boolean): Whether the widget is editable\n", + "* **``name``** (str): The title of the widget\n", + "* **``option_limit``** (int): Maximum number of options to display at once.\n", + "* **``placeholder``** (str): String displayed when no selection has been made.\n", + "* **``solid``** (boolean): Whether to display widget with solid or light style.\n", + "\n", + "___" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "multi_choice = pn.widgets.MultiChoice(name='MultiSelect', value=['Apple', 'Pear'],\n", + " options=['Apple', 'Banana', 'Pear', 'Strawberry'])\n", + "\n", + "pn.Column(multi_choice, height=200)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "``MultiChoice.value`` returns a list of the currently selected options:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "multi_choice.value" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `solid` option controls the style of the widget:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pn.Column(multi_choice.clone(solid=False), height=200)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Controls\n", + "\n", + "The `MultiChoice` widget exposes a number of options which can be changed from both Python and Javascript. Try out the effect of these parameters interactively:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pn.Row(multi_choice.controls(jslink=True), multi_choice)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/panel/tests/widgets/test_select.py b/panel/tests/widgets/test_select.py index e53b15dfc0..1165811e30 100644 --- a/panel/tests/widgets/test_select.py +++ b/panel/tests/widgets/test_select.py @@ -5,7 +5,9 @@ import numpy as np import pytest -from panel.widgets import Select, MultiSelect, CrossSelector, ToggleGroup +from panel.widgets import ( + CrossSelector, MultiChoice, MultiSelect, Select, ToggleGroup +) from panel.util import as_unicode @@ -153,6 +155,29 @@ def test_multi_select(document, comm): assert widget.value == ['C', 'A'] +def test_multi_choice(document, comm): + choice = MultiChoice(options=OrderedDict([('A', 'A'), ('1', 1), ('C', object)]), + value=[object, 1], name='MultiChoice') + + widget = choice.get_root(document, comm=comm) + + assert isinstance(widget, choice._widget_type) + assert widget.title == 'MultiChoice' + assert widget.value == ['C', '1'] + assert widget.options == ['A', '1', 'C'] + + widget.value = ['1'] + choice._comm_change({'value': ['1']}) + assert choice.value == [1] + + widget.value = ['A', 'C'] + choice._comm_change({'value': ['A', 'C']}) + assert choice.value == ['A', object] + + choice.value = [object, 'A'] + assert widget.value == ['C', 'A'] + + def test_multi_select_change_options(document, comm): select = MultiSelect(options=OrderedDict([('A', 'A'), ('1', 1), ('C', object)]), value=[object, 1], name='Select') diff --git a/panel/widgets/__init__.py b/panel/widgets/__init__.py index 715c148576..6ac13e8b82 100644 --- a/panel/widgets/__init__.py +++ b/panel/widgets/__init__.py @@ -21,6 +21,7 @@ ) from .select import (# noqa AutocompleteInput, CheckBoxGroup, CheckButtonGroup, CrossSelector, - MultiSelect, RadioButtonGroup, RadioBoxGroup, Select, ToggleGroup + MultiChoice, MultiSelect, RadioButtonGroup, RadioBoxGroup, Select, + ToggleGroup ) from .tables import DataFrame # noqa diff --git a/panel/widgets/select.py b/panel/widgets/select.py index 65e320bf15..c9bbd9963f 100644 --- a/panel/widgets/select.py +++ b/panel/widgets/select.py @@ -14,7 +14,7 @@ AutocompleteInput as _BkAutocompleteInput, CheckboxGroup as _BkCheckboxGroup, CheckboxButtonGroup as _BkCheckboxButtonGroup, MultiSelect as _BkMultiSelect, RadioButtonGroup as _BkRadioButtonGroup, RadioGroup as _BkRadioBoxGroup, - Select as _BkSelect) + Select as _BkSelect, MultiChoice as _BkMultiChoice) from ..layout import Column, VSpacer from ..util import as_unicode, isIn, indexOf @@ -115,16 +115,10 @@ def _get_embed_state(self, root, max_opts=3): lambda x: x.value, 'value', 'cb_obj.value') -class MultiSelect(Select): - - size = param.Integer(default=4, doc=""" - The number of items displayed at once (i.e. determines the - widget height).""") +class _MultiSelectBase(Select): value = param.List(default=[]) - _widget_type = _BkMultiSelect - _supports_embed = False def _process_param_change(self, msg): @@ -150,6 +144,34 @@ def _process_property_change(self, msg): return msg +class MultiSelect(_MultiSelectBase): + + size = param.Integer(default=4, doc=""" + The number of items displayed at once (i.e. determines the + widget height).""") + + _widget_type = _BkMultiSelect + + +class MultiChoice(_MultiSelectBase): + + delete_button = param.Boolean(default=True, doc=""" + Whether to display a button to delete a selected option.""") + + max_items = param.Integer(default=None, bounds=(1, None), doc=""" + Maximum number of options that can be selected.""") + + option_limit = param.Integer(default=None, bounds=(1, None), doc=""" + Maximum number of options to display at once.""") + + placeholder = param.String(default='', doc=""" + String displayed when no selection has been made.""") + + solid = param.Boolean(default=True, doc=""" + Whether to display widget with solid or light style.""") + + _widget_type = _BkMultiChoice + class AutocompleteInput(Widget): @@ -168,7 +190,6 @@ class AutocompleteInput(Widget): _rename = {'name': 'title', 'options': 'completions'} - class _RadioGroupBase(Select): _supports_embed = False