daattali / shinyjs

💡 Easily improve the user experience of your Shiny apps in seconds
https://deanattali.com/shinyjs
Other
734 stars 119 forks source link

Some functions of shinyjs are not working with renderUI #25

Closed geoabi closed 9 years ago

geoabi commented 9 years ago

Hi,

I have been using shinyjs and it's working very well, but when I tried to use it within a shiny dashboard dynamic UI using the renderUI() function, it partially works. As an example to show the issue, I have used the same code of the shinyjs example and put it inside of a renderUI(). In this example the toggle(), text() and reset() function are not working.

library(shiny)
library(shinydashboard)
library(shinyjs)

ui <- dashboardPage(
  dashboardHeader(),
  dashboardSidebar(),
  dashboardBody(
        shinyjs::useShinyjs(),
        uiOutput("test_UI")
    )
)

server <- function(input, output) { 
    output$test_UI <- renderUI({
        div(
            shinyjs::inlineCSS(list(.big = "font-size: 2em")),
            div(id = "myapp",
                    h2("shinyjs demo"),
                    checkboxInput("big", "Bigger text", FALSE),
                    textInput("name", "Name", ""),
                    a(id = "toggleAdvanced", "Show/hide advanced info", href = "#"),
                    shinyjs::hidden(
                        div(id = "advanced",
                            numericInput("age", "Age", 30),
                            textInput("company", "Company", "")
                        )
                    ),
                    p("Timestamp: ",
                        span(id = "time", date()),
                        a(id = "update", "Update", href = "#")
                    ),
                    actionButton("submit", "Submit"),
                    actionButton("reset", "Reset form")
            )
        )
    })
    observe({
        shinyjs::toggleState("submit", !is.null(input$name) && input$name != "")
    })
    shinyjs::onclick("toggleAdvanced",
                                     shinyjs::toggle(id = "advanced", anim = TRUE))    
    shinyjs::onclick("update", shinyjs::text("time", date()))
    observe({
        shinyjs::toggleClass("myapp", "big", input$big)
    })
    observeEvent(input$submit, {
        shinyjs::info("Thank you!")
    })
    observeEvent(input$reset, {
        shinyjs::reset("myapp")
    })                   
}
shinyApp(ui, server)

Everything is working if I don't use the uiOutput() function and put the code inside the ui.

Thank you

daattali commented 9 years ago

This is not a shinydashboard issue, the same is true if you're in a normal shiny app. For the reset function, the documentation says:

Note that this function only works on input widgets that are rendered in the UI. Any input that is created dynamically using uiOutput + renderUI will not be resettable.

The reason onclick isn't working also makes sense: when making the call to onclick(...), the javascript immediately registers a click on that element. But you're making that call before the element exists, so there is nothing to do. If you are rendering elements dynamically and you want to have an associated onclick event, the onclick needs to register after the element is created. I'm not sure what the best way to know when a UI is finished rendering (I wonder if Shiny has a nice way to do that or not), but for this example, since you're rendering a button and the initial value is 0, we could use that to know when the UI renders. It's a bit weird I admit, I hope shiny has a better way to do it, but off the top of my head this should work: wrap all the observe() statements inside a function, say function foo() { observer(...); observe(...) }. Then outside that function, simply have

    observe({
      if (!is.null(input$reset) && input$reset == 0) {
        foo()
      }
    })

so that all the observer shinyjs functions will run once when the HTML is ready As I said, this seems a bit weird, and there are probably better ways to get around this issue, but hopefully you understand what the problem is and can try to think of a better way to work with it

geoabi commented 9 years ago

Thank you for the explanation, now it make sense. I'll try to work around that with your suggestion. Regards.

ijlyttle commented 9 years ago

