Skip to content

Commit 08c6c43

Browse files
authored
Merge pull request #16 from nova-model/15-add-dialog-example
Update examples repo for nova-trame 1.0.0
2 parents b3175bc + c36b0df commit 08c6c43

File tree

22 files changed

+301
-240
lines changed

22 files changed

+301
-240
lines changed

README.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@ This repository contains examples of how to build application functionalities in
88
2. [Create a JSON editor for a Pydantic model](examples/pydantic_monaco)
99
3. [Conditionally rendering elements](examples/conditional_rendering)
1010
4. [Conditionally disabling elements](examples/conditional_disabling)
11-
5. [Changing Pydantic rules based on user input](examples/dynamic_pydantic_rules)
12-
6. [Complex Pydantic rules](examples/complex_pydantic_rules)
13-
7. [Selecting datafiles from the server](examples/data_selector)
14-
8. [Running a Galaxy tool](examples/run_galaxy_tool)
15-
9. [Running a Galaxy workflow](examples/run_galaxy_workflow)
16-
10. [Working with Plotly](examples/plotly)
17-
11. [Working with Matplotlib](examples/matplotlib)
18-
12. [Working with VTK](examples/vtk)
19-
13. [Synchronizing changes between tabs](examples/multitab)
11+
5. [Creating a dialog](examples/dialog)
12+
6. [Changing Pydantic rules based on user input](examples/dynamic_pydantic_rules)
13+
7. [Complex Pydantic rules](examples/complex_pydantic_rules)
14+
8. [Selecting datafiles from the server](examples/data_selector)
15+
9. [Running a Galaxy tool](examples/run_galaxy_tool)
16+
10. [Running a Galaxy workflow](examples/run_galaxy_workflow)
17+
11. [Working with Plotly](examples/plotly)
18+
12. [Working with Matplotlib](examples/matplotlib)
19+
13. [Working with VTK](examples/vtk)
20+
14. [Synchronizing changes between tabs](examples/multitab)
2021

2122
We also provide examples that take advantage of ORNL resources:
2223

examples/complex_pydantic_rules/view.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from nova.trame import ThemedApp
55
from nova.trame.view.components import InputField
66
from nova.trame.view.layouts import VBoxLayout
7-
from trame.widgets import vuetify3 as vuetify
87

98
from .model import Model
109
from .view_model import ViewModel
@@ -29,10 +28,9 @@ def __init__(self) -> None:
2928
def create_ui(self) -> None:
3029
with super().create_ui() as layout:
3130
with layout.content:
32-
with vuetify.VCard(classes="mx-auto my-4", max_width=600):
33-
with VBoxLayout(columns=3, gap="0.5em"):
34-
InputField(v_model="data.text", type="textarea")
35-
InputField(v_model="data.tokenized_text", readonly=True, type="textarea")
31+
with VBoxLayout(gap="0.5em", stretch=True):
32+
InputField(v_model="data.text", type="textarea")
33+
InputField(v_model="data.tokenized_text", readonly=True, type="textarea")
3634

3735
def create_vm(self) -> None:
3836
binding = TrameBinding(self.state)

examples/conditional_disabling/view.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from nova.trame import ThemedApp
55
from nova.trame.view.components import InputField
66
from nova.trame.view.layouts import GridLayout, HBoxLayout
7-
from trame.widgets import vuetify3 as vuetify
87

