user51462
user51462

Reputation: 2022

R Shiny - Pre-selecting the row and page of a datatable inside a modalDialog

The app below contains a selectInput of dataset IDs and a button View details which displays a modalDialog when clicked. The modal dialog has a datatable that contains some information about the datasets in the selectInput dropdown.

Here is a screenshot of the app on startup: enter image description here

Since the user can select a dataset either by selecting an option from the dropdown menu or by selecting a row in the datatable, I created a reactive value rv$selectedRow which stores the value of the selected dataset. When the modal is triggered, rv$selectedRow takes the value of input$data. When the Select button in the modal footer is clicked, rv$selectedRow takes the value of input$dfs_rows_selected and the selectInput is updated to reflect this new value. This is done by the two observeEvents in the code below.

When the user selects a row, closes the modal and opens it again, I would like the page and row of the selected dataset (input$data) to be pre-selected. I tried to achieve this using selection = list(mode = 'single', selected = rv$selectedRow) in the renderDT call. As you can see in the screenshot, row 1 should be pre-selected but it isn't. I feel like I'm missing a req() somewhere in the renderDT but I'm not sure. The value of rv$selectedRow checks out when I print it to the console, so I don't know why the selected argument of renderDT isn't working. I am also not sure how to store the page of the selected row. Any insight would be much much appreciated as I'm a little lost.

The app is as follows:

library(shiny)
library(DT)

datasets = data.frame(cbind(id = seq_len(4), name = c('iris', 'mtcars', 'satellite', 'credit')))

# UI ----------------------------------------------------------------------
ui = fluidPage(

  selectInput('data', 'Select dataset:', choices = datasets$id),

  actionButton('view', 'View details')

)

# SERVER ------------------------------------------------------------------
server <- shinyServer(function(input, output, session) {

  rv = reactiveValues(selectedRow = NULL, selectedPage = NULL)

  # Opening the modal
  observeEvent(input$view, {

    rv$selectedRow = req(input$data)

    print(paste("selectedRow on 'View':", rv$selectedRow))

    showModal(modalDialog(
      title = 'Available datasets',

      tags$b('Click on a row to select a dataset.'),

      br(),

      br(), 

      DT::dataTableOutput('dfs'),

      easyClose = F,
      footer = tagList(
        modalButton('Cancel'), 
        bsButton('select', 'Select')
        )
      )
    )

  })


  # Rendering the DT - pre-selection of row not working
  output$dfs <- renderDT({

    print(paste("selectedRow on 'renderDT':", rv$selectedRow))

    datasets

  }, 

  options = list(
    # displayStart = selectedPage,
    pageLength = 2
    ),
  filter = 'top',
  selection = list(mode = 'single', selected = rv$selectedRow), 
  rownames = F

  )

  # Saving the selected row and updating the selectInput
  observeEvent(input$select, {

    rv$selectedRow = req(input$dfs_rows_selected)

    print(paste("selectedRow on 'Select':", rv$selectedRow))

    updateSelectInput(session = session, inputId = 'data', selected = datasets[rv$selectedRow, 1])

    removeModal(session)

  })
})

shinyApp(ui, server)

Updated code:

As per this solution and the one posted by Wilmar below, using datatable() in the renderDT seemed to fix the problem -

library(shiny)
library(DT)

datasets = data.frame(cbind(id = seq_len(4), name = c('iris', 'mtcars', 'satellite', 'credit')))

# UI ----------------------------------------------------------------------
ui = fluidPage(

  selectInput('data', 'Select dataset:', choices = datasets$id),

  actionButton('view', 'View details')

)

# SERVER ------------------------------------------------------------------
server <- shinyServer(function(input, output, session) {

  rv = reactiveValues(selectedRow = NULL, selectedPage = NULL)

  # Opening the modal
  observeEvent(input$view, {

    print(paste("selectedRow on 'View':", rv$selectedRow))

    showModal(modalDialog(
      title = 'Available datasets',

      tags$b('Click on a row to select a dataset.'),

      br(),

      br(), 

      DT::dataTableOutput('dfs'),

      easyClose = F,
      footer = tagList(
        modalButton('Cancel'), 
        bsButton('select', 'Select')
        )
      )
    )

  })

  # Rendering the DT - pre-selection of row not working
  output$dfs <- renderDataTable({

    r = rv$selectedRow

    print(paste("selectedRow on 'renderDT':", r))

    datatable(
      datasets, 
      options = list(
        displayStart = as.numeric(r)-1,
        pageLength = 2
      ),
      filter = 'top',
      selection = list(mode = 'single', selected = r), 
      rownames = F

    )

  }, server = F)


  # Saving the selected row and updating the selectInput
  observeEvent(input$select, {

    rv$selectedRow = req(input$dfs_rows_selected)

    print(paste("selectedRow on 'Select':", rv$selectedRow))

    updateSelectInput(session = session, inputId = 'data', selected = datasets[rv$selectedRow, 1])

    removeModal(session)

  })

  observe({

    rv$selectedRow = input$data

  })

})

shinyApp(ui, server)

Upvotes: 1

Views: 1768

Answers (1)

Wilmar van Ommeren
Wilmar van Ommeren

Reputation: 7689

I guess this is what you're looking for. Your first problem was that you had to convert rv$selectedRow to numeric. Secondly it you were re-rendering your datatable everytime you pressed the "view" button. And thirdly you didn't do anything with your selectInput ("data").

I transformed rv$selectedRow to a numeric, moved your showModal to the ui and created an observer for your selectInput. In addition, I wrapped your datafarme in the datatable function, which I think is a bit more convenient.

Working example:

library(shiny)
library(DT)
library(shinyBS)

datasets = data.frame(cbind(id = seq_len(4), name = c('iris', 'mtcars', 'satellite', 'credit')))

# UI ----------------------------------------------------------------------
ui = fluidPage(

  selectInput('data', 'Select dataset:', choices = datasets$id),

  actionButton('view', 'View details'),
  tags$head(tags$style("#df_popup .modal-footer{ display:none}
                       #df_popup .modal-header .close{display:none}")),
  bsModal("df_popup", title='Available datasets', trigger='view', 
    tags$b('Click on a row to select a dataset.'),

    br(),

    br(), 

    DT::dataTableOutput('dfs'),

    column(12, align='right',
      modalButton('Cancel'), 
      bsButton('select', 'Select')
    )
  )
)

# SERVER ------------------------------------------------------------------
server <- shinyServer(function(input, output, session) {

  rv = reactiveValues(selectedRow = NULL, selectedPage = NULL)

  # Rendering the DT - pre-selection of row not working
  output$dfs <- renderDT({
    print(paste("selectedRow on 'renderDT':", rv$selectedRow))
    datatable(datasets, options = list(
      # displayStart = selectedPage,
      pageLength = 2
    ),
    filter = 'top',
    selection = list(mode = 'single', selected=c(as.numeric(rv$selectedRow))), 
    rownames = F)
  }, 
  )


  # Saving the selected row and updating the selectInput
  observeEvent(input$select, {
    rv$selectedRow = req(input$dfs_rows_selected)
    print(paste("selectedRow on 'Select':", rv$selectedRow))
    updateSelectInput(session = session, inputId = 'data', selected = datasets[rv$selectedRow, 1])
    toggleModal(session, 'df_popup')
  })


  observeEvent(input$data, {
    rv$selectedRow = input$data
    print(paste("selectedRow on 'data':", rv$selectedRow))
  })


})

shinyApp(ui, server)

Upvotes: 1

Related Questions