diff --git a/baukit/labwidget.py b/baukit/labwidget.py index ca08ed8..b71d1de 100644 --- a/baukit/labwidget.py +++ b/baukit/labwidget.py @@ -747,13 +747,13 @@ class Choice(Widget): A set of radio button choices. """ - def __init__(self, choices=None, selection=None, + def __init__(self, choices=None, value=None, **kwargs): super().__init__(**kwargs) if choices is None: choices = [] self.choices = Property(choices) - self.selection = Property(selection) + self.value = Property(value) def widget_js(self): # Note that the 'input' event would enable during-drag feedback, @@ -771,13 +771,13 @@ class Choice(Widget): element.innerHTML = lines.join(); } model.on('choices horizontal', render); - model.on('selection', (ev) => { + model.on('value', (ev) => { [...element.querySelectorAll('input')].forEach((e) => { e.checked = (e.value == ev.value); }) }); element.addEventListener('change', (e) => { - model.set('selection', element.choice.value); + model.set('value', element.choice.value); }); ''') @@ -786,9 +786,11 @@ class Choice(Widget): with show.enter('form', self.std_attrs(), out=out): for value in self.choices: with show.enter('label', out=out): - show.emit('input', - (show.attrs(checked=None) if value == self.selection else None), - name='choice', type='radio', value=value, out=out) + show.emit(show.PLAIN('input', + (show.attr(checked=None) if value == self.value else None), + name='choice', type='radio', value=value), out=out) + with show.enter('div', out=out): + out.append(html.escape(str(value))) return ''.join(out) class Menu(Widget): @@ -796,12 +798,12 @@ class Menu(Widget): A dropdown choice. """ - def __init__(self, choices=None, selection=None, **kwargs): + def __init__(self, choices=None, value=None, **kwargs): super().__init__(**kwargs) if choices is None: choices = [] self.choices = Property(choices) - self.selection = Property(selection) + self.value = Property(value) def widget_js(self): return minify(''' @@ -810,21 +812,21 @@ class Menu(Widget): .replace(/>/g, ">").replace(/"/g, """); } function render() { - var selection = model.get('selection'); + var value = model.get('value'); var lines = model.get('choices').map((c) => { return ''; }); element.menu.innerHTML = lines.join('\\n'); } - model.on('selection', (ev) => { + model.on('value', (ev) => { [...element.querySelectorAll('option')].forEach((e) => { e.selected = (e.value == ev.value); }) }); element.addEventListener('change', (e) => { - model.set('selection', element.menu.value); + model.set('value', element.menu.value); }); ''') @@ -834,7 +836,7 @@ class Menu(Widget): with show.enter(show.Tag('select', name='menu'), out=out): for value in self.choices: with show.enter(show.Tag('option', - (show.attr(selected=None) if value == self.selection else None), + (show.attr(selected=None) if value == self.value else None), value=value), out=out): out.append(html.escape(str(value))) return ''.join(out) @@ -900,11 +902,13 @@ class Datalist(Widget): out = [] with show.enter('form', self.std_attrs(), onsubmit='return false;', out=out): - show.emit('input', name='inp', list=self.datalist_id(), - autocomplete='off', out=out) - with show.enter(show.Tag('datalist'), id=self.datalist_id()): + show.emit(show.PLAIN('input', name='inp', list=self.datalist_id(), + value=self.value, autocomplete='off'), out=out) + with show.enter(show.PLAIN('datalist', show.style(display='none')), + id=self.datalist_id(), out=out): for value in self.choices: - show.emit('option', value=str(value)) + show.emit('option', value=str(value), out=out) + return ''.join(out) class Div(Widget): diff --git a/baukit/show.py b/baukit/show.py index dbe3323..75caa42 100644 --- a/baukit/show.py +++ b/baukit/show.py @@ -237,7 +237,8 @@ TR = Tag('tr', ChildTag(TD)) TABLE = Tag('table', ChildTag(TR)) # Remove defaults -PLAIN = Tag() +PLAIN = Tag(style(display=None, flex=None, flexFlow=None, gap=None), + ChildTag(None)) # The TIGHT style allows the content to provide the size, instead of # expanding to fill the available space. diff --git a/notebooks/using_show_and_widgets.ipynb b/notebooks/using_show_and_widgets.ipynb index dbe574d..7a281bd 100644 --- a/notebooks/using_show_and_widgets.ipynb +++ b/notebooks/using_show_and_widgets.ipynb @@ -191,9 +191,9 @@ "id": "b78f64fa", "metadata": {}, "source": [ - "## Tight columns and Wrapped rows\n", + "## Tight columns and wrapped rows\n", "\n", - "A few useful `show.style` instances are predefined as constants. For example, `show.style(display='inline-flex')` is also called `show.TIGHT`, because it provides a tight layout of rows that are sized to fit the content instead of making the flexbox expand to fill its container. Similaly `show.WRAP` makes a row that packs the data to the left and wraps it (like the way text flows), instad of as spaced-out columns." + "A few useful `show.style` instances are predefined as constants. For example, `show.style(display='inline-flex')` is also called `show.TIGHT`, because it provides a tight layout of rows that are sized to fit the content instead of making the flexbox expand to fill its container. Similaly `show.WRAP` makes a row that packs the data to the left and wraps it (like the way text flows), instead of as spaced-out columns." ] }, { @@ -213,7 +213,7 @@ "metadata": {}, "outputs": [], "source": [ - "show(show.TIGHT,\n", + "show(show.TIGHT, show.style(gap=0),\n", " [[[show.style(font='italic 24px serif'), 'lots of data',\n", " show.style(background='gainsboro'),\n", " show.WRAP, [f'({a},{b})' for a in range(i) for b in range(j)]]\n", @@ -361,6 +361,80 @@ "show([[div, show.style(flex=7), ra3]])" ] }, + { + "cell_type": "markdown", + "id": "21ecb0c0", + "metadata": {}, + "source": [ + "## Menus, Choices, and Datalists\n", + "\n", + "You can make dropdown menus using `Menu`; radio button choices using `Choice`; and editable dropdowns (combobox) using `Datalist`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8f3032e1", + "metadata": {}, + "outputs": [], + "source": [ + "from baukit import Menu\n", + "\n", + "menu = Menu(choices=list(range(0,101,10)))\n", + "ra4 = Range(step=10)\n", + "menu.value = ra4.prop('value')\n", + "\n", + "show([[menu, show.style(flex=7), ra4]])" + ] + }, + { + "cell_type": "markdown", + "id": "20d1070c", + "metadata": {}, + "source": [ + "Radio button choice arrays can be arranged horizontally or vertically based on the level of nesting." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8df5514a", + "metadata": {}, + "outputs": [], + "source": [ + "from baukit import Choice\n", + "\n", + "choice = Choice(choices=list(range(0,101,50)))\n", + "ra6 = Range(step=50)\n", + "choice.value = ra6.prop('value')\n", + "\n", + "show([[choice, show.style(flex=6), ra6]])\n", + "show([[[choice], show.style(flex=6), ra6]])" + ] + }, + { + "cell_type": "markdown", + "id": "8d85174a", + "metadata": {}, + "source": [ + "A Datalist allows free-form input while also providing a dropdown menu for choices." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "07a2ed9e", + "metadata": {}, + "outputs": [], + "source": [ + "from baukit import Datalist\n", + "dl = Datalist(choices=list(range(0,101,20)))\n", + "ra5 = Range(step=1)\n", + "dl.value = ra5.prop('value')\n", + "\n", + "show([[dl, show.style(flex=7), ra5]])" + ] + }, { "cell_type": "markdown", "id": "8b8a23a9",