I'm working on a Shiny app with a leaflet map that allows users to change names of points using popups. The idea is that you can click on a point, see the name and edit its textInput. The problem is that I don't know how to make the name reactive. I know how this works with an actionButton (using onclick) but I don't know how to create an event when the textInput is edited.
Here's a basic reprex with 1 point and a popup with an actionButton (it works: if you click the observeEvent button_EditName will be triggered) and a textInput field (but that does not trigger any thing because onclick or onChange are not parameters of textInput).
library(shiny)
library(leaflet)
library(sf)
ui <- fluidPage(
leafletOutput("Leaf"),
textOutput("test")
)
server <- function(input, output, session) {
test <- reactiveVal("")
### Init
Pts <- st_as_sf(data.frame(Lon=1, Lat=2, ID="Pt1", Popup=NA), coords = c("Lon","Lat"), crs=st_crs(4326))
Pts$Popup <- as.character(paste0(
actionButton(paste0("EditNameButton_", Pts$ID[1]),
label="Click",
onclick="Shiny.onInputChange(\"button_EditName\" , this.id)"
),
"<br>",
textInput(paste0("EditName_", Pts$ID[1]),
label="",
value=Pts$ID
)
))
Leaf <- leaflet() %>%
addTiles() %>%
addMarkers(data=Pts, popup=Pts$Popup)
### Events
observeEvent(input$button_EditName, {
print(input$button_EditName)
test("The button was clicked")
})
### Outputs
output$Leaf <- renderLeaflet(Leaf)
output$test <- renderText(test())
}
shinyApp(ui = ui, server = server)
Because you transform textInput directly as text and use it inside popup, you can't just observe the onchange event input[[paste0("EditName_", Pts$ID[1])]] like you would normally.
So you can write a helper function textIn to add this onchange behaviour manually. Here are some usual behaviours:
| observer | occurs when |
|---|---|
onblur |
an HTML element loses focus = when you click or tab out of it |
onchange |
the value of an HTML element is changed - also when you are typing and then hitting enter |
oninput |
an element gets input. = each keystroke |
library(shiny)
library(leaflet)
library(sf)
ui <- fluidPage(
leafletOutput("Leaf"),
textOutput("test")
)
textIn <- \(text_input_id) {
textInput(
paste0("EditName_", text_input_id),
label = "Edit name:",
placeholder = text_input_id
) |>
sub(
'type="text"',
'type="text" onchange="Shiny.onInputChange(\'text_onchange\', {id: this.id, value: this.value})"',
x = _)
}
server <- function(input, output, session) {
test <- reactiveVal("")
text <- reactiveVal("")
### Init
Pts <- st_as_sf(data.frame(Lon=1, Lat=2, ID="Pt1", Popup=NA), coords = c("Lon","Lat"), crs=st_crs(4326))
Pts$Popup <- as.character(paste0(
actionButton(paste0("EditNameButton_", Pts$ID[1]),
label="Click",
onclick="Shiny.onInputChange(\"button_EditName\" , this.id)"
),
"<br>",
textIn(Pts$ID[1])
)
)
Leaf <- leaflet() %>%
addTiles() %>%
addMarkers(data=Pts, popup=Pts$Popup)
### Events
observeEvent(input$button_EditName, {
print(input$button_EditName)
test("The button was clicked")
})
observeEvent(input$text_onchange, {
e <- input$text_onchange
print(e)
test(paste(e$id, "was changed to: ", e$value))
})
### Outputs
output$Leaf <- renderLeaflet(Leaf)
output$test <- renderText(test())
}
shinyApp(ui = ui, server = server)