0

I have a shiny module that displays a table with a comment column where users can input text on the client side and the comments then get stored in the database. Now, I want to add another column with checkboxes and store their corresponding values(TRUE/FALSE) in the database. Not sure how to retrieve checkbox values from the table. Below is my attempt on a sample data.

library(tidyverse)
library(shinyWidgets)
library(shiny)
library(htmlwidgets)


mtcars_df <- mtcars %>% 
  rownames_to_column(var="car")


writeback_UI <- function (id) {
  ns <- NS(id)

  DT::dataTableOutput(ns('records_tbl'))
  
}
shinyInput = function(FUN, len, id, ...) {
  inputs = character(len)
  for (i in seq_len(len)) {
    inputs[i] = as.character(FUN(paste0(id, i), label = NULL, ...))
  }
  inputs
}

# obtain the values of inputs
shinyValue = function(id, len) {
  unlist(lapply(seq_len(len), function(i) {
    value = input[[paste0(id, i)]]
    if (is.null(value)) NA else value
  }))
}




writeback_server <- function (id,records_data) {
  #stopifnot(is.reactive(records_data))
  shiny::moduleServer(id, function (input,output,session) {
    
    
    #initiate a reactive variable for storing comments
    comments_df <- reactiveVal(tibble(car=rownames(mtcars),comments=NA_character_))
    
    records_df <- reactive({
      records_data %>% 
        left_join(comments_df()) %>% 
        mutate(key_check= shinyInput(checkboxInput,nrow(.), 'cb_', value = TRUE))
        #mutate(check_values=shinyValue('cb_', nrow(.)))
    })
    
  

    

    
    
    
    output$records_tbl <- DT::renderDT({
      num_cols <- dim(records_df())[2]-2
      DT::datatable(
        records_df(),
        editable = list(target="column",disable=list(columns= 1:num_cols)),
        filter = "top",
        escape = FALSE,
        selection = 'none',
        options = list(
          dom = 't',
          paging = TRUE,
          ordering = FALSE,
          preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
          drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } '),
          pageLength = 10,
          scrollX=TRUE,
          buttons=c('copy','csv','excel')),
        
      )
    }
    )
    
    
    observe({
      req(input$records_tbl_cell_edit)
      comments_data <- records_df() %>% 
        slice(input$records_tbl_cell_edit$row) %>% 
        select(car) %>% 
        mutate(comment=input$records_tbl_cell_edit$value) %>% 
        filter(comment!="")
      
      
      comments_df(comments_df() %>% 
                    rows_upsert(comments_data) %>% 
                    distinct())
      
  
    }) %>% 
      bindEvent(input$records_tbl_cell_edit)
    
    

    
    
   
    
    
    return(
      reactive({records_data %>%
          left_join(comments_df())
      }))
    
    # 
  }
  )
}


WriteBackTestApp <- function() {
  
  mtcars_df <- mtcars %>% rownames_to_column(var = "car")
  
  ui <- fluidPage(
    writeback_UI("wb")
  )
  
  server <- function(input, output, session) {
    writeback_server("wb",mtcars_df)
  }
  shinyApp(ui, server)
}

WriteBackTestApp()
Mohamad Sahil
  • 165
  • 2
  • 12

1 Answers1

3

Like this? (I don't see any problem regarding the module)

library(shiny)
library(DT)

ui <- fluidPage(
  br(),
  fluidRow(
    column(
      6,
      DTOutput("dtable")
    ),
    column(
      6,
      verbatimTextOutput("reactiveDF")
    )
  )
)

shinyInput <- function(FUN, len, id, ...) {
  inputs <- character(len)
  for (i in seq_len(len)) {
    inputs[i] <- as.character(FUN(paste0(id, i), label = NULL, ...))
  }
  inputs
}

dat0 <- data.frame(
  fruit  = c("apple", "cherry", "pineapple", "pear"),
  letter = c("a", "b", "c", "d")
)

dat1 <- cbind(dat0, bool = FALSE)

dat2 <- cbind(
  dat0,
  check = shinyInput(checkboxInput, nrow(dat0), "checkb")
)

js <- c(
  "$('[id^=checkb]').on('click', function(){",
  "  var id = this.getAttribute('id');",
  "  var i = parseInt(/checkb(\\d+)/.exec(id)[1]);",
  "  var value = $(this).prop('checked');",
  "  var info = [{row: i, col: 3, value: value}];",
  "  Shiny.setInputValue('dtable_cell_edit:DT.cellInfo', info);",
  "})"
)

server <- function(input, output, session) {

  Dat <- reactiveVal(dat1)

  output[["dtable"]] <- renderDT({
    datatable(
      dat2, 
      rownames = TRUE,
      escape = FALSE,
      editable = list(target = "cell", disable = list(columns = 3)),
      selection = "none",
      callback = JS(js)
    )
  }, server = FALSE)

  observeEvent(input[["dtable_cell_edit"]], { 
    info <- input[["dtable_cell_edit"]] # this input contains the info of the edit
    print(info)
    Dat(editData(Dat(), info))
  })
  
  output[["reactiveDF"]] <- renderPrint({ 
    Dat()
  })

}

shinyApp(ui, server)

enter image description here


EDIT: with a module

library(shiny)
library(DT)

shinyInput <- function(FUN, len, id, ...) {
  inputs <- character(len)
  for (i in seq_len(len)) {
    inputs[i] <- as.character(FUN(paste0(id, i), label = NULL, ...))
  }
  inputs
}

dat0 <- data.frame(
  fruit  = c("apple", "cherry", "pineapple", "pear"),
  letter = c("a", "b", "c", "d")
)

dat1 <- cbind(dat0, bool = FALSE)

dat2 <- cbind(
  dat0,
  check = shinyInput(checkboxInput, nrow(dat0), "checkb")
)

js <- function(dtid, ns) {
  c(
    "$('[id^=checkb]').on('click', function(){",
    "  var id = this.getAttribute('id');",
    "  var i = parseInt(/checkb(\\d+)/.exec(id)[1]);",
    "  var value = $(this).prop('checked');",
    "  var info = [{row: i, col: 3, value: value}];",
    sprintf(
      "Shiny.setInputValue('%s', info);",
      ns(sprintf("%s_cell_edit:DT.cellInfo", dtid))
    ),
    "})"
  )
}


tableUI <- function(id) {
  ns <- NS(id)
  fluidRow(
    column(
      6,
      DTOutput(ns("dtable"))
    ),
    column(
      6,
      verbatimTextOutput(ns("reactiveDF"))
    )
  )
}

tableServer <- function(id) {
  moduleServer(id, function(input, output, session) {
    Dat <- reactiveVal(dat1)

    output[["dtable"]] <- renderDT(
      {
        datatable(
          dat2,
          rownames = TRUE,
          escape = FALSE,
          editable = list(target = "cell", disable = list(columns = 3)),
          selection = "none",
          callback = JS(js("dtable", session$ns))
        )
      },
      server = FALSE
    )

    observeEvent(input[["dtable_cell_edit"]], {
      info <- input[["dtable_cell_edit"]]
      Dat(editData(Dat(), info))
    })

    output[["reactiveDF"]] <- renderPrint({
      Dat()
    })
  })
}


ui <- fluidPage(
  br(),
  tableUI("xxx")
)

server <- function(input, output, session) {
  tableServer("xxx")
}

shinyApp(ui, server)

EDIT: multiple pages

If there is more than one page, replace

js <- function(dtid, ns) {
  c(
    "$('[id^=checkb]').on('click', function(){",
    "  var id = this.getAttribute('id');",
    "  var i = parseInt(/checkb(\\d+)/.exec(id)[1]);",
    "  var value = $(this).prop('checked');",
    "  var info = [{row: i, col: 3, value: value}];",
    sprintf(
      "Shiny.setInputValue('%s', info);",
      ns(sprintf("%s_cell_edit:DT.cellInfo", dtid))
    ),
    "})"
  )
}

with

js <- function(dtid, ns) {
  c(
    "$('body').on('click', '[id^=checkb]', function(){",
    "  var id = this.getAttribute('id');",
    "  var i = parseInt(/checkb(\\d+)/.exec(id)[1]);",
    "  var value = $(this).prop('checked');",
    "  var info = [{row: i, col: 3, value: value}];",
    sprintf(
      "Shiny.setInputValue('%s', info);",
      ns(sprintf("%s_cell_edit:DT.cellInfo", dtid))
    ),
    "})"
  )
}
Stéphane Laurent
  • 75,186
  • 15
  • 119
  • 225
  • It doesn't seem to be working in a module. I am just not able to access the check box values. – Mohamad Sahil Aug 02 '22 at 16:02
  • Please open a new question, I don't know what you're doing. Why are you using a module? You have repeated code? – Stéphane Laurent Aug 02 '22 at 16:10
  • I am collaborating with my co-worker. So it makes it easier to work independently on different parts of the app and then finally put all of them together. – Mohamad Sahil Aug 02 '22 at 16:16
  • Ok. See my edit for a module example. Is it what you want? – Stéphane Laurent Aug 02 '22 at 16:57
  • I noticed that the check box values don't get updated on next pages and only works on the first page. Is there any way to fix that? – Mohamad Sahil Aug 03 '22 at 06:38
  • Ah yes, that's because the checkboxes are not visible when the callback is executed. See my edit. – Stéphane Laurent Aug 03 '22 at 06:59
  • Perfect, thanks! When you have thousands of records, it slows down significantly. Is there any method to combat that? – Mohamad Sahil Aug 03 '22 at 20:57
  • I was wondering if it's possible to implement column filters on top with rhandsontable using custom renderer similar to what you see in this link. I would be happy to post it as a separate question. https://handsontable.com/docs/6.2.0/demo-filtering.html – Mohamad Sahil Aug 10 '22 at 17:17
  • Is there a way to make it so that when you double-click on the column with disabled editing that it doesn't show the underlying HTML tag? If you double-click on 'check' column outside the box it shows the HTML and messes up the print output. – Will Hipson Dec 08 '22 at 18:22
  • I have table with multiple columns with checkboxes. How would I go about retrieving the reactive booleans for these multiple columns. I am able to only get for one. – sutsabs May 25 '23 at 14:12
  • @utsabshrestha Open a new question. – Stéphane Laurent May 25 '23 at 14:36