Skip to content

Latest commit

 

History

History
234 lines (184 loc) · 9.33 KB

File metadata and controls

234 lines (184 loc) · 9.33 KB

ViewModel Reference

← README.md | 🌱

Overview

A ViewModel extends the base Widget class to group multiple widgets with shared state and behavior. It separates presentation logic from business logic by encapsulating both the widget tree structure and the state management.

Core Methods:

Method Required Description
template() Yes Returns the widget tree configuration object. Called once during ViewModel creation.
setProps(newProps) No Updates the ViewModel's props. Triggers onUpdateView() as default.

Lifecycle Hooks:

All lifecycle hooks are optional and should be declared with the override keyword.

Hook When Called Purpose
onCreateView() After ViewModel creation, before template() Initialize state, setup listeners, configure initial values
onTemplateCreated() After template() returns, before rendering Access rendered widgets, setup widget-specific logic
onUpdateView() When ViewModel updates (after setProps or update props via render) React to prop changes, re-render template by default
onDestroyView() Before ViewModel destruction Cleanup resources, remove listeners, clear references

Note on onUpdateView(): The onUpdateView() hook is called when props are updated via setProps() or when the ViewModel is updated via render(). By default, onUpdateView() calls m.render() which automatically re-renders the ViewModel using its template() function.

' Default implementation in BaseViewModel
sub onUpdateView()
    m.render()  ' Re-renders using template()
end sub

You can override onUpdateView() to customize update behavior:

  • Call m.render() to re-render the entire template (default behavior)
  • Call m.render(partialTemplate) to update specific parts
  • Skip rendering entirely for performance optimization
  • Add custom logic before/after re-rendering

Note on m.render() without arguments: When m.render() is called without arguments on a ViewModel, it automatically calls m.template() and re-renders the ViewModel with the returned configuration. This provides a simple way to refresh the entire UI based on current props and viewModelState.

Shared Properties:

Property Scope Mutability Description
props All widgets in ViewModel Read-only Configuration passed during render, available as m.props in all widgets
viewModelState All widgets in ViewModel Mutable Shared state, available as m.viewModelState in all widgets

Note: The FocusPlugin automatically manages viewModelState.isFocused only on ViewModel widgets (isViewModel = true). It is initialized to false if not already present and updated when focus state changes. Simple child widgets within a ViewModel template do NOT get this behavior because they share the parent ViewModel's viewModelState by reference, which would cause conflicts. Use the injected m.isFocused() widget method for reliable per-widget focus queries on any widget.

How it works:

  1. ViewModel class is instantiated
  2. ViewModel's root widget is added to virtual tree
  3. Default props and viewModelState are extended from config.
  4. Widget is decorated with framework methods and plugins
  5. onCreateView() is called (if defined)
  6. template() is called to get widget tree configuration
  7. Plugins are applied to configure widget behavior (fields, focus, observer, etc.)
  8. All child widgets in the tree share the same props and viewModelState references
  9. Widgets can access m.props and m.viewModelState in field expressions and hooks
  10. When ViewModel is destroyed, onDestroyView() is called and references are cleared

ViewModel as Root Widget

A ViewModel extends the base Widget class, which means the ViewModel instance IS the root widget of its template. When template() is called, the returned root-level configuration (nodeType, fields, focus, focusGroup, etc.) is deep-merged into the ViewModel instance itself.

This has an important consequence for the m scope in callbacks:

Root widget callbacksm refers to the ViewModel instance directly:

override function template() as object
    return {
        nodeType: "Group",
        focusGroup: {
            ' m = this ViewModel instance (the root widget)
            ' ViewModel methods can be called directly
            back: sub() m.goBack() : end sub
        },
        children: [...]
    }
end function

Child widget callbacksm refers to the individual child widget, NOT the ViewModel:

children: [{
    id: "backButton",
    nodeType: "Group",
    focus: {
        ' m = the backButton widget
        ' Use getViewModel() to access the owning ViewModel
        onSelect: sub() m.getViewModel().goBack() : end sub
    }
}]
Context m refers to How to access ViewModel
Root widget callbacks (focus, focusGroup, observer, fields, lifecycle) ViewModel instance m (direct)
Child widget callbacks The child widget m.getViewModel()
Child ViewModel callbacks (nested ViewModel) The child ViewModel m.getParentViewModel() for parent

Note: All child widgets (non-ViewModel) within the template tree share references to the ViewModel's props and viewModelState. This means m.props and m.viewModelState work in any widget callback within the tree, regardless of depth.

Structure:

class MyViewModel extends Rotor.ViewModel
    ' Shared configuration (default, extended during via config)
    props = {}

    ' Shared mutable state (default, extended during via config)
    viewModelState = {}

    ' Required: Returns widget tree configuration
    override function template() as object
        return { ... }
    end function

    ' Lifecycle hooks (optional)
    override sub onCreateView()
    override sub onTemplateCreated()
    override sub onDestroyView()

    ' onUpdateView - has default implementation that calls m.render()
    ' Override to customize update behavior
    override sub onUpdateView()
        ' Default: m.render() - re-renders using template()
        ' Custom: add logic, skip render, or render partial updates
    end sub

    ' Method for updating props - calls onUpdateView() after updating
    override sub setProps(newProps as object)
end class

ViewModel example:

class ButtonViewModel extends Rotor.ViewModel

    props = {
        label: "Click Me",
        enabled: true
    }

    viewModelState = {
        clickCount: 0
    }

    override function template() as object
        return {
            nodeType: "Group",
            children: [{
                id: "background",
                nodeType: "Rectangle",
                fields: {
                    width: 200,
                    height: 60,
                    color: function() as string typecast m as Rotor.ViewModel
                        if m.isFocused()
                            return "#FF5500"
                        else if m.props.enabled
                            return "#0055FF"
                        else
                            return "#CCCCCC"
                        end if
                    end function
                }
            }, {
                id: "text",
                nodeType: "Label",
                fields: {
                    text: m.props.label,
                    width: 200,
                    height: 60,
                    horizAlign: "center",
                    vertAlign: "center",
                    color: "#FFFFFF"
                }
            }]
        }
    end function

    override sub onCreateView()
        ' Initialize ViewModel
        print "ButtonViewModel created"

        ' Access dispatcher state using convenience methods
        dispatcher = m.connectDispatcher("appState")
        currentState = dispatcher.getState()

        ' Or use m.getStateFrom() convenience method
        currentState = m.getStateFrom("appState")

        ' Dispatch using convenience method
        m.dispatchTo("appState", { type: "BUTTON_CREATED" })
    end sub

end class

' Render the ViewModel
frameworkInstance.render({
    viewModel: ButtonViewModel
})

📚 Learn More

NEXT STEP: Fields Plugin

Reference Documentation:

Plugin Documentation:

Additional Documentation: