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)
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