shiny icon indicating copy to clipboard operation
shiny copied to clipboard

Form input tags cannot have `id="nodeName"` or `id="nodeType"` inside `sidebarPanel()`

Open jfunction opened this issue 10 months ago • 10 comments

I am on Shiny 1.10.0

If I create a downloadButton in an app it appears enabled. If I create a selectInput before the downloadButton it becomes disabled at startup.

See reprex.

System details

Browser Version:

Microsoft Edge
Version 133.0.3065.82 (Official build) (64-bit)

Output of sessionInfo():

R version 4.4.0 (2024-04-24 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 26100)

Matrix products: default


locale:
[1] LC_COLLATE=English_South Africa.utf8  LC_CTYPE=English_South Africa.utf8    LC_MONETARY=English_South Africa.utf8 LC_NUMERIC=C                         
[5] LC_TIME=English_South Africa.utf8    

time zone: Africa/Johannesburg
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] shiny_1.10.0

loaded via a namespace (and not attached):
 [1] jsonlite_1.8.9    compiler_4.4.0    promises_1.3.2    reprex_2.1.0      Rcpp_1.0.14       clipr_0.8.0       callr_3.7.6       later_1.4.1      
 [9] jquerylib_0.1.4   yaml_2.3.8        fastmap_1.2.0     mime_0.12         R6_2.5.1          knitr_1.47        tibble_3.2.1      R.cache_0.16.0   
[17] pillar_1.9.0      bslib_0.9.0       R.utils_2.12.3    rlang_1.1.4       utf8_1.2.4        cachem_1.1.0      httpuv_1.6.15     xfun_0.45        
[25] fs_1.6.5          sass_0.4.9        memoise_2.0.1     cli_3.6.3         withr_3.0.2       magrittr_2.0.3    ps_1.7.6          processx_3.8.4   
[33] digest_0.6.36     rstudioapi_0.17.1 fontawesome_0.5.3 xtable_1.8-4      lifecycle_1.0.4   R.methodsS3_1.8.2 R.oo_1.26.0       vctrs_0.6.5      
[41] evaluate_0.24.0   glue_1.7.0        styler_1.10.3     fansi_1.0.6       rmarkdown_2.27    purrr_1.0.2       pkgconfig_2.0.3   tools_4.4.0      
[49] htmltools_0.5.8.1

Steps to reproduce the problem

library(shiny)

ui <- fluidPage(
  selectInput("x", "X",
              choices = c("A", "B")),
  downloadButton(outputId = "y", label = "Y"),
)

server <- function(input, output, session) {
}

shinyApp(ui, server)
Shiny applications not supported in static R Markdown documents

Created on 2025-02-25 with reprex v2.1.0

jfunction avatar Feb 25 '25 14:02 jfunction

In a relatively recent version of Shiny, we started disabling the download button until the download handler is registered on the server side. This protects you against a bunch of weirdness that can happen with the download button if you don't have a handler in place.

What's very likely is that there's an ID conflict in your app. Check your input/output IDs, or run your app with shiny::devmode(TRUE) enabled.

Here's a working version of your app:

library(shiny)

ui <- fluidPage(
  selectInput("x", "X", choices = c("A", "B")),
  downloadButton(outputId = "y", label = "Y"),
)

server <- function(input, output, session) {
  output$y <- downloadHandler(
    filename = function() {
      "sample_data.csv"
    },
    content = function(file) {
      # Create some sample data
      data <- data.frame(
        ID = 1:5,
        Name = c("Alice", "Bob", "Charlie", "David", "Eve"),
        Value = rnorm(5)
      )
      # Write the data to a CSV file
      write.csv(data, file, row.names = FALSE)
    }
  )
}

shinyApp(ui, server)

gadenbuie avatar Feb 25 '25 15:02 gadenbuie

Thank you for the assist - I was able to get to the bottom of this, kind of.

I did indeed leave out important context in my original post - I named the selectInput(inputId="nodeType") and put things in a sidebarLayout. After making those changes the downloadHandler is not registering for some reason so it remains disabled. There are references to "nodeType" in the codebase (in particular in selectize) but I found that renaming it to "nodeFlowType" did the trick for me. Not sure I need to pull this thread any more :)

Here is a minimal breaking example building on the last one:

