I am populating a table by using Insert UI elements. I also want to delete both table entries and the inserted panels by using the remove UI elements.
I could delete the panels but as you can see in my demo App the corresponding table values are not deleted and the length of the table remains the same even after clicking the delete button.
How can I delete both the panels and their corresponding table values at the same time?
Why table values are not getting deleted?
library(shiny)
library(tidyverse)
DT <- data.frame(Year = c(1980,1985,1985,1990,1990,1995),
Events = c("Storm", "Earthquake", "Flood", "Draught",
"Earthquake", "Earthquake"),
Area_Loss = c(100, 200, 400, 500, 450,300),
Money = c(1000,2000,3000,4000,5000,6000))
ui <- fluidPage( h4("Updating InserUIs",
selectInput("events","Events",choices=as.character(DT$Events)),
tags$div(id = "Panels"),
actionButton("add","Add"),
tableOutput("table"),
verbatimTextOutput("text")
))
server <- function(session, input, output){
# Reactive values for the number of input panels
vals <- reactiveValues(btn = list(), observers = list())
observeEvent(input$add,ignoreNULL = FALSE,{
l <- length(vals$btn) +1
# Add Panels
for(i in l){
vals$btn[[i]]= insertUI(selector = "#Panels",
ui = splitLayout(id = paste0("Selection",i), where ="afterEnd",
cellWidths = rep("33.33%",3),
selectInput(paste0("year",i), "Year", choices = DT$Year,
selected = ""),
numericInput(paste0("area",i), "Area", min = 0, max = 10000,
value ="", step = 1),
numericInput(paste0("money",i), "Money", min = 0, max = 10000,
value = "", step =1),
div(id ="delete_div",actionButton(paste0("delete",i), "Delete"))
))}
# Update panels
for(i in l){
vals$observers = lapply(l, function(i)
observeEvent(input[[paste0("year",i)]],{
updateNumericInput(session,paste0("area",i),
"Area",min= 0, max= 50000,value = DT$Area_Loss
[DT$Year == input[[paste0("year",i)]]& DT$Events==
input$events] ,step = 0.1)
}))}
for(i in l){
vals$observers = lapply(l, function(i)
observeEvent(input[[paste0("year",i)]],{
updateNumericInput(session,paste0("money",i),
"Money",min= 0, max= 50000,value = DT$Money
[DT$Year == input[[paste0("year",i)]]& DT$Events==
input$events] ,step = 0.1)
}))}
# Delete Panels
for(i in l){
observeEvent(input[[paste0("delete",i)]],{
shiny::removeUI(selector = paste0("#Selection",i))
i <- length(vals$btn) - 1
})}
})
# Reactive table generated from the user inputs
Table <- reactive({
l <- 1:length(vals$btn)
for(i in l){
Year <- unlist(lapply(l, function(i)input[[paste0("year",i)]]))
Area <- unlist(lapply(l, function(i)input[[paste0("area",i)]]))
Money <- unlist(lapply(l, function(i)input[[paste0("money",i)]]))
}
DF0 <- data.frame(Event = input$events,
Year = Year,
Area_loss = Area,
Money = Money
)
DF0
})
# Visualizing the raective table
output$table <- renderTable({
Table()
})
}
shinyApp(ui,server)
Thanks all of you in advance, any suggestion will help me to progress in my app.
I think your problem can be quiet elegantly solved with modules. See comments in the code for details.
library(shiny)
library(dplyr)
DT <- data.frame(Year = c(1980,1985,1985,1990,1990,1995),
Events = c("Storm", "Earthquake", "Flood", "Draught",
"Earthquake", "Earthquake"),
Area_Loss = c(100, 200, 400, 500, 450,300),
Money = c(1000,2000,3000,4000,5000,6000))
##############################Module#############################
## a module consists of all elements which belong together
## i.e. year, area, money and delete button
## take note about the ns() construct which allows for
## namespacing and through this mechanism we can have several
## instances of this module
YAM_ui <- function(id) {
ns <- NS(id)
fluidRow(
id = id,
h3(id),
column(width = 3,
selectInput(ns("year"),
"Year",
DT$Year,
"")),
column(width = 4,
numericInput(ns("area"),
"Area",
0,
0,
10000,
1)),
column(width = 4,
numericInput(ns("money"),
"Money",
0,
0,
10000,
1)),
column(width = 1,
actionButton(ns("delete"), "Delete"))
)
}
## in the server you can access the elements simply by input$element_name
## we have one input reactive (event) which comes from the main app and
## holds the value of the event selectInput
## we return
## - a killSwitch to signal the main app to delete this module
## - a reactive which returns the data from all inputs organized in a data frame
YAM_server <- function(input, output, session, event) {
killMe <- reactiveVal(FALSE)
observe({
req(input$year)
req(event())
updateNumericInput(session,
"area",
min = 0,
max = 50000,
value = DT$Area_Loss[DT$Year == input$year &
DT$Events == event()] ,
step = 0.1)
updateNumericInput(session,
"money",
min = 0,
max = 50000,
value = DT$Money[DT$Year == input$year &
DT$Events == event()] ,
step = 0.1)
})
get_data <- reactive({
req(!is.null(input$year), !is.null(input$area), !is.null(input$money), event())
data.frame(event = event(),
year = input$year,
area = ifelse(input$area == "", NA, input$area),
money = ifelse(input$money == "", NA, input$money))
})
observeEvent(input$delete,
killMe(TRUE))
return(list(delete = killMe,
get_data = get_data))
}
##############################MainApp##############################
ui <- fluidPage(
titlePanel("Modules"),
sidebarLayout(
sidebarPanel(
h4("Updating Inserted UIs"),
selectInput("events",
"Events",
unique(DT$Events)),
actionButton("add",
"Add"),
tableOutput("table")
),
mainPanel(
tags$div(id = "Panels")
)
)
)
## in the main App we have
## - a reactive (handlers) which holds all reactives of all the modules
## - a list (observers) where we create (and delete) observers for the kill
## switch
## When we add a row, we use insertUI to create the html and callModule
## to switch on the modules server logic. We pass the event reactive to
## the module to make it available within the module.
## When we observe a press to the delete button, we remove the handler
## from the lists and remove the corresponding html via removeUI.
## The data table is then updated automatically, because we removed the handler
## and it is not seen in the loop
## To get the table all we have to do is to loop through all handlers and
## call the get_data reactive from the modules to get the data
server <- function(input, output, session) {
handlers <- reactiveVal(list())
observers <- list()
n <- 1
get_event <- reactive({
input$events
})
observeEvent(input$add, {
id <- paste0("row_", n)
n <<- n + 1
insertUI("#Panels",
"beforeEnd",
YAM_ui(id)
)
new_handler <- setNames(list(callModule(YAM_server,
id,
get_event)),
id)
handler_list <- c(handlers(), new_handler)
handlers(handler_list)
})
observe({
hds <- handlers()
req(length(hds) > 0)
new <- setdiff(names(hds),
names(observers))
obs <- setNames(lapply(new, function(n) {
observeEvent(hds[[n]]$delete(), {
removeUI(paste0("#", n))
hds <- handlers()
hds[n] <- NULL
handlers(hds)
observers[n] <<- NULL
}, ignoreInit = TRUE)
}), new)
observers <<- c(observers, obs)
})
output$table <- renderTable({
hds <- req(handlers())
req(length(hds) > 0)
tbl_list <- lapply(hds, function(h) {
h$get_data()
})
do.call(rbind, tbl_list)
})
}
shinyApp(ui, server)
I agree with #thothal that modules help when adding and removing sections of UI and the corresponding data. I've taken a lot of inspiration from their answer and come up with a slightly cleaner (IMHO) implementation.
I've only modified the final server function, where I have managed to do away with the need to keep a list of observers and have captured most of the lifecycle functionality into the add_module function
# utility to hide away the mess of updating the reactiveVal(list())
update_values <- function(values, name, value) {
vals <- values()
vals[[name]] <- value
values(vals)
}
add_module <- function(values, name, server, delete_hook = NULL, remove_selector = NULL) {
# add module server's return to values list
update_values(values, name, server)
# if module has a reactive we should monitor for deleting, do so
if (!is.null(delete_hook)) {
observeEvent(
server[[delete_hook]](), {
removeUI(selector = remove_selector) # remove the ui
update_values(values, name, NULL) # remove the server from our values list
},
ignoreInit = TRUE,
once = TRUE
)
}
}
server <- function(input, output, session) {
handlers <- reactiveVal(list())
get_event <- reactive({
input$events
})
# new
observeEvent(input$add, {
id <- paste0("row_", input$add)
insertUI("#Panels", "beforeEnd", YAM_ui(id))
add_module(
handlers,
name = id,
server = callModule(YAM_server, id, get_event),
delete_hook = "delete",
remove_selector = paste0("#", id)
)
})
# unchanged
output$table <- renderTable({
hds <- req(handlers())
req(length(hds) > 0)
tbl_list <- lapply(hds, function(h) {
h$get_data()
})
do.call(rbind, tbl_list)
})
}
shinyApp(ui, server)
Related
In the below code, I took one of the examples from https://community.rstudio.com/t/shiny-app-with-dynamic-number-of-datatables/2405/4 for dynamically adding tables. The example used tables rendered with DT and I made minor modifications to use it for rhandsontable.
However, I'm having trouble making the tables independent of one another. When adding a new table, it should be "seeded" with the default values per dataframe data1 and its related rowNames1, but thereafter they should be independent as illustrated below. I'm fairly sure the solution has something to do with creating a dynamic equivalent of the reactive uiTable1 used in the code for holding table values, but I don't know how to do this.
Any ideas for accomplishing this?
Inputs into the base (master) and added tables will be used elsewhere in the full code this is intended for.
Code:
library(rhandsontable)
library(shiny)
rowNames1 <- c('A','B','C','Sum')
data1 <- data.frame(row.names = rowNames1, 'Col 1' = c(1,1,0,2), check.names = FALSE)
ui <- fluidPage(
rHandsontableOutput('hottable1'), # undeletable base table
actionButton("addTbl", "Add table"), # adds new table
tags$div(id = "placeholder")
)
server <- function(input, output) {
uiTable1 <- reactiveVal(data1) # undeletable base table
rv <- reactiveValues() # used for dynamic table add/removal
# records changes to base table and will need same for added tables:
observeEvent(input$hottable1,{uiTable1(hot_to_r(input$hottable1))})
output$hottable1 <- renderRHandsontable({
rhandsontable(uiTable1(),rowHeaderWidth = 100, useTypes = TRUE)
})
# adds column summation to last row of table, will need for all added tables too:
observe({
req(input$hottable1)
DF <- hot_to_r(input$hottable1)
DF[setdiff(rowNames1, "Sum"),]
DF["Sum",] <- colSums(DF[setdiff(rowNames1, "Sum"),, drop = FALSE], na.rm = TRUE)
uiTable1(DF)
})
# dynamically add/remove tables:
observeEvent(input$addTbl, {
divID <- gsub("\\.", "", format(Sys.time(), "%H%M%OS3"))
dtID <- paste0(divID, "DT")
btnID <- paste0(divID, "rmv")
insertUI(
selector = "#placeholder",
ui = tags$div(id = divID,
actionButton(btnID, "Remove table", class = "pull-left btn btn-danger"),
rHandsontableOutput(dtID),
hr()
)
)
output[[dtID]] <- renderRHandsontable({
rhandsontable(uiTable1(),rowHeaderWidth = 100, useTypes = TRUE)
})
# remove table from the app when remove button clicked
observeEvent(input[[btnID]], {
removeUI(selector = paste0("#", divID))
rv[[divID]] <- NULL
}, ignoreInit = TRUE, once = TRUE)
})
}
shinyApp(ui,server)
We can use reactiveValues to store the information of each new table. These tables will start with the values of the base table available at that time. Afterwards they will stop reacting to changes inside the main table.
First we create the base table uiTable1 <- reactiveValues(table_base = data1) # undeletable base table
And finally all the subsequent tables will be created as uiTable1[[glue("{divID}table")]] <- uiTable$table_base
library(rhandsontable)
library(shiny)
library(glue)
rowNames1 <- c("A", "B", "C", "Sum")
data1 <- data.frame(row.names = rowNames1, "Col 1" = c(1, 1, 0, 2), check.names = FALSE)
ui <- fluidPage(
rHandsontableOutput("hottable1"), # undeletable base table
actionButton("addTbl", "Add table"), # adds new table
tags$div(id = "placeholder")
)
server <- function(input, output) {
uiTable1 <- reactiveValues(table_base = data1) # undeletable base table
rv <- reactiveValues() # used for dynamic table add/removal
# records changes to base table and will need same for added tables:
observeEvent(input$hottable1, {
uiTable1$table_base <- hot_to_r(input$hottable1)
})
output$hottable1 <- renderRHandsontable({
rhandsontable(uiTable1$table_base, rowHeaderWidth = 100, useTypes = TRUE)
})
# adds column summation to last row of table, will need for all added tables too:
observe({
req(input$hottable1)
DF <- hot_to_r(input$hottable1)
DF[setdiff(rowNames1, "Sum"), ]
DF["Sum", ] <- colSums(DF[setdiff(rowNames1, "Sum"), , drop = FALSE], na.rm = TRUE)
uiTable1$table_base <- DF
})
# dynamically add/remove tables:
observeEvent(input$addTbl, {
divID <- gsub("\\.", "", format(Sys.time(), "%H%M%OS3"))
dtID <- paste0(divID, "DT")
btnID <- paste0(divID, "rmv")
# capture the current state of the main table
uiTable1[[glue("{divID}table")]] <- uiTable1$table_base
insertUI(
selector = "#placeholder",
ui = tags$div(
id = divID,
actionButton(btnID, "Remove table", class = "pull-left btn btn-danger"),
rHandsontableOutput(dtID),
hr()
)
)
output[[dtID]] <- renderRHandsontable({
req(uiTable1[[glue("{divID}table")]])
rhandsontable(uiTable1[[glue("{divID}table")]], rowHeaderWidth = 100, useTypes = TRUE)
})
# adds column summation to last row of table, will need for all added tables too:
observeEvent(input[[dtID]], {
DF <- hot_to_r(input[[dtID]])
DF[setdiff(rowNames1, "Sum"), ]
DF["Sum", ] <- colSums(DF[setdiff(rowNames1, "Sum"), , drop = FALSE], na.rm = TRUE)
uiTable1[[glue("{divID}table")]] <- DF # update the table with the sum
})
# remove table from the app when remove button clicked
observeEvent(input[[btnID]],
{
removeUI(selector = paste0("#", divID))
rv[[divID]] <- NULL
uiTable1[[glue("{divID}table")]] <- NULL
},
ignoreInit = TRUE,
once = TRUE
)
})
}
shinyApp(ui, server)
I am building app where a user can make edits to a datatable and the hit a button to reflect the changes in a non-editable copy of this datatable (in the final project, I will need to have two datasets that need to be matched manually), but for now this small MWE shows the problem I have with making a copy of the reactive table in which changes can be made, without changing the data of the original reactive table. I would like to make this app work, where you click edit a cell in the table dat_joined$data/output$mytable and that those changes do reflect in a new table mydf$data/output$table2. To do mydf$data initially (before any changes are made) needs to be a copy of dat_joined$data This is a follow up on this question and answer: how to make a copy of a reactive value in shiny server function
library(shiny)
library(DT)
library(shinyWidgets)
library(tidyverse)
# create master dataframe
dat_total <- tibble(ID_1 = 1:10, names = letters[1:10],
ID_2 = 11:20, names_2 = LETTERS[c(3:5, 1, 2, 6:8, 10, 9)])
shinyApp(
ui = fluidPage(
title = 'Radio button and a dropdown manue ',
sliderInput("n_rows_table", "Number of rows:",
min = 0, max = 10,
value = 5),
actionBttn(
inputId = "button_1",
label = "Make tables",
size = "sm",
color = "warning"
),
DT::dataTableOutput("mytable"),
actionBttn(
inputId = "button_2",
label = "Process",
size = "sm",
color = "success"),
DT::dataTableOutput("table2")),
server = function(input, output, session) {
# set up reactive values
dat_left <- reactiveValues(data=NULL)
dat_right <- reactiveValues(data=NULL)
dat_joined <- reactiveValues(data=NULL)
# create reactive daraframe
dat <- eventReactive(input$button_1, {
dat_total[1:input$n_rows_table, ] %>%
rowid_to_column()})
# Split the data into a right and a left set
observe({
dat_left$data <- dat() %>%
select(rowid, ID_1, names)
})
observe({
dat_right$data <- dat() %>%
select(rowid, ID_2, names_2,ID_1)
})
# join these again
# This is needed because my actual app will
# be used to manually match 2 datasets
observe({
if (is.null( dat_right$data )) {
NULL
}else{
dat_joined$data <- left_join(dat_left$data,
dat_right$data,
by = "rowid")
}
})
# Print the the datasets
output$mytable <- renderDT({
datatable(dat_joined$data ,
rownames = F,
editable = "cell")
})
# I want to make a copy of the dat_joined$data dataset into dat$mydf
# none of these function as expected
#mydf <- reactiveValues(data=isolate(dat_joined$data))
#mydf <- reactiveValues(data=local(dat_joined$data))
#mydf <- reactiveValues(data=dat_joined$data)
#mydf <- reactiveValues(data=NULL)
# This works, but only saves the cells to w
mydf <- reactiveValues(data=matrix(NA, nrow=10, ncol = 5))
# Ideally the computation only happens when this both an edit is made
# and the button is pressed (now I need to press it between every edit)
# validate_event <- reactive({
# req(input$mytable_cell_edit) & req(input$button_2)
# })
#observeEvent(input$button_2validate_event(), { DOes not work
observeEvent(input$button_2,{
info = input$mytable_cell_edit
str(info)
i = info$row
j = info$col
v = info$value
mydf$data[i, j] <- DT::coerceValue(v, mydf$data[i, j])
})
# print
output[["table2"]] <- renderDT({
datatable(mydf$data)
})
}
)
Any changes you make in the top table is reflected in the bottom table after you press the button "Process". Try this
library(shiny)
library(DT)
library(shinyWidgets)
library(tidyverse)
# create master dataframe
dat_total <- tibble(ID_1 = 1:10, names = letters[1:10],
ID_2 = 11:20, names_2 = LETTERS[c(3:5, 1, 2, 6:8, 10, 9)])
shinyApp(
ui = fluidPage(
title = 'Radio button and a dropdown manue ',
sliderInput("n_rows_table", "Number of rows:",
min = 0, max = 10,
value = 5),
actionBttn(
inputId = "button_1",
label = "Make tables",
size = "sm",
color = "warning"
),
DT::dataTableOutput("mytable"),
actionBttn(
inputId = "button_2",
label = "Process",
size = "sm",
color = "success"),
DT::dataTableOutput("table2")),
server = function(input, output, session) {
# set up reactive values
dat_left <- reactiveValues(data=NULL)
dat_right <- reactiveValues(data=NULL)
dat_joined <- reactiveValues(data=NULL)
dfon <- reactiveValues(top=NULL,
bottom=NULL)
# create reactive daraframe
dat <- eventReactive(input$button_1, {
dat_total[1:input$n_rows_table, ] %>%
rowid_to_column()})
# Split the data into a right and a left set
observe({
req(dat())
dat_left$data <- dat() %>%
dplyr::select(rowid, ID_1, names)
})
observe({
req(dat())
dat_right$data <- dat() %>%
dplyr::select(rowid, ID_2, names_2,ID_1)
})
# join these again
# This is needed because my actual app will
# be used to manually match 2 datasets
observe({
req(dat())
if (!is.null( dat_right$data )) {
dat_joined$data <- left_join(dat_left$data,
dat_right$data,
by = "rowid")
}
})
observe({ ###assign your orig data to a reactiveValues object
req(dat_joined$data)
if (!is.null(dat_joined$data)) {
dfon$top <- dat_joined$data
}
})
# Print the the datasets
output$mytable <- renderDT({
datatable(dfon$top,
rownames = F,
editable = "cell")
})
# Ideally the computation only happens when this both an edit is made
# and the button is pressed (now I need to press it between every edit)
observeEvent(input$mytable_cell_edit, {
info = input$mytable_cell_edit
str(info)
#i = info$row
#j = info$col + 1 # offset by 1
#v = info$value
#dfon$top[i, j] <<- DT::coerceValue(v, dfon$top[i, j])
dfon$top <<- editData(dfon$top, info)
})
observeEvent(input$button_2,{
dfon$bottom <- dfon$top
output$table2 <- renderDT({
datatable(dfon$bottom)
})
})
## further editing of dfon$bottom is performed below...with...observeEvent(input$table2_cell_edit, {...
}
)
In the output below, I have entered cccc for 3rd element in names column, but I have not clicked on the button Process. Therefore, the edited cell is not reflected in the bottom table.
I would like to create many multiple selectize inputs which are connected with each other. In other words : if an item is selected in one of the selectizeinputs i would like that it disappears from the other selectizeinputs' choices. In addition, i would like that the number of selectize inputs corresponds to the number selected in a numericinput.
The example below is working. The only question I have left is on the following line :
X = 1:100, ####### QUESTION HERE
Instead of 1:100, i would like to put something like 1:input$ui_number but I have the following error in R :
Error in .getReactiveEnvironment()$currentContext() : Operation not allowed without an active reactive context.
And if I put a "reactive" or an "observe" function around the lapply, the observeEvent does not work anymore. Any trick for me ?
Thank you for your help !
modalities <- LETTERS[1:10]
library(shiny)
app <- shinyApp(
ui = tabPanel("Change modalities",
numericInput("ui_number", label="Number of modalities",min = 1, max = 4, value=3),
uiOutput("renderui")
),
server = function(input, output, session) {
output$renderui <- renderUI({
output = tagList()
for(i in 1:input$ui_number){
output[[i]] = tagList()
output[[i]][[1]] = selectizeInput(paste0("ui_mod_choose",i), label=paste0("Modality ",i),choices=modalities, multiple = TRUE)
}
return(output)
})
lapply(
X = 1:100, ####### QUESTION HERE
FUN = function(j){
observeEvent({
input[[paste0("ui_mod_choose",j)]]
},
{
sapply(1:input$ui_number,function(i){
vecteur <- do.call(c,lapply((1:input$ui_number)[-i],function(i){input[[paste0("ui_mod_choose",i)]]}))
updateSelectizeInput(session,paste0("ui_mod_choose",i),choices= modalities[!modalities %in% vecteur],selected = input[[paste0("ui_mod_choose",i)]])
})
},
ignoreNULL = FALSE)
}
)
observeEvent({
input$ui_num
},
{
sapply(1:nput$ui_num,function(i){
updateSelectizeInput(session,paste0("ui_mod_choose",i),choice= modalities,selected=NULL)
})
}
)
}
)
runApp(app)
You could have a single observe() instead of multiple observeEvent():
library(shiny)
modalities <- LETTERS[1:10]
ui = tabPanel("Change modalities",
numericInput("ui_number", label = "Number of modalities",
min = 1, max = 4, value = 3),
uiOutput("renderui"))
server = function(input, output, session) {
# Generate modalities select lists
output$renderui <- renderUI({
output = tagList()
for (i in seq_len(input$ui_number)) {
output[[i]] = selectizeInput(paste0("ui_mod_choose", i),
label = paste0("Modality ", i),
choices = modalities, multiple = TRUE)
}
return(output)
})
# Remove selected modalities from other select lists
observe({
n <- isolate(input$ui_number)
for (i in seq_len(n)) {
vecteur <- unlist(lapply((1:n)[-i], function(i)
input[[paste0("ui_mod_choose",i)]]))
updateSelectizeInput(session, paste0("ui_mod_choose",i),
choices = setdiff(modalities, vecteur),
selected = input[[paste0("ui_mod_choose",i)]])
}
})
}
runApp(shinyApp(ui, server))
I am using rhandsontable in a Shiny App and I would like to know how to use the getSelected() method of Handsontable in this case, as I intend to apply changes on the data.frame.
thank you!
You can obtain the selected row, column, range, and cell values, as well as the edited cells using selectCallback=TRUE. You can edit a cell by double-clicking on it, and accept the changes by pressing "return" or "enter".
Minimal example:
library(shiny)
library(rhandsontable)
ui=fluidPage(
rHandsontableOutput('table'),
verbatimTextOutput('selected')
)
server=function(input,output,session)({
df=data.frame(N=c(1:10),L=LETTERS[1:10],M=LETTERS[11:20])
output$table=renderRHandsontable(
rhandsontable(df,selectCallback = TRUE,readOnly = FALSE)
)
output$selected=renderPrint({
cat('Selected Row:',input$table_select$select$r)
cat('\nSelected Column:',input$table_select$select$c)
cat('\nSelected Cell Value:',
input$table_select$data[[
input$table_select$select$r]][[input$table_select$select$c]])
cat('\nSelected Range: R',input$table_select$select$r,
'C',input$table_select$select$c,':R',input$table_select$select$r2,
'C',input$table_select$select$c2,sep="")
cat('\nChanged Cell Row Column:',input$table$changes$changes[[1]][[1]],
input$table$changes$changes[[1]][[2]])
cat('\nChanged Cell Old Value:',input$table$changes$changes[[1]][[3]])
cat('\nChanged Cell New Value:',input$table$changes$changes[[1]][[4]])
})
}) # end server
shinyApp(ui = ui, server = server)
While rhandsontable is a real good implementation of handsontable (credit goes to #jrowen), currently it does not include getSelected().
The event of a user altering any cell (including selecting / deselecting a checkbox) is tracked by shiny. This gives the opportunity to use checkboxes to let the user to select (or de-select) one or more rows.
Unfortunately the logic to understand what has been selected needs to be developed on the server side by your code.
The snippet of code below may give you some idea on how to manage it.
options(warn=-1)
library(rhandsontable)
library(shiny)
options(warn=-1)
quantity <- id <- 1:20
label <- paste0("lab","-",quantity)
pick <- FALSE
iris_ <- data.frame(id=id,pick=pick, quantity=quantity,label=label,iris[1:20,] ,stringsAsFactors = FALSE)
mtcars_ <- data.frame(id=id,pick=pick, quantity=quantity,label=label,mtcars[1:20,] ,stringsAsFactors = FALSE)
iris_$Species <- NULL # i.e. no factors
#---------------------------
ui <- fluidPage(
fluidRow(
column(6,rHandsontableOutput('demTb')),
column(3,uiOutput("demSli")),
column(3, radioButtons("inButtn", label=NULL, choices= c("iris","mtcars"), selected = "iris", inline = TRUE))
)
)
server <- function(session, input, output) {
selData <- ""
output$demSli <- renderUI({
if(is.null(input$demTb) ) return()
isolate({
df_ <- hot_to_r(input$demTb)
index <- which(df_$pick==T)
if(length(index)==0) return()
labs <- iris_$label[index]
pages <- "test"
iter <- length(labs)
buttn <- 1
valLabs <- sapply(1:iter, function(i) {
if(is.null(input[[paste0(pages,"d",labs[i],buttn)]] )) {
0
} else { as.numeric(input[[paste0(pages,"d",labs[i],buttn)]]) }
})
#
toRender <- lapply(1:iter, function(i) {
sliderInput(inputId = paste0(pages,"d",labs[i],buttn),
label = h6(paste0(labs[i],"")),
min = -100,
max = 100,
step = 1,
value = valLabs[i],
post="%",
ticks = FALSE, animate = FALSE)
})
})
return(toRender)
})
#--------------------
rds <- reactive({
# if( is.null(input$demTb) ) {
if( input$inButtn == "iris") {
if(selData == "" | selData == "mtcars") {
selData <<- "iris"
return(iris_) # first time for iris
}
} else {
if(selData == "iris" ) {
selData <<- "mtcars"
return(mtcars_) # first time for mtcars
}
}
df_ <- hot_to_r(input$demTb)
isolate({
index <- which(df_$pick==T)
if(length(index)==0) return(df_)
labs <- iris_$label[index]
pages <- "test"
iter <- length(labs)
buttn <- 1
}) # end isolate
valLabs <- sapply(1:iter, function(i) {
if(is.null(input[[paste0(pages,"d",labs[i],buttn)]] )) {
0
} else {
as.numeric(input[[paste0(pages,"d",labs[i],buttn)]])/100
}
})
dft_ <- data.frame(label=labs, multi=valLabs, stringsAsFactors = FALSE)
dft_ <- merge(iris_,dft_,by="label", all.x=T)
dft_$quantity <- sapply(1:length(dft_$quantity), function(z) {
if( is.na( dft_$multi[z]) ) {
dft_$quantity[z]
} else { iris_$quantity[z]*(1 + dft_$multi[z]) }
})
dft_[with(dft_,order(as.numeric(id))),]
df_[with(df_,order(as.numeric(id))),]
df_$quantity <- df_$quantity
return(df_)
})
output$demTb <- renderRHandsontable({
if(is.null(rds() )) return()
df_ <- rds()
df_ <- df_[with(df_,order(as.numeric(id))),]
rhandsontable(df_, readOnly = FALSE, rowHeaders= NULL, useTypes= TRUE) %>%
hot_table(highlightCol = TRUE, highlightRow = TRUE)
})
}
shinyApp(ui, server)
In my shiny app I have a dynamic input using renderUI.
This works very well, and another part of the program captures the input of the sliders.
When the application changes of status (e.g. when the button "update model" is pressed) I still need to display / use sliders with similar labels but as they are "new" the value needs to be re-initialised to zero.
The problem is that the sliders have a memory. If I re-use the same inputId
paste0(Labv[i], "_v",buttn)
shiny will have the old value associated to it.
Currently my code is using the variable buttn to bypass the problem: every time the status changes I create "new" sliders.
On the other hand the more the users will use the app, the more garbage will be collected into shiny.
I tried to use renderUI to send the list of elements to NULL, experimenting with sending a list of
updateTextInput(session, paste0(lbs[i],"_v",buttn),
label = NULL, value = NULL )
or tags$div("foo", NULL) but in each case the actual variable was rendered as text, which is worst!
# Added simplified example
library(shiny)
library(data.table)
#
dt_ = data.table( Month = month.abb[1:5],
A=rnorm(5, mean = 5, sd = 4),
B=rnorm(5, mean = 5, sd = 4),
C=rnorm(5, mean = 5, sd = 4),
D=rnorm(5, mean = 5, sd = 4),
E=rnorm(5, mean = 5, sd = 4))
dt_[,id :=.I]
dt <- copy(dt_)
setkey(dt_, "Month")
setkey(dt, "Month")
shinyApp(
ui = fluidPage(
fluidRow(
column(4,
actionButton("saveButton", "Update Model"))),
fluidRow(
column(6, dataTableOutput('DT')),
column(3, br(),br(),checkboxGroupInput("pick",h6("Picker"),
month.abb[1:5])),
column(3, uiOutput('foo'))),
fluidRow(
column(4, verbatimTextOutput('vals')))
),
server = function(session,input, output) {
valPpu <- reactiveValues()
valPpu$buttonF <- 1
valPpu$dt_ <- dt_
##
output$DT <- renderDataTable({
if(length(input$pick) > 0 ) {
# browser()
isolate( { labs <- input$pick } ) #
buttn <- valPpu$buttonF
iter <- length(labs)
valLabs <- sapply(1:iter, function(i) {
as.numeric(input[[paste0(labs[i],"_v",buttn)]]) })
if( iter == sum(sapply(valLabs,length)) ) {
cPerc <- valLabs
cPerc <- as.data.table(cPerc)
cPercDt <- cbind(Month=labs,cPerc)
ival <- which(dt[["Month"]]
%in% cPercDt[["Month"]])
setkey(cPercDt, "Month")
for(j in LETTERS[1:5]) set(dt_, i=ival,
j=j, dt[cPercDt][[j]] * (1 + dt_[cPercDt][["cPerc"]]) )
valPpu$dt_ <- dt_
} }
dt_[order(id),]
}, options = list(
scrollX = TRUE,
scrollY = "250px" ,
scrollCollapse = TRUE,
paging = FALSE,
searching = FALSE,
ordering = FALSE )
)
##
output$foo <- renderUI({
if(is.null(input$saveButton)) { return() }
if(length(input$pick) > 0 ) {
labs <- input$pick
iter <- length(labs)
buttn <- isolate(valPpu$buttonF )
valLabs <- sapply(1:iter, function(i) {
if(is.null(input[[paste0(labs[i],"_v",buttn)]] )) {
0
} else { as.numeric(input[[paste0(labs[i],"_v",buttn)]]) }
})
#
toRender <- lapply(1:iter, function(i) {
sliderInput(inputId = paste0(labs[i], "_v",buttn),
label = h6(paste0(labs[i],"")),
min = -1,
max = 1,
step = 0.01,
value = valLabs[i],
# format = "##0.#%",
ticks = FALSE, animate = FALSE)
})
toRender
}
})
observe({
if(is.null(input$saveButton)) { return() }
if(input$saveButton < valPpu$buttonF) { return() }
valPpu$buttonF <- valPpu$buttonF + 1
dt <<- valPpu$dt_
# TODO: add proper saving code
})
}
)
In the actual app the checkboxGroupInput is also driven from the server with renderUI and is reset when the "update model" is pressed. Also, there are more "events" in the UI that I haven't added to the code.
Any idea?
So your current approach actually works. FWIW, the sliders have been removed from HTML, so you do not need to worry about that. For the old values stored in input, such as input[['Jan_v1']] when the button has been clicked twice (and you only need input[['Jan_v2']]), I do not see why you care so much about them unless your total memory is less than a few kilobytes, because you only need a few bytes to store these values. It is probably true that you cannot remove these values from input, but I'd suggest you not spend time on this issue until it becomes a real problem.