How to save edits made using rhandsontable r package - r

My R program works as expected. It shows a table containing my dataFrame, and lets me edit the values.
How do I capture those values and save them to my dataframe, or a copy of my dataframe?
require(shiny)
library(rhandsontable)
DF = data.frame(val = 1:10, bool = TRUE, big = LETTERS[1:10],
small = letters[1:10],
dt = seq(from = Sys.Date(), by = "days", length.out = 10),
stringsAsFactors = F)
rhandsontable(DF, rowHeaders = NULL)
EDIT:
The above code produces a table with rows and columns. I can edit any of the rows and columns. But when I look at my dataFrame, those edits do not appear. What I am trying to figure out is what do I need to change so I can capture the new values that were edited.

I know this thread's been dead for years, but it's the first StackOverflow result on this problem.
With the help of this post - https://cxbonilla.github.io/2017-03-04-rhot-csv-edit/, I've come up with this:
library(shiny)
library(rhandsontable)
values <- list()
setHot <- function(x)
values[["hot"]] <<- x
DF <- data.frame(val = 1:10, bool = TRUE, big = LETTERS[1:10],
small = letters[1:10],
dt = seq(from = Sys.Date(), by = "days", length.out = 10),
stringsAsFactors = FALSE)
ui <- fluidPage(
rHandsontableOutput("hot"),
br(),
actionButton("saveBtn", "Save changes")
)
server <- function(input, output, session) {
observe({
input$saveBtn # update dataframe file each time the button is pressed
if (!is.null(values[["hot"]])) { # if there's a table input
DF <<- values$hot
}
})
observe({
if (!is.null(input$hot)){
DF <- (hot_to_r(input$hot))
setHot(DF)
}
})
output$hot <- renderRHandsontable({
rhandsontable(DF) %>% # actual rhandsontable object
hot_table(highlightCol = TRUE, highlightRow = TRUE, readOnly = TRUE) %>%
hot_col("big", readOnly = FALSE) %>%
hot_col("small", readOnly = FALSE)
})
}
shinyApp(ui = ui, server = server)
However, I don't like my solution on the part of DF <<- values$hot as I previously had problems with saving changes to the global environment. I've couldn't figure it out any other way, though.

It seems to be accessible now via input$NAME_OF_rHandsontableOutput and can be converted to a data.frame via hot_to_r().
Reproducible example:
library(shiny)
library(rhandsontable)
ui <- fluidPage(
rHandsontableOutput("hottable")
)
server <- function(input, output, session) {
observe({
print(hot_to_r(input$hottable))
})
output$hottable <- renderRHandsontable({
rhandsontable(mtcars)
})
}
shinyApp(ui, server)

I was able to accomplish this with a more simple solution for saving data while the app is open and after it is closed for shiny 1.7++
Create an observe event dependent upon a save button clicked at any point when the app is open. I've scaled this method in more complex apps where you have a selectizeinput for swapping in and out different data frames into the rhandsontable, each of which are edited, saved and recalled while the app is open.
In the server:
observeEvent(input$save, { #button is the name of the save button, change as needed
df <<- hot_to_r(input$rhandsontable) #replace rhandsontable with the name of your own
}) #df is the data frame that have it access when the app starts
In the UI:
actionButton("save","Save Edits")

I don't know what you want to recover exactly, but this seems to work:
DF <- rhandsontable(DF, rowHeaders = NULL)
library(jsonlite)
fromJSON(DF$x$data)

If you are using Shiny then input$table$changes$changes can give you the edited value with row and column index. Below is the code if you want to update only specific cell and not the complete table using hot_to_t().
library(shiny)
library(rhandsontable)
DF = data.frame(val = 1:10, bool = TRUE, big = LETTERS[1:10],
small = letters[1:10],
dt = seq(from = Sys.Date(), by = "days", length.out = 10),
stringsAsFactors = F)
ui <- fluidPage(
rHandsontableOutput('table')
)
server <- function(input, output) {
X = reactiveValues(data = DF)
output$table <- rhandsontable::renderRHandsontable({
rhandsontable(X$data, rowHeaders = NULL)
})
observeEvent(input$table$changes$changes,{
row = input$table$changes$changes[[1]][[1]]
col = input$table$changes$changes[[1]][[2]]
value = input$table$changes$changes[[1]][[4]]
X$data[row,col] = value
})
}
shinyApp(ui, server)