98
from .model import Model
109
from .view_model import ViewModel
@@ -29,18 +28,17 @@ def __init__(self) -> None:
2928
def create_ui(self) -> None:
3029
with super().create_ui() as layout:
3130
with layout.content:
32-
with vuetify.VCard(classes="mx-auto my-4", max_width=600):
33-
with GridLayout(columns=2, classes="mb-2", gap="0.5em"):
34-
InputField(v_model="data.email_address")
35-
InputField(v_model="data.full_name")
36-
37-
with HBoxLayout(gap="0.5em", valign="center"):
38-
InputField(v_model="data.disable_phone_field", type="checkbox")
39-
40-
# Now, we can use disable_phone_field to conditionally disable the phone number field.
41-
# Note that because we need to bind to a parameter that doesn't start with v_ (indicating a
42-
# v-directive in Vue.js), we need to use the Trame tuple syntax to bind to disabled.
43-
InputField(v_model="data.phone_number", disabled=("data.disable_phone_field",))
31+
with GridLayout(columns=2, classes="mb-2", gap="0.5em"):
32+
InputField(v_model="data.email_address")
33+
InputField(v_model="data.full_name")
34+
35+
with HBoxLayout(gap="0.5em", valign="center"):
36+
InputField(v_model="data.disable_phone_field", type="checkbox")
37+
38+
# Now, we can use disable_phone_field to conditionally disable the phone number field.
39+
# Note that because we need to bind to a parameter that doesn't start with v_ (indicating a
40+
# v-directive in Vue.js), we need to use the Trame tuple syntax to bind to disabled.
41+
InputField(v_model="data.phone_number", disabled=("data.disable_phone_field",))
4442

4543
def create_vm(self) -> None:
4644
binding = TrameBinding(self.state)

examples/conditional_rendering/view.py

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,26 +30,25 @@ def __init__(self) -> None:
3030
def create_ui(self) -> None:
3131
with super().create_ui() as layout:
3232
with layout.content:
33-
with vuetify.VCard(classes="mx-auto my-4", max_width=600):
34-
with GridLayout(columns=2, classes="mb-2", gap="0.5em"):
35-
InputField(v_model="data.email_address")
36-
InputField(v_model="data.full_name")
37-
38-
with HBoxLayout(classes="mb-2", gap="0.5em", valign="center"):
39-
InputField(v_model="data.show_phone_field", type="checkbox")
40-
41-
# Now, we can use show_phone_field to conditionally render the phone number field.
42-
InputField(v_if="data.show_phone_field", v_model="data.phone_number")
43-
# Following a v_if, we can use v_else_if and v_else.
44-
html.Span("{{ data.full_name }}'s phone number is hidden.", v_else_if="data.full_name")
45-
html.Span("Phone number is hidden.", v_else=True)
46-
47-
with VBoxLayout(gap="0.5em"):
48-
# We can also use v_show to conditionally render a field. Note that when using v_show, you won't
49-
# be able to use v_else_if or v_else afterwards. There is a deeper discussion of the differences
50-
# between v_if and v_show in the Vue.js documentation: https://vuejs.org/guide/essentials/conditional#v-if-vs-v-show.
51-
vuetify.VBtn("Toggle Comments Field", click=self.view_model.toggle_comments)
52-
InputField(v_show="data.show_comments_field", v_model="data.comments", type="textarea")
33+
with GridLayout(columns=2, classes="mb-2", gap="0.5em"):
34+
InputField(v_model="data.email_address")
35+
InputField(v_model="data.full_name")
36+
37+
with HBoxLayout(classes="mb-2", gap="0.5em", valign="center"):
38+
InputField(v_model="data.show_phone_field", type="checkbox")
39+
40+
# Now, we can use show_phone_field to conditionally render the phone number field.
41+
InputField(v_if="data.show_phone_field", v_model="data.phone_number")
42+
# Following a v_if, we can use v_else_if and v_else.
43+
html.Span("{{ data.full_name }}'s phone number is hidden.", v_else_if="data.full_name")
44+
html.Span("Phone number is hidden.", v_else=True)
45+
46+
with VBoxLayout(gap="0.5em", stretch=True):
47+
# We can also use v_show to conditionally render a field. Note that when using v_show, you won't
48+
# be able to use v_else_if or v_else afterwards. There is a deeper discussion of the differences
49+
# between v_if and v_show in the Vue.js documentation: https://vuejs.org/guide/essentials/conditional#v-if-vs-v-show.
50+
vuetify.VBtn("Toggle Comments Field", click=self.view_model.toggle_comments)
51+
InputField(v_show="data.show_comments_field", v_model="data.comments", type="textarea")
5352

5453
def create_vm(self) -> None:
5554
binding = TrameBinding(self.state)

examples/data_selector/view.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from nova.trame.view.components import DataSelector
88
from nova.trame.view.layouts import VBoxLayout
99
from trame.widgets import html
10-
from trame.widgets import vuetify3 as vuetify
1110

1211
from .model import Model
1312
from .view_model import ViewModel
@@ -34,17 +33,16 @@ def create_ui(self) -> None:
3433

3534
with super().create_ui() as layout:
3635
with layout.content:
37-
with vuetify.VCard(classes="mx-auto my-4 pa-1", max_width=600):
38-
with VBoxLayout(height=400):
39-
# Please note that this is a dangerous operation. You should ensure that you restrict this
40-
# component to only expose files that are strictly necessary to making your application
41-
# functional.
42-
DataSelector(
43-
v_model="data.selected_files",
44-
directory=os.environ.get("HOME", "/"),
45-
classes="mb-1",
46-
)
47-
html.Span("You have selected {{ data.selected_files.length }} files.")
36+
with VBoxLayout(stretch=True):
37+
# Please note that this is a dangerous operation. You should ensure that you restrict this
38+
# component to only expose files that are strictly necessary to making your application
39+
# functional.
40+
DataSelector(
41+
v_model="data.selected_files",
42+
directory=os.environ.get("HOME", "/"),
43+
classes="mb-1",
44+
)
45+
html.Span("You have selected {{ data.selected_files.length }} files.")
4846

4947
def create_vm(self) -> None:
5048
binding = TrameBinding(self.state)

examples/dialog/main.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
"""Runs the dialog example."""
2+
3+
from examples.dialog.view import App
4+
5+
6+
def main() -> None:
7+
app = App()
8+
app.server.start(open_browser=False)
9+
10+
11+
if __name__ == "__main__":
12+
main()

examples/dialog/view.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""View for dialog example."""
2+
3+
from nova.mvvm.trame_binding import TrameBinding
4+
from nova.trame import ThemedApp
5+
from nova.trame.view.layouts import VBoxLayout
6+
from trame.widgets import client
7+
from trame.widgets import vuetify3 as vuetify
8+
9+
from .view_model import ViewModel
10+
11+
12+
class App(ThemedApp):
13+
"""View for dialog example."""
14+
15+
def __init__(self) -> None:
16+
super().__init__()
17+
18+
self.create_vm()
19+
# If you forget to call connect, then the application will crash when you attempt to update the view.
20+
self.view_model.view_state_bind.connect("view_state")
21+
22+
self.create_ui()
23+
24+
def create_ui(self) -> None:
25+
with super().create_ui() as layout:
26+
with layout.content:
27+
with VBoxLayout(halign="center", valign="center", stretch=True):
28+
vuetify.VBtn("Open the Dialog", click=self.view_model.open_dialog)
29+
30+
# An important note about working with Trame is that it doesn't automatically listen to changes in
31+
# Pydantic fields. While our InputField component handles this automatically, when you are using other
32+
# components to modify fields then you are responsible for ensuring that the field changes are synced to
33+
# the server properly. Fortunately, Trame provides a mechanism for managing this easily via the
34+
# DeepReactive component.
35+
with client.DeepReactive("view_state"):
36+
# VDialog will automatically set view_state.dialog_open to False when the user clicks outside of the
37+
# dialog. Try moving the VDialog outside of the DeepReactive context manager and see how it changes
38+
# the behavior when attempting to close the dialog.
39+
with vuetify.VDialog(v_model="view_state.dialog_open", width=400):
40+
with vuetify.VCard(classes="pa-4"):
41+
vuetify.VCardTitle("Dialog")
42+
vuetify.VCardSubtitle("Click anywhere outside of the dialog to dismiss.")
43+
44+
def create_vm(self) -> None:
45+
binding = TrameBinding(self.state)
46+
47+
self.view_model = ViewModel(binding)

examples/dialog/view_model.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"""View model implementation for dialog example."""
2+
3+
from nova.mvvm.interface import BindingInterface
4+
from pydantic import BaseModel, Field
5+
6+
7+
class ViewState(BaseModel):
8+
"""Pydantic model for holding view state."""
9+
10+
dialog_open: bool = Field(default=False)
11+
12+
13+
class ViewModel:
14+
"""View model implementation for dialog example."""
15+
16+
def __init__(self, binding: BindingInterface) -> None:
17+
self.view_state = ViewState()
18+
19+
self.view_state_bind = binding.new_bind(self.view_state)
20+
21+
def open_dialog(self) -> None:
22+
self.view_state.dialog_open = True
23+
self.view_state_bind.update_in_view(self.view_state)

examples/dynamic_pydantic_rules/view.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
from nova.trame import ThemedApp
55
from nova.trame.view.components import InputField
66
from nova.trame.view.layouts import GridLayout
7-
from trame.widgets import vuetify3 as vuetify
87

98
from .model import Model
109
from .view_model import ViewModel
@@ -29,11 +28,10 @@ def __init__(self) -> None:
2928
def create_ui(self) -> None:
3029
with super().create_ui() as layout:
3130
with layout.content:
32-
with vuetify.VCard(classes="mx-auto my-4", max_width=600):
33-
with GridLayout(columns=3, gap="0.5em"):
34-
InputField(v_model="data.min")
35-
InputField(v_model="data.max")
36-
InputField(v_model="data.value")
31+
with GridLayout(columns=3, gap="0.5em"):
32+
InputField(v_model="data.min")
33+
InputField(v_model="data.max")
34+
InputField(v_model="data.value")
3735

3836
def create_vm(self) -> None:
3937
binding = TrameBinding(self.state)

examples/matplotlib/view.py

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from nova.trame.view.components import InputField
88
from nova.trame.view.components.visualization import MatplotlibFigure
99
from nova.trame.view.layouts import GridLayout, HBoxLayout
10-
from trame.widgets import vuetify3 as vuetify
1110

1211
from .model import Model
1312
from .view_model import ViewModel
@@ -33,20 +32,15 @@ def __init__(self) -> None:
3332
def create_ui(self) -> None:
3433
with super().create_ui() as layout:
3534
with layout.content:
36-
with vuetify.VCard(
37-
classes="d-flex flex-column mx-auto my-4", max_width=1200, style="height: calc(100vh - 120px);"
38-
):
39-
with GridLayout(classes="mb-4", columns=2, gap="1em"):
40-
InputField("plot_data.data_points")
41-
InputField("plot_data.function", type="select")
42-
43-
with HBoxLayout(classes="flex-1-0 overflow-hidden"):
44-
# Try increasing the number of data points to something very large and observe the drop in
45-
# performance. Then, set webagg to True and try again and see the difference. webagg is a
46-
# server-side rendering mode for Matplotlib.
47-
self.figure_view = MatplotlibFigure(
48-
classes="h-100 w-100", figure=self.view_model.get_updated_figure(), webagg=False
49-
)
35+
with GridLayout(classes="mb-4", columns=2, gap="1em"):
36+
InputField("plot_data.data_points")
37+
InputField("plot_data.function", type="select")
38+
39+
with HBoxLayout(classes="overflow-hidden", stretch=True):
40+
# Try increasing the number of data points to something very large and observe the drop in
41+
# performance. Then, set webagg to True and try again and see the difference. webagg is a
42+
# server-side rendering mode for Matplotlib.
43+
self.figure_view = MatplotlibFigure(figure=self.view_model.get_updated_figure(), webagg=False)
5044

5145
def create_vm(self) -> None:
5246
binding = TrameBinding(self.state)

0 commit comments

Comments
 (0)