Write data table with text inputs to csv in shiny - r

I have a dataTableOutput that includes a column of textInputs, following the instructions from this question:
Text Input Column in a table in shiny
I modified the code, so that the save button writes the data to a local csv file. However, I'm unable do get the content of the textInput. The column entries contain this kind of information:
<div class="form-group shiny-input-container">
<label class="control-label shiny-label-null" for="cbox_1"></label>
<input id="cbox_1" type="text" class="form-control" value=""/>
</div>
How do I get the actual value of the user input from cbox_1 and the other textInputs? Here is my complete code:
library(shiny)
library(DT)
library(dplyr)
shinyApp(
ui <- fluidPage(DT::dataTableOutput("ruless"),
fluidRow(column(4, offset = 1, actionButton("save", "Save", width = 200),))),
server <- function(input, output) {
values <- reactiveValues(data = NULL)
values$data <- as.data.frame(
cbind(c("a", "d", "b", "c", "e", "f"),
c(1463, 159, 54, 52, 52, 220),
c(0.7315, 0.0795, 0.027, 0.026, 0.026, 0.11)
)
)
shinyInput = function(FUN, len, id, ...) {
#validate(need(character(len)>0,message=paste("")))
inputs = character(len)
for (i in seq_len(len)) {
inputs[i] = as.character(FUN(paste0(id, i), label = NULL, ...))
}
inputs
}
dataExpTable <- reactive({
data.frame(delete=shinyInput(textInput,nrow(values$data),"cbox_"), values$data)
})
output$ruless <- DT::renderDataTable({
datatable(
dataExpTable(),
selection="multiple",
escape = FALSE,
filter = list(position = 'top', clear = FALSE),
extensions = list("ColReorder" = NULL, "Buttons" = NULL),
options = list(
dom = 'BRrltpi',
autoWidth=TRUE,
lengthMenu = list(c(10, 50, -1), c('10', '50', 'All')),
ColReorder = TRUE,
preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } '),
buttons = list(
'copy',
'print',
list(
extend = 'collection',
buttons = c('csv', 'excel', 'pdf'),
text = 'Download',
selected = TRUE
)
)
)
)
})
observeEvent(input$save, {
write.csv2(dataExpTable(), "test.csv", row.names = FALSE)
})
}
)

A colleauge helped me with the solution. The content of the textInputs can be accessed via the Ids that are created by the shinyInput function. It combines a prefix with the row number of the given data frame. A csv output via the save button could look like this:
observeEvent(
eventExpr = input$save,
handlerExpr = {
out_col <- character(nrow(values$data))
for (i in seq_len(nrow(values$data))) {
out_col[i] <- input[[paste0("cbox_", i)]]
}
out_df <- cbind(out_col, values$data)
write.csv2(out_df, "test.csv", row.names = FALSE)
}
)
I tried a similar approach before, but it did not work. I removed these options when creating the datatable:
preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }')
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ')
However, they are mandatory to access the textInputs via their Ids.

Related

Shiny: subset dataTable with checkbox only works for the first time

The following code was modified from the answer to an earlier post R Shiny, how to make datatable react to checkboxes in datatable. It works fine in its original form. Now I added a "subset data" button so that users can remove the unchecked rows from the display. (I also added a "load data" button because later I would like the data file to be uploaded by users.) Curiously it only works for the first time, then it ceases responding. Could someone please help figuring out the problem?
Here is the code:
library(shiny)
library(DT)
shinyApp(
ui = fluidPage(
actionButton("LoadData", label="Load data", class="btn-primary"),
DT::dataTableOutput('x1'),
verbatimTextOutput('x2'),
actionButton("SubsetDT", label="Subset data", class="btn-primary")
),
server = function(input, output, session) {
# create a character vector of shiny inputs
shinyInput = function(FUN, len, id, value, ...) {
if (length(value) == 1) value <- rep(value, len)
inputs = character(len)
for (i in seq_len(len)) {
inputs[i] = as.character(FUN(paste0(id, i), label = NULL, value = value[i]))
}
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)) TRUE else value
}))
}
n = 6
df = data.frame(
cb = shinyInput(checkboxInput, n, 'cb_', value = TRUE, width='1px'),
month = month.abb[1:n],
YN = rep(TRUE, n),
ID = seq_len(n),
stringsAsFactors = FALSE)
loopData = reactive({
df$cb <<- shinyInput(checkboxInput, n, 'cb_', value = shinyValue('cb_', n), width='1px')
df$YN <<- shinyValue('cb_', n)
df
})
observeEvent(input$LoadData, {
output$x1 = DT::renderDataTable(
isolate(loopData()),
escape = FALSE, selection = 'none',
options = list(
dom = 't', paging = FALSE, ordering = FALSE,
preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ')
))
})
observeEvent(input$SubsetDT, {
output$x1 = DT::renderDataTable(
isolate(subset(loopData(),YN==T)),
escape = FALSE, selection = 'none',
options = list(
dom = 't', paging = FALSE, ordering = FALSE,
preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ')
))
})
proxy = dataTableProxy('x1')
observe({
replaceData(proxy, loopData(), resetPaging = FALSE)
})
output$x2 = renderPrint({
data.frame(Like = shinyValue('cb_', n))
})
}
)
Many thanks in advance.

How to disable double click reactivity for specific columns in R Datatable

I am working on a DT in my Shiny app which contains Shiny Input objects and some editable columns.
Here is a reproducible example,
library(shiny)
library(DT)
ui <- fluidPage(
fluidRow(
DTOutput('table1')
)
)
helper_fun <- function(FUN, len, id, ...) {
inputs = character(len)
for (i in seq_len(len)) {
inputs[i] = as.character(FUN(paste0(id, i), selected = 1, ...))
}
inputs
}
server <- function(input, output, session) {
output$table1 <- renderDT({
dat <- data.frame(Name = c('A', 'B')
, column1 = helper_fun(FUN = selectInput, len = length(2), id = 'selector_', label = NULL, choices = c(1, 2), width="100px")
)
dat
}, editable = list(target = 'cell', disable = list(columns = c(2))), server = FALSE, escape = FALSE
, options = list(preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ')))
}
shinyApp(ui, server)
I would like to be able to edit 1 of the columns and disable editing on the columns with shiny Input values. Using editable = list(target = 'cell', disable = list(column = c(2))) does not have the desired effect as this disables editing but exposes the underlying html when the cell is double clicked.
How can I disable this double click effect on certain columns?
Here is a way:
CSS <- "
table tbody td:nth-child(3) {pointer-events: none;}
table tbody td:nth-child(3)>div {pointer-events: auto;}
"
ui <- fluidPage(
tags$head(tags$style(HTML(CSS))),
fluidRow(
DTOutput('table1')
)
)

Register shiny checkboxInput value on DT

I am able to embed checkbox in each cell of a DT column from reading this post.
Imagine I have a dataframe with a logical column named "value" containing some TRUE values, and I want the checkbox in "value_check" column to appear as checked for those that are TRUE upon app start, like shown below: How would I do so?
library(shiny)
library(DT)
df <- data.frame(item = c("a", "b", "c"), value = c(TRUE, NA, NA))
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
}
## obtaining checkbox value
shinyValue = function(id, len) {
unlist(lapply(seq_len(len), function(i) {
value = input[[paste0(id, i)]]
if (is.null(value)) FALSE else value
}))
}
server <- function(input, output, session) {
output$tbl <- renderDT(server = FALSE, escape = FALSE, editable = TRUE, options = list(
dom = 't', paging = FALSE, ordering = FALSE,
preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ')
), {
df$value_check <- shinyInput(checkboxInput, nrow(df), "check")
df
}
)
}
ui <- fluidPage(
DTOutput("tbl")
)
shinyApp(ui, server)
I thought I had a solution but now I'm afraid I misunderstood your question. With the code below, the values in the value column are updated according to the status of the corresponding checkbox in the value_check column.
js <- c(
"$('[id^=check]').on('click', function(){",
" var id = this.getAttribute('id');",
" var i = parseInt(/check(\\d+)/.exec(id)[1]);",
" var value = $(this).prop('checked');",
" var cell = table.cell(i-1, 2).data(value).draw();",
"})"
)
then
output$tbl <- renderDT(server = FALSE, escape = FALSE, editable = TRUE,
callback = JS(js),
options = list(
dom = 't', paging = FALSE, ordering = FALSE,
preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ')
), {
df$value_check <- shinyInput(checkboxInput, nrow(df), "check")
df
}
)
But now I understand that you ask for the "converse" : check the checkbox according to the value in the value column. Right ? However don't you need the above as well ?
Then, to answer your question as I understand it now, I would do:
shinyCheckboxes <- function(len, id, checked){
inputs <- character(len)
for (i in seq_len(len)) {
inputs[i] <- as.character(checkboxInput(paste0(id, i), label=NULL,
value = checked[i]))
}
inputs
}
then
checked <- sapply(df$value, isTRUE)
df$value_check <- shinyCheckboxes(nrow(df), "check", checked)
Is it what you want ?

removing data from DT in app and buttons in DT, shiny R

I have DT where I added checkboxes as a first column. Their idea is to give the user the option of removing selected rows and placing them in a file. After pressing the button cold "delete", the data is sent to the file, but I do not know how to update DT, to get data without those deleted rows.
Code:
library(shiny)
library(DT)
library(dplyr)
shinyApp(
ui <- fluidPage(DT::dataTableOutput("ruless"),
fluidRow(column(4, offset = 1, actionButton("delete", "Delete", width = 200)))),
server <- function(input, output) {
values <- reactiveValues(data = NULL)
values$data <- as.data.frame(
cbind(c("a", "d", "b", "c", "e", "f"),
c(1463, 159, 54, 52, 52, 220),
c(0.7315, 0.0795, 0.027, 0.026, 0.026, 0.11)
)
)
shinyInput = function(FUN, len, id, ...) {
#validate(need(character(len)>0,message=paste("")))
inputs = character(len)
for (i in seq_len(len)) {
inputs[i] = as.character(FUN(paste0(id, i), label = NULL, ...))
}
inputs
}
output$ruless <- DT::renderDataTable({
datatable(
data.frame(delete=shinyInput(checkboxInput,nrow(values$data),"cbox_"), values$data),
selection="multiple",
escape = FALSE,
filter = list(position = 'top', clear = FALSE),
extensions = list("ColReorder" = NULL, "Buttons" = NULL),
options = list(
dom = 'BRrltpi',
autoWidth=TRUE,
lengthMenu = list(c(10, 50, -1), c('10', '50', 'All')),
ColReorder = TRUE,
preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } '),
buttons = list(
'copy',
'print',
list(
extend = 'collection',
buttons = c('csv', 'excel', 'pdf'),
text = 'Download',
selected = TRUE
)
)
)
)
})
shinyValue = function(id, len) {
unlist(lapply(seq_len(len), function(i) {
value = input[[paste0(id, i)]]
if (is.null(value)) NA else value
}))
}
observeEvent(input$delete, {
checkbox_rules <- data.frame(selected=shinyValue("cbox_",nrow(values$data)))
marked_rules <- as.data.frame(values$data[(which(checkbox_rules == TRUE)),])
if (file.exists("delete_file.csv")){
delete_file <- as.data.frame(read_csv2("delete_file.csv", col_names = TRUE))
delete_file <- as.data.frame(rbind(delete_file, marked_rules))
delete_file <- delete_file[!duplicated(delete_file), ]
write.csv2(delete_file, file = "delete_file.csv", sep=";", row.names = FALSE)
}
else{
write.csv2(marked_rules, file = "delete_file.csv", sep=";", row.names = FALSE)
}
})
}
)
I would like also to replace delete button and rename rest of buttons above DT. I am thinking about something like that:
I it possible to do? Thank in advance!
Is it possible?
You can update the data.frame in the observer like this
values$data <- values$data[!checkbox_rules,]
At the end it should look something like this
observeEvent(input$delete, {
checkbox_rules <- data.frame(selected=shinyValue("cbox_",nrow(values$data)))
marked_rules <- as.data.frame(values$data[(which(checkbox_rules == TRUE)),])
values$data <- values$data[!checkbox_rules,]
if (file.exists("delete_file.csv")){
delete_file <- as.data.frame(read_csv2("delete_file.csv", col_names = TRUE))
delete_file <- as.data.frame(rbind(delete_file, marked_rules))
delete_file <- delete_file[!duplicated(delete_file), ]
write.csv2(delete_file, file = "delete_file.csv", sep=";", row.names = FALSE)
}
else{
write.csv2(marked_rules, file = "delete_file.csv", sep=";", row.names = FALSE)
}
})
hope this helps!

