2

I am trying to dynamically add and remove UI elements to a python shiny application. For adding elements I want to have a generic "Add element" button. For removal however, I want to have an individual "X" button at the corner of each element. Adding elements already works as it should. I am currently struggling with observing the clicks on each individual "X" button.

The code I have currently is this:

from shiny import App, ui, reactive

# Define the UI
app_ui = ui.page_fluid(
    ui.input_action_button("add_square", "Add Square", class_="btn btn-primary"),
    ui.div(id="square_container", class_="container"),
    ui.tags.style(
        """
        .rounded-square {
            width: 120px;
            height: 120px;
            background-color: #f9f9f9; /* Light gray background */
            border-radius: 15%;
            display: flex;
            justify-content: center;
            align-items: center;
            margin: 10px;
            box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
            position: relative;
        }
        .container {
            display: flex;
            flex-wrap: wrap;
            gap: 15px;
            margin-top: 20px;
            justify-content: center;
        }
        .delete-btn {
            position: absolute;
            top: 5px;
            right: 5px;
            background-color: #ff4d4d;
            color: white;
            border: none;
            border-radius: 50%;
            width: 20px;
            height: 20px;
            font-size: 12px;
            cursor: pointer;
            text-align: center;
            line-height: 17px;
            padding: 0;
        }
        .delete-btn:hover {
            background-color: #e60000;
        }
        """
    ),
)


# Define the server logic
def server(input, output, session):
    # Reactive list to store square IDs
    square_ids = reactive.Value([])

    @reactive.Effect
    @reactive.event(input.add_square)
    def add_square():
        """Add a new square dynamically."""
        current_ids = square_ids.get()

        # Generate a new unique ID for the square
        new_id = max(current_ids, default=0) + 1
        current_ids.append(new_id)
        square_ids.set(current_ids)

        # Add a new square dynamically into the DOM
        ui.insert_ui(
            ui=ui.div(
                ui.TagList(
                    ui.input_text(
                        f"text_{new_id}", None, placeholder=f"Square {new_id}"
                    ),
                    ui.input_action_button(
                        f"delete_{new_id}", "X", class_="delete-btn"
                    ),
                ),
                id=f"square_{new_id}",  # Unique ID for the square
                class_="rounded-square",
            ),
            selector="#square_container",  # Add inside the container
            where="beforeEnd",
        )

    @reactive.effect
    def monitor_delete_buttons():
        """Monitor delete button clicks and handle square deletions."""
        current_ids = square_ids.get()

        for square_id in current_ids:
            # Check if the delete button for `square_id` has been clicked
            if input.get(f"delete_{square_id}", 0) > 0:
                # Remove the square from the UI
                ui.remove_ui(selector=f"#square_{square_id}")

                # Update the reactive square list
                updated_ids = [s for s in current_ids if s != square_id]
                square_ids.set(updated_ids)

                # Reset the delete input to avoid duplicate actions
                session.reset_input(f"delete_{square_id}")
                break


# Create the Shiny app
app = App(app_ui, server)
1
  • 1
    do you get any error message in console when you run it? If you get error then show it in question. Commented Aug 11 at 11:55

1 Answer 1

2

I think: inside add_square() you have to create new function for every button using @reactive.event(getattr(input, f"delete_{new_id}"))

@reactive.Effect
@reactive.event(input.add_square)
def add_square():
    """Add a new square dynamically."""
    current_ids = square_ids.get()

    # Generate a new unique ID for the square
    new_id = max(current_ids, default=0) + 1
    current_ids.append(new_id)
    square_ids.set(current_ids)

    # Add a new square dynamically into the DOM
    ui.insert_ui(
        ui=ui.div(
            ui.TagList(
                ui.input_text(
                    f"text_{new_id}", None, placeholder=f"Square {new_id}"
                ),
                ui.input_action_button(
                    f"delete_{new_id}", "X", class_="delete-btn"
                ),
            ),
            id=f"square_{new_id}",  # Unique ID for the square
            class_="rounded-square",
        ),
        selector="#square_container",  # Add inside the container
        where="beforeEnd",
    )

    # --- created inside add_square() ---

    @reactive.effect
    @reactive.event(getattr(input, f"delete_{new_id}"))
    def delete_button():
        print('delete_button')
        ui.remove_ui(selector=f"#square_{new_id}")

But I don't know how it will behave if you delete button with max ID and create new button which will have the same ID. It will create the same function @reactive.event(getattr(input, f"delete_{new_id}")) second time and it can make problem

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.