Doug Fir
Doug Fir

Reputation: 21322

Update datatable based on user input, include update of corresponding columns, not just the one edited

The small app below generates a DT::datatable with two columns x,y. X starts out being a random number with rnorm. y should be whatever x is plus 1.

The app allows the user to edit column x in a DT::datatable. I have built it so that the user can amend column x, however, column y does not update as expected, it just stays the same.

Shiny code:

library(shiny)
library(tidyverse)
library(shinydashboard)
library(scales)
library(DT)


# define functions

## generate example data
create_sample_df <- function(x) {
    data.frame(
        x = x %>% unlist
    ) %>% mutate(y = x + 1)
}

## render DT
render_dt = function(data, editable = 'cell', server = TRUE, ...) {
    renderDT(data, selection = 'none', server = server, editable = editable, ...)
}


# UI ----

header <- dashboardHeader(title = 'blah')
sidebar <- dashboardSidebar()
body <- dashboardBody(DT::DTOutput('ex_df'))
ui <- dashboardPage(header, sidebar, body)

# Server ----
server <- function(input, output) {
    
    x <- rnorm(10, 0, 2) %>% as.integer %>%  as.list
    
    # the df to be displayed as a DT::datatable. 
    ex_df <- reactive({create_sample_df(x)})
    
    ## set to initially be the on open result of ex_df, before any user input
    reactivs <- reactiveValues(ex_df = ex_df)
    
    observeEvent(input$ex_df_cell_edit, {
        info = input$ex_df_cell_edit
        str(info)
        i = info$row
        j = info$col
        v = info$value

        # update budgets, which in turn is used to generate data during create_sample_df()
        x[[i]] <<- v
        
        # now update the reactive values object with the newly generated df
        reactivs$ex_df <<- reactive({create_sample_df(x)})
    })
    
    output$ex_df <- render_dt(data = reactivs$ex_df(),
                              rownames = FALSE,
                              list(target = 'cell', 
                                   disable = list(columns = c(1))))
    
}

shinyApp(ui, server)

Screen shot: enter image description here

In the screen I am about to edit the first row in column x from -1 to say 10. After hitting enter, desired result is that for row 1, x value is 10 an y value is 11.

Currently this does not happen, y stays the same no matter what. Also, The first attempt to edit column x does not work, only after the second attempt does the new value persist.

Upvotes: 1

Views: 644

Answers (1)

starja
starja

Reputation: 10375

The problem is how you pass the reactive data to your custom render_dt. I'm not completely sure why, but changes to reactivs$ex_df are not recognised. The changes you see in the x column are not due to the updated ex_df, but the changes directly made in the table. Therefore, I changed it back to using renderDT directly. I've made some additional changes:

  • ex_df itself is not reactive. It is stored in a reactiveValues object, where every entry itself is already reactive.
  • assignments to reactiveValues don't need <<-
  • the edited value in cell_edit is a character vector
library(shiny)
library(tidyverse)
library(shinydashboard)
library(scales)
library(DT)


# define functions

## generate example data
create_sample_df <- function(x) {
  data.frame(
    x = x %>% unlist
  ) %>% mutate(y = x + 1)
}


# UI ----

header <- dashboardHeader(title = 'blah')
sidebar <- dashboardSidebar()
body <- dashboardBody(DT::DTOutput('ex_df'))
ui <- dashboardPage(header, sidebar, body)

# Server ----
server <- function(input, output) {
  
  x <- rnorm(10, 0, 2) %>% as.integer %>%  as.list
  
  # the df to be displayed as a DT::datatable. 
  ex_df <- create_sample_df(x)
  
  ## set to initially be the on open result of ex_df, before any user input
  reactivs <- reactiveValues(ex_df = ex_df)
  
  observeEvent(input$ex_df_cell_edit, {
    info = input$ex_df_cell_edit
    str(info)
    i = info$row
    j = info$col
    v = info$value
    
    # update budgets, which in turn is used to generate data during create_sample_df()
    x[[i]] <<- as.numeric(v)
    
    # now update the reactive values object with the newly generated df
    reactivs$ex_df <- create_sample_df(x)
  })
  
  output$ex_df <- renderDT({
    datatable(reactivs$ex_df,
              editable = "cell",
              rownames = FALSE,
              options = list(target = 'cell', 
                             disable = list(columns = c(1))))
  })
  
}

shinyApp(ui, server)

Edit

here a solution without observeEvent and only a reactive() for ex_df. Then you can pass the unevaluated reactive to your render_dt function:

library(shiny)
library(tidyverse)
library(shinydashboard)
library(scales)
library(DT)


# define functions

## generate example data
create_sample_df <- function(x) {
  data.frame(
    x = x %>% unlist
  ) %>% mutate(y = x + 1)
}

## render DT
render_dt = function(data_in, editable = 'cell', server = TRUE, ...) {
  renderDT(data_in(), selection = 'none', server = server, editable = editable, ...)
}


# UI ----

header <- dashboardHeader(title = 'blah')
sidebar <- dashboardSidebar()
body <- dashboardBody(DT::DTOutput('ex_df'))
ui <- dashboardPage(header, sidebar, body)

# Server ----
server <- function(input, output) {
  
  x <- rnorm(10, 0, 2) %>% as.integer %>%  as.list
  
  # the df to be displayed as a DT::datatable. 
  setup_df <- create_sample_df(x)
  
  ex_df <- eventReactive(input$ex_df_cell_edit, {
    # on startup
    if (is.null(input$ex_df_cell_edit)) {
      setup_df
      
      # for edits
    } else {
      
      
      info = input$ex_df_cell_edit
      str(info)
      i = info$row
      j = info$col
      v = info$value
      
      # update budgets, which in turn is used to generate data during create_sample_df()
      x[[i]] <<- as.numeric(v)
      
      create_sample_df(x)
    }
  },
  ignoreNULL = FALSE)
  
  output$ex_df <- render_dt(data_in = ex_df,
                            rownames = FALSE,
                            list(target = 'cell', 
                                 disable = list(columns = c(1))))
  
}

shinyApp(ui, server)

Upvotes: 2

Related Questions