Just a "me-too" comment to agree that it would be nice if shiny provide a reactive to indicate that the html has finished rendering. (I think I'll head over to the shiny google group to ask the question).

I am seeing the same sort of thing, but with a wrinkle: for a multiple selectInput, NULL is used to indicate that the element is not there yet, NULL is also used to indicate that the element exists, but there is no selection.

Really digging this package, though!

daattali commented 9 years ago

@ijlyttle I spoke with Joe Cheng (shiny author) a few days ago and he actually told me that they're planning on soon adding some way to know when a renderUI is finished, so hopefully in a few weeks this will be possible!

The issue with NULL in selectize input: maybe post that in the google group to see what Joe or other shiny devs think. Although, now that I think about it, I think there is another way to know whether or not an input exists, but I'm not sure if it's recommended or not. If you want to know if input$foo exists, instead of checking if it's NULL, you can probably check if ("foo" %in% names(input)).

Hope this helps

ijlyttle commented 9 years ago

@daattali Thanks! I will try that out.

As my old calculus prof said: "Any trick used twice is a method"

daattali commented 9 years ago

@geoabi and @ijlyttle I've added support for reset to work with dynamically generated elements. I'm still trying to think of a way to make onclick work, but let me know if reset works for you.

Example:

runApp(shinyApp(
  ui = fluidPage(
    useShinyjs(),
    uiOutput("ui"),
    actionButton("reset", "reset")
  ),
  server = function(input, output) {
    output$ui <- renderUI({
      textInput("text", "text", "hello")
    })
    observeEvent(input$reset, {
      reset("text")
    })
  }
))
daattali commented 9 years ago

@geoabi @ijlyttle I was able to find a solution to the onclick problem as well. Now everything should work with UI that's generated with renderUI. The example from the first post in this thread fully works now. Please let me know if you confirm it works

ijlyttle commented 9 years ago

Will check it out ASAP - just to say thanks for now.

geoabi commented 9 years ago

@daattali everything in the example in the first post is working now without any problems. I also checked onclick on one of my app that I'm developing and now I can use it with dynamically created elements. This is a very useful feature and I really appreciate your hard work to make this possible. Thank you very much.

ferroao commented 8 years ago

thanks

daattali commented 7 years ago

Sorry @cscheeder that question is not related to shinyjs, it's a general shiny question. You could try asking on stackoverflow, it's a great place to get help with shiny problems

cscheeder commented 7 years ago

Yes, you're right...I realized after my post that "shinyjs" is a package itself rather than a tag in the thread. Thank you for your professional handling of my scattiness    

Gesendet: Donnerstag, 27. Oktober 2016 um 19:30 Uhr Von: "Dean Attali" notifications@github.com An: daattali/shinyjs shinyjs@noreply.github.com Cc: cscheeder christian.scheeder@gmx.de, Mention mention@noreply.github.com Betreff: Re: [daattali/shinyjs] Some functions of shinyjs are not working with renderUI (#25)

Sorry @cscheeder that question is not related to shinyjs, it's a general shiny question. You could try asking on stackoverflow, it's a great place to get help with shiny problems

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.

 

NarendraSadhanala commented 5 years ago

Hi @daattali , Thank you for the making such a wonderful package, I am trying to use runjs with renderUI, then it is not working for the first time.

Could you please suggest me how can I proceed with it?

Usecase: I am building an excel file using downloadhandler and I want to download the file without clicking the downloadbutton so I am using runjs function from shinyjs package.

this is the code i am using in server.R output$downloadData <- downloadHandler( filename = function(file) { paste0("example", ".xlsx") }, content = function(con) { write.xlsx2(df, con, sheetName="example", append=TRUE) }

} )

and then using this runjs("$('#downloadData')[0].click();")

and in UI.R UiOutput("downloadData")

but it is not entering the output$downloadData <- downloadHandler loop for the first time... in the second run when the button is built, then it is working fine and downloading the file without user clicking the download button.

Please suggest if there is any way that I can use to make it work

Thanks in advance!!!

daattali commented 5 years ago

@NarendraSadhanala your question is not an issue in shinyjs, unfortunately I do not have the bandwidth to resolve personal questions. Please use an appropriate forum such as stackoverflow or the Rstudio Community forums to ask for help