Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Download links and downloadHandlers do not work from inside a PivotItem #196

Open
justacec opened this issue Jun 8, 2023 · 3 comments
Open

Comments

@justacec
Copy link

justacec commented Jun 8, 2023

Code example

library(shiny)
library(shiny.fluent)
library(shinyjs)

ui <- fluentPage(
  useShinyjs(),

  # This one is outside the Pivot and will likely work correctly
  div(
    downloadButton("download_outside", label = "")
  ),

  Pivot(
    PivotItem(id = 'pivot1', headerText = 'One', alwaysRender = TRUE, tagList(
      # This one is inside the Pivot and will not work correctly
      div(
        downloadButton("download_inside", label = "")
      )
    ))
  )
)

server <- function(input, output, session) {  
  output$download_inside <- downloadHandler(
    filename = function() {
      paste("data-", Sys.Date(), ".csv", sep="")
    },
    content = function(file) {
      write.csv(iris, file)
    }
  )

  output$download_outside <- downloadHandler(
    filename = function() {
      paste("data-", Sys.Date(), ".csv", sep="")
    },
    content = function(file) {
      write.csv(iris, file)
    }
  )
}

shiny::runApp(shinyApp(ui, server), launch.browser = FALSE)

Bug description

I was following your example to enable download links in shiny.fluent as presented in #39. While that example works, I found that the downloadHandler was not updating the href target in the link, if it was inside a Pivot structure. In this case, the link just opens a new version of the application in a new tab, vice presenting the user with a download file dialog.

The example code shows both cases simultaneously for side-by-side comparison.

The provided code example here strips out your PrimaryButton and its callback because the issue seems to be the way that the downloadHandler updates the href on the typically hidden downloadButton and I wanted to focus only on that. I have also removed the hidden aspect so that the mouseover link pops up for easy reference

Expected behavior

The href of the shiny downloadButton would be correctly set

Comments

As an aside, I have also attempted to set the output options to not suspend when hidden, just in case that was an issue. Changing that had no effect.

@justacec
Copy link
Author

justacec commented Jun 8, 2023

My suspicion on this is that the downloadButton is hidden inside the react_data structure which is rendered after the page has been downloaded after the server function is executed. When the server function executes, there is no button in the DOM for the id selector to catch it, and therefore the href is not updated to the correction value.

A fix for this would be some sort of callback to be executed after the data is rendered, but not sure how that would work.

A secondary deeper dirtier fix for this would be to have a hidden download button on the main page with reactive values for the filename and the data that can be updated by later nested modules. The reactive values would just need to be passed to each of the follow-on modules.

I am not sure if this is the issue, what are your thoughts?

@justacec
Copy link
Author

justacec commented Jun 8, 2023

I have a potential working solution which honors keeping all of the relevant code in the same file (for modularity) and it supports nested modules cleanly. The idea is to inject a static hidden download button from the server function right after the body tag which means that it completly circumvents the React system. Then, when the downloadHandler is called, the DOM contains the element.

library(shiny)
library(shiny.fluent)
library(shinyjs)

ui <- fluentPage(
  useShinyjs(),

  # This one is outside the Pivot and will likely work correctly
  div(
    downloadButton("download_outside", label = "")
  ),

  Pivot(
    PivotItem(id = 'pivot1', headerText = 'One', alwaysRender = TRUE, tagList(
      # This one is inside the Pivot and will not work correctly
      div(
        PrimaryButton.shinyInput('downloadButton', text = 'Download', iconProps = list(iconName = "Download"))      )
    ))
  )
)

server <- function(input, output, session) {
  insertUI('body', 'afterBegin', session = session, ui = tagList(
      div(
      style = 'visibility: hidden; display: none',
      downloadButton('download_inside', label = 'Download')
      )
  ))

  observeEvent(input$downloadButton, {
    click('download_inside')
  })

  output$download_inside <- downloadHandler(
    filename = function() {
      paste("data-", Sys.Date(), ".csv", sep="")
    },
    content = function(file) {
      write.csv(iris, file)
    }
  )

  output$download_outside <- downloadHandler(
    filename = function() {
      paste("data-", Sys.Date(), ".csv", sep="")
    },
    content = function(file) {
      write.csv(iris, file)
    }
  )

  outputOptions(output, 'download_inside', suspendWhenHidden = FALSE)

}

shiny::runApp(shinyApp(ui, server), launch.browser = FALSE)

@jakubsob
Copy link
Contributor

Thanks @justacec for taking time to submit such a detailed description of the issue.

The issue boils down to downloadHandler not working in reactContext, we'll use this example as a starting point to fixing the issue:

library(shiny)
library(shiny.fluent)
library(shinyjs)

ui <- fluentPage(
  useShinyjs(),
  # This one is outside the Pivot and will likely work correctly
  downloadButton("download_outside", label = ""),
  shiny.react:::ReactContext(
    # This one is inside the Pivot and will not work correctly
    downloadButton("download_inside", label = "")
  )
)

server <- function(input, output, session) {  
  output$download_inside <- downloadHandler(
    filename = function() {
      paste("data-", Sys.Date(), ".csv", sep="")
    },
    content = function(file) {
      write.csv(iris, file)
    }
  )

  output$download_outside <- downloadHandler(
    filename = function() {
      paste("data-", Sys.Date(), ".csv", sep="")
    },
    content = function(file) {
      write.csv(iris, file)
    }
  )
}

shiny::runApp(shinyApp(ui, server))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants