Search code examples
pythonnicegui

Binding and updating values from select option to table row cells in NiceGUI


I have multiple different tabs that all collect data based on multiple different dropdowns and inputs in a class specific to each tab. As a verification of this process, I decided to output the state dictionary of the specific class of the tab in the table. But when I change the input of the dropdown or input section, it does not update the table.

Here is the following code for the app.py:

# def program header table ui
@ui.refreshable
def ph_table_refreshable(columns, rows):
    ui.table(columns=columns, rows = rows, row_key = 'attribute')

# tab settings
with ui.tabs() as tabs:
    ui.tab('Program Header')

programheader= ProgramHeader()
with ui.tab_panels(tabs):
    with ui.tab_panel('Program Header'):
        with ui.row().classes('w-full'):
            with ui.column():
                programheader.op_id = ui.number(label="Operation ID", min=0, value = 0).value
                programheader.machine_name = ui.select(label="Machine Name", options=machines).value
                programheader.print_reference = ui.input(label="Print Reference").value
                programheader.num_operations = ui.number(label="Number of Operations", value=2).value
            with ui.column():
                ui.label("Program Header Properties")
                ph_columns, ph_rows = programheader.get_table_data
                ph_table_refreshable(ph_columns, ph_rows)
                ui.button("Update", on_click=ph_table_refreshable.refresh())

ui.run()

and here it is for the program header class being imported from another script called sequences.py:

class ProgramHeader:
    def __init__(
        self, 
        op_id: int = 0, 
        machine_name: str = '', 
        print_reference: str = '', 
        num_operations: int = 2, 
        operation_notes: str = None, 
        choice: str = 'active', 
        up_to_date: str = None
    ):
        # input variables
        self.op_id = op_id
        self.machine_name = machine_name
        self.print_reference = print_reference
        self.num_operations = num_operations
        
        # not input variables
        self.operation_notes = "" if not operation_notes else f" - {operation_notes}"
        self.choice = choice.upper()
        self.up_to_date = up_to_date
        self.date = datetime.now().date()
        self.total_date = f"{self.date} AND UP" if not self.up_to_date else f"{self.date} UP THROUGH {self.up_to_date}"

    @property
    def get_table_data(self):
        columns = [
            {'name': 'attribute', 'label': 'Attribute', 'field': 'attribute', 'required': True, 'align': 'left'},
            {'name': 'value', 'label': 'Value', 'field': 'value'}
        ]

        rows = [{'attribute': k, 'value': v} for k, v in self.__dict__.items()]
        return columns, rows

I have tried following the bindable properties on the documentation website but I have not been able to understand how to use them. Any help would be appreciated, thank you!


Solution

  • Without checking every detail of your code, the button's click handler seems wrong:

    ui.button("Update", on_click=ph_table_refreshable.refresh())
    

    should be

    ui.button("Update", on_click=ph_table_refreshable.refresh)
    

    Otherwise you call refresh once when instantiating the button, but because the refresh function returns None, the on_click handler is set to None.


    Update

    The other thing causing trouble is that you read the value of the input element right when constructing them. At this time the value is None. Later you never read these values again, so the table doesn't change.

    I moved this part into ph_table_refreshable(), which should access the programheader and the input elements itself to get the most recent values whenever it is refreshed:

    class ProgramHeader:
    
        def __init__(self, op_id: int = 0, machine_name: str = '', print_reference: str = '', num_operations: int = 2,
                     operation_notes: str = None, choice: str = 'active', up_to_date: str = None):
            self.op_id = op_id
            self.machine_name = machine_name
            self.print_reference = print_reference
            self.num_operations = num_operations
            self.operation_notes = "" if not operation_notes else f" - {operation_notes}"
            self.choice = choice.upper()
            self.up_to_date = up_to_date
            self.date = datetime.now().date()
            self.total_date = f"{self.date} AND UP" if not self.up_to_date else f"{self.date} UP THROUGH {self.up_to_date}"
    
        @property
        def table_data(self):
            columns = [{'name': 'attribute', 'label': 'Attribute', 'field': 'attribute', 'required': True, 'align': 'left'},
                       {'name': 'value', 'label': 'Value', 'field': 'value'}]
            rows = [{'attribute': k, 'value': v} for k, v in self.__dict__.items()]
            return columns, rows
    
    @ui.refreshable
    def ph_table_refreshable():
        programheader.op_id = op_id.value
        programheader.machine_name = machine_name.value
        programheader.print_reference = print_reference.value
        programheader.num_operations = num_operations.value
        columns, rows = programheader.table_data
        ui.table(columns=columns, rows=rows, row_key='attribute')
    
    programheader = ProgramHeader()
    with ui.row().classes('w-full'):
        with ui.column():
            op_id = ui.number(label="Operation ID", min=0, value=0)
            machine_name = ui.select(label="Machine Name", options=['A', 'B'])
            print_reference = ui.input(label="Print Reference")
            num_operations = ui.number(label="Number of Operations", value=2)
        with ui.column():
            ui.label("Program Header Properties")
            ph_table_refreshable()
            ui.button("Update", on_click=ph_table_refreshable.refresh)