Skip to content

ModelForm — NiceGUI Binding

examples/03_form_binding.py · run it with uv run python examples/03_form_binding.py

ModelForm — NiceGUI Binding

The left panel shows a ModelForm. The right panel uses NiceGUI's bind_text_from / bind_visibility_from to display the same Python object's fields live — they update whenever the form writes a validated change back to the object.

num++ / num-- modify user.num directly, bypassing the form. The bound label updates immediately; the form input does not — NiceView does not (yet) support two-way binding from the object back into the widget.

Source
"""
# ModelForm — NiceGUI Binding

The left panel shows a `ModelForm`. The right panel uses NiceGUI's
`bind_text_from` / `bind_visibility_from` to display the same Python
object's fields live — they update whenever the form writes a validated
change back to the object.

`num++` / `num--` modify `user.num` directly, bypassing the form.
The bound label updates immediately; the form input does **not** — NiceView
does not (yet) support two-way binding from the object back into the widget.
"""

import pydantic
from nicegui import ui
from niceview import ModelForm


class User(pydantic.BaseModel):
    name: str = pydantic.Field(max_length=20, title='Name')
    age: int = pydantic.Field(ge=0, le=120, title='Age')
    num: int = pydantic.Field(default=0, title='Counter')
    active: bool = pydantic.Field(default=True, title='Active')


user = User(name='Alice', age=30)


@ui.page('/')
def page():
    ui.markdown(__doc__ or '')
    ui.separator()

    with ui.row().classes('w-full items-start gap-8'):
        with ui.card():
            ui.label('Form').classes('text-h6')
            ModelForm.from_item(user).render()

        with ui.card():
            ui.label('Bound values').classes('text-h6')
            ui.label('Name: ').bind_text_from(user, 'name', backward=lambda v: f'Name: {v}')
            ui.label('Age: ').bind_text_from(user, 'age', backward=lambda v: f'Age: {v}')
            ui.label('Counter: ').bind_text_from(user, 'num', backward=lambda v: f'Counter: {v}')
            ui.label('● active').bind_visibility_from(user, 'active')
            ui.label('○ inactive').bind_visibility_from(user, 'active', value=False)

            ui.separator()
            ui.label('Direct object mutation (bypasses form):').classes('text-caption')
            with ui.row():
                ui.button('Counter++', on_click=lambda: setattr(user, 'num', user.num + 1))
                ui.button('Counter--', on_click=lambda: setattr(user, 'num', user.num - 1))


ui.run(title='03 — Form Binding')