Here's an example from related post How to add columns to a data frame rendered with rhandsontable in R Shiny with an action button?, which started with Tonio Liebrand's solution above but rendered reactively with columns added by the user via action button so you can see the table evolve and see how manual edits to the table stick around:
library(shiny)
library(rhandsontable)
myDF <- data.frame(x = c(1, 2, 3))
ui <- fluidPage(rHandsontableOutput('hottable'),
br(),
actionButton('addCol', 'Add'))
server <- function(input, output, session) {
EmptyTbl <- reactiveVal(myDF)
observeEvent(input$hottable, {
EmptyTbl(hot_to_r(input$hottable))
})
output$hottable <- renderRHandsontable({
rhandsontable(EmptyTbl())
})
observeEvent(input$addCol, {
newCol <- data.frame(c(1, 2, 3))
names(newCol) <- paste("Col", ncol(hot_to_r(input$hottable)) + 1)
EmptyTbl(cbind(EmptyTbl(), newCol))
})
}
shinyApp(ui, server)

Related

Edit DT Datatable Server-side in Shiny App

Trying to work through an example to edit DT tables as shown here. Not sure my code is updating server-side as I'm hoping. I'm computing a mean of a variable just to see if things are being updated. I'm using renderUI as my real world problem is outputting more than just a table.
Thank you for any suggestions on how to get the table to update server side.
library(shiny)
library(DT)
bp <- data.frame(weights = rep(1,5), x = rnorm(5))
shinyApp(
ui = fluidPage(
#DT::dataTableOutput("x5"),
uiOutput("x5"),
verbatimTextOutput('test')
),
server = function(input, output, session) {
bp <- bp
output$x5 <- renderUI({
tableOut <- DT::datatable(bp, editable = TRUE, filter = list(position = 'bottom'))
})
# edit a single cell
proxy = dataTableProxy('x5')
observeEvent(input$x5_cell_edit, {
info = input$x5_cell_edit
bp <- editData(bp, info)
replaceData(proxy, bp, resetPaging = FALSE)
})
output$test <- renderPrint({
mean(bp$weights)
})
}
)

Reactive/Calculate column in dynamic Rhandsontable

I am using rhandsonpackage and am using solution from below link from this to achieve following scenario - "The change of drop down should present user with a different set of input, that they further may modify, while some other columns continue recalculating"
R Shiny App: Reactive/Calculate column in Rhandsontable
It works perfectly when the initial DF(initialized as previous <- reactive({DF})) is static.
However if DF is dynamic and lets say different based on a dropdown selection, the logic doesn't work.
The core reason is that inside 'MyChanges' definition, even when this dynamic DF is updated, object keeps on working the old input$hotable1 (since the is.null(input$hotable1) condition is never met again). Hence although the dynamic DF is updated correctly in 'previous', it won't reflect in the 'MyChanges'. I tried setting a flag to capture when the dropdown changes and setting the input$hottable1 to NULL but it's a read only object and that operation errors out.
Here is the modified code snippet to reproduce the issue. Again, main issues is that at line 26, it ignores the updated 'previous()' object. Really appreciate any help with a resolution on this!
#rm(list = ls())
library(shiny)
library(rhandsontable)
library(shinyWidgets)
## Create the dataset
getdynamicDF <- function(selection){
if(selection=="a"){return(data.frame(num = 1:10, price = 1:10,Total = 1:10,stringsAsFactors = FALSE))}
else if (selection=="b"){return(data.frame(num = 11:20, price = 1:10,Total = 1:10,stringsAsFactors = FALSE))}
else if (selection=="c"){return(data.frame(num = 21:30, price = 1:10,Total = 1:10,stringsAsFactors = FALSE))}
}
# DF = data.frame(num = 1:10, price = 1:10,Total = 1:10,stringsAsFactors = FALSE)
numberofrows <- 10
server <- shinyServer(function(input, output, session) {
# Initiate your table
# dynamicDF <- function(option)
previous <- reactive({
getdynamicDF(input$mydropdown)
})
MyChanges <- reactive({
if(is.null(input$hotable1)){return(previous())}
else if(!identical(previous(),input$hotable1)){
# hot.to.df function will convert your updated table into the dataframe
mytable <- as.data.frame(hot_to_r(input$hotable1))
# here the second column is a function of the first and it will be multipled by 100 given the values in the first column
mytable <- mytable[1:numberofrows,]
# Add some test cases
mytable[,1][is.na(mytable[,1])] <- 1
mytable[,2][is.na(mytable[,2])] <- 1
mytable[,3] <- mytable[,1]*mytable[,2]
mytable
}
})
output$hotable1 <- renderRHandsontable({rhandsontable(MyChanges())})
})
ui <- basicPage(mainPanel(pickerInput(
inputId = "mydropdown",
label = "Option",
choices = c("a", "b", "c")
),
rHandsontableOutput("hotable1")))
shinyApp(ui, server)
Perhaps this is what you are looking for.
library(shiny)
library(rhandsontable)
library(shinyWidgets)
## Create the dataset
getdynamicDF <- function(selection){
if(selection=="a"){return(data.frame(num = 1:10, price = 1:10,Total = 1:10,stringsAsFactors = FALSE))}
else if (selection=="b"){return(data.frame(num = 11:20, price = 1:10,Total = 1:10,stringsAsFactors = FALSE))}
else if (selection=="c"){return(data.frame(num = 21:30, price = 1:10,Total = 1:10,stringsAsFactors = FALSE))}
}
#numberofrows <- 10
server <- shinyServer(function(input, output, session) {
new <- reactiveValues(dt=NULL)
previous <- eventReactive(input$mydropdown, {
req(input$mydropdown)
getdynamicDF(input$mydropdown)
})
observe({new$dt<-previous()})
observeEvent(input$hotable1, {
mytable <- as.data.frame(hot_to_r(input$hotable1))
mytable[,3] <- mytable[,1]*mytable[,2]
new$dt <- mytable
})
output$hotable1 <- renderRHandsontable({
rhandsontable(new$dt)
})
})
ui <- basicPage(mainPanel(pickerInput(
inputId = "mydropdown",
label = "Option",
choices = c("a", "b", "c")
),
rHandsontableOutput("hotable1")))
shinyApp(ui, server)

