How to excract data from edited datatable in shiny - r

I want to creat an shiny app where users have to edit datatable.
There is the code contains reproductible exemple:
library(shiny)
library(dplyr)
library(DT)
line<-c(1,1,1,1,1)
op<-c(155,155,155,156,156)
batch<-c(1,2,3,1,2)
voile<-c(1,NA,NA,NA,NA)
depot<-c(2,NA,2,NA,NA)
boe<-data.frame(line,op,batch)
ui <- fluidPage(
# Application title
titlePanel("test dust"),
actionButton("refresh", label = "refresh"),
DT::dataTableOutput("mytable"),
actionButton("save", label = "save"),
)
# Define server logic required to draw a histogram
server <- function(input, output) {
DTdust<- eventReactive(input$refresh, {
DTdust <-data.frame(line,op,batch,voile,depot)
})
merged<-reactive({
merged<-merge(boe,DTdust(),all.x = TRUE)
})
mergedfiltred<-reactive({
mergedfiltred<- filter(merged(),is.na(voile)|is.na(depot) )
})
output$mytable = DT::renderDataTable( mergedfiltred(),editable = list(target = 'cell',
disable = list(columns = c(1:3))),selection = 'none'
)
}
# Run the application
shinyApp(ui = ui, server = server)
I wish this works like this —>
When user clic on refresh button. Dtdust.csv (here simulated) is read , then it merged with boe.csv (simulated too) an filter to get only rows without resulta for voile and depot col.
And display this merged filtred ino editable datatable .
This part works.
After i want to extract the data from edited datatable to make some processing on it (extract rows completed, rbind it on dtdust and save as dtdust.csv. But that’s ok i think.)
I’ m in trouble to extract edited datatable.
I see some exemple to do it with classic dataframe but it not work with reactive one.
I’m beeginner so if you can comment a lot your answers i can learn how to and not just ctrl+c ctrl+v your code :)
Thanks

You need to define a reactiveValues data frame. Then you need to update it via observeEvent whenever any cell is modified via mytable_cell_edit. The updated dataframe is now available in the server side, and part of it is now printed in the second table. You can use DF1$data for further analysis or subsetting. Full updated code is below.
library(shiny)
library(dplyr)
library(DT)
line<-c(1,1,1,1,1)
op<-c(155,155,155,156,156)
batch<-c(1,2,3,1,2)
voile<-c(1,NA,NA,NA,NA)
depot<-c(2,NA,2,NA,NA)
boe<-data.frame(line,op,batch)
ui <- fluidPage(
# Application title
titlePanel("test dust"),
actionButton("refresh", label = "refresh"),
DTOutput("mytable"), DTOutput("tb2"),
actionButton("save", label = "save"),
)
# Define server logic required to draw a histogram
server <- function(input, output) {
DF1 <- reactiveValues(data=NULL)
DTdust<- eventReactive(input$refresh, {
req(input$refresh)
DTdust <-data.frame(line,op,batch,voile,depot)
})
merged<-reactive({
req(DTdust())
merged<-merge(boe,DTdust(),all.x = TRUE)
})
mergedfiltred<-reactive({
mergedfiltred <- filter(merged(),is.na(voile)|is.na(depot) )
DF1$data <- mergedfiltred
mergedfiltred
})
output$mytable = renderDT(
mergedfiltred(),
editable = list(target = 'cell', disable = list(columns = c(1:3))), selection = 'none'
)
observeEvent(input$mytable_cell_edit, {
info = input$mytable_cell_edit
str(info)
i = info$row
j = info$col
v = info$value
DF1$data[i, j] <<- DT::coerceValue(v, DF1$data[i, j])
})
output$tb2 <- renderDT({
df2 <- DF1$data[,2:5]
plen <- nrow(df2)
datatable(df2, class = 'cell-border stripe',
options = list(dom = 't', pageLength = plen, initComplete = JS(
"function(settings, json) {",
"$(this.api().table().header()).css({'background-color': '#000', 'color': '#fff'});",
"}")))
})
}
# Run the application
shinyApp(ui = ui, server = server)

Hi thanks for your solution #YBS.
I finaly find a solution by myself half an hour after asking here... (i previously turning arround hours and hours).
There is what i do :
output$x2 = DT::renderDataTable({
req(dat$x2)
DT::datatable(dat$x2)
})
dat <- reactiveValues()
# update edited data
observeEvent(input$mytable_cell_edit, {
data_table <- dat$x2
data_table[input$mytable_cell_edit$row, input$mytable_cell_edit$col] <- as.numeric(input$mytable_cell_edit$value)
dat$x2 <- data_table
})
Have a good day

