0

I want to trigger a reactive containing the input id of the last input clicked. What I have works fine for some inputs like numericInput and textInput. But it doesn't work for selectInput or selectizeInput. I've tried using a variety of selectors in the JS expression, but none are capturing selectInput or selectizeInput.

Here's a reprex. When you click on either of the first two inputs, the renderText updates, but it doesn't with the last two.

library(shiny)

ui <- fluidPage(
  tags$head(
    tags$script(
      htmlwidgets::JS("$( document ).on('click', '.form-control, .shiny-bound-input, .selectized', function() {
                        Shiny.setInputValue('last_input', this.id);
                      });")
    )
  ),
  
  numericInput("num1", "Numeric", 0),
  textInput("text1", "Text"),
  selectInput("select1", "Select", choices = LETTERS[1:4]),
  selectInput("selectize1", "Selectize", choices = letters[1:4]),
  
  textOutput("textout")
)

server <- function(input, output, session) {
  
  output$textout <- renderText({
    input$last_input
  })
}

shinyApp(ui, server)
Will Hipson
  • 366
  • 2
  • 9
  • I tried this more generic JS expression and it also didn't work: `$(document).ready(function(){ $('input').on('click', function(evt){ Shiny.setInputValue('last_input', evt.target.id); }); })` – Will Hipson Apr 29 '22 at 17:03
  • Looking in Developer Tools, it seems that shiny's select and selectize has `display: none`, so it won't trigger a click: `` – Will Hipson Apr 29 '22 at 17:06

2 Answers2

0

That's because the id is attributed to the select element and you don't click this element.

My first idea was to search for the first parent element having an id:

tags$script(
  HTML("$( document ).on('click', '.form-control, .shiny-bound-input, .selectized', function() {
          var $parentWithId = $(this).closest('*[id]');
          Shiny.setInputValue('last_input', $parentWithId.attr('id'));
        });")
)

but that still doesn't work for selectizeInput.

Maybe you rather want:

tags$script(
  HTML("$(document).on('shiny:inputchanged', function(event) {
          Shiny.setInputValue('last_input', event.name);
        });")
)

which returns the id of the last changed input.


EDIT

As you noted in your comment, this method also sets last_input when the change is caused from the server by an update* function. Here is how to prevent that:

tags$script(
  HTML("$(document)
         .on('shiny:updateinput', function(e) {
            $(e.target).one('shiny:inputchanged', function(ev) {
              ev.stopPropagation();
            });
          })
          .on('shiny:inputchanged', function(e) {
            Shiny.setInputValue('last_input', e.name);
          });")
)
Stéphane Laurent
  • 75,186
  • 15
  • 119
  • 225
  • Thanks, but unfortunately it doesn't work for my case. I'm trying to use the last input clicked to determine whether a user changed the input versus whether it changed via an update* function. Your suggestion would result it triggering for both cases. – Will Hipson Apr 29 '22 at 17:42
  • 1
    @WillHipson You're right. But one can prevent that - please see my edit. – Stéphane Laurent Apr 29 '22 at 19:32
  • Is there anywhere I can find more info/examples on using JS to stop an update* function from invalidating and triggering a reactive? It would help me to bypass creating a `last_input` reactive altogether. – Will Hipson May 03 '22 at 13:23
  • Created a separate issue specifically for this question: https://stackoverflow.com/questions/72101238/stop-updateinput-from-invalidating-reactive – Will Hipson May 03 '22 at 14:48
  • I was unable to get this solution working. Turns out I needed to exclude the `'last_input'` value from the JaveScript. You can find my clarifying question and its answer here: https://stackoverflow.com/a/75395277/7742981 – Simon.S.A. Feb 09 '23 at 19:42
0

I had the same problem and after a while, I found the solution. First of all, you must identify the beginning string of the selectInput id: let's assume that the id begins by the string id_name. Shiny selectInput is contained in a div that changes when one clicks on the input, and it is after the click that the selectected value is assigned to select element which contains the input id named id. To get the id of selectInput when clicking on the input, you have to apply jquery change-event when the div changes by doing this:

tags$head(
tags$script(
HTML(
"$(document).ready(function(){
  $('div').on('change', 'select',function(evt){
    if(evt.target.id.match('^id_name')=='id_name'){
        Shiny.setInputValue('select_id', evt.target.id);
    }
  });
});"
)
)
)
KnMateo
  • 11
  • 3