library(shiny)

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      selectInput("nodeType", "X", choices = c("A", "B")),
      downloadButton(outputId = "y", label = "Y")
    ),
    mainPanel("empty")
  )
)

server <- function(input, output, session) {
  output$y <- downloadHandler(
    filename = function() {
      "sample_data.csv"
    },
    content = function(file) {
      # Create some sample data
      data <- data.frame(
        ID = 1:5,
        Name = c("Alice", "Bob", "Charlie", "David", "Eve"),
        Value = rnorm(5)
      )
      # Write the data to a CSV file
      write.csv(data, file, row.names = FALSE)
    }
  )
}

shinyApp(ui, server)

jfunction avatar Feb 25 '25 18:02 jfunction

Actually, I wonder if this is related to downloadButton being in sidebarPanel, which should contain input controls only?

jfunction avatar Feb 25 '25 18:02 jfunction

Actually, I wonder if this is related to downloadButton being in sidebarPanel, which should contain input controls only?

It's much much more likely that there's a conflict in your IDs. Can you go back to the state when the app wasn't working? If so, with the app open in the browser, if you open the browser's developer tools (by right-clicking anywhere on the page and selecting "Inspect" or "Inspect element"), the Console tab should have warnings or error messages pointing to duplicate ID problems.

gadenbuie avatar Feb 25 '25 20:02 gadenbuie

Thanks - there are no warnings about duplicates (I tried refresh also).

Image

The above was a pretty compact breaking example. If you remove the sidebarLayout/sidebarPanel/mainPanel the button won't be disabled and if you replace "nodeType" with something else it also won't be disabled. So I think internally "nodeType" is being used somewhere. Also just to note the js console gives the same output when I "fix" the minimal example in the above ways.

jfunction avatar Feb 25 '25 20:02 jfunction

The above was a pretty compact breaking example. If you remove the sidebarLayout/sidebarPanel/mainPanel the button won't be disabled and if you replace "nodeType" with something else it also won't be disabled. So I think internally "nodeType" is being used somewhere.

Wow. Sorry, I hadn't run the example yet, but you're right! That's... bizarre. Here's the smallest reprex I can make, which comes down to having a <select id="nodeType"> tag inside a <form> tag with the download button:

library(shiny)

ui <- fluidPage(
  tags$form(
    tags$select(id="nodeType"),
    downloadButton(outputId = "y", label = "Y"),
  )
)

server <- function(input, output, session) {
  output$y <- downloadHandler(
    filename = function() "sample.txt",
    content = function(file) {
      writeLines(Sys.time(), file)
    }
  )
}

shinyApp(ui, server)

Example on shinylive

The <form> tag comes into play because that's what sidebarPanel() uses, but if you use bslib::page_sidebar() you avoid this problem entirely.

ui <- bslib::page_sidebar(
  sidebar = sidebar(
    tags$select(id="nodeType"),
    downloadButton(outputId = "y", label = "Y"),
  )
)

I'm amazed that the reprex doesn't throw any console errors, but I can get a console error by changing id = "nodeType" to id = "nodeName". Both are element properties and it seems that something is causing jQuery to try to read the nodeName or nodeType property when the select input with that name appears in a <form>...

...actually even this causes an console error:

ui <- fluidPage(
  tags$form(
    tags$select(id="nodeName")
  )
)
> e.nodeName.toLowerCase is not a function

I'm going to re-open the issue because this is clearly not expected!

gadenbuie avatar Feb 25 '25 20:02 gadenbuie

Seems like this is also an old and still open issue in React: https://github.com/search?q=repo%3Afacebook%2Freact+%22nodeName.toLowerCase%22&ref=opensearch&type=issues

gadenbuie avatar Feb 25 '25 21:02 gadenbuie

Also any form input elements with the id="nodeType" is enough:

  • tags$select(id = "nodeType")
  • tags$input(id = "nodeType")
  • tags$button(id = "nodeType"), etc...

gadenbuie avatar Feb 25 '25 21:02 gadenbuie

Yuck (see method 3 in the question)

jcheng5 avatar Feb 25 '25 23:02 jcheng5

WOW, this is the safe way to read e.nodeName/e.nodeType:

Object.getOwnPropertyDescriptor(Node.prototype, "nodeName").get.call(e)

and even then it's breaking encapsulation pretty hard!

jcheng5 avatar Feb 25 '25 23:02 jcheng5