Related

Rshiny : Add a free text for comments

I have a data coming from a server. Now I want to add a free text column ( editable) to add comments to my R shiny application. Once that is done , I want to save it in SQLLite and bring it back once it is refreshed. Please help me with the pointers.
library(shiny)
library(ggplot2) # for the diamonds dataset
ui <- fluidPage(
title = "Examples of DataTables",
sidebarLayout(
sidebarPanel(
conditionalPanel(
'input.dataset === "diamonds"'
)
),
mainPanel(
tabsetPanel(
id = 'dataset',
tabPanel("diamonds", DT::dataTableOutput("mytable1"))
)
)
)
)
library(DT)
server <- function(input, output) {
# choose columns to display
diamonds2 = diamonds[sample(nrow(diamonds), 1000), ]
diamonds2$test <- ifelse(diamonds2$x > diamonds2$y,TRUE,FALSE)
output$mytable1 <- DT::renderDataTable({
DT::datatable(diamonds2[, drop = FALSE],extensions = 'FixedColumns',options = list(
dom = 't',
scrollX = TRUE,
fixedColumns = list(leftColumns =10)
)) %>%
formatStyle(
'x', 'test',
backgroundColor = styleEqual(c(TRUE, FALSE), c('gray', 'yellow'))
)
})
}
Please guide how can I add free text in the end of the table and save it.
Thanks in advance.
Regards,
R
Here is a solution based on DTs editable option. (See this for more information)
Each time the user edits a cell in the "comment" column it is saved to a sqlite database and loaded again after restarting the app:
library(shiny)
library(DT)
library(ggplot2) # diamonds dataset
library(RSQLite)
library(DBI)
# choose columns to display
diamonds2 = diamonds[sample(nrow(diamonds), 1000),]
diamonds2$test <- ifelse(diamonds2$x > diamonds2$y, TRUE, FALSE)
diamonds2$id <- seq_len(nrow(diamonds2))
diamonds2$comment <- NA_character_
con <- dbConnect(RSQLite::SQLite(), "diamonds.db")
if(!"diamonds" %in% dbListTables(con)){
dbWriteTable(con, "diamonds", diamonds2)
}
ui <- fluidPage(title = "Examples of DataTables",
sidebarLayout(sidebarPanel(
conditionalPanel('input.dataset === "diamonds"')
),
mainPanel(tabsetPanel(
id = 'dataset',
tabPanel("diamonds", DT::dataTableOutput("mytable1"))
))))
server <- function(input, output, session) {
# use sqlInterpolate() for production app
# https://shiny.rstudio.com/articles/sql-injections.html
dbDiamonds <- dbGetQuery(con, "SELECT * FROM diamonds;")
output$mytable1 <- DT::renderDataTable({
DT::datatable(
dbDiamonds,
# extensions = 'FixedColumns',
options = list(
dom = 't',
scrollX = TRUE
# , fixedColumns = list(leftColumns = 10)
),
editable = TRUE,
# editable = list(target = "column", disable = list(columns = which(names(diamonds2) %in% setdiff(names(diamonds2), "comment"))))
) %>% formatStyle('x', 'test', backgroundColor = styleEqual(c(TRUE, FALSE), c('gray', 'yellow')))
})
observeEvent(input$mytable1_cell_edit, {
if(input$mytable1_cell_edit$col == which(names(dbDiamonds) == "comment")){
dbExecute(con, sprintf("UPDATE diamonds SET comment = '%s' WHERE id = %s", input$mytable1_cell_edit$value, input$mytable1_cell_edit$row))
}
})
}
shinyApp(ui, server, onStart = function() {
onStop(function() {
dbDisconnect(con) # close connection on app stop
})
})
Initially I wanted to disable editing for all columns except "comment", however, it seems I've found a bug.
The following example adds a <input type="text"> element to each row of the table, where you can add your free text. A simple JavaScript event listener reacts on changes to the text boxes and stores them in the Shiny variable free_text which you can then process on the shiny side according to your needs (in this toy example it is simply output to a verbatimTextOutput).
As for the storing: I would add a save button, which reads input$free_text and saves it back to the data base. To display the text then again in the text boxes is as easy as adding the value in the mutate statement like this mutate(free_text = sprintf("<input type=\"text\" class = \"free-text\" value = \"%s\" />", free_text_field_name))
library(shiny)
library(DT)
library(dplyr)
ui <- fluidPage(
tags$head(
tags$script(
HTML(
"$(function() {
// input event fires for every change, consider maybe a debounce
// or the 'change' event (then it is only triggered if the text box
// loses focus)
$('#tab').on('input', function() {
const inputs = $(this).find('.free-text').map(function() {
return this.value;
})
Shiny.setInputValue('free_text', inputs.get());
})
})
"
)
)
),
fluidRow(
verbatimTextOutput("out")
),
fluidRow(
dataTableOutput("tab")
)
)
server <- function(input, output, session) {
output$tab <- renderDataTable({
my_dat <- mtcars %>%
mutate(free_text =
sprintf("<input type=\"text\" class = \"free-text\" value = \"\" />"))
datatable(my_dat, escape = FALSE,
options = list(dom = "t", pageLength = nrow(mtcars)))
})
output$out <- renderPrint(input$free_text)
}
shinyApp(ui, server)
You may want to have a look at the handsontable package, which allows editing of (columns of) datatable outputs. In your case, you can create a character column and allow editing through the handsontable.
On the topic of persisting data: you table would need either a separate column with comments, or a separate table that maps observations to comment, which is joined. The best solution depends on the volume of comments you expect: if you expect comment to appears sporadically, a separate table may be the best solution. If you expect comments for nearly every row, direct integration into the table may be more favourable. It then becomes a matter of writing to and loading from an SQL database based on user events.

