0

I am building app where a user can make edits to a datatable and the hit a button to reflect the changes in a non-editable copy of this datatable (in the final project, I will need to have two datasets that need to be matched manually), but for now this small MWE shows the problem I have with making a copy of the reactive table in which changes can be made, without changing the data of the original reactive table. I would like to make this app work, where you click edit a cell in the table dat_joined$data/output$mytable and that those changes do reflect in a new table mydf$data/output$table2. To do mydf$data initially (before any changes are made) needs to be a copy of dat_joined$data This is a follow up on this question and answer: how to make a copy of a reactive value in shiny server function


library(shiny)
library(DT)
library(shinyWidgets)
library(tidyverse)


# create master dataframe
dat_total <- tibble(ID_1 = 1:10,  names =   letters[1:10],
                    ID_2 = 11:20, names_2 = LETTERS[c(3:5, 1, 2, 6:8, 10, 9)])


shinyApp(
  ui = fluidPage(
    title = 'Radio button and a dropdown manue ',
    sliderInput("n_rows_table", "Number of rows:",
                min = 0, max = 10,
                value = 5),
    actionBttn(
      inputId = "button_1",
      label = "Make tables",
      size = "sm",
      color = "warning"
    ),
    DT::dataTableOutput("mytable"),
    actionBttn(
      inputId = "button_2",
      label = "Process",
      size = "sm",
      color = "success"),
    DT::dataTableOutput("table2")),
  
  server = function(input, output, session) {
    
    # set up reactive values
    dat_left <- reactiveValues(data=NULL)
    dat_right <- reactiveValues(data=NULL)
    dat_joined <- reactiveValues(data=NULL)
    
    
    # create reactive daraframe
    dat <- eventReactive(input$button_1, {
      dat_total[1:input$n_rows_table, ] %>%
        rowid_to_column()})
    
    
    # Split the data into a right and a left set
    
    observe({
      dat_left$data <- dat() %>%
        select(rowid, ID_1, names)
    })
    
    observe({
      dat_right$data <- dat() %>%
        select(rowid,  ID_2, names_2,ID_1)
    })
    
    
    # join these again
    # This is needed because my actual app will
    # be used to manually  match 2 datasets
    observe({
      if (is.null( dat_right$data )) {
        NULL   
      }else{
        dat_joined$data <- left_join(dat_left$data,
                                     dat_right$data,
                                     by = "rowid")
      }
    })
    
    
    # Print the the datasets
    
    output$mytable <- renderDT({
      datatable(dat_joined$data , 
                rownames = F,
                editable = "cell")
    })
    # I want to make a copy of the dat_joined$data dataset into dat$mydf
    # none of these function as expected
    
    #mydf <- reactiveValues(data=isolate(dat_joined$data))
    #mydf <- reactiveValues(data=local(dat_joined$data))
    #mydf <- reactiveValues(data=dat_joined$data)
    #mydf <- reactiveValues(data=NULL)
    
    # This works, but only saves the cells to w
    mydf <- reactiveValues(data=matrix(NA, nrow=10, ncol = 5))
    
    # Ideally the computation only happens when this both an edit is made 
    # and the button is pressed (now I need to press it between every edit)
    
    # validate_event <- reactive({
    #   req(input$mytable_cell_edit) & req(input$button_2)
    # })
    
    
    #observeEvent(input$button_2validate_event(), {  DOes not work
  
      observeEvent(input$button_2,{
      info = input$mytable_cell_edit
      str(info)
      i = info$row
      j = info$col
      v = info$value
      
      mydf$data[i, j] <- DT::coerceValue(v, mydf$data[i, j])
      
    })
    
    
    # print
    output[["table2"]] <- renderDT({
      datatable(mydf$data)
    })
    
  }
)
Gh_Shiny
  • 39
  • 5
  • For what it's work, `reactiveValues` is a list-like structure (note the plural 's'). It can contain all your reactive values in a single object, just give the entries different names. – MrGumble Feb 23 '21 at 12:41
  • Where exactly is your issue? – MrGumble Feb 23 '21 at 12:42
  • I would like to make this app work, where you click edit a cell in the table dat_joined$data/output$mytable and that those changes do reflect in a new table mydf$data/output$table2. To do mydf$data initially (before any changes are made) needs to be a copy of dat_joined$data – Gh_Shiny Feb 23 '21 at 12:49
  • I am not sure I understand your question fully. Do you wish to see the edited changes in table 1 (on top) be reflected in the table 2 (bottom) only when a button_2 is pressed? – YBS Feb 23 '21 at 12:58
  • Yes (that is one espect), but mostly I want changes I make in the table on top to reflect in the table in the bottom, while also allowing me to (jn a later stage of the app) make changes to the bottom table (drop rows, reorder columns, match data from `ID_1` to `ID_2`,etc.) without those changes reflecting in the table on the top. – Gh_Shiny Feb 23 '21 at 13:01
  • basically, this app will be used to verify the results of a matching algorithm and, where necessary, manually override the results. So the end goal is realigning ID_1 with ID_2 so that a matching with A, b with B, etc. – Gh_Shiny Feb 23 '21 at 13:04

