[Python] How to access the id of a dynamically created button from a reactive event to...

Discussão em 'Python' iniciado por Stack, Junho 23, 2025.

  1. Stack

    Stack Membro Participativo

    I'm building a Shiny for Python dashboard where I have multiple "test rig" buttons, dynamically created from a list of items. When a user clicks one of these buttons, I want a modal to pop up, displaying data specific to that exact rig.

    The challenge I'm facing is reliably getting the id of the specific ui.input_action_button that was clicked within the server function's reactive.event listener, especially when these listeners are defined within a loop. Currently, my buttons either don't trigger their events or only the last button's logic seems to work for all clicks.

    I want a reactive.event to fire for that specific button but to not have to make a new event for each button. I need to access the unique id of the clicked button (e.g., "my_button_1", "my_button_2"). This id should then be used to retrieve and display specific data for that item in a ui.modal. Problem Description: I have a list of items (e.g., ["Item 1", "Item 2", "Item 3", "Item 4"]). I iterate over this list to create a ui.input_action_button for each item, giving each button a unique ID (e.g., my_button_1, my_button_2).

    In the server function, I attempt to create reactive.event listeners in a loop, one for each button. I then use a reactive.Value (last_clicked_button_id) to centrally manage which button was clicked, and a separate reactive.Effect observes this reactive.Value to trigger the modal.

    The issue is that when I click any button, the expected print statements within the reactive.event for that button do not fire, or only the reactive.event associated with the last dynamically created button seems to respond. Consequently, the modal either doesn't open or only ever shows data for the last item.

    I've tried:

    Generating unique IDs for each button in the UI. Using getattr(input, button_id)() to access the button's value. Employing a reactive.Value (last_clicked_button_id) to propagate the clicked ID to a central modal-triggering effect. Implementing a "factory function" pattern for reactive.event definitions inside the loop to ensure correct closure and unique listener registration. Adding extensive print statements in both UI generation and server-side reactive functions to trace execution flow. (In the attached MRE, you can see these prints in both the console and in a UI log.)

    **from shiny import App, ui, reactive, render
    from htmltools import css

    # --- 1. UI Definition ---
    app_ui = ui.page_fluid(
    ui.h2("Dynamic Buttons for Item Details"),
    ui.p("Click any button to open a modal with its specific details."),
    ui.hr(),

    ui.layout_columns(
    # Dynamically create several buttons.
    # The '*' unpacks the list comprehension into separate arguments for ui.layout_columns.
    *[ui.div(ui.input_action_button(f"my_button_{i}", f"Item {i}", class_="btn-info w-100 mb-2"), style=css(width="100%"))
    for i in range(1, 5)]
    ),
    ui.hr(),
    ui.h3("Server Log Trace:"),
    ui.pre(ui.output_text_verbatim("server_log_output"))
    )

    # --- 2. Server Logic ---
    def server(input, output, session):
    # A reactive value to store the ID of the button that was last clicked.
    # This acts as a central trigger for the modal.
    last_clicked_button_id = reactive.Value(None)

    # Simulate a log to display in the UI for easier debugging
    log_messages = reactive.Value([])

    @output
    @render.text
    def server_log_output():
    # This function is a reactive context, so accessing log_messages.get() is fine here.
    return "\n".join(log_messages.get())

    # Factory function to create a unique reactive.event for EACH button
    def create_button_click_effect(button_id_to_listen):
    @reactive.event(getattr(input, button_id_to_listen)) # Listen to the specific button's input
    def _button_click_handler():
    # This is a reactive context, so modifying log_messages is fine here.
    current_click_count = getattr(input, button_id_to_listen)()

    # This print statement is crucial for debugging!
    log_messages.set(log_messages.get() + [f"[{button_id_to_listen}] Clicked! Count: {current_click_count}"])
    print(f"Server console: Button '{button_id_to_listen}' clicked! Count: {current_click_count}")

    if current_click_count is not None and current_click_count > 0:
    # Set the central reactive value with the ID of the button that was clicked.
    last_clicked_button_id.set(button_id_to_listen)
    return _button_click_handler # Return the decorated function

    # Loop to create a reactive event listener for each dynamically generated button
    for i in range(1, 5):
    button_id = f"my_button_{i}"
    # Call the factory function to create and register the effect for this specific button
    create_button_click_effect(button_id)

    # REMOVED: log_messages.set() from here, as it's outside a reactive context.
    # Keep the print() for initial server startup trace in the console.
    print(f"Server console: Registered listener for: {button_id}")

    # This reactive effect will trigger whenever 'last_clicked_button_id' changes.
    @reactive.Effect
    @reactive.event(last_clicked_button_id)
    def show_item_modal():
    # This is a reactive context, so accessing and modifying log_messages is fine here.
    clicked_id = last_clicked_button_id.get()
    if clicked_id is None:
    return # Do nothing if no button has been clicked yet

    item_name = clicked_id.replace("my_button_", "Item ")

    log_messages.set(log_messages.get() + [f"[Modal Trigger] Displaying modal for: {item_name} (ID: {clicked_id})"])
    print(f"Server console: Modal triggered for: {item_name} (ID: {clicked_id})")

    # Open a modal with content specific to the clicked button
    ui.modal_show(
    ui.modal(
    ui.h3(f"Details for {item_name}"),
    ui.p(f"You clicked the button with ID: '{clicked_id}'."),
    ui.p("In a real app, this is where you'd load and display data specific to this item."),
    size="md"
    )
    )

    # --- 3. Create and Run the Shiny App ---
    app = App(app_ui, server)

    Continue reading...

Compartilhe esta Página