R Shiny DT - edit values in table with reactive - prevent paging reset - r

I have created an application using reactiveValues to populate the datatable similar to the response here R Shiny DT - edit values in table with reactive
What I cannot seem to figure out is after editing the table it resets to the first page. This does not happen when the datatable is not created from a reactiveValues table.
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]))
replaceData(proxy, x(), resetPaging = FALSE) # important I have tried with and without this line no impact on page resetting
})
output$print <- renderPrint({
x$df
})
}
)
I wanted to share that I figured out how to solve this, still a little confused as to why it was so complicated but if anyone else has the same issue.
x = reactiveValues(df = NULL)
x$df2 = NULL
#Tells which data to pull based on how the OSC list is populated
FundData <- observe({
if(input$SelectFunds == 'None Selected'){
x$df <- head(QDataAnalystSummary,0)
}else if(input$SelectFunds == 'Load OSC List'){
tmp_funds <- subset(QDataAnalystSummary,as.character(QDataAnalystSummary$OSC) %in% as.character(OSCData()$OSC))
tmp_funds <- subset(tmp_funds,tmp_funds$`Quarter End` == CurrentQtr)
tmp_funds_new <- NULL
for(i in 1:nrow(OSCData())){
tmp_fd <- subset(tmp_funds,tmp_funds$OSC == OSCData()$OSC[i])
tmp_funds_new <- rbind(tmp_funds_new,tmp_fd)
}
tmp_funds_new
x$df <- tmp_funds_new
x$df2 <- tmp_funds_new
}
else if(input$SelectFunds == 'Search By Analyst'){
tmp_funds5 <- subset(QDataAnalystSummary,QDataAnalystSummary$Analyst %in% input$Analyst)
tmp_funds5 <- subset(tmp_funds5,tmp_funds5$`Quarter End` == CurrentQtr)
x$df <- tmp_funds5
})
#Summary Table showing general fund info and current quarter stats for IC Meeting Funds
output$summary_ic <- DT::renderDT({
QDAW <- subset(QDataAnalystSummary,QDataAnalystSummary$OSC %in% OSCData()$OSC & QDataAnalystSummary$`Quarter End` ==CurrentQtr)
if(nrow(QDAW) >= 1){
QDAW$Notes <- ''
QDAW_new <- NULL
for(i in 1:nrow(OSCData())){
tmp_fd <- subset(QDAW,QDAW$OSC == OSCData()$OSC[i])
QDAW_new <- rbind(QDAW_new,tmp_fd)
}
QDAW <- QDAW_new
}
datatable(QDAW,
rownames = FALSE,caption = "Current Quarter Data",
filter = "top",extensions = c('Buttons','ColReorder'),editable = list(target = 'cell',disable = list(columns=c(0:9))),
options = list(initComplete = JS(
"function(settings, json) {",
"$(this.api().table().header()).css({'background-color':'rgb(85,114,138)','color': '#fff'});","}"),
autoWidth = TRUE,
columnDefs = list(list(width = '2%', targets = list(0,1,6,7,8,9)),
# list(width = '20%', targets = list()),
list(className = 'dt-center',targets = c(1,5,6,7,8,9)),
list(visible = FALSE, targets = c(0,5,8,9))
)
,resetPaging = FALSE,
bFilter = 1,bSortClasses = 1,
aLengthMenu = list(c(10,20,50, -1), list('10','20','50','All')),iDisplayLength = 10,
searchHighlight = TRUE,
dom = 'Blfrtip',buttons = c('colvis','excel','copy'),colReorder= TRUE,
scrollX = TRUE,
# scrollY = "1000px",
fixedHeader = TRUE,
paging=TRUE,server = TRUE))%>%
formatRound('Trailing Average', 2) %>%
formatStyle('Trailing Average',
color = 'white',
background = styleInterval(c(50,75),c('green','orange','red'))) %>%
formatCurrency('AUM')
})
proxy = dataTableProxy('summary_ic')
observeEvent(input$summary_ic_cell_edit, {
info = input$summary_ic_cell_edit
str(info)
i = info$row
j = info$col + 1 # column index offset by 1
v = info$value
x$df2[i, j] <<- isolate(DT::coerceValue(v, x$df2[i, j]))
replaceData(proxy, x$df2, resetPaging = FALSE, rownames = FALSE)
})

Related

How to automate datatableProxy and table cells editing to a list of dataframes within Rshiny?

In this reproducible example the user can edit the values of each tables before saving them into a csv file.
library(shiny)
library(DT)
library(data.table)
### A. Creating dummies dataframes
dummy_df1 = iris[45:55,]
dummy_df1$soil_type = c("A", "A", "C", "A", "D", "B", "A", "C", "D", "B")
dummy_df2 = iris[1:5,]
dummy_df3 = iris[1:3,2:5]
tables_list = list(dummy_df1, dummy_df2, dummy_df3)
pimped_tables =list()
### B. Pimping dataframes
for (i in 1:length(tables_list)) {
#1. transposing table
pimped_tables[[i]] <- data.table::transpose(tables_list[[i]])
#2. colnames and rownames formatting
rownames(pimped_tables[[i]]) <- colnames(tables_list[[i]])
colnames(pimped_tables[[i]]) <- unlist(pimped_tables[[i]][row.names(pimped_tables[[i]])=='Species',])
pimped_tables[[i]]<- pimped_tables[[i]][!row.names(pimped_tables[[i]])=='Species',]
}
### C. UI
ui = fluidPage(fluidRow(
column(width = 1),
column(width =10,
DT::dataTableOutput("df1"),
actionButton("saveBtn1","save changes"),fluidRow(br()),
DT::dataTableOutput("df2"),
actionButton("saveBtn2","save changes"),fluidRow(br()),
DT::dataTableOutput("df3"),
actionButton("saveBtn3","save changes")),
column(width=1)))
#D. Server part
server = function(input, output, session){
x = reactiveValues(df = NULL)
observe({
df <- pimped_tables[[1]]
x$df <- df
})
output$df1 = DT::renderDataTable({
datatable(
caption = HTML('<h3 style="color:#4dab44"><b> Dummy df1 </h3'),
x$df,
rownames = TRUE,
options = list(
scrollX = TRUE,
columnDefs = list(list(className = 'dt-center', targets = "_all")),
selection = 'none'),
selection=list(mode="single", target="cell"),
editable = list(target = 'cell', disable = list(columns = 0)))
})
proxy_df1 = dataTableProxy('df1')
observeEvent(input$df1_cell_edit,{
info = input$df1_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]))
})
observeEvent(input$saveBtn1,{
df1_initial_format = data.table::transpose(x$df)
colnames(df1_initial_format) = rownames(x$df)
df1_initial_format$Species = colnames(x$df)
print(df1_initial_format)
write.csv(df1_initial_format, "dummy_df1.csv")
})
x2 = reactiveValues(df = NULL)
observe({
df <- pimped_tables[[2]]
x2$df <- df
})
output$df2 = DT::renderDataTable({
datatable(
caption = HTML('<h3 style="color:#4dab44"><b> Dummy df2 </h3'),
x2$df,
rownames = TRUE,
options = list(
scrollX = TRUE,
columnDefs = list(list(className = 'dt-center', targets = "_all")),
selection = 'none'),
selection=list(mode="single", target="cell"),
editable = list(target = 'cell', disable = list(columns = 0)))
})
proxy_df2 = dataTableProxy('df2')
observeEvent(input$df2_cell_edit,{
info = input$df2_cell_edit
str(info)
i = info$row
j = info$col
v = info$value
x2$df[i, j] <- isolate(DT::coerceValue(v, x2$df[i, j]))
})
observeEvent(input$saveBtn2,{
df2_initial_format = data.table::transpose(x2$df)
colnames(df2_initial_format) = rownames(x2$df)
df2_initial_format$Species = colnames(x2$df)
print(df2_initial_format)
write.csv(df2_initial_format, "dummy_df2.csv")
})
x3 = reactiveValues(df = NULL)
observe({
df <- pimped_tables[[3]]
x3$df <- df
})
output$df3 = DT::renderDataTable({
datatable(
caption = HTML('<h3 style="color:#4dab44"><b> Dummy df3 </h3'),
x3$df,
rownames = TRUE,
options = list(
scrollX = TRUE,
columnDefs = list(list(className = 'dt-center', targets = "_all")),
selection = 'none'),
selection=list(mode="single", target="cell"),
editable = list(target = 'cell', disable = list(columns = 0)))
})
proxy_df3 = dataTableProxy('df3')
observeEvent(input$df3_cell_edit,{
info = input$df3_cell_edit
str(info)
i = info$row
j = info$col
v = info$value
x3$df[i, j] <- isolate(DT::coerceValue(v, x3$df[i, j]))
})
observeEvent(input$saveBtn3,{
df3_initial_format = data.table::transpose(x3$df)
colnames(df3_initial_format) = rownames(x3$df)
df3_initial_format$Species = colnames(x3$df)
print(df3_initial_format)
write.csv(df3_initial_format, "dummy_df3.csv")
})
}
shinyApp(ui=ui, server=server)
Since I would like to reproduce this behaviour for 37 tables in my actual ShinyApp I was wondering if there was a way to not create a new dataTableProxy and a new observeEvent based on the table_cell_edit for every table ? Is it possible to apply a function to my list of dataframes that would do the same job ?
You could use Shiny modules:
Create a module UI to display one table : tableUI
Create a module Server to hande actions on this table : tableServer
In the application Server :
Generate the application UI with renderUI calling tableUI in a loop for each table
Call tableServer in a loop for each table
library(shiny)
library(DT)
library(data.table)
### A. Creating dummies dataframes
dummy_df1 = iris[45:55,]
dummy_df1$soil_type = c("A", "A", "C", "A", "D", "B", "A", "C", "D", "B","B")
dummy_df2 = iris[1:5,]
dummy_df3 = iris[1:3,2:5]
tables_list = list(dummy_df1, dummy_df2, dummy_df3)
pimped_tables =list()
### B. Pimping dataframes
for (i in 1:length(tables_list)) {
#1. transposing table
pimped_tables[[i]] <- data.table::transpose(tables_list[[i]])
#2. colnames and rownames formatting
rownames(pimped_tables[[i]]) <- colnames(tables_list[[i]])
colnames(pimped_tables[[i]]) <- unlist(pimped_tables[[i]][row.names(pimped_tables[[i]])=='Species',])
pimped_tables[[i]]<- pimped_tables[[i]][!row.names(pimped_tables[[i]])=='Species',]
}
### C. module UI
tableUI <- function(id) {
ns <- NS(id)
tagList(
DT::dataTableOutput(ns('table')),
actionButton(ns("saveBtn"),"save changes"),
fluidRow(br())
)
}
### D. App UI
ui = fluidPage(fluidRow(
column(width = 1),
column(width =10,
uiOutput("tables"),
column(width=1))))
### E. module Server
tableServer <- function(id,data) {
moduleServer(
id,
function(input, output, session) {
x = reactiveValues(df = data)
output$table = DT::renderDataTable({
datatable(
caption = HTML(paste0('<h3 style="color:#4dab44"><b> Dummy ',id,' </h3')),
x$df,
rownames = TRUE,
options = list(
scrollX = TRUE,
columnDefs = list(list(className = 'dt-center', targets = "_all")),
selection = 'none'),
selection=list(mode="single", target="cell"),
editable = list(target = 'cell', disable = list(columns = 0)))
})
proxy_table = dataTableProxy('table')
observeEvent(input$table_cell_edit,{
info = input$table_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]))
})
observeEvent(input$saveBtn,{
df_initial_format = data.table::transpose(x$df)
colnames(df_initial_format) = rownames(x$df)
df_initial_format$Species = colnames(x$df)
print(df_initial_format)
write.csv(df_initial_format, paste0("dummy_",id,".csv"))
})
})
}
### F. App Server
server = function(input, output, session){
output$tables <- renderUI({
lapply(1:length(pimped_tables),function(i) tagList(tableUI(paste0("df",i)),fluidRow(br())))
})
for (i in 1:length(pimped_tables)) { tableServer(id=paste0("df",i),data=pimped_tables[[i]])}
}
shinyApp(ui=ui, server=server)

Capture filtered results from a datatable and store it as a new dataset in Shiny

I have a shiny application that loads a couple of datasets (diamonds & mtcars) and displays them as datatable in the mainpanel. I am trying to implement a couple of functionality
1. Store datasets: Once the user create filters in the datatable, allow them to store the filtered results as a new dataset.
2. Remove datasets: Allow the users to remove any datasets from the list of created datasets
app.R
library(shiny)
library(shinyWidgets)
library(dplyr)
library(tidyverse)
library(shinyjs)
ui <- fluidPage(
titlePanel("Dataset Tool"),
sidebarLayout(
sidebarPanel(width = 3,
conditionalPanel(
condition = "input.tabs=='Datasets'",
uiOutput("ui_datasets"),
uiOutput("ui_storedataset"),
br(), br(),
wellPanel(
checkboxInput("data_remove", "Remove dataset from memory",
FALSE),
conditionalPanel(
condition = "input.data_remove == true",
uiOutput("ui_removedataset"),
actionButton("removeDataSetButton",
"Remove dataset")
)
)
)
),
mainPanel(
tabsetPanel(id = "tabs",
tabPanel("Datasets",
DT::dataTableOutput("datatable")
)
)
)
)
)
server = function(input, output,session) {
my_data <- new.env()
my_state <- list()
my_info <- reactiveValues()
datasetlist <- c()
my_df <- list()
df <- list()
df_names <- c("diamonds", "mtcars")
for (j in df_names) {
df[[j]] <- get(j)
datasetlist <- c(datasetlist, j)
}
my_info[["datasetlist"]] <- datasetlist
my_df[["df"]] <- df
output$ui_datasets <- renderUI({
tagList(
selectInput(
inputId = "dataset",
label = "Datasets:",
choices = my_info[["datasetlist"]],
multiple = FALSE
)
)
})
output$ui_storedataset <- renderUI({
tagList(
wellPanel(
tags$table(
tags$td(textInput("stored_name",
"Store new dataset as:",
"",
placeholder = "name of the dataset")),
tags$td(actionButton("view_store",
"Store"),
style = "padding-right:30px;")
)
)
)
})
observeEvent(input$datatable_search_columns, {
my_state$datatable_search_columns <<- input$datatable_search_columns
})
observeEvent(input$datatable_state, {
my_state$datatable_state <<-
if (is.null(input$datatable_state)) list() else input$datatable_state
})
output$datatable <- DT::renderDataTable({
dat <- df[[(input$dataset)]]
search <- my_state$datatable_state$search$search
if (is.null(search)) search <- ""
fbox <- if (nrow(dat) > 5e6) "none" else list(position = "top")
DT::datatable(
dat,
filter = fbox,
selection = "none",
rownames = FALSE,
fillContainer = FALSE,
escape = FALSE,
style = "bootstrap",
options = list(
stateSave = TRUE,
searchCols = lapply(my_state$datatable_search_columns, function(x) list(search = x)),
search = list(search = search, regex = TRUE),
order = {
if (is.null(my_state$datatable_state$order)) {
list()
} else {
my_state$datatable_state$order
}
},
columnDefs = list(
list(orderSequence = c("desc", "asc"), targets = "_all"),
list(className = "dt-center", targets = "_all")
),
autoWidth = TRUE,
processing = isTRUE(fbox == "none"),
pageLength = {
if (is.null(my_state$datatable_state$length)) 10 else my_state$datatable_state$length
},
lengthMenu = list(c(5, 10, 25, 50, -1), c("5", "10", "25", "50", "All"))
),
callback = DT::JS('$(window).on("unload", function() { table.state.clear(); })')
)
})
observeEvent(input$view_store, {
req(input$stored_name)
dataset <- (input$stored_name)
if (input$stored_name != dataset) {
updateTextInput(session, inputId = "stored_name", value = dataset)
}
my_data[[dataset]] <- get(input$dataset)
updateSelectInput(session = session, inputId = "dataset",
selected = input$dataset)
})
output$ui_removedataset <- renderUI({
selectInput(
inputId = "removeDataset",
label = NULL,
choices = my_info[["datasetlist"]],
selected = NULL,
multiple = TRUE,
size = length(my_info[["datasetlist"]]),
selectize = FALSE
)
})
observeEvent(input$removeDataSetButton, {
if (is.null(input$removeDataset)) return()
datasets <- my_info[["datasetlist"]]
if (length(datasets) > 1) {
removeDataset <- input$removeDataset
if (length(datasets) == length(removeDataset)) {
removeDataset <- removeDataset[-1]
}
suppressWarnings(rm(list = removeDataset, envir = my_data))
my_info[["datasetlist"]] <- datasets[-which(datasets %in% removeDataset)]
}
})
}
shinyApp(ui = ui, server = server)
I think the "removedatasets" functionality works fine. I am not able to get the "store dataset" functionality to work. I am not sure how to capture the filtered datatable from the environment to store and add it to the list of datasets.
I would really appreciate any help on this. Thanks.
Here is a solution that recreates the filtering on the original data based on the filter inputs stored in my_state$datatable_search_columns. The strings are turned into the correct filter conditions which are then applied to the data set before saving. Note that I haven't tested it with a condition in the global search bar:
library(shiny)
library(shinyWidgets)
library(dplyr)
library(tidyverse)
library(shinyjs)
ui <- fluidPage(
titlePanel("Dataset Tool"),
sidebarLayout(
sidebarPanel(width = 3,
conditionalPanel(
condition = "input.tabs=='Datasets'",
uiOutput("ui_datasets"),
uiOutput("ui_storedataset"),
br(), br(),
wellPanel(
checkboxInput("data_remove", "Remove dataset from memory",
FALSE),
conditionalPanel(
condition = "input.data_remove == true",
uiOutput("ui_removedataset"),
actionButton("removeDataSetButton",
"Remove dataset")
)
)
)
),
mainPanel(
tabsetPanel(id = "tabs",
tabPanel("Datasets",
DT::dataTableOutput("datatable")
)
)
)
)
)
server = function(input, output,session) {
my_data <- new.env()
my_state <- list()
my_info <- reactiveValues()
datasetlist <- c()
my_df <- list()
df <- list()
df_names <- c("diamonds", "mtcars")
for (j in df_names) {
df[[j]] <- get(j)
datasetlist <- c(datasetlist, j)
}
my_info[["datasetlist"]] <- datasetlist
my_df[["df"]] <- df
output$ui_datasets <- renderUI({
tagList(
selectInput(
inputId = "dataset",
label = "Datasets:",
choices = my_info[["datasetlist"]],
multiple = FALSE
)
)
})
output$ui_storedataset <- renderUI({
tagList(
wellPanel(
tags$table(
tags$td(textInput("stored_name",
"Store new dataset as:",
"",
placeholder = "name of the dataset")),
tags$td(actionButton("view_store",
"Store"),
style = "padding-right:30px;")
)
)
)
})
observeEvent(input$datatable_search_columns, {
my_state$datatable_search_columns <<- input$datatable_search_columns
})
observeEvent(input$datatable_state, {
my_state$datatable_state <<-
if (is.null(input$datatable_state)) list() else input$datatable_state
})
output$datatable <- DT::renderDataTable({
dat <- df[[(input$dataset)]]
search <- my_state$datatable_state$search$search
if (is.null(search)) search <- ""
fbox <- if (nrow(dat) > 5e6) "none" else list(position = "top")
DT::datatable(
dat,
filter = fbox,
selection = "none",
rownames = FALSE,
fillContainer = FALSE,
escape = FALSE,
style = "bootstrap",
options = list(
stateSave = TRUE,
searchCols = lapply(my_state$datatable_search_columns, function(x) list(search = x)),
search = list(search = search, regex = TRUE),
order = {
if (is.null(my_state$datatable_state$order)) {
list()
} else {
my_state$datatable_state$order
}
},
columnDefs = list(
list(orderSequence = c("desc", "asc"), targets = "_all"),
list(className = "dt-center", targets = "_all")
),
autoWidth = TRUE,
processing = isTRUE(fbox == "none"),
pageLength = {
if (is.null(my_state$datatable_state$length)) 10 else my_state$datatable_state$length
},
lengthMenu = list(c(5, 10, 25, 50, -1), c("5", "10", "25", "50", "All"))
),
callback = DT::JS('$(window).on("unload", function() { table.state.clear(); })')
)
})
observeEvent(input$view_store, {
req(input$stored_name)
dataset <- (input$stored_name)
if (input$stored_name != dataset) {
updateTextInput(session, inputId = "stored_name", value = dataset)
}
# get filter conditions
filter_conditions <- lapply(my_state$datatable_search_columns, function(column) {
# check if it is a numerical filter and extract the values
if (str_detect(column, "\\.\\.\\.")) {
vals <- strsplit(column, " ")
c(as.numeric(vals[[1]][1]), as.numeric(vals[[1]][3])) # min/max values
} else {
if (column == "") {
NA
} else {
vals <- strsplit(column, "\"")
index <- seq(from = 2, to = length(vals[[1]]), by = 2)
as.character(vals[[1]][index])
}
}
})
# do the filtering
temp <- get(input$dataset)
temp <- as.data.frame(temp)
for (i in seq_along(filter_conditions)) {
current_vals <- filter_conditions[[i]]
if (all(is.numeric(current_vals))) {
# it's a numeric column
temp <- temp[temp[, i] >= current_vals[1] & temp[, i] <= current_vals[2], ]
}
if (all(is.character(current_vals))) {
# it's a character column
temp[, i] <- as.character(temp[, i])
temp <- temp[temp[, i] %in% current_vals, ]
}
}
my_data[[dataset]] <- temp
updateSelectInput(session = session, inputId = "dataset",
selected = input$dataset)
})
output$ui_removedataset <- renderUI({
selectInput(
inputId = "removeDataset",
label = NULL,
choices = my_info[["datasetlist"]],
selected = NULL,
multiple = TRUE,
size = length(my_info[["datasetlist"]]),
selectize = FALSE
)
})
observeEvent(input$removeDataSetButton, {
if (is.null(input$removeDataset)) return()
datasets <- my_info[["datasetlist"]]
if (length(datasets) > 1) {
removeDataset <- input$removeDataset
if (length(datasets) == length(removeDataset)) {
removeDataset <- removeDataset[-1]
}
suppressWarnings(rm(list = removeDataset, envir = my_data))
my_info[["datasetlist"]] <- datasets[-which(datasets %in% removeDataset)]
}
})
}
shinyApp(ui = ui, server = server)
Edit
Here is a version where you can select the changed dataset after storing it:
library(shiny)
library(shinyWidgets)
library(dplyr)
library(tidyverse)
library(shinyjs)
ui <- fluidPage(
titlePanel("Dataset Tool"),
sidebarLayout(
sidebarPanel(width = 3,
conditionalPanel(
condition = "input.tabs=='Datasets'",
uiOutput("ui_datasets"),
uiOutput("ui_storedataset"),
br(), br(),
wellPanel(
checkboxInput("data_remove", "Remove dataset from memory",
FALSE),
conditionalPanel(
condition = "input.data_remove == true",
uiOutput("ui_removedataset"),
actionButton("removeDataSetButton",
"Remove dataset")
)
)
)
),
mainPanel(
tabsetPanel(id = "tabs",
tabPanel("Datasets",
DT::dataTableOutput("datatable")
)
)
)
)
)
server = function(input, output,session) {
my_data <- new.env()
my_state <- list()
my_info <- reactiveValues()
datasetlist <- c()
my_df <- list()
df <- list()
df_names <- c("diamonds", "mtcars")
for (j in df_names) {
df[[j]] <- get(j)
datasetlist <- c(datasetlist, j)
}
my_info[["datasetlist"]] <- datasetlist
my_df[["df"]] <- df
output$ui_datasets <- renderUI({
tagList(
selectInput(
inputId = "dataset",
label = "Datasets:",
choices = my_info[["datasetlist"]],
multiple = FALSE
)
)
})
output$ui_storedataset <- renderUI({
tagList(
wellPanel(
tags$table(
tags$td(textInput("stored_name",
"Store new dataset as:",
"",
placeholder = "name of the dataset")),
tags$td(actionButton("view_store",
"Store"),
style = "padding-right:30px;")
)
)
)
})
observeEvent(input$datatable_search_columns, {
my_state$datatable_search_columns <<- input$datatable_search_columns
})
observeEvent(input$datatable_state, {
my_state$datatable_state <<-
if (is.null(input$datatable_state)) list() else input$datatable_state
})
output$datatable <- DT::renderDataTable({
dat <- df[[(input$dataset)]]
search <- my_state$datatable_state$search$search
if (is.null(search)) search <- ""
fbox <- if (nrow(dat) > 5e6) "none" else list(position = "top")
DT::datatable(
dat,
filter = fbox,
selection = "none",
rownames = FALSE,
fillContainer = FALSE,
escape = FALSE,
style = "bootstrap",
options = list(
stateSave = TRUE,
searchCols = lapply(my_state$datatable_search_columns, function(x) list(search = x)),
search = list(search = search, regex = TRUE),
order = {
if (is.null(my_state$datatable_state$order)) {
list()
} else {
my_state$datatable_state$order
}
},
columnDefs = list(
list(orderSequence = c("desc", "asc"), targets = "_all"),
list(className = "dt-center", targets = "_all")
),
autoWidth = TRUE,
processing = isTRUE(fbox == "none"),
pageLength = {
if (is.null(my_state$datatable_state$length)) 10 else my_state$datatable_state$length
},
lengthMenu = list(c(5, 10, 25, 50, -1), c("5", "10", "25", "50", "All"))
),
callback = DT::JS('$(window).on("unload", function() { table.state.clear(); })')
)
})
observeEvent(input$view_store, {
req(input$stored_name)
dataset <- (input$stored_name)
if (input$stored_name != dataset) {
updateTextInput(session, inputId = "stored_name", value = dataset)
}
# get filter conditions
filter_conditions <- lapply(my_state$datatable_search_columns, function(column) {
# check if it is a numerical filter and extract the values
if (str_detect(column, "\\.\\.\\.")) {
vals <- strsplit(column, " ")
c(as.numeric(vals[[1]][1]), as.numeric(vals[[1]][3])) # min/max values
} else {
if (column == "") {
NA
} else {
vals <- strsplit(column, "\"")
index <- seq(from = 2, to = length(vals[[1]]), by = 2)
as.character(vals[[1]][index])
}
}
})
# do the filtering
temp <- get(input$dataset)
temp <- as.data.frame(temp)
for (i in seq_along(filter_conditions)) {
current_vals <- filter_conditions[[i]]
if (all(is.numeric(current_vals))) {
# it's a numeric column
temp <- temp[temp[, i] >= current_vals[1] & temp[, i] <= current_vals[2], ]
}
if (all(is.character(current_vals))) {
# it's a character column
temp[, i] <- as.character(temp[, i])
temp <- temp[temp[, i] %in% current_vals, ]
}
}
df[[dataset]] <<- temp
my_info[["datasetlist"]] <- c(my_info[["datasetlist"]], input$stored_name)
updateSelectInput(session = session, inputId = "dataset",
selected = input$dataset)
})
output$ui_removedataset <- renderUI({
selectInput(
inputId = "removeDataset",
label = NULL,
choices = my_info[["datasetlist"]],
selected = NULL,
multiple = TRUE,
size = length(my_info[["datasetlist"]]),
selectize = FALSE
)
})
observeEvent(input$removeDataSetButton, {
if (is.null(input$removeDataset)) return()
datasets <- my_info[["datasetlist"]]
if (length(datasets) > 1) {
removeDataset <- input$removeDataset
if (length(datasets) == length(removeDataset)) {
removeDataset <- removeDataset[-1]
}
suppressWarnings(rm(list = removeDataset, envir = my_data))
my_info[["datasetlist"]] <- datasets[-which(datasets %in% removeDataset)]
}
})
}
shinyApp(ui = ui, server = server)
A few issues with your code I've noticed:
I recommend not to use get, this makes it less clear and debuggable where the data comes from; I'd work directly with the lists/reactives where your data is stored to retrieve it
there is something going on with the filters set in the table; they stay even when you switch datasets, I think you have to put some work into that
you have a lot of similar lists (like my_df and df) (and I think you don't use both), which makes your code harder to understand
try to use more observeEvent/updateXXInput as it's a bit faster than doing all the renderUI on the server side

R shiny editable table with reactive filters - update filters with table edits

edit: Here is the solution to the original problem. I found it after scouring stack and the other part, persistent filters was found on a blog. May anyone who finds this never have to suffer like I have.
source_data <-
iris %>%
mutate(Species = as.factor(Species))
source_data$Date <- Sys.time() + seq_len(nrow(source_data))
# default global search value
if (!exists("default_search")) default_search <- ""
# default column search values
if (!exists("default_search_columns")) default_search_columns <- NULL
shinyApp(
ui = fluidPage(
DT::dataTableOutput('dataTable')
),
server = function(input, output, session) {
reactive_values <- reactiveValues(source_data = NULL)
observe({
reactive_values$source_data <- source_data
})
output$dataTable <- DT::renderDataTable(
reactive_values$source_data,
editable = list(target = "cell", disable = list(columns = c(1, 2))),
filter = "top",
selection = 'none',
options = list(
scrollX = TRUE,
stateSave = FALSE,
searchCols = default_search_columns,
search = list(
regex = FALSE,
caseInsensitive = FALSE,
search = default_search
)
)
)
proxy <- dataTableProxy('dataTable')
observe({
input$dataTable_cell_edit
# when it updates, save the search strings so they're not lost
isolate({
# update global search and column search strings
default_search <- input$dataTable_search
default_search_columns <- c("", input$dataTable_search_columns)
# update the search terms on the proxy table (see below)
proxy %>%
updateSearch(keywords =
list(global = default_search,
columns = default_search_columns))
})
})
observeEvent(input$dataTable_cell_edit, {
info = input$dataTable_cell_edit
str(info)
i <- info$row
j <- info$col
v <- info$value
reactive_values$source_data[i, j] <<- DT:::coerceValue(v, reactive_values$source_data[i, j])
source_data[i, j] <<- DT:::coerceValue(v, reactive_values$source_data[i, j])
replaceData(proxy, source_data, resetPaging = FALSE, rownames = FALSE)
})
}
)
I have spent days trying to find just the right solution to this problem and while I've seen many discussions nothing quite "works" how I need it to.
I need my solution to meet these requirements;
the table is editable
There are filters that are reactive to the contents of the table
When new values are entered into the table the edits are a) saved into the data b) reflected in the filters
I've tried DT while it has the nicest looking output I couldn't get the DT filters to update and if you made an edit and filtered the table the edit would be reverted.
rHandsOnTable had a better looking edit option but same issues as above.
dqshiny, an augment for rHandsonTable enables me to save the data and it updates the filter, but the filter options weren't good, the "select" input doesn't seem let me select nothing to display all results. And because my actual data has a lot of text in each box as I horizontally scroll the height of the cells change and this makes the filters and cell widths desync.
With that said here is what I've tried, I hope someone can help me figure out
### DT that doesn't update filters but saves content
shinyApp(
ui = fluidPage(
DT::dataTableOutput('x1')
),
server = function(input, output, session) {
x = iris
x$Date = Sys.time() + seq_len(nrow(x))
output$x1 = DT::renderDataTable(x, editable = TRUE, filter = "top", selection = 'none', rownames = FALSE)
proxy = dataTableProxy('x1')
observeEvent(input$x1_cell_edit, {
info = input$x1_cell_edit
str(info)
i = info$row
j = info$col + 1
v = info$value
x[i, j] <<- DT:::coerceValue(v, x[i, j])
replaceData(proxy, x, resetPaging = FALSE, rownames = FALSE)
})
}
)
dqShiny "works" but in my full dataset when I set each column's filter type something must be wrong with how it processes the data because it's discarding a lot of rows out of hand and I can't figure out why. Also can't turn off filters for specific columns. all or nothing as far as I can tell.
# library(tidyverse)
# library(shiny)
# library(rhandsontable)
# install.packages("remotes")
# library(remotes)
# remotes::install_github("daqana/dqshiny")
# library(dqshiny)
shinyApp(
ui = fluidPage(
dq_handsontable_output("randomTable", 9L)
),
server = function(input, output, session) {
hw <- c("Hello", "my", "funny", "world!")
data <- data.frame(A = rep(hw, 500), B = hw[c(2,3,4,1)],
C = 1:500, D = Sys.Date() - 0:499, stringsAsFactors = FALSE)
dq_render_handsontable(
"randomTable",
data = data,
width_align = TRUE,
filters = c("Select"),
table_param =
list(
height = 800,
readOnly = TRUE,
stretchH = "all",
highlightCol = TRUE,
highlightRow = TRUE
),
col_param =
list(
list(col = c("A", "B"), readOnly = FALSE, colWidths = "100%"),
list(col = c("C", "D"), colWidths = 300)
),
horizontal_scroll = TRUE
)
}
)
and then simple hands on table that I can't get to work even a little.
shinyApp(
ui = fluidPage(
rHandsontableOutput("randomTable")
),
server = function(input, output, session) {
hw <- c("Hello", "my", "funny", "world!")
data <- data.frame(
A = rep(hw, 500),
B = hw[c(2, 3, 4, 1)],
C = 1:500,
D = Sys.Date() - 0:499,
stringsAsFactors = FALSE
)
output$randomTable <- renderRHandsontable({
data %>%
rhandsontable(
height = 800,
readOnly = TRUE,
stretchH = "all",
colWidths = "100%"
) %>%
hot_col(c("A", "B"), readOnly = FALSE) %>%
hot_col(c("C", "D"), colWidths = 300) %>%
hot_table(highlightCol = TRUE, highlightRow = TRUE)
})
}
)
Perhaps you are looking for this
### DT updates filters
shinyApp(
ui = fluidPage(
DT::dataTableOutput('x1')
),
server = function(input, output, session) {
dfx <- reactiveValues(data=NULL)
observe({
x <- iris
x$Date = Sys.time() + seq_len(nrow(x))
dfx$data <- x
})
output$x1 = renderDT(dfx$data, editable = TRUE, filter = "top", selection = 'none', rownames = FALSE)
#proxy = dataTableProxy('x1')
observeEvent(input$x1_cell_edit, {
info = input$x1_cell_edit
str(info)
i = info$row
j = info$col + 1
v = info$value
dfx$data[i, j] <<- DT:::coerceValue(v, dfx$data[i, j])
#replaceData(proxy, x, resetPaging = FALSE, rownames = FALSE)
})
}
)

Updating PostgreSQL database from R shiny app

Please suggest how to update PostgreSQL database from R shiny app. I would like to be able to update values in table "testUpdate" in a PostgreSQL database:
Update "YN" after a checkbox is checked in 'x1' Data-table.
Update "Note" after "save_changes" button is pressed.
I've created fake data so you could see how the app works. Alternatively, I've included the data source. I haven't found one method that works well with R. Please suggest an implementation.
library(dplyr)
library(dbplyr)
library(DBI)
library(DT)
library(data.table)
library(shinyjs)
library(shinydashboard)
library(shinycssloaders)
library(tidyr)
library(tableHTML)
library(shiny)
library(RPostgreSQL)
pool <- pool::dbPool(drv = dbDriver("PostgreSQL"),
dbname = "postgreDatabase",
host = "11.111.11.1",
port = '12342',
user = "fdc",
password = "password")
shinyApp(
ui = fluidPage(
tabPanel("Test",
sidebarLayout(position = "right",
sidebarPanel(id="sidebar",
(DT::dataTableOutput("y1"))),
mainPanel(
(DT::dataTableOutput("x1")))
))),
server = function(input, output, session) {
buttonInput <- function(FUN, len, id, ...) {
inputs <- character(len)
for (i in seq_len(len)) {
inputs[i] <- as.character(FUN(paste0(id, i), ...))
}
inputs
}
# create a character vector of shiny inputs
shinyInput = function(FUN, len, id, value, width) {
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], width = width))
}
inputs
}
# obtain the values of inputs
shinyValue = function(id, len, initial) {
vapply(seq_len(len), function(i) {
value = input[[paste0(id, i)]]
if (is.null(value)) initial[i] else value
}, FUN.VALUE = logical(1))
}
#created fake data so you can run the app without the db.
n = 10
YN = rep(c(FALSE, TRUE), times = c(5,5))
df1 = data.frame(
cb = shinyInput(checkboxInput, n, 'cb_',
value = YN, width='30px'),
month = month.abb[1:n],
YN = YN,
ID = seq_len(n),
stringsAsFactors = FALSE
)
#####alternatively data comes from table called "testUpdate"
testUpdate <- tbl(db_pool,"testUpdate") %>% collect()
testUpdate_cols <- testUpdate %>%
select(ID, month, YN, Note)
vals <- reactiveValues()
vals$Data <- data.table(
ID = seq_len(n),
Note = c("test notes", "testing", "changed", "serial number", "", "", "", "", "testing", ""),
'Update Note' = buttonInput(
FUN = actionButton,
len = n,
id = 'button_',
label = "?",
onclick = 'Shiny.onInputChange(\"GoToNoteClick\", this.id)'
)
)
observeEvent(input$GoToNoteClick, {
showModal(modal_modify)
})
modal_modify<-modalDialog(
fluidPage(
textAreaInput(
"run_notes",
label = "Notes:",
width = "100%",
height = "100px"
),
actionButton("save_changes", "Save changes")
),
size="l"
)
get_sel <- reactive({
w <- input$x1_rows_selected
df1[w,] -> out
print(out)
out
})
filterMain <- reactive({
req(input$x1_rows_selected)
w <- input$x1_rows_selected
id_sel <- df1[w,'ID']
print(id_sel)
vals$Data %>% filter(ID %in% id_sel) -> out
out
})
output$y1 <- DT::renderDataTable(
datatable(
{
filterMain()
}
,escape = FALSE,
#class = "display compact",
rownames=F,
selection='none',
options = list(
dom = 't', paging = FALSE, ordering = FALSE)
))
loopData = reactive({
values = shinyValue('cb_', n, initial = YN)
dat = df1
dat$cb = shinyInput(checkboxInput, n, 'cb_',
value = values,
width = '30px')
dat$YN = values
dat
})
observeEvent(input$save_changes, {
req(vals$Data)
selected_row=as.numeric(gsub("button_","",input$GoToNoteClick))
print(selected_row)
curid <- vals$Data[selected_row,1]
print(curid)
print(input$run_notes)
vals$Data$Note[vals$Data$ID %in% curid] <- input$run_notes
##write changes
#write data back to postgreSQL
qry = paste0("UPDATE SET Note = '';")
print(qry)
dbSendQuery(conn = db_pool, statement = qry)
removeModal()
#dbDisconnect(db_pool)
})
output$x1 = renderDT(
df1, class = "display compact",
escape = FALSE, selection = 'single', rownames=F,
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, rownames=F)
})
}
)
The database was updated using the following function from here:database bulk update
updateDB <- function(editedValue, id, field, pool, tbl){
conn <- poolCheckout(pool)
id = id
col = field
value = editedValue
query <- glue::glue_sql("UPDATE {`tbl`} SET
{`col`} = {value}
WHERE runid = {id}
", .con = conn)
dbExecute(conn, sqlInterpolate(ANSI(), query))
poolReturn(conn)
return(invisible())
}
onStop(function() {
poolClose(db_pool)
})
The functionally works great in Rstudio Server Pro, however doesn't work in a published app via Rstudio Connect. Any suggestion on how to make this work in Rstudio connect would be extremely helpful.
Thanks

Editable Data Table in R

I am trying to edit data table and update the records post row editing on click of action button "Update Table". How to retrive/display new Data table records reactively post modifying fields in existing Data table record?
library(shiny)
library(shinyjs)
library(DT)
library(data.table)
mydata = data.frame(id=letters[1:5], val=sample(10,5,T))
lengthofData <- nrow(mydata)
mydata[["Constraint Type"]] <- c(">")
))
mydata[["Constraint Value"]] <- c(1)
ui = fluidPage(dataTableOutput("table"),
actionButton("goButton", "Update Table"),
dataTableOutput("newtable"))
server = function(input,output){
x <- mydata
output$table <- renderDataTable( x,server = FALSE,
escape = FALSE,
selection = 'none')
proxy = dataTableProxy('table')
xNew<-reactiveValues()
observeEvent(input$x1_cell_edit, {
info = input$x1_cell_edit
str(info)
i = info$row
j = info$col + 1
v = info$value
x[i, j] <<- DT:::coerceValue(v, x[i, j])
replaceData(proxy, x, resetPaging = FALSE, rownames = FALSE)
xNew<<-x
})
observeEvent(input$goButton,{
output$newtable <- renderDataTable( xNew(),server = FALSE,
escape = FALSE,
selection = 'none')
})
}
shinyApp(ui,server)

Resources