ID for rhandsontable inside modal in R Shiny - r

My goal is to display an (conditional) editable rhandsontable inside a modal by clicking an actionbutton. Creating and displaying the table worked fine, however I'm struggling on how to properly assign an ID in the ui for the table in order to recognize user changes.
Any help is appreciated, thank you :)
library(shiny)
ui <- fluidPage(
actionButton('openModal', "Open Modal")
# rHandsontableOutput("DataEditor") # doesn't work
)
server <- function(input, output, session) {
observeEvent(input$openModal, {
irrelevant_condition <- FALSE # include that rhandsontable doesn't have to appear always
if (irrelevant_condition == TRUE) {
return(showModal(modalDialog("Choose some variables to display first")))
} else {
# display rhandsontable if user made a valid choice
showModal(modalDialog(
updDataEditor()
))
}
})
updDataEditor <- function() {
output$DataEditor <- renderRHandsontable({
# in real app some conditional calculations leading to a DF called 'current.DF'
# why function?: in real app with variation, depending on some inputs the user chose
current.DF <- data.frame(Name = c("Name1", "Name2"), value1 = c(0,0), value2=c(0,0)) # example df
rhandsontable(current.DF)
})
}
observeEvent(input$DataEditor, {
# Here's the problem
# won't get called when DataEditor is modified by the user
browser()
return()
})
}
shinyApp(ui,server)

This works like this:
library(shiny)
library(rhandsontable)
ui <- fluidPage(
actionButton('openModal', "Open Modal")
)
server <- function(input, output, session) {
current.DF <- reactive(
data.frame(Name = c("Name1", "Name2"), value1 = c(0,0), value2=c(0,0))
)
observeEvent(input$openModal, {
irrelevant_condition <- FALSE # include that rhandsontable doesn't have to appear always
if (irrelevant_condition == TRUE) {
return(showModal(modalDialog("Choose some variables to display first")))
} else {
# display rhandsontable if user made a valid choice
showModal(modalDialog(
rHandsontableOutput("DataEditor")
))
}
})
output$DataEditor <- renderRHandsontable({
rhandsontable(current.DF())
})
observeEvent(input$DataEditor, {
print(input$DataEditor$changes)
})
}
shinyApp(ui,server)
Is it what you want? For the dataframe which can change I would use a reactive value. If only some cells are changed you can use the set_data function, better.
EDIT Solving the problem raised in the comments:
output$DataEditor <- renderRHandsontable({
rhandsontable(current.DF()) %>% htmlwidgets::onRender(
"function(el){var hot = this.hot; setTimeout(function(){hot.render();}, 1000)}"
)
})

Related

How run a shiny app inside of a function?

I would like to run a function that has a shiny app inside, but I can't.
Running this example separately, I first remove column one from my input data frame; then I run shiny to change whatever is necessary in the data frame and, when I close the window, a new object is saved with the changes; and finally I create a new column in the data frame.
This is an example script, but I would like that, when executing the function, the shiny window opens and some things are changed in the data frame for the user interactively. Could someone help?
library(shiny)
library(rhandsontable)
my_function <- function(x){
select <- x[,-1]
ui <- fluidPage(
fluidRow(
column(
width = 12,
rHandsontableOutput("myTable")
)))
server <- function(input, output, session) {
# dummy dataframe
df = select
# convert it to a "rhansontable" object
output$myTable <- renderRHandsontable({rhandsontable(df)
})
observeEvent(input$myTable, {
test_df = hot_to_r(input$myTable)
assign('my_data_frame',test_df,envir=.GlobalEnv)
# browser() # uncomment for debugging
})
}
shinyApp(ui, server)
my_data_frame2 <- my_data_frame %>%
mutate(new_column_test = "hello")
return(my_data_frame2)
}
my_function(mtcars)
Hi you almost made it you don't want to return anything but add the data simply using assign
library(shiny)
library(rhandsontable)
myapp_function <- function(data) {
ui <- basicPage(
actionButton("quit", label = "Close"),
actionButton("create", label = "Create copy"),
textInput("name","Set dataframe name", value = "my_data_frame"),
rHandsontableOutput("myTable")
)
server <- function(input, output, session) {
output$myTable <- renderRHandsontable({
rhandsontable(data)
})
observeEvent(input$create, {
assign( input$name, hot_to_r(input$myTable), envir=.GlobalEnv)
})
observeEvent(input$quit,{
stopApp()
})
}
## launch app
shinyApp(ui, server,options=c(shiny.launch.browser = .rs.invokeShinyPaneViewer))
}
## test
myapp_function(iris)
myapp_function(mtcars)
myapp_function(PlantGrowth)
I would suggest to create the ui and server outside of the myapp_function - otherwise it will become a very large function...also creating a function inside another function is not the best practise.

