Skip to content

Commit

Permalink
Fix: form visibility (#507)
Browse files Browse the repository at this point in the history
fix: Form visibility #484
  • Loading branch information
mturoci committed Jul 29, 2021
1 parent 4a23fd3 commit 8e5498b
Show file tree
Hide file tree
Showing 70 changed files with 207 additions and 366 deletions.
41 changes: 41 additions & 0 deletions py/examples/form_visibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Form / Visible
# Use "visible" property to control whether form element should be shown / hidden.
# ---
from h2o_wave import main, app, Q, ui


@app('/demo')
async def serve(q: Q):
if not q.client.initialized:
q.page['example'] = ui.form_card(box='1 1 4 10', items=[
ui.text_xl(content='First text'),
ui.text_l(content='Second text'),
ui.text_m(content='Third text'),
ui.text_s(content='Fourth text'),
ui.inline([
ui.button(name='left1', label='Left1'),
ui.button(name='left2', label='Left2'),
ui.button(name='left3', label='Left3'),
]),
ui.buttons(justify='end', items=[
ui.button(name='right1', label='Right1'),
ui.button(name='right2', label='Right2'),
ui.button(name='right3', label='Right3'),
]),
ui.buttons(items=[ui.button(name='show', label='Show'), ui.button(name='hide', label='Hide')])
])
q.client.initialized = True
items = q.page['example'].items
items_to_hide = [
items[0].text_xl,
items[2].text_m,
items[4].inline.items[0].button,
items[5].buttons.items[2].button,
]
if q.args.hide:
for i in items_to_hide:
i.visible = False
if q.args.show:
for i in items_to_hide:
i.visible = True
await q.page.save()
1 change: 1 addition & 0 deletions py/examples/tour.conf
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ layout.py
layout_size.py
layout_responsive.py
form.py
form_visibility.py
text.py
text_sizes.py
label.py
Expand Down
20 changes: 20 additions & 0 deletions py/h2o_wave/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -5245,26 +5245,32 @@ def __init__(
items: List[Stat],
justify: Optional[str] = None,
inset: Optional[bool] = None,
visible: Optional[bool] = None,
):
_guard_vector('Stats.items', items, (Stat,), False, False, False)
_guard_enum('Stats.justify', justify, _StatsJustify, True)
_guard_scalar('Stats.inset', inset, (bool,), False, True, False)
_guard_scalar('Stats.visible', visible, (bool,), False, True, False)
self.items = items
"""The individual stats to be displayed."""
self.justify = justify
"""Specifies how to lay out the individual stats. Defaults to 'start'. One of 'start', 'end', 'center', 'between', 'around'. See enum h2o_wave.ui.StatsJustify."""
self.inset = inset
"""Whether to display the stats with a contrasting background."""
self.visible = visible
"""True if the component should be visible. Defaults to true."""

def dump(self) -> Dict:
"""Returns the contents of this object as a dict."""
_guard_vector('Stats.items', self.items, (Stat,), False, False, False)
_guard_enum('Stats.justify', self.justify, _StatsJustify, True)
_guard_scalar('Stats.inset', self.inset, (bool,), False, True, False)
_guard_scalar('Stats.visible', self.visible, (bool,), False, True, False)
return _dump(
items=[__e.dump() for __e in self.items],
justify=self.justify,
inset=self.inset,
visible=self.visible,
)

@staticmethod
Expand All @@ -5276,13 +5282,17 @@ def load(__d: Dict) -> 'Stats':
_guard_enum('Stats.justify', __d_justify, _StatsJustify, True)
__d_inset: Any = __d.get('inset')
_guard_scalar('Stats.inset', __d_inset, (bool,), False, True, False)
__d_visible: Any = __d.get('visible')
_guard_scalar('Stats.visible', __d_visible, (bool,), False, True, False)
items: List[Stat] = [Stat.load(__e) for __e in __d_items]
justify: Optional[str] = __d_justify
inset: Optional[bool] = __d_inset
visible: Optional[bool] = __d_visible
return Stats(
items,
justify,
inset,
visible,
)


Expand Down Expand Up @@ -5352,11 +5362,13 @@ def __init__(
type: Optional[str] = None,
image: Optional[str] = None,
path: Optional[str] = None,
visible: Optional[bool] = None,
):
_guard_scalar('Image.title', title, (str,), False, False, False)
_guard_scalar('Image.type', type, (str,), False, True, False)
_guard_scalar('Image.image', image, (str,), False, True, False)
_guard_scalar('Image.path', path, (str,), False, True, False)
_guard_scalar('Image.visible', visible, (bool,), False, True, False)
self.title = title
"""The image title, typically displayed as a tooltip."""
self.type = type
Expand All @@ -5365,18 +5377,22 @@ def __init__(
"""Image data, base64-encoded."""
self.path = path
"""The path or URL or data URL of the image, e.g. `/foo.png` or `http://example.com/foo.png` or `data:image/png;base64,???`."""
self.visible = visible
"""True if the component should be visible. Defaults to true."""

def dump(self) -> Dict:
"""Returns the contents of this object as a dict."""
_guard_scalar('Image.title', self.title, (str,), False, False, False)
_guard_scalar('Image.type', self.type, (str,), False, True, False)
_guard_scalar('Image.image', self.image, (str,), False, True, False)
_guard_scalar('Image.path', self.path, (str,), False, True, False)
_guard_scalar('Image.visible', self.visible, (bool,), False, True, False)
return _dump(
title=self.title,
type=self.type,
image=self.image,
path=self.path,
visible=self.visible,
)

@staticmethod
Expand All @@ -5390,15 +5406,19 @@ def load(__d: Dict) -> 'Image':
_guard_scalar('Image.image', __d_image, (str,), False, True, False)
__d_path: Any = __d.get('path')
_guard_scalar('Image.path', __d_path, (str,), False, True, False)
__d_visible: Any = __d.get('visible')
_guard_scalar('Image.visible', __d_visible, (bool,), False, True, False)
title: str = __d_title
type: Optional[str] = __d_type
image: Optional[str] = __d_image
path: Optional[str] = __d_path
visible: Optional[bool] = __d_visible
return Image(
title,
type,
image,
path,
visible,
)


Expand Down
6 changes: 6 additions & 0 deletions py/h2o_wave/ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -1978,20 +1978,23 @@ def stats(
items: List[Stat],
justify: Optional[str] = None,
inset: Optional[bool] = None,
visible: Optional[bool] = None,
) -> Component:
"""Create a set of stats laid out horizontally.
Args:
items: The individual stats to be displayed.
justify: Specifies how to lay out the individual stats. Defaults to 'start'. One of 'start', 'end', 'center', 'between', 'around'. See enum h2o_wave.ui.StatsJustify.
inset: Whether to display the stats with a contrasting background.
visible: True if the component should be visible. Defaults to true.
Returns:
A `h2o_wave.types.Stats` instance.
"""
return Component(stats=Stats(
items,
justify,
inset,
visible,
))


Expand Down Expand Up @@ -2021,6 +2024,7 @@ def image(
type: Optional[str] = None,
image: Optional[str] = None,
path: Optional[str] = None,
visible: Optional[bool] = None,
) -> Component:
"""Create an image.
Expand All @@ -2029,6 +2033,7 @@ def image(
type: The image MIME subtype. One of `apng`, `bmp`, `gif`, `x-icon`, `jpeg`, `png`, `webp`. Required only if `image` is set.
image: Image data, base64-encoded.
path: The path or URL or data URL of the image, e.g. `/foo.png` or `http://example.com/foo.png` or `data:image/png;base64,???`.
visible: True if the component should be visible. Defaults to true.
Returns:
A `h2o_wave.types.Image` instance.
"""
Expand All @@ -2037,6 +2042,7 @@ def image(
type,
image,
path,
visible,
))


Expand Down
16 changes: 12 additions & 4 deletions r/R/ui.R
Original file line number Diff line number Diff line change
Expand Up @@ -2302,19 +2302,23 @@ ui_stat <- function(
#' @param justify Specifies how to lay out the individual stats. Defaults to 'start'.
#' One of 'start', 'end', 'center', 'between', 'around'. See enum h2o_wave.ui.StatsJustify.
#' @param inset Whether to display the stats with a contrasting background.
#' @param visible True if the component should be visible. Defaults to true.
#' @return A Stats instance.
#' @export
ui_stats <- function(
items,
justify = NULL,
inset = NULL) {
inset = NULL,
visible = NULL) {
.guard_vector("items", "WaveStat", items)
# TODO Validate justify
.guard_scalar("inset", "logical", inset)
.guard_scalar("visible", "logical", visible)
.o <- list(stats=list(
items=items,
justify=justify,
inset=inset))
inset=inset,
visible=visible))
class(.o) <- append(class(.o), c(.wave_obj, "WaveComponent"))
return(.o)
}
Expand Down Expand Up @@ -2348,22 +2352,26 @@ ui_inline <- function(
#' @param type The image MIME subtype. One of `apng`, `bmp`, `gif`, `x-icon`, `jpeg`, `png`, `webp`. Required only if `image` is set.
#' @param image Image data, base64-encoded.
#' @param path The path or URL or data URL of the image, e.g. `/foo.png` or `http://example.com/foo.png` or `data:image/png;base64,???`.
#' @param visible True if the component should be visible. Defaults to true.
#' @return A Image instance.
#' @export
ui_image <- function(
title,
type = NULL,
image = NULL,
path = NULL) {
path = NULL,
visible = NULL) {
.guard_scalar("title", "character", title)
.guard_scalar("type", "character", type)
.guard_scalar("image", "character", image)
.guard_scalar("path", "character", path)
.guard_scalar("visible", "logical", visible)
.o <- list(image=list(
title=title,
type=type,
image=image,
path=path))
path=path,
visible=visible))
class(.o) <- append(class(.o), c(.wave_obj, "WaveComponent"))
return(.o)
}
Expand Down
14 changes: 1 addition & 13 deletions ui/src/button.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import { fireEvent, render } from '@testing-library/react'
import React from 'react'
import { Buttons, XButtons, XStandAloneButton } from './button'
import { Buttons, XButtons } from './button'
import { wave } from './ui'

const name = 'test-btn'
Expand Down Expand Up @@ -43,18 +43,6 @@ describe('Button.tsx', () => {
expect(queryByTestId(name)).toBeInTheDocument()
})

it('Does not display buttons when visible set to false', () => {
const { queryByTestId } = render(<XButtons model={{ ...btnProps, visible: false }} />)
expect(queryByTestId(name)).toBeInTheDocument()
expect(queryByTestId(name)).not.toBeVisible()
})

it('Does not display standalone button when visible set to false', () => {
const { queryByTestId } = render(<XStandAloneButton model={{ ...btnProps.items[0].button!, visible: false }} />)
expect(queryByTestId(name)).toBeInTheDocument()
expect(queryByTestId(name)).not.toBeVisible()
})

it('Calls sync() after click', () => {
const
pushMock = jest.fn(),
Expand Down
37 changes: 19 additions & 18 deletions ui/src/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { B, Dict, Id, S } from 'h2o-wave'
import React from 'react'
import { stylesheet } from 'typestyle'
import { Component } from './form'
import { displayMixin } from './theme'
import { XToolTip } from './tooltip'
import { bond, wave } from './ui'

Expand Down Expand Up @@ -90,29 +89,31 @@ const
}

const
XButton = bond(({ model: m }: { model: Button }) => {
wave.args[m.name] = false
XButton = bond(({ model: { name, visible = true, link, label, disabled, icon, caption, value, primary } }: { model: Button }) => {
wave.args[name] = false
const
onClick = () => {
if (m.name.startsWith('#')) {
window.location.hash = m.name.substr(1)
if (name.startsWith('#')) {
window.location.hash = name.substr(1)
return
}
wave.args[m.name] = m.value === undefined || m.value
wave.args[name] = value === undefined || value
wave.push()
},
render = () => {
if (m.link) {
return <Fluent.Link data-test={m.name} disabled={m.disabled} onClick={onClick}>{m.label}</Fluent.Link>
// HACK: Our visibility logic in XComponents doesn't count with nested components, e.g. Butttons > Button.
const styles: Fluent.IButtonStyles = { root: visible ? {} : { display: 'none' } }
if (link) {
return <Fluent.Link data-test={name} disabled={disabled} onClick={onClick} styles={styles}>{label}</Fluent.Link>
}
const btnProps: Fluent.IButtonProps = { text: m.label, disabled: m.disabled, onClick, iconProps: { iconName: m.icon } }
return m.caption?.length
? m.primary
? <Fluent.CompoundButton {...btnProps} data-test={m.name} primary secondaryText={m.caption} />
: <Fluent.CompoundButton {...btnProps} data-test={m.name} secondaryText={m.caption} />
: m.primary
? <Fluent.PrimaryButton {...btnProps} data-test={m.name} />
: <Fluent.DefaultButton {...btnProps} data-test={m.name} />
const btnProps: Fluent.IButtonProps = { text: label, disabled, onClick, styles, iconProps: { iconName: icon } }
return caption?.length
? primary
? <Fluent.CompoundButton {...btnProps} data-test={name} primary secondaryText={caption} />
: <Fluent.CompoundButton {...btnProps} data-test={name} secondaryText={caption} />
: primary
? <Fluent.PrimaryButton {...btnProps} data-test={name} />
: <Fluent.DefaultButton {...btnProps} data-test={name} />
}
return { render }
})
Expand All @@ -125,13 +126,13 @@ export const
</XToolTip>
))
return (
<div data-test={m.name} className={css.buttons} style={displayMixin(m.visible)}>
<div data-test={m.name} className={css.buttons}>
<Fluent.Stack horizontal horizontalAlign={justifications[m.justify || '']} tokens={{ childrenGap: 10 }}>{children}</Fluent.Stack>
</div>
)
},
XStandAloneButton = ({ model: m }: { model: Button }) => (
<div className={css.buttons} style={displayMixin(m.visible)}>
<div className={css.buttons}>
<XButton key={m.name} model={m}>{m.label}</XButton>
</div>
)
2 changes: 0 additions & 2 deletions ui/src/checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import * as Fluent from '@fluentui/react'
import { B, Id, S } from 'h2o-wave'
import React from 'react'
import { displayMixin } from './theme'
import { bond, wave } from './ui'

/**
Expand Down Expand Up @@ -64,7 +63,6 @@ export const
render = () => (
<Fluent.Checkbox
data-test={m.name}
style={displayMixin(m.visible)}
inputProps={{ 'data-test': m.name } as any} // HACK: data-test does not work on root as of this version
label={m.label}
defaultIndeterminate={m.indeterminate}
Expand Down

0 comments on commit 8e5498b

Please sign in to comment.