Text Input Column in a table in shiny

I have an output table being displayed in shiny. I would like to have a column attached to the table which would take in text input (comments) from the user for each row.
Can this be done in R shiny?
Yes you can do some thing like this
library(shiny)
library(DT)
library(dplyr)
shinyApp(
ui <- fluidPage(DT::dataTableOutput("ruless"),
fluidRow(column(4, offset = 1, actionButton("save", "Save", width = 200)))),
server <- function(input, output) {
values <- reactiveValues(data = NULL)
values$data <- as.data.frame(
cbind(c("a", "d", "b", "c", "e", "f"),
c(1463, 159, 54, 52, 52, 220),
c(0.7315, 0.0795, 0.027, 0.026, 0.026, 0.11)
)
)
shinyInput = function(FUN, len, id, ...) {
#validate(need(character(len)>0,message=paste("")))
inputs = character(len)
for (i in seq_len(len)) {
inputs[i] = as.character(FUN(paste0(id, i), label = NULL, ...))
}
inputs
}
output$ruless <- DT::renderDataTable({
datatable(
data.frame(delete=shinyInput(textInput,nrow(values$data),"cbox_"), values$data),
selection="multiple",
escape = FALSE,
filter = list(position = 'top', clear = FALSE),
extensions = list("ColReorder" = NULL, "Buttons" = NULL),
options = list(
dom = 'BRrltpi',
autoWidth=TRUE,
lengthMenu = list(c(10, 50, -1), c('10', '50', 'All')),
ColReorder = TRUE,
preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } '),
buttons = list(
'copy',
'print',
list(
extend = 'collection',
buttons = c('csv', 'excel', 'pdf'),
text = 'Download',
selected = TRUE
)
)
)
)
})
}
)

Resources