1 Answers1

3

Any changes you make in the top table is reflected in the bottom table after you press the button "Process". Try this

library(shiny)
library(DT)
library(shinyWidgets)
library(tidyverse)


# create master dataframe
dat_total <- tibble(ID_1 = 1:10,  names =   letters[1:10],
                    ID_2 = 11:20, names_2 = LETTERS[c(3:5, 1, 2, 6:8, 10, 9)])


shinyApp(
  ui = fluidPage(
    title = 'Radio button and a dropdown manue ',
    sliderInput("n_rows_table", "Number of rows:",
                min = 0, max = 10,
                value = 5),
    actionBttn(
      inputId = "button_1",
      label = "Make tables",
      size = "sm",
      color = "warning"
    ),
    DT::dataTableOutput("mytable"),
    actionBttn(
      inputId = "button_2",
      label = "Process",
      size = "sm",
      color = "success"),
    DT::dataTableOutput("table2")),
  
  server = function(input, output, session) {
    
    # set up reactive values
    dat_left <- reactiveValues(data=NULL)
    dat_right <- reactiveValues(data=NULL)
    dat_joined <- reactiveValues(data=NULL)
    dfon <- reactiveValues(top=NULL,
                           bottom=NULL)
    
    # create reactive daraframe
    dat <- eventReactive(input$button_1, {
      dat_total[1:input$n_rows_table, ] %>%
        rowid_to_column()})
    
    
    # Split the data into a right and a left set
    
    observe({
      req(dat())
      dat_left$data <- dat() %>%
        dplyr::select(rowid, ID_1, names)
    })
    
    observe({
      req(dat())
      dat_right$data <- dat() %>%
        dplyr::select(rowid,  ID_2, names_2,ID_1)
    })
    
    
    # join these again
    # This is needed because my actual app will
    # be used to manually  match 2 datasets
    observe({
      req(dat())
      if (!is.null( dat_right$data )) {
        dat_joined$data <- left_join(dat_left$data,
                                     dat_right$data,
                                     by = "rowid")
      }
    })
    
    observe({ ###assign your orig data to a reactiveValues object
      req(dat_joined$data)
      if (!is.null(dat_joined$data)) {
        dfon$top <- dat_joined$data 
      }
    })
    
    
    # Print the the datasets
    
    output$mytable <- renderDT({
      datatable(dfon$top, 
                rownames = F,
                editable = "cell")
    })
    
    # Ideally the computation only happens when this both an edit is made 
    # and the button is pressed (now I need to press it between every edit)

    observeEvent(input$mytable_cell_edit, {
      info = input$mytable_cell_edit
      str(info)
      #i = info$row
      #j = info$col + 1  # offset by 1
      #v = info$value
      
      #dfon$top[i, j] <<- DT::coerceValue(v, dfon$top[i, j])
      dfon$top <<- editData(dfon$top, info)
    })
    
    observeEvent(input$button_2,{
      dfon$bottom <- dfon$top
      output$table2 <- renderDT({
        datatable(dfon$bottom)
      })
    })
    
    ## further editing of dfon$bottom is performed below...with...observeEvent(input$table2_cell_edit, {...
    
  }
)

In the output below, I have entered cccc for 3rd element in names column, but I have not clicked on the button Process. Therefore, the edited cell is not reflected in the bottom table.

output

YBS
  • 19,324
  • 2
  • 9
  • 27
  • Thank you, this almost works, but I Try to edit any of the numeric variables (ID_1.x, ID_2, or ID_1.y), it crashes with the message **"Warning in DT::coerceValue(v, dfon$top[i, j]) : The data type is not supported: tbl_df, tbl, data.frame Warning: Error in : Assigned data `DT::coerceValue(v, dfon$top[i, j])` must be compatible with existing data. i Error occurred for column `ID_2`. x Can't convert to .`"** – Gh_Shiny Feb 23 '21 at 14:09
  • I got that error before when I had `j = info$col`. So, try `j = info$col`, instead of `j = info$col + 1` – YBS Feb 23 '21 at 15:00
  • Perhaps you need to update your packages, including DT. – YBS Feb 23 '21 at 16:42
  • I think it is just forces the output to be a character string even if it originally was a numeric. I have found a way aorund it. – Gh_Shiny Feb 23 '21 at 16:57
  • You need `j = info$col`, if you have `rownames=FALSE` in `renderDT`, otherwise, you will need `j = info$col + 1`. – YBS Feb 23 '21 at 17:09
  • I have given a alternate solution where there is no need to off set. Also, it eliminates the warning message. – YBS Feb 23 '21 at 17:38