Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: form visibility #507

Merged
merged 4 commits into from
Jul 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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