SHINY Summarise info based on sheet selected by user after uploading file

My goal is that user uploads an Excel file. Then, the user selects which sheets wants to be summarised, after the selection has been updated. I have managed to update selectInput with the name of the sheets but I have not been able to find\understand how to summarise based on what the sheet selected by the user. Thanks for any help.
library(shiny)
library(shinythemes)
library(data.table)
library(ggplot2)
library(dplyr)
library(readxl)
not_sel <- "Not Selected"
# Define UI for application that draws a histogram
ui <- fluidPage('MAIN TITLE',
theme = shinytheme('flatly'),
tabsetPanel(
sidebarLayout(
sidebarPanel(
fileInput('files','Import File', accept = c('.csv','.xlsx'),
multiple = F),
actionButton('boton1', 'Load', icon = icon('table')),
br(),
selectInput("indices", "Select SHEET:", choices = c(not_sel))
),
mainPanel(
tabsetPanel(
tabPanel('Data',
tableOutput('tabla'),
tableOutput('cabeza')),
tabPanel('Stats',
# selectInput('var01', 'Variable to summarise', choices = c(not_sel)),
tableOutput('stats')),
)
)
)
)
)
##############
server <- function(input, output, session) {
options(shiny.maxRequestSize=10*1024^2)
df <- eventReactive(input$boton1, {
req(input$files)
if(is.null(input$files))return(NULL)
# else{
read_excel(input$files$datapath)
# }
})
# Sheets of file uploaded
sheets_name <- reactive({
if (!is.null(input$files)) {
return(excel_sheets(path = input$files$datapath))
} else {
return(NULL)
}
})
# Update inputSelector with sheet names
observeEvent(df(),{
choices <- c(sheets_name())
updateSelectInput(inputId = "indices", choices = choices)
})
# DATA Tab
## This will show the name of the file
output$tabla <- renderTable({
input$files$name
})
## This Shows the head() but it is only showing the first sheet
output$cabeza <- renderTable({
tabla <- as_tibble(bind_cols(Date = format(as.Date(df()$Date),"%Y-%m-%d"),
Close.Price = df()$Close))
head(tabla)
})
# HERE is where I do not know how to calculate based on selection
# Table for STATS
output$stats <- renderTable({
datos <- df()
Value <- c(round(mean(datos$Close,na.rm = T),2)
)
Statistic <- c("Mean")
data.table(Statistic, Value)
})
}
# Run the application
shinyApp(ui = ui, server = server)
I want to assume that by knowing how to calculate mean based on the sheet selected, I. can replicate the code for the top rows (head()) shown in the Data Panel.
If I missed a similar question asked, I would appreciate any link and I will try the solution proposed first.
As I cannot share the file, this is how the file would look:
After working with this answer I made my app work. If there is a 'cleaner'/'better' answer, I will be happy to read.
Following the recommendation in the linked answer my server ended up like this:
ui <-fluidPage{
#My UI stayed the same with the exception of adding
uiOutput("dropdownUI") #Whererever I needed to appear
}
server <- function(input, output, session) {
...ANSWER FROM THE LINK...
## STATS Tab
output$stats <- renderTable({
Values <- c(round(mean(Dat()[,2],na.rm = T),2)
)
Statistics <- c("Mean")
data.table(Statistics, Values)
})
}

How to save input to data frame, and use it later in Shiny?

I want to save the value from username input if it doesn't exist in data frame, and render text if it already exists (for reprex purpose).
Rendering text part works perfectly, but I don't know how to save it and use it later.
I want to save the value permanently, not only on current session
I've got this error:
Warning: Error in <-: invalid (NULL) left side of assignment
library(shiny)
ui <- fluidPage(
textInput("username", "username"),
actionButton("save", "Save!"),
textOutput("confirmation")
)
server <- function(input, output, session) {
df <- reactive(data.frame(column1 = "user1"))
exist <- reactive(input$username %in% df()$column1)
observeEvent(input$save, {
if (exist() == TRUE) {
output$confirmation <- renderText("Username already exists!")
} else {
df() <- rbind(df(), input$username) # <-- THIS dosn't work
}
})
}
shinyApp(ui, server)
EDIT:
Thanks to #I_O answer, I figured out this solution
reactiveVal() keep the changes in current session.
write_csv() and read_csv() part, allows app to use previously saved usernames.
saved_df <- read_csv("C:\\Users\\Przemo\\Documents\\R\\leaRn\\Shiny\\Moodtracker\\testers\\test_safe.csv")
ui <- fluidPage(
textInput("username", "username"),
actionButton("save", "Save!"),
textOutput("confirmation")
)
server <- function(input, output, session) {
df <- reactiveVal(saved_df)
exist <- reactive(input$username %in% df()$column1)
observeEvent(input$save, {
if (exist() == TRUE) {
output$confirmation <- renderText("Username already exists!")
} else {
output$confirmation <- renderText("")
df(rbind(df(), input$username))
write_csv(df(), "C:\\Users\\Przemo\\Documents\\R\\leaRn\\Shiny\\Moodtracker\\testers\\test_safe.csv")
}
})
}
shinyApp(ui, server)

Update a variable in R Shiny

I would need some help with the missing code here:
selectInput("portfolio",
"Portfolio:",
c("p1","p2"))
## missing code:
## if input$portfolio == "p1" do a bunch of calculations and spit out the variable var (a tibble).
# variable var goes into a reactiveVal...
table <- reactiveVal()
table(var)
On the server you can, set table (not a great name, perhaps use something else, like my_table? to reactiveValues(), and then observe for changes in input$portfolio
table <- reactiveValues(var=NULL)
observeEvent(input$portfolio, {
if(input$portfolio == "p1") {
table$var = <- someFunction()
}
})
Here is a full example, using mtcars
library(shiny)
ui <- fluidPage(
selectInput("make","Make:", choices = rownames(mtcars)),
tableOutput("subtable")
)
server <- function(input, output, session) {
subtable <- reactiveValues(var=NULL)
observeEvent(input$make, {
subtable$var = dplyr::filter(cbind(makes,mtcars), makes == input$make)
})
output$subtable <- renderTable(subtable$var)
}
shinyApp(ui, server)

Shiny DTedit, show or hide insert/new button based on rows_selected status in second DTedit table

I have two DTedit tables which are functionally related
I do not want users to get the Insert/New button in DT#2 when no row is selected in DT#1
I have Table1_Results$rows_selected to test if selection exists (length>0)
I also identified the id of the 'New button' in DT#2 as being Table2_add
But do not succeed to make the length of Table1_Results$rows_selected trigger the shinyjs show() or hide() action for DT#2
Could anyone please share some reactivity command to do this!
the following code is not working but illustrates my aim
observe(Table1_Results$rows_selected,{
if (length(Table1_Results$rows_selected)) {
shinyjs::show('Table2_add')
} else {
shinyjs::hide('Table2_add')
}
})
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.)
This manual test using a button works
observeEvent(input$showhide, {
toggle('Table2_add')
})
So it is really the reactive testing of the Table1_Results$rows_selected which is lacking
Thanks in advance
In the code below:
I cannot clear the selected row in the observed textoutput
I do not succeed to hide the New button
Note: I use DTedit because it allows other features not shown here
AIMs:
1) when no drink is selected, hide the New button for containers
2) manage <table>$rows_selected so that it reflects the current status
library("shiny")
library("shinyjs")
library("DT")
library("DTedit")
server <- function(input, output) {
Drink_Results <- dtedit(
input, output,
name = 'Drink',
thedata = data.frame(
ID = c(1:3),
drink = c('Tea', 'Coffea', 'Water'),
stringsAsFactors = FALSE
)
)
# create proxy to clear row selection (found 'Drinkdt' by looking in the source)
Drink_proxy <- DT::dataTableProxy('Drinkdt')
Container_Results <- dtedit(
input, output,
name = 'Container',
thedata = data.frame(
ID = c(1:3),
Container = c('Cup', 'Glass', 'Pint'),
stringsAsFactors = FALSE
)
)
# create proxy to clear row selection
Container_proxy <- DT::dataTableProxy('Container')
# manually toggle visibility for New button
observeEvent(input$showhide, {
shinyjs::toggle('Container_add')
})
# clear Drink row selection
observeEvent(input$clearrows, {
Drink_proxy %>% selectRows(NULL)
})
# when no drink is selected, hide the New button for containers
observeEvent(Drink_Results$rows_selected, {
if ( length(Drink_Results$rows_selected) ) {
shinyjs::show('Container_add')
} else {
shinyjs::hide('Container_add')
}
})
# attempt to react on clearing the row-selection
choice <- reactive({
paste0(Drink_Results$rows_selected, " - ", Container_Results$rows_selected)
})
# output current combination
output$choice <- renderText({ as.character(choice()) })
}
ui <- tagList(useShinyjs(),
fluidPage(
shinyFeedback::useShinyFeedback(),
h3('What will you drink?'),
uiOutput('Drink'),
# manually clear row selections
actionButton(inputId="clearrows", label="clear selected drink", icon=icon('trash')),
hr(),
h3("What container do you prefer?"),
uiOutput('Container'),
hr(),
# manually hide the New button
actionButton(inputId="showhide", label="toggle New buttons", icon=icon('refresh')),
hr(),
# show current user choices
textOutput('choice'),
)
)
shinyApp(ui = ui, server = server)
The reactive for selected row is input$Drinkdt_rows_selected in your case, based on the source code. If you use that, your code works fine. Try this
server <- function(input, output) {
## could not install DTedit. So, made a copy of the function
source("C:\\RStuff\\GWS\\dtedit.R", local=TRUE)
Drink_Results <- dtedit(
input, output,
name = 'Drink',
thedata = data.frame(
ID = c(1:3),
drink = c('Tea', 'Coffea', 'Water'),
stringsAsFactors = FALSE
)
)
name <- "Drink"
# create proxy to clear row selection (found Drinkdt by looking in the source)
Drink_proxy <- DT::dataTableProxy('Drinkdt')
Container_Results <- dtedit(
input, output,
name = 'Container',
thedata = data.frame(
ID = c(1:3),
Container = c('Cup', 'Glass', 'Pint'),
stringsAsFactors = FALSE
)
)
# create proxy to clear row selection
Container_proxy <- DT::dataTableProxy('Container')
# clear Drink row selection
observeEvent(input$clearrows, {
Drink_proxy %>% selectRows(NULL)
shinyjs::hide('Container_add')
})
sel <- reactive({!is.null(input[[paste0(name, 'dt_rows_selected')]])})
observe({
print(sel())
print(input$Drinkdt_rows_selected)
})
# when no drink is selected, hide the New button for containers
observe({
#observeEvent(input[[paste0(name, 'dt_rows_selected')]], {
if ( length(input[[paste0(name, 'dt_rows_selected')]])>0 ) {
shinyjs::show('Container_add')
}else {
shinyjs::hide('Container_add')
}
})
observeEvent(Drink_Results$thedata, {
message(Drink_Results$thedata)
})
observeEvent(input[[paste0(name, 'dt_rows_selected')]], ignoreNULL = FALSE, {
# 'no' (NULL) row will be 'selected' after each edit of the data
message(paste("Selected row:", input[[paste0(name, 'dt_rows_selected')]]))
})
# attempt to react on clearing the row-selection
choice <- reactive({
if (is.null(input[[paste0(name, 'dt_rows_selected')]])) {
paste0("Drink not selected")
}else {
paste0(input[[paste0(name, 'dt_rows_selected')]], " - ", input$Containerdt_rows_selected)
}
})
observeEvent(input$showhide, {
toggle('Container_add')
})
# output current combination
output$choice <- renderText({ choice() })
}
ui <- fluidPage(
shinyFeedback::useShinyFeedback(),
useShinyjs(),
h3('What will you drink?'),
uiOutput('Drink'),
# manually clear row selections
actionButton(inputId="clearrows", label="clear selected drink", icon=icon('trash')),
hr(),
h3("What container do you prefer?"),
uiOutput('Container'),
hr(),
# manually hide the New button
actionButton(inputId="showhide", label="toggle New buttons", icon=icon('refresh')),
hr(),
# show current user choices
textOutput('choice'),
)
shinyApp(ui = ui, server = server)
btw - it should be mentioned that the original code was not using the jbryer version of DTedit (v1.0.0) , which does not return $rows_selected. The modified DavidPatShuiFong version of DTedit (v 2.2.3+) does return $rows_selected.
The original code presented above used an observeEvent which, by default, has ignoreNULL = TRUE. That doesn't work, because if no row is selected, then $rows_selected will return NULL.
One option is to set ignoreNULL = FALSE. Unfortunately, this still leaves the problem that shinyjs::hide does not work on first execute, perhaps because 'Container_add' does not yet exist on first pass. Adding an invalidateLater which only executes a few times fixes that problem.
library("shiny")
library("shinyjs")
library("DT")
library("DTedit")
server <- function(input, output, session) {
Drink_Results <- dtedit(
input, output,
name = 'Drink',
thedata = data.frame(
ID = c(1:3),
drink = c('Tea', 'Coffea', 'Water'),
stringsAsFactors = FALSE
)
)
# create proxy to clear row selection (found 'Drinkdt' by looking in the source)
Drink_proxy <- DT::dataTableProxy('Drinkdt')
Container_Results <- dtedit(
input, output,
name = 'Container',
thedata = data.frame(
ID = c(1:3),
Container = c('Cup', 'Glass', 'Pint'),
stringsAsFactors = FALSE
)
)
# create proxy to clear row selection
Container_proxy <- DT::dataTableProxy('Container')
# manually toggle visibility for New button
observeEvent(input$showhide, {
shinyjs::toggle('Container_add')
})
# clear Drink row selection
observeEvent(input$clearrows, ignoreNULL = FALSE, {
Drink_proxy %>% selectRows(NULL)
shinyjs::hide('Container_add')
})
# when no drink is selected, hide the New button for containers
invalidateCount <- reactiveVal(0)
observe({
# need to execute this observe more than once
# (?because 'Container_add' does not actually exist first time?)
if (isolate(invalidateCount()) < 1) {
shiny::invalidateLater(200, session) # 200ms delay
}
isolate(invalidateCount(invalidateCount() + 1))
print(paste0("row selected:", Drink_Results$rows_selected))
if (!is.null(Drink_Results$rows_selected)) {
shinyjs::show('Container_add')
} else {
shinyjs::hide('Container_add')
}
})
}
ui <- tagList(useShinyjs(),
fluidPage(
h3('What will you drink?'),
uiOutput('Drink'),
# manually clear row selections
actionButton(inputId="clearrows", label="clear selected drink", icon=icon('trash')),
hr(),
h3("What container do you prefer?"),
uiOutput('Container'),
hr(),
# manually hide the New button
actionButton(inputId="showhide", label="toggle New buttons", icon=icon('refresh')),
hr(),
# show current user choices
textOutput('choice'),
)
)
shinyApp(ui = ui, server = server)

Resources