How to use bookmark to document the edited contents in a DT table in Shiny?

I have a Shiny app with a Bookmark button and a DT table that allow the users to edit the contents (https://yuchenw.shinyapps.io/DT_Bookmark/). However, it seems like the Bookmark function cannot document the edited contents in the DT table.
Here is an example. I changed the car name in the first row to "Mazda RX4 aaaaa", and then I clicked "Bookmark button". It can generate an URL. But when I copied and pasted the URL to a new browser, it shows the original state of the app.
Is there a way to make the Bookmark function working? Here is the code.
library(shiny)
library(DT)
ui <- fluidPage(
titlePanel("Bookmark DT Example"),
sidebarLayout(
sidebarPanel(
bookmarkButton()
),
mainPanel(
DTOutput(outputId = "mDT")
)
)
)
server <- function(input, output){
rev <- reactiveValues(dat = mtcars)
output$mDT <- renderDT(
mtcars,
rownames = TRUE,
selection = "none",
editable = TRUE
)
dat_proxy <- dataTableProxy("mDT")
observeEvent(input$mDT_cell_edit, {
rev$dat <- editData(rev$dat, input$mDT_cell_edit, dat_proxy)
})
}
shinyApp(ui, server, enableBookmarking = "url")
The last modification to the datatable is registered in input$mDT_cell_edit.
input$mDT_cell_edit is saved in the bookmarked state, and you can use onRestore to restore it.
However, the full data used in the DT isn't saved : you could use onBookmark to save it too.
As this goes over the 2000 characters allowed by an url, you need to store the bookmark on the server with enableBookmarking = "server".
This is what is done in the code below, to show the way to move forward. It would of course be more efficient to save/restore the list of modifications only.
library(shiny)
library(DT)
server <- function(input, output){
rev <- reactiveValues(dat = mtcars)
output$mDT <- renderDT(
rev$dat,
rownames = TRUE,
selection = "none",
editable = TRUE
)
dat_proxy <- dataTableProxy("mDT")
observeEvent(input$mDT_cell_edit, {
info <- input$mDT_cell_edit
i <- info$row
j <- info$col
if (j>0) {
rev$dat[i, j] <<- DT::coerceValue(info$value, rev$dat[i, j])}
else {
row.names(rev$dat)[i] <- info$value
}
DT::replaceData(dat_proxy, rev$dat, resetPaging = FALSE, rownames = T)
})
onBookmark(function(state) {
state$values$rev_dat <- rev$dat
})
# restore table selection and search
onRestored(function(state) {
if (!identical(rev$dat,state$values$rev_dat)) {
rev$dat <- state$values$rev_dat
DT::replaceData(dat_proxy, state$values$rev_dat, resetPaging = FALSE, rownames = T)
}
})
}
shinyApp(ui, server, enableBookmarking = "server")

DT with Shiny: Multipage editable DataTable jumps to first page after an edit

I have the following program. As the title suggests, every time I edit an item on pages after the first page the table goes back to the first page. I'd like the table to stay on the page I'm editing without jumping back to the first page.
I've seen this problem on other threads here but their solutions don't seem to work with current version of DT and shiny packages.
library(shiny)
library(DT)
shinyApp(
ui = fluidPage(
DTOutput('x1'),
verbatimTextOutput("print")
),
server = function(input, output, session) {
x = reactiveValues(df = NULL)
observe({
df <- iris
df$Date = Sys.time() + seq_len(nrow(df))
x$df <- df
})
output$x1 = renderDT(x$df, selection = 'none', editable = TRUE)
proxy = dataTableProxy('x1')
observeEvent(input$x1_cell_edit, {
info = input$x1_cell_edit
str(info)
i = info$row
j = info$col
v = info$value
x$df[i, j] <- isolate(DT::coerceValue(v, x$df[i, j]))
})
output$print <- renderPrint({
x$df
})
}
)
Any help would be appreciated
You can do like this:
library(shiny)
library(DT)
shinyApp(
ui = fluidPage(
DTOutput('x1'),
verbatimTextOutput("print")
),
server = function(input, output, session) {
dat <- reactiveVal(cbind(iris, Date = Sys.time() + seq_len(nrow(iris))))
output$x1 = renderDT(isolate(dat()), selection = 'none', editable = TRUE)
proxy = dataTableProxy('x1')
observeEvent(input$x1_cell_edit, {
info = input$x1_cell_edit
dat(editData(dat(), info, proxy, resetPaging = FALSE))
})
output$print <- renderPrint({
dat()
})
}
)
Please see DT-edit. I have copied the 2 relevant examples below:
library(shiny)
library(DT)
dt_output = function(title, id) {
fluidRow(column(
12, h1(paste0('Table ', sub('.*?([0-9]+)$', '\\1', id), ': ', title)),
hr(), DTOutput(id)
))
}
render_dt = function(data, editable = 'cell', server = TRUE, ...) {
renderDT(data, selection = 'none', server = server, editable = editable, ...)
}
shinyApp(
ui = fluidPage(
title = 'Double-click to edit table cells',
dt_output('client-side processing (editable = "cell")', 'x1'),
dt_output('server-side processing (editable = "cell")', 'x5')
),
server = function(input, output, session) {
d1 = iris
d1$Date = Sys.time() + seq_len(nrow(d1))
d5 = d1
options(DT.options = list(pageLength = 5))
# client-side processing
output$x1 = render_dt(d1, 'cell', FALSE)
observe(str(input$x1_cell_edit))
# server-side processing
output$x5 = render_dt(d5, 'cell')
# edit a single cell
proxy5 = dataTableProxy('x5')
observeEvent(input$x5_cell_edit, {
info = input$x5_cell_edit
str(info) # check what info looks like (a data frame of 3 columns)
d5 <<- editData(d5, info)
replaceData(proxy5, d5, resetPaging = FALSE) # important
# the above steps can be merged into a single editData() call; see examples below
})
}
)
I am not sure why you seem to be unnecessarily complicating the process with your reactiveValues but that is probably the cause of your table needing to refresh back to the first page.

How to replaceData in DT rendered in R shiny using the datatable function

I have an R shiny app with a DT datatable that is rendered using the datatable function in order to set various options. I would like to use dataTableProxy and replaceData to update the data in the table, but all the examples I can find assume the DT is rendered directly from the data object, not using the datatable function. The reprex below shows what I would like to do, but replaceData doesn't work in this pattern. How do I do this? Thanks.
# based on
# https://community.rstudio.com/t/reorder-data-table-with-seleceted-rows-first/4254
library(shiny)
library(DT)
ui = fluidPage(
actionButton("button1", "Randomize"),
fluidRow(
column(6,
h4("Works"),
DT::dataTableOutput('table1', width="90%")),
column(6,
h4("Doesn't Work"),
DT::dataTableOutput('table2', width="90%"))
)
)
server = function(input, output, session) {
my <- reactiveValues(data = iris)
output$table1 <- DT::renderDataTable(isolate(my$data))
output$table2 <- DT::renderDataTable({
DT::datatable(isolate(my$data),
options = list(lengthChange=FALSE, ordering=FALSE, searching=FALSE,
columnDefs=list(list(className='dt-center', targets="_all")),
stateSave=TRUE, info=FALSE),
class = "nowrap cell-border hover stripe",
rownames = FALSE,
editable = FALSE
) %>%
DT::formatStyle('Sepal.Width', `text-align`="center")
})
observeEvent(input$button1, {
# calculate new row order
row_order <- sample(1:nrow(my$data))
my$data <- my$data[row_order, ]
proxy1 <- DT::dataTableProxy('table1')
DT::replaceData(proxy1, my$data)
proxy2 <- DT::dataTableProxy('table2')
DT::replaceData(proxy2, my$data)
})
}
shinyApp(ui, server)
Update: Very strangely, removing rownames = FALSE made it all possible. I'm not exactly sure why, but probably rownames might be essential for replacing Data.
# based on
# https://community.rstudio.com/t/reorder-data-table-with-seleceted-rows-first/4254
library(shiny)
library(DT)
ui = fluidPage(
actionButton("button1", "Randomize"),
fluidRow(
column(6,
h4("Works"),
DT::dataTableOutput('table1', width="90%")),
column(6,
h4("Doesn't Work"),
DT::dataTableOutput('table2', width="90%"))
)
)
server = function(input, output, session) {
my <- reactiveValues(data = iris)
output$table1 <- DT::renderDataTable(isolate(my$data))
output$table2 <- DT::renderDataTable({
DT::datatable(isolate(my$data),
options = list(lengthChange=FALSE, ordering=FALSE, searching=FALSE,
columnDefs=list(list(className='dt-center', targets="_all")),
stateSave=TRUE, info=FALSE),
class = "nowrap cell-border hover stripe",
# rownames = FALSE,
editable = FALSE
) %>%
DT::formatStyle('Sepal.Width', `text-align`="center")
})
observeEvent(input$button1, {
# calculate new row order
row_order <- sample(1:nrow(my$data))
my$data <- my$data[row_order, ]
proxy1 <- DT::dataTableProxy('table1')
DT::replaceData(proxy1, my$data)
proxy2 <- DT::dataTableProxy('table2')
DT::replaceData(proxy2, my$data)
})
}
shinyApp(ui, server)

Get selected columns in DT table

I am developing a shiny app where user can select multiple columns in a big dataset to create a subset of this dataset. I use the package DT to render the table nicely in the shiny app.
I previously used version 0.2 of DT package where the following code was working :
library("DT")
library("shiny")
ui <- fluidPage(
DT::dataTableOutput('table1'),
DT::dataTableOutput("table2")
)
server <- function(input, output) {
output$table1 <- DT::renderDataTable({
datatable(mtcars, extensions = 'Select', selection = 'none', options = list(ordering = FALSE, searching = FALSE, pageLength = 25, select = list(style = 'os', items = 'column')),
callback = JS(
"table.on( 'click.dt', 'tbody td', function (e) {",
"var type = table.select.items();",
"var idx = table[type + 's']({selected: true}).indexes().toArray();",
"var DT_id = table.table().container().parentNode.id;",
"Shiny.onInputChange(DT_id + '_columns_selected', idx);",
"})"
))
})
output$table2 <- DT::renderDataTable({
subset_table <- mtcars[,input$table1_columns_selected]
datatable(subset_table)
})
}
shinyApp(ui = ui, server = server)
Unfortunately, this code is not working anymore (I am now under version 0.4). The input$table1_columns_selected does not render the indices of the selected columns.
According to this https://rstudio.github.io/DT/shiny.html there is now a functionnality to select multiples rows, but I can't figure out how to do the same with columns.
Any idea ?
Thank you very much for your help !
I am not sure why you need to use the callback argument to do this. Here's a simplified approach -
library("DT")
library("shiny")
ui <- fluidPage(
DT::dataTableOutput('table1'),
DT::dataTableOutput("table2")
)
server <- function(input, output) {
output$table1 <- DT::renderDataTable({
datatable(mtcars, extensions = 'Select', selection = list(target = "column"), options = list(ordering = FALSE, searching = FALSE, pageLength = 25))
})
output$table2 <- DT::renderDataTable({
subset_table <- mtcars[, input$table1_columns_selected, drop = F]
datatable(subset_table)
})
}
shinyApp(ui = ui, server = server)
Note the change in the datatable arguments in output$table1. Hope this is what you were looking for.
I have tested your code and its working fine for me (see picture below) and i am also using DT package version 0.4.
So my guess is that, its not DT package problem but something else in your global configuration that is causing the issue.

Resources