Skip to content

Commit

Permalink
don't pass values to js at all
Browse files Browse the repository at this point in the history
  • Loading branch information
eimrek committed Jun 26, 2024
1 parent cc62045 commit bc1b1f7
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 22 deletions.
16 changes: 8 additions & 8 deletions example.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@
"\n",
"from widget_dropdown import DropdownWidget\n",
"options = [\n",
" {\"value\": \"\", \"text\": \"Select an option\", \"disabled\": True},\n",
" {\"value\": \"option1\", \"text\": \"Option 1\", \"disabled\": False},\n",
" {\"text\": \"Select an option\", \"value\": \"\", \"disabled\": True},\n",
" {\"text\": \"Option 1\", \"value\": 1, \"disabled\": False},\n",
" {\n",
" \"group\": \"Group 1\",\n",
" \"options\": [\n",
" {\"value\": {\"test\": 1}, \"text\": \"Option 2\", \"disabled\": False},\n",
" {\"value\": \"option3\", \"text\": \"Option 3\", \"disabled\": True},\n",
" {\"text\": \"Option 2\", \"value\": {\"test\": 1}, \"disabled\": False},\n",
" {\"text\": \"Option 3\", \"value\": \"option3\", \"disabled\": True},\n",
" ],\n",
" },\n",
" {\n",
" \"group\": \"Group 2\",\n",
" \"options\": [\n",
" {\"value\": [\"test\"], \"text\": \"Option 4\", \"disabled\": False},\n",
" {\"text\": \"Option 4\", \"value\": [\"test\"], \"disabled\": False},\n",
" ],\n",
" },\n",
"]\n",
Expand All @@ -48,7 +48,7 @@
"outputs": [],
"source": [
"# change the selected value\n",
"w.value = \"option1\""
"w.value = 1"
]
},
{
Expand All @@ -69,8 +69,8 @@
"source": [
"# change the options\n",
"w.options = [\n",
" {\"value\": \"new1\", \"text\": \"New1\", \"disabled\": False},\n",
" {\"value\": {\"test\": 1}, \"text\": \"New2\", \"disabled\": False},\n",
" {\"text\": \"New1\", \"value\": \"new1\", \"disabled\": False},\n",
" {\"text\": \"New2\", \"value\": {\"test\": 1}, \"disabled\": False},\n",
"]"
]
},
Expand Down
6 changes: 3 additions & 3 deletions js/widget.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ function render({ model, el }) {
selectElement.selectedIndex = 0;
}

setOptions(model.get("options"));
setOptions(model.get("_options_js"));

model.on("change:options", () => {
setOptions(model.get("options"));
model.on("change:_options_js", () => {
setOptions(model.get("_options_js"));
});

selectElement.addEventListener("change", function (event) {
Expand Down
33 changes: 22 additions & 11 deletions src/widget_dropdown/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import copy
import importlib.metadata
import pathlib

Expand All @@ -9,32 +10,32 @@
except importlib.metadata.PackageNotFoundError:
__version__ = "unknown"

DEFAULT_OPTIONS = [{"value": "", "text": "No options", "disabled": True}]
DEFAULT_OPTIONS = [{"text": "No options", "value": "", "disabled": True}]


class DropdownWidget(anywidget.AnyWidget):
"""
A Jupyter widget that displays a dropdown menu.
Selection entries are represented by a dictionary with the following
Dropdown entries are represented by a dictionary with the following
structure:
.. code-block:: python
{"value": value, "text": text, "disabled": True/False}
{"text": text, "value": value, "disabled": True/False}
where the value can be a custom python object. These values are not
used in the javascript frontend, which just tracks the selected index.
Selection entries can be grouped under a common title:
Entries can be grouped under a common title:
.. code-block:: python
{
"group": "Group 1",
"options": [
{"value": value1, "text": "Option 1", "disabled": False},
{"value": value2, "text": "Option 2", "disabled": True},
{"text": "Option 1", "value": value1, "disabled": False},
{"text": "Option 2", "value": value2, "disabled": True},
],
}
Expand All @@ -43,7 +44,11 @@ class DropdownWidget(anywidget.AnyWidget):
_esm = pathlib.Path(__file__).parent / "static" / "widget.js"
_css = pathlib.Path(__file__).parent / "static" / "widget.css"

options = traitlets.List([]).tag(sync=True)
# options traitlet is not synced to js, as it can contain non-serializable values
options = traitlets.List([])

# values are removed in _options_js and it is synced to js
_options_js = traitlets.List([]).tag(sync=True)

index = traitlets.Int(0).tag(sync=True)
disabled = traitlets.Bool(False).tag(sync=True)
Expand All @@ -66,15 +71,21 @@ def __init__(self, options=None, **kwargs):
@traitlets.observe("options")
def _observe_options(self, change):
self.values = []
self._get_values(change.new)
temp_options = copy.deepcopy(change.new)
self._parse_values(temp_options)
self._options_js = temp_options

def _get_values(self, options):
"""Extract values from the options dict."""
def _parse_values(self, options):
"""
Extract values from the input dict into self.values
and deletes them from the original dict.
"""
for opt in options:
if "group" in opt:
self._get_values(opt["options"])
self._parse_values(opt["options"])
else:
self.values.append(opt["value"])
del opt["value"]

@traitlets.observe("index")
def _observe_index(self, change):
Expand Down

0 comments on commit bc1b1f7

Please sign in to comment.