# Copyright 2026 Daitum
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Event system for defining user interactions and application behaviour.
This module provides the ModelEvent class, which represents sequences of actions
that execute in response to user interactions (clicks, changes, etc.) or other
triggers. Events enable complex workflows including data manipulation, navigation,
modal dialogs, transactions, and more.
Main Components
---------------
**Core Classes:**
- ModelEvent: Container for a sequence of actions to execute
- EditorEvent: Wrapper connecting editor interactions to model events
**Action Categories:**
The ModelEvent class supports the following action types organised by purpose:
UI Navigation:
- add_show_modal_action(): Display modal dialogs
- add_close_modal_action(): Close modal dialogs
- add_switch_view_action(): Navigate to different views
Data Operations:
- add_set_table_value_action(): Set field values in table rows
- add_set_name_value_action(): Update Parameters or Calculations
- add_copy_values_action(): Copy values between rows
- add_clear_values_action(): Clear field values in rows
Row Management:
- add_insert_row_action(): Insert new blank rows
- add_duplicate_row_action(): Duplicate existing rows
- add_delete_row_action(): Delete rows from tables
State Management:
- add_set_context_action(): Update context variable values
Transaction Control:
- add_begin_transaction(): Start data transaction
- add_commit_transaction(): Save transaction changes
- add_rollback_transaction(): Discard transaction changes
External Operations:
- add_run_report_action(): Execute reports
- add_run_data_source_action(): Refresh data sources
Navigation:
- add_model_editor_navigate_action(): Open the Daitum model editor
Event Execution Model
---------------------
**Action Sequences:**
ModelEvent contains an ordered list of actions that execute sequentially.
Actions are executed in the order they were added to the event.
**Conditional Execution:**
Every action supports optional conditional execution via the `condition`
parameter. When provided, the action only executes if the condition
context variable evaluates to true.
**Transaction Safety:**
Use transaction actions to group data operations into atomic units:
- BEGIN starts a transaction
- COMMIT saves all changes
- ROLLBACK discards all changes
Transactions ensure data consistency when multiple operations must
succeed or fail together.
Row Selection Modes
-------------------
Row operations support multiple selection strategies via RowSelectionMode:
- INDEX: Select row by numeric position
- KEY: Select row by unique key field value
When using KEY mode, provide a key_field to identify the matching field.
Insert and Delete Modes
------------------------
**InsertMode (row insertion):**
- END_OF_TABLE: Insert at table end
- START_OF_TABLE: Insert at table beginning
- AFTER_TARGET: Insert after reference row
- BEFORE_TARGET: Insert before reference row
**DeleteMode (row deletion):**
- TARGET: Delete starting at target row
- AFTER_TARGET: Delete starting after target row
- BEFORE_TARGET: Delete starting before target row
Value Sources
-------------
Many actions accept values from multiple sources:
- **Value objects**: Constant values (IntegerValue, StringValue, etc.)
- **ContextVariable**: Dynamic values from context
- **int/str literals**: Direct constant values for row indices
The action automatically determines the appropriate source type.
Modal Dialogs
-------------
Modal actions support workflow patterns:
- show_modal with transactional=True automatically begins a transaction
- User can commit (save) or rollback (cancel) changes in the modal
- Close modal without transaction for non-data-modifying dialogs
Examples
--------
Simple button click event::
from daitum_ui.model_event import ModelEvent
# Create event that switches views
on_click_event = ModelEvent()
on_click_event.add_switch_view_action(view_id="detail_view")
Opening a modal dialog::
# Event to show edit modal with transaction
edit_event = ModelEvent()
edit_event.add_show_modal_action(
modal_id="edit_form_modal",
transactional=True # Automatically begins transaction
)
Inserting a new row::
from daitum_ui._events import InsertMode
# Event to insert row at end of table
insert_event = ModelEvent()
insert_event.add_insert_row_action(
target_table=customers_table,
insertion_point=InsertMode.END_OF_TABLE,
new_index=new_row_context_var # Store new row index
)
Deleting a row::
from daitum_ui._events import RowSelectionMode
# Event to delete selected row
delete_event = ModelEvent()
delete_event.add_delete_row_action(
target_table=products_table,
row_count=1,
select_mode=RowSelectionMode.INDEX,
row=selected_row_context_var
)
Duplicating a row::
from daitum_ui._events import RowSelectionMode, InsertMode
# Event to duplicate current row
duplicate_event = ModelEvent()
duplicate_event.add_duplicate_row_action(
target_table=orders_table,
row=current_row_context_var,
select_mode=RowSelectionMode.INDEX,
insertion_point=InsertMode.AFTER_TARGET
)
Setting table field values::
from daitum_ui.data import StringValue
# Event to update customer name
update_name_event = ModelEvent()
update_name_event.add_set_table_value_action(
value_source=StringValue("John Doe"),
target_table=customers_table,
target_field=customers_table.name_field,
target_row=selected_row_context_var
)
Setting values from context variables::
# Copy value from input context variable to table
save_input_event = ModelEvent()
save_input_event.add_set_table_value_action(
value_source=user_input_context_var, # ContextVariable
target_table=settings_table,
target_field=settings_table.value_field,
target_row=0
)
Updating context variables::
from daitum_ui.data import IntegerValue
from daitum_ui._events import ValueType
# Event to set selected row ID
select_row_event = ModelEvent()
select_row_event.add_set_context_action(
variable=selected_row_id_context_var,
value=IntegerValue(42),
value_type=ValueType.CONSTANT
)
Copying values between rows::
# Event to copy fields from source to target row
copy_event = ModelEvent()
copy_event.add_copy_values_action(
source=source_row_context_var,
target=target_row_context_var,
table=products_table,
fields=[
products_table.name_field,
products_table.price_field,
products_table.category_field
]
)
Clearing field values::
# Event to clear fields in a row
clear_event = ModelEvent()
clear_event.add_clear_values_action(
source_row=current_row_context_var,
table=orders_table,
fields=[
orders_table.notes_field,
orders_table.discount_field
]
)
Transaction management::
# Event with explicit transaction control
save_event = ModelEvent()
save_event.add_begin_transaction()
save_event.add_set_table_value_action(...)
save_event.add_set_table_value_action(...)
save_event.add_commit_transaction()
# Event to cancel changes
cancel_event = ModelEvent()
cancel_event.add_rollback_transaction()
cancel_event.add_close_modal_action()
Conditional execution::
# Only execute action if condition is true
conditional_event = ModelEvent()
conditional_event.add_switch_view_action(
view_id="admin_panel",
condition=is_admin_context_var # Only if user is admin
)
Complex multi-step workflow::
# Save form and navigate workflow
save_and_navigate_event = ModelEvent()
# 1. Begin transaction
save_and_navigate_event.add_begin_transaction()
# 2. Save form values
save_and_navigate_event.add_set_table_value_action(
value_source=name_input_context_var,
target_table=customers_table,
target_field=customers_table.name_field,
target_row=current_customer_context_var
)
save_and_navigate_event.add_set_table_value_action(
value_source=email_input_context_var,
target_table=customers_table,
target_field=customers_table.email_field,
target_row=current_customer_context_var
)
# 3. Commit transaction
save_and_navigate_event.add_commit_transaction()
# 4. Close modal
save_and_navigate_event.add_close_modal_action()
# 5. Refresh data
save_and_navigate_event.add_run_data_source_action("customer_list_source")
# 6. Navigate to list view
save_and_navigate_event.add_switch_view_action("customer_list_view")
Running reports::
# Event to generate and download report
report_event = ModelEvent()
report_event.add_run_report_action(report_name="monthly_sales_report")
Refreshing data sources::
# Event to reload data from server
refresh_event = ModelEvent()
refresh_event.add_run_data_source_action(data_source_name="product_inventory")
Editor events for UI interactions::
from daitum_ui.model_event import EditorEvent
from daitum_ui._events import EditorEventType
# Button click event
button_click = EditorEvent(
type=EditorEventType.ON_CLICK,
event=save_and_navigate_event
)
# Value change event
value_change = EditorEvent(
type=EditorEventType.ON_CHANGE,
event=update_calculation_event
)
Setting named values (Parameters/Calculations)::
from daitum_ui.data import DecimalValue
# Update parameter value
update_param_event = ModelEvent()
update_param_event.add_set_name_value_action(
value_source=DecimalValue(0.15),
name_value_target=tax_rate_parameter
)
Row operations with key-based selection::
from daitum_ui._events import RowSelectionMode
# Delete row by unique key instead of index
delete_by_key_event = ModelEvent()
delete_by_key_event.add_delete_row_action(
target_table=users_table,
row_count=1,
select_mode=RowSelectionMode.KEY,
row=user_id_context_var,
key_field=users_table.user_id_field
)
Value matching for copy operations::
# Copy values using field matching instead of index
copy_by_value_event = ModelEvent()
copy_by_value_event.add_copy_values_action(
source=source_product_id_context_var,
target=target_product_id_context_var,
table=products_table,
fields=[products_table.price_field],
match_field=products_table.product_id_field # Match by ID
)
Navigating to the model editor::
# Open a specific model in a new tab
navigate_event = ModelEvent()
navigate_event.add_model_editor_navigate_action(
model_id=model_id_field,
scenario_id=scenario_id_field,
open_new_tab=True,
)
"""
from dataclasses import dataclass
from daitum_model import Calculation, Field, Parameter, Table
from daitum_ui._buildable import Buildable
from daitum_ui.context_variable import ContextVariable
from ._events import (
ActionType,
ClearValuesArgs,
ConstantSource,
ContextVariableSource,
CopyValuesArgs,
DeleteRowArgs,
DuplicateRowArgs,
EditorEventType,
EventArgs,
InsertRowArgs,
ModelTransactionArgs,
NamedValueTarget,
NavigateArgs,
OpenModalArgs,
RowSelectionMode,
RunDataSourceArgs,
RunReportArgs,
SetContextEventArgs,
SetContextEventArgsValue,
SetValueArgs,
SetViewArgs,
Source,
TableValueTarget,
Target,
ValueType,
)
from ._link_destination import ModelEditorLinkDestination
from .data import Value
[docs]
class ModelEvent(Buildable):
"""
Represents a model-level event composed of a sequence of actions.
Attributes:
actions (List[EventArgs]):
A list of actions (event argument instances) to be executed as part of the model event.
"""
[docs]
def __init__(self):
"""
Initializes an empty ModelEvent with no actions.
"""
self.actions: list[EventArgs] = []
[docs]
def add_show_modal_action(
self,
modal_id: str,
transactional: bool = True,
condition: ContextVariable | None = None,
):
"""
Adds an action to show a modal dialog.
Args:
modal_id (str): The modal object to display.
transactional (bool): If True, begins a transaction after showing the modal.
Defaults to True.
condition (Optional[ContextVariable]): Context variable controlling conditional
execution of the action. If provided, the action only executes when the
context variable evaluates to true.
"""
action = OpenModalArgs(modal_id)
action.condition_context_variable = condition.id if condition else None
self.actions.append(action)
if transactional:
self.add_begin_transaction()
[docs]
def add_close_modal_action(self, condition: ContextVariable | None = None):
"""
Adds an action to close any currently open modal.
Args:
condition (Optional[ContextVariable]): Context variable controlling conditional
execution of the action. If provided, the action only executes when the
context variable evaluates to true.
"""
action = OpenModalArgs(None)
action.condition_context_variable = condition.id if condition else None
self.actions.append(action)
[docs]
def add_run_report_action(self, report_name: str, condition: ContextVariable | None = None):
"""
Adds an action to run a specified report.
Args:
report_name (str): Name of the report to execute.
condition (Optional[ContextVariable]): Context variable controlling conditional
execution of the action. If provided, the action only executes when the
context variable evaluates to true.
"""
action = RunReportArgs(report_name)
action.condition_context_variable = condition.id if condition else None
self.actions.append(action)
[docs]
def add_switch_view_action(self, view_id: str, condition: ContextVariable | None = None):
"""
Adds an action to change the currently active view.
Args:
view_id (str): The view object to switch to.
condition (Optional[ContextVariable]): Context variable controlling conditional
execution of the action. If provided, the action only executes when the
context variable evaluates to true.
"""
action = SetViewArgs(view_id)
action.condition_context_variable = condition.id if condition else None
self.actions.append(action)
[docs]
def add_begin_transaction(self, condition: ContextVariable | None = None):
"""
Adds an action to begin a transaction.
Args:
condition (Optional[ContextVariable]): Context variable controlling conditional
execution of the action. If provided, the action only executes when the
context variable evaluates to true.
"""
action = ModelTransactionArgs(ActionType.BEGIN)
action.condition_context_variable = condition.id if condition else None
self.actions.append(action)
[docs]
def add_rollback_transaction(self, condition: ContextVariable | None = None):
"""
Adds an action to roll back the current transaction.
Args:
condition (Optional[ContextVariable]): Context variable controlling conditional
execution of the action. If provided, the action only executes when the
context variable evaluates to true.
"""
action = ModelTransactionArgs(ActionType.ROLLBACK)
action.condition_context_variable = condition.id if condition else None
self.actions.append(action)
[docs]
def add_commit_transaction(self, condition: ContextVariable | None = None):
"""
Adds an action to commit the current transaction.
Args:
condition (Optional[ContextVariable]): Context variable controlling conditional
execution of the action. If provided, the action only executes when the
context variable evaluates to true.
"""
action = ModelTransactionArgs(ActionType.COMMIT)
action.condition_context_variable = condition.id if condition else None
self.actions.append(action)
[docs]
def add_set_context_action(
self,
variable: ContextVariable,
value: Value,
value_type: ValueType = ValueType.CONSTANT,
condition: ContextVariable | None = None,
):
"""
Adds an action to set a context variable's value.
Args:
variable (ContextVariable): The context variable to update.
value (Value): The value to assign to the context variable.
value_type (ValueType): Indicates the source or interpretation of the value.
Defaults to CONSTANT.
condition (Optional[ContextVariable]): Context variable controlling conditional
execution of the action. If provided, the action only executes when the
context variable evaluates to true.
"""
values = SetContextEventArgsValue(
variable_id=variable.id, value=value.get_value(), value_type=value_type
)
action = SetContextEventArgs([values])
action.condition_context_variable = condition.id if condition else None
self.actions.append(action)
[docs]
def add_copy_values_action(
self,
source: ContextVariable,
target: ContextVariable,
table: Table,
fields: list[Field],
condition: ContextVariable | None = None,
) -> "CopyValuesArgs":
"""
Adds an action to copy values from one row to another within the same table.
Args:
source (ContextVariable): Context variable identifying the source row.
target (ContextVariable): Context variable identifying the target row.
table (Table): The table containing both source and target rows.
fields (list[Field]): List of fields whose values will be copied from source to target.
condition (Optional[ContextVariable]): Context variable controlling conditional
execution of the action. If provided, the action only executes when the
context variable evaluates to true.
Returns:
CopyValuesArgs: The created action. Set ``action.match_field`` and
``action.match_by_value`` directly for value-based row matching.
"""
action = CopyValuesArgs(source.id, target.id, table.id)
action.field_ids = [field.id for field in fields] if fields else []
action.condition_context_variable = condition.id if condition else None
self.actions.append(action)
return action
[docs]
def add_clear_values_action(
self,
source_row: ContextVariable,
table: Table,
fields: list[Field],
match_field: Field | None = None,
condition: ContextVariable | None = None,
):
"""
Adds an action to clear field values in a specific row of a table.
Args:
source_row (ContextVariable): Context variable identifying the row whose values
will be cleared.
table (Table): The table containing the row to be modified.
fields (list[Field]): List of fields whose values will be cleared in the specified row.
match_field (Optional[Field]): Field used for value-based row identification instead
of index-based identification. If provided, the row is identified by comparing
this field's value.
condition (Optional[ContextVariable]): Context variable controlling conditional
execution of the action. If provided, the action only executes when the
context variable evaluates to true.
"""
action = ClearValuesArgs(source_row.id, table.id)
action.field_ids = [field.id for field in fields] if fields else []
if match_field:
action.match_field = match_field.id
action.match_by_value = True
action.condition_context_variable = condition.id if condition else None
self.actions.append(action)
[docs]
def add_duplicate_row_action(
self,
target_table: Table,
row: ContextVariable,
select_mode: RowSelectionMode,
condition: ContextVariable | None = None,
) -> "DuplicateRowArgs":
"""
Adds an action to duplicate a row in a table.
Args:
target_table (Table): Table in which to duplicate a row.
row (ContextVariable): Context variable holding the source row index or key.
select_mode (RowSelectionMode): Determines how the source row is selected
(by index, by key, etc.).
condition (Optional[ContextVariable]): Context variable controlling conditional
execution of the action. If provided, the action only executes when the
context variable evaluates to true.
Returns:
DuplicateRowArgs: The created action. Set ``action.key_field``,
``action.insertion_point``, ``action.new_index_context_variable_id``, and
``action.append_table_to_new_variable`` directly for further configuration.
"""
action = DuplicateRowArgs(target_table.id, row.id, select_mode)
action.condition_context_variable = condition.id if condition else None
self.actions.append(action)
return action
[docs]
def add_insert_row_action(
self,
target_table: Table,
select_mode: RowSelectionMode = RowSelectionMode.INDEX,
row: ContextVariable | None = None,
condition: ContextVariable | None = None,
) -> "InsertRowArgs":
"""
Adds an action to insert a new blank row into a table.
Args:
target_table (Table): Table to insert the new row into.
select_mode (RowSelectionMode): Defines how the reference row is selected
(by index, by key, etc.). Defaults to INDEX.
row (Optional[ContextVariable]): Context variable indicating the reference point
for insertion. Not required when insertion_point is END_OF_TABLE or START_OF_TABLE.
condition (Optional[ContextVariable]): Context variable controlling conditional
execution of the action. If provided, the action only executes when the
context variable evaluates to true.
Returns:
InsertRowArgs: The created action. Set ``action.key_field``,
``action.insertion_point``, ``action.new_index_context_variable_id``, and
``action.append_table_to_new_variable`` directly for further configuration.
"""
action = InsertRowArgs(target_table.id, select_mode)
action.row_context_variable_id = row.id if row else None
action.condition_context_variable = condition.id if condition else None
self.actions.append(action)
return action
[docs]
def add_delete_row_action(
self,
target_table: Table,
row_count: int,
select_mode: RowSelectionMode,
row: ContextVariable | None = None,
condition: ContextVariable | None = None,
) -> "DeleteRowArgs":
"""
Adds an action to delete one or more rows from a table.
Args:
target_table (Table): Table from which rows will be deleted.
row_count (int): Number of consecutive rows to delete.
select_mode (RowSelectionMode): Defines how the starting row is selected for deletion
(by index, by key, etc.).
row (Optional[ContextVariable]): Context variable holding the row index or key
identifying the deletion reference point.
condition (Optional[ContextVariable]): Context variable controlling conditional
execution of the action. If provided, the action only executes when the
context variable evaluates to true.
Returns:
DeleteRowArgs: The created action. Set ``action.key_field`` and
``action.delete_point`` directly for further configuration.
"""
action = DeleteRowArgs(target_table.id, select_mode)
action.row_count = row_count
action.row_context_variable_id = row.id if row else None
action.condition_context_variable = condition.id if condition else None
self.actions.append(action)
return action
[docs]
def add_run_data_source_action(
self, data_source_name: str, condition: ContextVariable | None = None
):
"""
Adds an action to run or refresh a data source.
Args:
data_source_name (str): Name of the data source to execute or refresh.
condition (Optional[ContextVariable]): Context variable controlling conditional
execution of the action. If provided, the action only executes when the
context variable evaluates to true.
"""
action = RunDataSourceArgs(data_source_name)
action.condition_context_variable = condition.id if condition else None
self.actions.append(action)
[docs]
def add_set_table_value_action(
self,
value_source: Value | ContextVariable,
target_table: Table,
target_field: Field,
target_row: int | ContextVariable,
condition: ContextVariable | None = None,
):
"""
Adds an action to set a value in a specific table field and row.
Args:
value_source (Value | ContextVariable): The source of the value to set. Can be
either a constant Value or a ContextVariable whose value will be used.
target_table (Table): The table containing the field to be updated.
target_field (Field): The field in the target table that will receive the new value.
Must be a field that exists in the target_table.
target_row (int | ContextVariable): Identifies the row to update.
condition (Optional[ContextVariable]): Context variable controlling conditional
execution of the action. If provided, the action only executes when the
context variable evaluates to true.
Raises:
ValueError: If target_field is not a field in target_table.
"""
if isinstance(value_source, Value):
source: Source = ConstantSource(value_source.get_value())
else:
source = ContextVariableSource(value_source.id)
if target_field not in target_table.get_fields():
raise ValueError(f"{target_field} is not in the table: {target_table}.")
if isinstance(target_row, int):
row_source: Source = ConstantSource(target_row)
else:
row_source = ContextVariableSource(target_row.id)
target = TableValueTarget(row_source, target_table.id, target_field.id)
action = SetValueArgs(source, target)
action.condition_context_variable = condition.id if condition else None
self.actions.append(action)
[docs]
def add_set_name_value_action(
self,
value_source: Value | ContextVariable,
name_value_target: Parameter | Calculation,
condition: ContextVariable | None = None,
):
"""
Adds an action to set the value of a named value (Parameter or Calculation).
Args:
value_source (Value | ContextVariable): The source of the value to set. Can be
either a constant Value or a ContextVariable whose value will be used.
name_value_target (Parameter | Calculation): The named value (Parameter or
Calculation) that will receive the new value.
condition (Optional[ContextVariable]): Context variable controlling conditional
execution of the action. If provided, the action only executes when the
context variable evaluates to true.
"""
if isinstance(value_source, Value):
source: Source = ConstantSource(value_source.get_value())
else:
source = ContextVariableSource(value_source.id)
target: Target = NamedValueTarget(name_value_target.id)
action = SetValueArgs(source, target)
action.condition_context_variable = condition.id if condition else None
self.actions.append(action)
[docs]
def add_model_editor_navigate_action(
self,
model_id: Field | Parameter | Calculation | ContextVariable,
scenario_id: Field | Parameter | Calculation | ContextVariable | None = None,
open_new_tab: bool = False,
condition: ContextVariable | None = None,
):
"""
Adds an action that navigates to the Daitum model editor.
Both ``model_id`` and ``scenario_id`` are optional. When provided, they are
resolved at runtime to identify the target model and scenario. Omitting
them opens the editor without pre-selecting a model or scenario.
Args:
model_id (Field | Parameter | Calculation | ContextVariable):
Value identifying the model to open. Must resolve to an integer.
scenario_id (Field | Parameter | Calculation | ContextVariable | None):
Value identifying the scenario to open within the selected model.
Must resolve to an integer.
open_new_tab (bool): If ``True``, the editor opens in a new browser tab.
Defaults to ``False``.
condition (Optional[ContextVariable]): Context variable controlling
conditional execution of the action. If provided, the action only
executes when the context variable evaluates to true.
"""
model_editor_link = ModelEditorLinkDestination(model_id, scenario_id, open_new_tab)
action = NavigateArgs(model_editor_link)
action.condition_context_variable = condition.id if condition else None
self.actions.append(action)
[docs]
@dataclass
class EditorEvent(Buildable):
"""
Represents an event triggered from the editor (e.g. user interaction),
which encapsulates a model event.
Attributes:
type (EditorEventType):
The type of editor event that was triggered (ON_CLICK, ON_CHANGE).
event (ModelEvent):
The model event containing the list of actions to be executed in response.
"""
type: EditorEventType
event: ModelEvent