0

I have a shiny application using DT as follows (code snippet):

output$table <- DT::renderDataTable({
    DT::datatable(
      data = table, 
      escape = FALSE,
      rownames = FALSE,
      selection = "single",
      options = list(...),
      fnDrawCallback = htmlwidgets::JS('function(){HTMLWidgets.staticRender();}')
    ))) %>%
    spk_add_deps()

where the sparkline column in table is created using the sparklines function spk_chr. This works as expected and I have nice sparklines in the intended column using the JS fnDrawCallback. I had the need to edit the object in DT and I started using the edit features in DT. This also works but it is aesthetically not so pleasing (IMHO).

As a result I wanted to start using DTedit. I wrote a shiny application and I can make this work except for the sparklines feature. I am using David Fong's version of DTedit (version 2.2.1, see here). Function dtedit returns a reactiveValues object and that cannot be passed to spk_add_deps as the latter requires an htlmwidget. I struggle to find a way to make this work. How do I add the required dependencies for sparklines to the dtedit object? Can anyone help?

Here is the relevant section of the application using DTedit:

table_react <- reactiveVal(table)

table_dt <- dtedit(
  input, output,
  name = 'table',
  thedata = table_react,
  datatable.rownames = FALSE, # needed for the format*() functions to work
  edit.cols = edit_month,
  datatable.call = function(...) { DT::datatable(...) },
  datatable.options = list(
                      dom = "t",
                      ordering = FALSE,
                      paging = FALSE,
                      autoWidth = FALSE,
                      scrollY = "100vh",
                      scrollCollapse = FALSE,
                      fnDrawCallback = htmlwidgets::JS('function(){HTMLWidgets.staticRender();}'),
                      columnDefs = list(...),
  callback.update = KPI.update.callback
  ) 
Paul van Oppen
  • 1,443
  • 1
  • 9
  • 18

1 Answers1

0

Here is a modified version of the 'basic' example which is more-or-less self-contained.

library(shiny)
library(DT)
library(magrittr)
library(sparkline)
library(DTedit)

table <- data.frame(
  id = c('spark1', 'spark2'),
  spark = c(
    spk_chr(values = 1:3, elementId = 'spark1'),
    spk_chr(values = 3:1, elementId = 'spark2')
  )
)

shiny::shinyApp(
  ui = shiny::fluidPage(DT::DTOutput('table')),
  server = function(input, output) {
    output$table = DT::renderDataTable({
      DT::datatable(
        data = table,
        escape = FALSE,
        rownames = FALSE,
        selection = "single",
        options(fnDrawCallback = htmlwidgets::JS('function(){HTMLWidgets.staticRender();}'))
      ) %>%
        sparkline::spk_add_deps()
    })
  }
)

and here is an example of doing the same with DTedit, tested on current develop version (which will later become 2.2.4)

library(shiny)
library(DT)
library(magrittr)
library(sparkline)
library(DTedit)

table <- data.frame(
  id = c('spark1', 'spark2'),
  spark = c(
    spk_chr(values = 1:3, elementId = 'spark1'),
    spk_chr(values = 3:1, elementId = 'spark2')
  )
)

table_react <- shiny::reactiveVal(table)

server <- function(input, output) {

  sparkline_Results <- DTedit::dtedit(
    input, output,
    name = 'sparkline',
    thedata = table,
    datatable.rownames = FALSE,
    edit.cols = c("id"), # *not* editing the sparkline column!
    datatable.call = function(...) {
      arguments <- list(...)
      arguments$escape <- 1 # escape only the first column (not the sparkline column)
      do.call(DT::datatable, arguments) %>% # call DT::datatable with modified arguments
        sparkline::spk_add_deps()
    },
    datatable.options = list(
      fnDrawCallback = htmlwidgets::JS('function(){HTMLWidgets.staticRender();}')
    )
  )
}

ui <- shiny::fluidPage(
  shiny::h3('Sparkline example'),
  shiny::uiOutput('sparkline')
)

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

Note that the column containing the sparkline data is excluded from editing, as there is no current method to edit sparkline data.

And here is some a modified example which uses shinyalert (version 2.0.0) to allow sparkline data to be edited! Note that this example requires the current develop branch of DTedit (or version 2.2.4+, when 2.2.4 is released), as this example relies on a bugfix to an error which occurs when only one column can be edited.

library(shiny)
library(DT)
library(magrittr)
library(sparkline)
library(shinyalert) # version 2.0.0
library(DTedit) # version 2.2.4 (when released), or current 'develop' branch

table <- data.frame(
  id = c('spark1', 'spark2'),
  spark = c(
    sparkline::spk_chr(values = 1:3, elementId = 'spark1'),
    sparkline::spk_chr(values = 3:1, elementId = 'spark2')
  )
)

table_react <- shiny::reactiveVal(table)

server <- function(input, output) {

  my.callback.actionButton <- function(data, row, buttonID) {
    # data - the current copy of 'thedata'
    # row - the row number of the clicked button
    # buttonID - the buttonID of the clicked button

    # in this case, only one action button per row,
    # so no need to pay attention to buttonID

    shinyalert::shinyalert(
      html = TRUE,
      text = shiny::tagList(
        shiny::textInput(
          "sparkvector",
          "List of numbers",
          paste(as.character(1:5), collapse = ", ")
        )
      ),
      showCancelButton = TRUE,
      callbackR = function(x) {
        if (x == TRUE) {
          # only if closed without the 'escape/cancel'
          temp <- table_react()
          temp[row, 2] <- sparkline::spk_chr(
            as.numeric(unlist(strsplit(input$sparkvector, ","))),
            elementId = temp[row, 1]
          )
          table_react(temp)
          # since table_react is a reactive,
          # dtedit will 'react' to the change in table_react
        }
      }
    )
    return(data)
  }

  sparkline_Results <- DTedit::dtedit(
    input, output,
    name = 'sparkline',
    thedata = table_react,
    datatable.rownames = FALSE,
    edit.cols = c("id"), # *not* editing the sparkline column!
    datatable.call = function(...) {
      arguments <- list(...)
      arguments$escape <- 1 # escape only the first column (not the sparkline column)
      do.call(DT::datatable, arguments) %>% # call DT::datatable with modified arguments
        sparkline::spk_add_deps()
    },
    datatable.options = list(
      fnDrawCallback = htmlwidgets::JS('function(){HTMLWidgets.staticRender();}')
    ),
    action.buttons = list(
      myaction = list( # the 'myaction' name is arbitrary
        columnLabel = "Spark edit",
        buttonLabel = "Edit spark",
        buttonPrefix = "sparky"
      )
    ),
    callback.actionButton = my.callback.actionButton
  )

  shiny::observeEvent(
    sparkline_Results$thedata, ignoreInit = TRUE, {
    # update local copy of the table when DTedit's copy is edited
      table_react(sparkline_Results$thedata)
    })
}

ui <- shiny::fluidPage(
  shinyalert::useShinyalert(),
  shiny::h3('Sparkline example'),
  shiny::uiOutput('sparkline')
)

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

David Fong
  • 506
  • 4
  • 3