MVVM Example Using Trame Framework
This example demonstrates a simple application using the MVVM (Model-View-ViewModel) design pattern. We’ll use the Trame framework along with the TrameBinding interface from nova-mvvm.
For more information and examples, we recommend looking at the tutorial from the Neutrons Open Visualization and Analysis (NOVA) Framework Developer Workshop.
MVVM consists of three parts:
Model: Represents the data and business logic.
View: Represents the UI.
ViewModel: Acts as a bridge between the Model and the View.
Model
Create a model.py file to define the main model.
model.py
"""Module for the main model."""
from pydantic import BaseModel, Field
class MainModel(BaseModel):
"""
A model class.
This class uses Pydantic (https://docs.pydantic.dev/latest/) for
data validation, metadata, and examples that can improve UI generation.
"""
username: str = Field(
default="test_name",
min_length=1,
title="User Name",
description="Please provide the name of the user",
examples=["user"],
)
password: str = Field(default="test_password", title="User Password")
ViewModel
Create a view_model.py file to define the ViewModel logic.
view_model.py
"""Module for the main ViewModel."""
from typing import Any, Dict
from nova.mvvm.interface import BindingInterface
from ..models.main_model import MainModel
class MainViewModel:
"""ViewModel class: manages data-view bindings and reacts to UI changes."""
def __init__(self, model: MainModel, binding: BindingInterface):
self.model = model
# Create a bind between the ViewModel and the View
self.config_bind = binding.new_bind(
self.model,
callback_after_update=self.change_callback
)
def change_callback(self, results: Dict[str, Any]) -> None:
if results["error"]:
print(f"Error in fields {results['errored']}, model not changed")
else:
print(f"Model fields updated: {results['updated']}")
def update_view(self) -> None:
self.config_bind.update_in_view(self.model)
View
Create a view.py file to define the UI.
view.py
"""Main view file."""
import logging
from nova.mvvm.trame_binding import TrameBinding
from nova.trame import ThemedApp
from trame.app import get_server
from nova.trame.view.components import InputField
from trame.widgets import vuetify3 as vuetify
from ..view_models.main import MainViewModel
from ..models.main_model import MainModel
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
class MainApp(ThemedApp):
"""Main application view class. Renders UI elements."""
def __init__(self) -> None:
super().__init__()
self.server = get_server(None, client_type="vue3")
binding = TrameBinding(self.server.state)
self.server.state.trame__title = "Test Project"
model = MainModel()
self.view_model = MainViewModel(model, binding)
self.view_model.config_bind.connect("config")
self.create_ui()
def create_ui(self) -> None:
self.state.trame__title = "Test Project"
with super().create_ui() as layout:
layout.toolbar_title.set_text("Test Project")
with layout.pre_content:
pass
with layout.content:
with vuetify.VRow(align="center", classes="mt-4"):
InputField("config.username")
with vuetify.VRow(align="center"):
InputField("config.password")
with layout.post_content:
pass
return layout
Application Entry Point
Create a main.py file to start the application.
main.py
"""Main Application."""
def main() -> None:
from .views.main import MainApp
app = MainApp()
app.server.start()