How to display an rhandsontable in Shiny that is dependent on selectInput choices

I have a select input box where the user can select from three choices. Having made the selection I want a table to appear whereby they can enter some additional values associated with those choices.
I then want to display a summary table which contains a summary of the values that have been entered.
When I run the code below I get the following error:
Error in .getReactiveEnvironment()$currentContext() : Operation not
allowed without an active reactive context. (You tried to do something
that can only be done from inside a reactive expression or observer.)
library(shiny)
library(rhandsontable)
library(dplyr)
ui <- fluidPage(
mainPanel(
selectInput("section", "Section", choices = c("A","B","C"), multiple = TRUE),
rHandsontableOutput('table')
)
)
server = function(input, output, session) {
SectionList <- input$section
Section <- eventReactive(input$section, {
section_table <- data.frame(Section = SectionList, input1 = 0, input2 = 0)
return(section_table)
})
output$table <- renderRHandsontable({
if(!is.null(input$table)){
DF = hot_to_r(input$table)
} else {
DF = data.frame(Section = "A", input1 = 0, input2 = 0)
}
rhandsontable(DF) %>%
hot_col(col = "input1") %>%
hot_col(col = "input2")
})
results_summary <- eventReactive(input$table, {
DF = output$table
summary <- DF %>% group_by(Section) %>%
summarise(input1 = mean(input1),
input2 = mean(input2))
})
output$results <- renderTable({
results_summary()
})
}
shinyApp(ui, server)
I haven't fully understood what you are aiming to do, but the error is produced because of the line:
SectionList <- input$section
this line has to go to a reactive environment. If you change this to, e.g.
Section <- eventReactive(input$section, {
SectionList <- input$section
section_table <- data.frame(Section = SectionList, input1 = 0, input2 = 0)
return(section_table)
})
the error disappears. Hope this helps!

Using tapply in Shiny to find mean of a column

I am running into trouble using the tapply function. I am pulling two vectors from the same data frame which was created from a reactive variable. The first I am calling from a user inputted selection, and the second is one that I have created to keep my code generalisable and to use in my sort function. My sample code is shown below using the r-bloggers example. The data is here.
https://redirect.viglink.com/?format=go&jsonp=vglnk_150821851345614&key=949efb41171ac6ec1bf7f206d57e90b8&libId=j8v6cnh201021u9s000DAhzunvtas&loc=https%3A%2F%2Fwww.r-bloggers.com%2Fbuilding-shiny-apps-an-interactive-tutorial%2F&v=1&out=http%3A%2F%2Fdeanattali.com%2Ffiles%2Fbcl-data.csv&ref=https%3A%2F%2Fduckduckgo.com%2F&title=Building%20Shiny%20apps%20%E2%80%93%20an%20interactive%20tutorial%20%7C%20R-bloggers&txt=here
The error it throws is that they are not the same length, even though their attribute and class print outs are exactly the same.
I know that this is not the best code in the world, but I just threw together a quick example.
library(shiny)
library(tidyverse)
bcl <- read.csv("bcl-data.csv", stringsAsFactors = FALSE)
ui <- fluidPage(titlePanel("Sampling Strategies"),
sidebarLayout(
sidebarPanel(
selectInput("XDATA","xdata",
choices = c(names(bcl))),
selectInput("YDATA","ydata",
choices = c(names(bcl)))
),
mainPanel(
tabsetPanel(
tabPanel("The table",tableOutput("mytable"))
))
))
server <- function(input, output, session) {
filtered <- reactive({
bcl <- bcl %>% mutate(ID = 1:nrow(bcl))
})
output$mytable <- renderTable({
dataset <- filtered() %>% mutate(sampled = "white")
sample.rows <- sample(dataset$ID, 5, replace = FALSE)
dataset$sampled[sample.rows] <- "black"
final <- tapply(dataset[input$XDATA], list(dataset$sampled),mean)[["black"]]
return(final)
})
}
shinyApp(ui = ui, server = server)
Cheers
Edit* Sorry my bad, forgot to change over the drop list codes. All I am interested is one generic xdata vector that can be selected from the loaded data set. I then sample it, and want to find the mean value from the sampled indices.
One of the problems is in the subsetting. the [ still returns a data.frame. So, we need [[. If we look at ?tapply
tapply(X, INDEX, FUN = NULL, ..., default = NA, simplify = TRUE)
where
X is an atomic object, typically a vector
ui <- fluidPage(titlePanel("Sampling Strategies"),
sidebarLayout(
sidebarPanel(
selectInput("XDATA","xdata",
choices = c(names(bcl)[5:7])),
selectInput("YDATA","ydata",
choices = c(names(bcl)))
),
mainPanel(
tabsetPanel(
tabPanel("The table",tableOutput("mytable"))
))
))
server <- function(input, output, session) {
filtered <- reactive({
bcl <- bcl %>% mutate(ID = row_number())
})
output$mytable <- renderTable({
dataset <- filtered() %>% mutate(sampled = "white")
sample.rows <- sample(dataset$ID, 20, replace = FALSE)
dataset$sampled[sample.rows] <- "black"
final <- tapply(dataset[[input$XDATA]], list(dataset$sampled),mean, na.rm = TRUE, simplify = TRUE)
return(final)
})
}
shinyApp(ui = ui, server = server)
-output

Using eventReactive with a rendering function

I've been stuck on this problem for two days now, and I would love some help from people much smarter than me. I am using a package called "shinyTable"(https://github.com/trestletech/shinyTable), and I am having a hard time manipulating it. Basically, how can I make this table change its size based on input$rows IF I click on the "submit" button?Here is a working code w/o the "submit" button:
library(shinythemes)
library(shiny)
library(shinyTable)
ui <- fluidPage(theme = shinytheme("slate"),titlePanel(HTML("<h1> <font face=\"Rockwell Extra Bold\" color=\"#b42000\"><b><b>R/Econ</b></b></font> <font face=\"Lucida Calligraphy\" colsor=\"white\" >Model</font></h1>")),
sidebarLayout(
sidebarPanel(
numericInput("rows", label = h3("Number of Rows"), value = 20),
numericInput("cols", label = h3("Number of Columns"), value = 2)
),
mainPanel(
htable("tbl")
)
)
)
server <- function(input, output)
{
output$tbl <- renderHtable({
if (is.null(input$tbl)){
# Seed the element with some data initially
tbl <- data.frame(list(num1=1:input$rows,
num2=(1:input$rows)*20,
letter=LETTERS[1:(input$rows)]))
cachedTbl <<- tbl
print(tbl)
return(tbl)
} else{
cachedTbl <<- input$tbl
print(input$tbl)
return(input$tbl)
}
})
}
shinyApp(ui = ui, server = server)
Now, I want the table's size to change dynamically when my input$rows or input$cols changes. I cannot for the life of me figure out how to make this work. I tried the following:
myx<-eventReactive (input$submit, {
output$tbl <- renderHtable({
if (is.null(input$tbl)){
tbl <- data.frame(list(num1=1:input$rows,
num2=(1:input$rows)*20,
letter=LETTERS[1:(input$rows)]))
cachedTbl <<- tbl
print(tbl)
return(tbl)
} else{
cachedTbl <<- input$tbl
print(input$tbl)
return(input$tbl)
}
})
})
But this doesn't work. My thought process was that if the submit button is clicked, it would recreate the table. I want input$rows to change the size of the table, but neither my changing the size nor my clicking on a submit button does anything. In fact, adding eventReactive changes the table to where it has no values, and no values can be inputted. I'm honestly lost. I tried other variations of this such as this:
myx<-eventReactive (input$submit, {
if (is.null(input$tbl)){
tbl <- data.frame(list(num1=1:input$rows,
num2=(1:input$rows)*20,
letter=LETTERS[1:(input$rows)]))
cachedTbl <<- tbl
print(tbl)
return(tbl)
} else{
cachedTbl <<- input$tbl
print(input$tbl)
}
})
#-------
# myx2<-eventReactive (cachedTbl, {
# })
output$tbl <- renderHtable({
tbl<<-myx()
print(data.frame(tbl))#Tried and failed using myx()
return(data.frame(tbl))
})
In doing this, I thought I can make the table reactive and then pass it to renderHTable. All these attempts share the fact that I'm trying to make things reactive.
How can I make this table change its size based on input$rows IF I click on the "submit" button? Please help!
This should get you started. As per my comment, you should use rhandsontable. This package uses the same underlying JS library, handsontable.JS, but it is well supported and it is on Cran (disclaimer: I'm a minor contributor to this package).
The working example below is based on rhandsontable. For simplicity I've only implemented the change of the number of rows.
Please take into account that I haven't implemented any type of caching mechanism, either to a global variable, or to a reactive variable, as it wasn't necessary, but it can easily be added.
This is the only example that I know of a library working in shiny where there is an output$something linked to an input$something.
In this case the input$tbl in the code refer to the table, but to be converted to a data frame it needs to be transformed by the convenience function hot_to_r (handsontable to R).
I am sure you are already familiar with this: you use hot_to_r(input$tbl) to check if the user has changed anything in the displayed table (assuming it is not read-only). shinyTable has a much more complicated mechanism, but it is prone to races.
library(shinythemes)
library(shiny)
library(rhandsontable)
ui <- fluidPage(theme = shinytheme("slate"),titlePanel(HTML("<h1> <font face=\"Rockwell Extra Bold\" color=\"#b42000\"><b><b>R/Econ</b></b></font> <font face=\"Lucida Calligraphy\" colsor=\"white\" >Model</font></h1>")),
sidebarLayout(
sidebarPanel(
numericInput("rows", label = h3("Number of Rows"), value = 20),
numericInput("cols", label = h3("Number of Columns"), value = 2)
),
mainPanel(
rHandsontableOutput("tbl")
)
)
)
server <- function(input, output, session) {
data = reactive({
if (is.null(input$tbl)) {
DF <- data.frame(num1 = 1:input$rows, bool = TRUE, nm = LETTERS[1:input$rows],
dt = seq(from = Sys.Date(), by = "days", length.out = input$rows),
stringsAsFactors = F)
} else if(nrow(hot_to_r(input$tbl)) == input$rows) {
DF <- hot_to_r(input$tbl)
} else {
DF <- data.frame(num1 = 1:input$rows, bool = TRUE, nm = LETTERS[1:input$rows],
dt = seq(from = Sys.Date(), by = "days", length.out = input$rows),
stringsAsFactors = F)
}
DF
})
output$tbl <- renderRHandsontable({
if (is.null(input$rows) | is.null(input$cols)) return()
df = data()
if (!is.null(df))
rhandsontable(df, stretchH = "all")
})
}
shinyApp(ui = ui, server = server)
Please let me know if this works for you, else I'll do my best to change it as per your needs.

Resources