You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I'd like to use form verification: At least, check that no input is empty. If at least one input is empty, inform the user and keep all previous inputs. On every submit, all inputs are part of q.args. If the form is part of a card, this works fine. If the form is part of a dialog, it gets inconsistent:
When submitting the form for the first time, all inputs, empty or not, are part of q.args and can be passed as values to the reloaded form.
When submitting the form for the second time, NO inputs are part of q.args even not those that were visible before in the ui. The ui now also appears empty.
Funnily, when submitting the form for a third time, ONLY the inputs that had a value initially are part of q.args but are now empty
From the fourth time on, no inputs are part of q.args unless provided
Considering this it can get a extra funny:
Submit the form with 1 value missing. The other values show up in the form.
If you now add the missing value and submit again, the previously present values disappear and only the recent added input is maintained
The game continues unless all inputs are filled in one go. Clicking on an input is not enough, it must be edited to be registered as a change
Expected behavior
I'd expect a dialog form to behave the same as a regular page form. But I am aware that it is not recommended to use dialogs for "content-heavy" workflows, so I'm also wondering if this behavior is "intended" or just a limitation of how the dialog box is implemented.
Steps To Reproduce
The app has two pages: Page Form and Dialog Form
On Page Form:
Click Send without any input -> q.args includes all input fields
Click Send without any input multiple times -> q.args includes all input fields
Click Send with partial input -> q.args includes all input fields and the ui shows the previous input
Click Send with partial input multiple times-> q.args includes all input fields and the ui shows the previous input
Click Send with complete input -> q.args includes all input fields. The form is submitted and the fields are cleared
Click Reset -> The fields are cleared
On Dialog Form:
Repeat the same steps as for Page Form
It will NOT show the same result. Instead, the "Actual behavior" will occur
Example code:
fromh2o_waveimportmain, app, Q, ui, on, run_on, data, corefromtypingimportOptional, List# Use for page cards that should be removed when navigating away.# For pages that should be always present on screen use q.page[key] = ...defadd_card(q, name, card) ->None:
q.client.cards.add(name)
q.page[name] =card# Remove all the cards related to navigation.defclear_cards(q, ignore: Optional[List[str]] = []) ->None:
ifnotq.client.cards:
returnfornameinq.client.cards.copy():
ifnamenotinignore:
delq.page[name]
q.client.cards.remove(name)
########################################################################## PAGE FORM ######################################################################################################################################defmake_page1_items(q: Q, message: str=""):
items= [
ui.textbox("page_input_1", label="Para 1", value=q.args.page_input_1ifq.args.page_input_1else""),
ui.textbox("page_input_2", label="Para 2", value=q.args.page_input_2ifq.args.page_input_2else""),
ui.textbox("page_input_3", label="Para 3", value=q.args.page_input_3ifq.args.page_input_3else""),
ui.buttons([
ui.button("page_send", label="Send", primary=True),
ui.button("page_reset", label="Reset", primary=False),
])
]
ifmessage:
items.append(ui.text_m(f'<span style="color: red">{message}</span>'))
returnitems@on('#page1')asyncdefpage1(q: Q):
q.page['sidebar'].value='#page1'clear_cards(q) # When routing, drop all the cards except of the main ones (header, sidebar, meta).add_card(q, "page_form", ui.form_card(
box="horizontal",
items=make_page1_items(q)
))
@on('page_send')asyncdefpage_send(q: Q):
print(q.args)
ifall([q.args.page_input_1, q.args.page_input_2, q.args.page_input_3]):
q.args.page_input_1=q.args.page_input_2=q.args.page_input_3=""add_card(q, "page_form", ui.form_card(
box="horizontal",
items=make_page1_items(q, message="Sent!")
))
else:
add_card(q, "page_form", ui.form_card(
box="horizontal",
items=make_page1_items(q, message="No field may be empty!")
))
@on('page_reset')asyncdefpage_reset(q: Q):
print(q.args)
q.args.page_input_1=q.args.page_input_2=q.args.page_input_3=""add_card(q, "page_form", ui.form_card(
box="horizontal",
items=make_page1_items(q, message="Reset!")
))
########################################################################## POPUP FORM #####################################################################################################################################defmake_popup_items(q: Q, message: str=""):
items= [
ui.textbox("popup_input_1", label="Para 1", value=q.args.popup_input_1ifq.args.popup_input_1else""),
ui.textbox("popup_input_2", label="Para 2", value=q.args.popup_input_2ifq.args.popup_input_2else""),
ui.textbox("popup_input_3", label="Para 3", value=q.args.popup_input_3ifq.args.popup_input_3else""),
ui.buttons([
ui.button("popup_send", label="Send", primary=True),
ui.button("popup_reset", label="Reset", primary=False),
])
]
ifmessage:
items.append(ui.text_m(f'<span style="color: red">{message}</span>'))
returnitems@on('#page2')asyncdefpage2(q: Q):
q.page['sidebar'].value='#page2'clear_cards(q) # When routing, drop all the cards except of the main ones (header, sidebar, meta).add_card(q, "popup_form", ui.form_card(
box="horizontal",
items=[ui.button("open_dialog", "Open Dialog")]
))
@on("open_dialog")asyncdefpage2(q: Q):
q.page['meta'].dialog=ui.dialog(
title='Command',
name="popup",
closable=True,
events=['dismissed'],
items=make_popup_items(q)
)
@on(f'popup.dismissed')asyncdefcommanding_popup_dismissed(q: Q):
q.page['meta'].dialog=None@on('popup_send')asyncdefpopup_send(q: Q):
print(q.args)
ifall([q.args.popup_input_1, q.args.popup_input_2, q.args.popup_input_3]):
q.args.popup_input_1=q.args.popup_input_2=q.args.popup_input_3=""q.page['meta'].dialog.items=make_popup_items(q, message="Sent")
else:
q.page['meta'].dialog.items=make_popup_items(q, message="No field may be empty!")
awaitq.page.save()
@on('popup_reset')asyncdefpopup_reset(q: Q):
print(q.args)
q.args.popup_input_1=q.args.popup_input_2=q.args.popup_input_3=""q.page['meta'].dialog.items=make_popup_items(q, message="Reset")
awaitq.page.save()
##################################################################################################################################################asyncdefinit(q: Q) ->None:
q.page['meta'] =ui.meta_card(box='', layouts=[ui.layout(breakpoint='xs', min_height='100vh', zones=[
ui.zone('main', size='1', direction=ui.ZoneDirection.ROW, zones=[
ui.zone('sidebar', size='250px'),
ui.zone('body', zones=[
ui.zone('content', zones=[
# Specify various zones and use the one that is currently needed. Empty zones are ignored.ui.zone('horizontal', direction=ui.ZoneDirection.ROW),
ui.zone('vertical'),
ui.zone('grid', direction=ui.ZoneDirection.ROW, wrap='stretch', justify='center')
]),
]),
])
])])
q.page['sidebar'] =ui.nav_card(
box='sidebar', color='primary', title='My App', subtitle="Let's conquer the world!",
value=f'#{q.args["#"]}'ifq.args['#'] else'#page1',
image='https://wave.h2o.ai/img/h2o-logo.svg', items=[
ui.nav_group('Menu', items=[
ui.nav_item(name='#page1', label='Page Form'),
ui.nav_item(name='#page2', label='Dialog Form'),
]),
],
secondary_items=[
ui.persona(title='John Doe', subtitle='Developer', size='s',
image='https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&h=750&w=1260'),
]
)
# If no active hash present, render page1.ifq.args['#'] isNone:
awaitpage1(q)
@app('/')asyncdefserve(q: Q):
# Run only once per client connection.ifnotq.client.initialized:
q.client.cards=set()
awaitinit(q)
q.client.initialized=True# Handle routing.awaitrun_on(q)
awaitq.page.save()
The text was updated successfully, but these errors were encountered:
Thank you for all the details! While this bug and resolution are discussed, I wanted to let you know about copy_expando which might help if you are currently blocked.
You are currently doing a lot of work in the Query Arguments q.args variable, but you are looking at content for each browser session that you want to access across many calls. Instead, it would be preferred to use the q.client variable. These will persist across interactions in the client session, and be separate for each browser session.
You can assign each args to a client in your popup_send function.
...
q.client.popup_input_3=q.args.popup_input_3
Or, if your form gets long and this is tedious, you can copy all values from q.args into q.client such as
fromh2o_waveimport ... copy_expando@app('/')asyncdefserve(q: Q):
copy_expando(q.args, q.client)
...
# replace all q.args.variable with q.client.variable
Hi, thanks for the feedback! I am using q.client to buffer certain things but was not aware of copy_expando. That looks like a good workaround for my actual use case where I generate the input forms dynamically.
Regarding using q.args a lot: I don't understand why q.client should be the preferred solution for this. I tend to use q.client for information I want to persist during the client session over multiple pages and that is not always form related. To me it feels natural to use q.args to persist data for the current form since I then don't have to worry about cleaning it when I move to the next page/form. There, I did run once into the problem that I was buffering data in q.client for a form template that I used on multiple pages and thus had to add a reset to that specific buffer just in case.
So, of course that's not a biggy, but with q.args I could skip these steps :) Still, happy to learn about better alternatives!
Wave SDK Version, OS
Windows 10
Python 3.10.7
h2o-wave 1.0.0
Actual behavior
I'd like to use form verification: At least, check that no input is empty. If at least one input is empty, inform the user and keep all previous inputs. On every submit, all inputs are part of
q.args
. If the form is part of a card, this works fine. If the form is part of a dialog, it gets inconsistent:q.args
and can be passed as values to the reloaded form.q.args
even not those that were visible before in the ui. The ui now also appears empty.q.args
but are now emptyq.args
unless providedConsidering this it can get a extra funny:
Expected behavior
I'd expect a dialog form to behave the same as a regular page form. But I am aware that it is not recommended to use dialogs for "content-heavy" workflows, so I'm also wondering if this behavior is "intended" or just a limitation of how the dialog box is implemented.
Steps To Reproduce
The app has two pages:
Page Form
andDialog Form
On
Page Form
:Send
without any input ->q.args
includes all input fieldsSend
without any input multiple times ->q.args
includes all input fieldsSend
with partial input ->q.args
includes all input fields and the ui shows the previous inputSend
with partial input multiple times->q.args
includes all input fields and the ui shows the previous inputSend
with complete input ->q.args
includes all input fields. The form is submitted and the fields are clearedReset
-> The fields are clearedOn
Dialog Form
:Page Form
Example code:
The text was updated successfully, but these errors were encountered: