Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ This repository contains examples of how to build application functionalities in
2. [Create a JSON editor for a Pydantic model](examples/pydantic_monaco)
3. [Conditionally rendering elements](examples/conditional_rendering)
4. [Conditionally disabling elements](examples/conditional_disabling)
5. [Changing Pydantic rules based on user input](examples/dynamic_pydantic_rules)
6. [Complex Pydantic rules](examples/complex_pydantic_rules)
7. [Selecting datafiles from the server](examples/data_selector)
8. [Running a Galaxy tool](examples/run_galaxy_tool)
9. [Running a Galaxy workflow](examples/run_galaxy_workflow)
10. [Working with Plotly](examples/plotly)
11. [Working with Matplotlib](examples/matplotlib)
12. [Working with VTK](examples/vtk)
13. [Synchronizing changes between tabs](examples/multitab)
5. [Creating a dialog](examples/dialog)
6. [Changing Pydantic rules based on user input](examples/dynamic_pydantic_rules)
7. [Complex Pydantic rules](examples/complex_pydantic_rules)
8. [Selecting datafiles from the server](examples/data_selector)
9. [Running a Galaxy tool](examples/run_galaxy_tool)
10. [Running a Galaxy workflow](examples/run_galaxy_workflow)
11. [Working with Plotly](examples/plotly)
12. [Working with Matplotlib](examples/matplotlib)
13. [Working with VTK](examples/vtk)
14. [Synchronizing changes between tabs](examples/multitab)

We also provide examples that take advantage of ORNL resources:

Expand Down
8 changes: 3 additions & 5 deletions examples/complex_pydantic_rules/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from nova.trame import ThemedApp
from nova.trame.view.components import InputField
from nova.trame.view.layouts import VBoxLayout
from trame.widgets import vuetify3 as vuetify

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

def create_vm(self) -> None:
binding = TrameBinding(self.state)
Expand Down
24 changes: 11 additions & 13 deletions examples/conditional_disabling/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from nova.trame import ThemedApp
from nova.trame.view.components import InputField
from nova.trame.view.layouts import GridLayout, HBoxLayout
from trame.widgets import vuetify3 as vuetify

from .model import Model
from .view_model import ViewModel
Expand All @@ -29,18 +28,17 @@ def __init__(self) -> None:
def create_ui(self) -> None:
with super().create_ui() as layout:
with layout.content:
with vuetify.VCard(classes="mx-auto my-4", max_width=600):
with GridLayout(columns=2, classes="mb-2", gap="0.5em"):
InputField(v_model="data.email_address")
InputField(v_model="data.full_name")

with HBoxLayout(gap="0.5em", valign="center"):
InputField(v_model="data.disable_phone_field", type="checkbox")

# Now, we can use disable_phone_field to conditionally disable the phone number field.
# Note that because we need to bind to a parameter that doesn't start with v_ (indicating a
# v-directive in Vue.js), we need to use the Trame tuple syntax to bind to disabled.
InputField(v_model="data.phone_number", disabled=("data.disable_phone_field",))
with GridLayout(columns=2, classes="mb-2", gap="0.5em"):
InputField(v_model="data.email_address")
InputField(v_model="data.full_name")

with HBoxLayout(gap="0.5em", valign="center"):
InputField(v_model="data.disable_phone_field", type="checkbox")

# Now, we can use disable_phone_field to conditionally disable the phone number field.
# Note that because we need to bind to a parameter that doesn't start with v_ (indicating a
# v-directive in Vue.js), we need to use the Trame tuple syntax to bind to disabled.
InputField(v_model="data.phone_number", disabled=("data.disable_phone_field",))

def create_vm(self) -> None:
binding = TrameBinding(self.state)
Expand Down
39 changes: 19 additions & 20 deletions examples/conditional_rendering/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,25 @@ def __init__(self) -> None:
def create_ui(self) -> None:
with super().create_ui() as layout:
with layout.content:
with vuetify.VCard(classes="mx-auto my-4", max_width=600):
with GridLayout(columns=2, classes="mb-2", gap="0.5em"):
InputField(v_model="data.email_address")
InputField(v_model="data.full_name")

with HBoxLayout(classes="mb-2", gap="0.5em", valign="center"):
InputField(v_model="data.show_phone_field", type="checkbox")

# Now, we can use show_phone_field to conditionally render the phone number field.
InputField(v_if="data.show_phone_field", v_model="data.phone_number")
# Following a v_if, we can use v_else_if and v_else.
html.Span("{{ data.full_name }}'s phone number is hidden.", v_else_if="data.full_name")
html.Span("Phone number is hidden.", v_else=True)

with VBoxLayout(gap="0.5em"):
# We can also use v_show to conditionally render a field. Note that when using v_show, you won't
# be able to use v_else_if or v_else afterwards. There is a deeper discussion of the differences
# between v_if and v_show in the Vue.js documentation: https://vuejs.org/guide/essentials/conditional#v-if-vs-v-show.
vuetify.VBtn("Toggle Comments Field", click=self.view_model.toggle_comments)
InputField(v_show="data.show_comments_field", v_model="data.comments", type="textarea")
with GridLayout(columns=2, classes="mb-2", gap="0.5em"):
InputField(v_model="data.email_address")
InputField(v_model="data.full_name")

with HBoxLayout(classes="mb-2", gap="0.5em", valign="center"):
InputField(v_model="data.show_phone_field", type="checkbox")

# Now, we can use show_phone_field to conditionally render the phone number field.
InputField(v_if="data.show_phone_field", v_model="data.phone_number")
# Following a v_if, we can use v_else_if and v_else.
html.Span("{{ data.full_name }}'s phone number is hidden.", v_else_if="data.full_name")
html.Span("Phone number is hidden.", v_else=True)

with VBoxLayout(gap="0.5em", stretch=True):
# We can also use v_show to conditionally render a field. Note that when using v_show, you won't
# be able to use v_else_if or v_else afterwards. There is a deeper discussion of the differences
# between v_if and v_show in the Vue.js documentation: https://vuejs.org/guide/essentials/conditional#v-if-vs-v-show.
vuetify.VBtn("Toggle Comments Field", click=self.view_model.toggle_comments)
InputField(v_show="data.show_comments_field", v_model="data.comments", type="textarea")

def create_vm(self) -> None:
binding = TrameBinding(self.state)
Expand Down
22 changes: 10 additions & 12 deletions examples/data_selector/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from nova.trame.view.components import DataSelector
from nova.trame.view.layouts import VBoxLayout
from trame.widgets import html
from trame.widgets import vuetify3 as vuetify

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

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

def create_vm(self) -> None:
binding = TrameBinding(self.state)
Expand Down
12 changes: 12 additions & 0 deletions examples/dialog/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Runs the dialog example."""

from examples.dialog.view import App


def main() -> None:
app = App()
app.server.start(open_browser=False)


if __name__ == "__main__":
main()
47 changes: 47 additions & 0 deletions examples/dialog/view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""View for dialog example."""

from nova.mvvm.trame_binding import TrameBinding
from nova.trame import ThemedApp
from nova.trame.view.layouts import VBoxLayout
from trame.widgets import client
from trame.widgets import vuetify3 as vuetify

from .view_model import ViewModel


class App(ThemedApp):
"""View for dialog example."""

def __init__(self) -> None:
super().__init__()

self.create_vm()
# If you forget to call connect, then the application will crash when you attempt to update the view.
self.view_model.view_state_bind.connect("view_state")

self.create_ui()

def create_ui(self) -> None:
with super().create_ui() as layout:
with layout.content:
with VBoxLayout(halign="center", valign="center", stretch=True):
vuetify.VBtn("Open the Dialog", click=self.view_model.open_dialog)

# An important note about working with Trame is that it doesn't automatically listen to changes in
# Pydantic fields. While our InputField component handles this automatically, when you are using other
# components to modify fields then you are responsible for ensuring that the field changes are synced to
# the server properly. Fortunately, Trame provides a mechanism for managing this easily via the
# DeepReactive component.
with client.DeepReactive("view_state"):
# VDialog will automatically set view_state.dialog_open to False when the user clicks outside of the
# dialog. Try moving the VDialog outside of the DeepReactive context manager and see how it changes
# the behavior when attempting to close the dialog.
with vuetify.VDialog(v_model="view_state.dialog_open", width=400):
with vuetify.VCard(classes="pa-4"):
vuetify.VCardTitle("Dialog")
vuetify.VCardSubtitle("Click anywhere outside of the dialog to dismiss.")

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

self.view_model = ViewModel(binding)
23 changes: 23 additions & 0 deletions examples/dialog/view_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""View model implementation for dialog example."""

from nova.mvvm.interface import BindingInterface
from pydantic import BaseModel, Field


class ViewState(BaseModel):
"""Pydantic model for holding view state."""

dialog_open: bool = Field(default=False)


class ViewModel:
"""View model implementation for dialog example."""

def __init__(self, binding: BindingInterface) -> None:
self.view_state = ViewState()

self.view_state_bind = binding.new_bind(self.view_state)

def open_dialog(self) -> None:
self.view_state.dialog_open = True
self.view_state_bind.update_in_view(self.view_state)
10 changes: 4 additions & 6 deletions examples/dynamic_pydantic_rules/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from nova.trame import ThemedApp
from nova.trame.view.components import InputField
from nova.trame.view.layouts import GridLayout
from trame.widgets import vuetify3 as vuetify

from .model import Model
from .view_model import ViewModel
Expand All @@ -29,11 +28,10 @@ def __init__(self) -> None:
def create_ui(self) -> None:
with super().create_ui() as layout:
with layout.content:
with vuetify.VCard(classes="mx-auto my-4", max_width=600):
with GridLayout(columns=3, gap="0.5em"):
InputField(v_model="data.min")
InputField(v_model="data.max")
InputField(v_model="data.value")
with GridLayout(columns=3, gap="0.5em"):
InputField(v_model="data.min")
InputField(v_model="data.max")
InputField(v_model="data.value")

def create_vm(self) -> None:
binding = TrameBinding(self.state)
Expand Down
24 changes: 9 additions & 15 deletions examples/matplotlib/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from nova.trame.view.components import InputField
from nova.trame.view.components.visualization import MatplotlibFigure
from nova.trame.view.layouts import GridLayout, HBoxLayout
from trame.widgets import vuetify3 as vuetify

from .model import Model
from .view_model import ViewModel
Expand All @@ -33,20 +32,15 @@ def __init__(self) -> None:
def create_ui(self) -> None:
with super().create_ui() as layout:
with layout.content:
with vuetify.VCard(
classes="d-flex flex-column mx-auto my-4", max_width=1200, style="height: calc(100vh - 120px);"
):
with GridLayout(classes="mb-4", columns=2, gap="1em"):
InputField("plot_data.data_points")
InputField("plot_data.function", type="select")

with HBoxLayout(classes="flex-1-0 overflow-hidden"):
# Try increasing the number of data points to something very large and observe the drop in
# performance. Then, set webagg to True and try again and see the difference. webagg is a
# server-side rendering mode for Matplotlib.
self.figure_view = MatplotlibFigure(
classes="h-100 w-100", figure=self.view_model.get_updated_figure(), webagg=False
)
with GridLayout(classes="mb-4", columns=2, gap="1em"):
InputField("plot_data.data_points")
InputField("plot_data.function", type="select")

with HBoxLayout(classes="overflow-hidden", stretch=True):
# Try increasing the number of data points to something very large and observe the drop in
# performance. Then, set webagg to True and try again and see the difference. webagg is a
# server-side rendering mode for Matplotlib.
self.figure_view = MatplotlibFigure(figure=self.view_model.get_updated_figure(), webagg=False)

def create_vm(self) -> None:
binding = TrameBinding(self.state)
Expand Down
10 changes: 6 additions & 4 deletions examples/multitab/view/input.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
"""View for the input tab."""

from typing import Any

from nova.trame.view.components import InputField
from nova.trame.view.layouts import GridLayout


class InputTab:
"""View for the input tab."""

def __init__(self) -> None:
self.create_ui()
def __init__(self, **kwargs: Any) -> None:
self.create_ui(**kwargs)

def create_ui(self) -> None:
with GridLayout(columns=3, classes="mb-4", gap="0.5em"):
def create_ui(self, **kwargs: Any) -> None:
with GridLayout(columns=3, classes="mb-4", gap="0.5em", **kwargs):
InputField("inputs.count")
InputField("inputs.min")
InputField("inputs.max")
18 changes: 7 additions & 11 deletions examples/multitab/view/main_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from nova.mvvm.trame_binding import TrameBinding
from nova.trame import ThemedApp
from trame.widgets import client
from trame.widgets import vuetify3 as vuetify

from ..model import InputModel, StatsModel
Expand All @@ -28,19 +29,14 @@ def __init__(self) -> None:
def create_ui(self) -> None:
with super().create_ui() as layout:
with layout.pre_content:
with vuetify.VTabs(
v_model="view_state.active_tab", classes="pl-8", update_modelValue="flushState('view_state')"
):
vuetify.VTab("Input", value=0)
vuetify.VTab("Statistics", value=1)
with client.DeepReactive("view_state"):
with vuetify.VTabs(v_model="view_state.active_tab", classes="pl-8"):
vuetify.VTab("Input", value=0)
vuetify.VTab("Statistics", value=1)

with layout.content:
with vuetify.VCard(classes="d-flex flex-column", style="height: calc(100vh - 150px);"):
with vuetify.VWindow(v_model="view_state.active_tab"):
with vuetify.VWindowItem(value=0):
InputTab()
with vuetify.VWindowItem(value=1):
StatsTab()
InputTab(v_if="view_state.active_tab == 0")
StatsTab(v_if="view_state.active_tab == 1")

def create_vm(self) -> None:
binding = TrameBinding(self.state)
Expand Down
Loading