How do I save numeric inputs in Shiny during a session so they can be updated/overwritten by the user - r

I'm look to create some functionality in a shiny app. I would like the user to be able to -
Click on an action button that opens up a modal to enter numeric inputs (the first time, values will be 0.
The user enters numeric inputs and hits "Submit"
A new dataframe is generated with the user's inputs.
If the user clicks the action button to open the modal again, the values that they entered the first time will still show as the default values.
Here is code to generate the basic app with the action button and modal. How do i edit this code for my usecase. I feel like I need to somehow set the value in the numericInput to read from some table that continually gets overwritten, but i'm not sure how to do it.
library(shiny)
library(tidyverse)
# Define the Shiny app UI
ui <- fluidPage(
# action button
actionButton(inputId = "edit_value", label = "Edit Value"),
# table output
verbatimTextOutput("table")
)
# Define the Shiny app server
server <- function(input, output, session) {
# reactive dataframe. this could have an unknown number of rows
df <- reactive({
tibble(
id = c(1, 2, 3, 4, 5),
name = c("Item1", "Item2", "Item3", "Item4", "Item5"),
value = c(0, 0, 0, 0, 0)
)
})
# modal pop up
observeEvent(eventExpr = input$edit_value, handlerExpr = {
showModal(modalDialog(
title = "Edit Value",
lapply(df()$name, function(name) {
numericInput(
inputId = paste0(name, "_value"),
label = name,
min = 0,
value = 0
)
}),
footer = tagList(
actionButton(inputId = "submit_add_value", label = "Submit"),
modalButton("Close")
)
))
})
# capture numeric inputs in modal pop up
temp_data <- eventReactive(input$submit_add_value, {
df() %>%
select(id, name) %>%
mutate(id = as.factor(id)) %>%
distinct() %>%
mutate(new_value = map_dbl(name, function(sym) {
input[[paste0(sym, "_value")]]
}))
})
# print numeric inputs in modal pop up
output$table <- renderPrint({temp_data()})
}
# Run the Shiny app
shinyApp(ui, server)

Related

How to count number of times the selectInput box is clicked?

The below MWE code uses observeEvent() functions and reactiveVal() to track and show the number of times the selectInput() box is clicked and the number of times the actionButton() is clicked.
The actionButton() tracking works fine. Note how in output$... in the code the selectInput() and actionButton() work differently, looks weird to me.
Anyhow, I'm having trouble having selectInput() track the same as actionButton(). When the App is first invoked, the user clicking the first option rendered ("Cyl") in the selectInput() box is not counted as a click, when I would like it to count as a click. And if the same choice is clicked in the selectInput() ("Trans" for example) more than once, the clicks > 1 aren't counted as clicks when I would each click to be counted. Basically, anytime the user clicks in the selectInput() box it needs to be included as a "click". Is there any way to do this?
In the full code this matters because the selectInput() in that box triggers a removeUI() and the list of choices is dynamic and sequentially renumbered with every click.
Code:
library(shiny)
ui = fluidPage(hr(),
selectInput("selInput",label=NULL,c("Cyl"="cyl","Trans"="am","Gears"="gear"),selected=NULL),
actionButton("addBtn","Add"), hr(),
textOutput("clickSelInput"),
textOutput("clickAddBtn"),
tableOutput("data")
)
server = function(input, output) {
x = reactiveVal(0)
y = reactiveVal(0)
output$data <- renderTable({mtcars[1:10, c("mpg", input$selInput), drop = FALSE]})
observeEvent(input$selInput,{x(x()+1)})
observeEvent(input$addBtn,{y(y()+1)})
output$clickSelInput <- renderText({paste('Select Input clicks =',x()-1)})
output$clickAddBtn <- renderText({paste('Add Button clicks =',y())})
}
shinyApp(ui, server)
Adding "multiple = TRUE" to the selectInput() in this case resolves the question. Also allow the removal of the weird -1 from the out$selInput..x()-1)}) in the OP. This also works fine for the larger App this is intended for. See revised OP code with changes from OP commented (further down is the "larger App" where this functionality matters):
library(shiny)
ui = fluidPage(hr(),
selectInput("selInput",
label=NULL,
c("Cyl"="cyl","Trans"="am","Gears"="gear"),
selected=NULL,
multiple=TRUE # added this
),
actionButton("addBtn","Add"), hr(),
textOutput("clickSelInput"),
textOutput("clickAddBtn"),
tableOutput("data")
)
server = function(input, output) {
x = reactiveVal(0)
y = reactiveVal(0)
output$data <- renderTable({mtcars[1:10, c("mpg", input$selInput), drop = FALSE]})
observeEvent(input$selInput,{x(x()+1)})
observeEvent(input$addBtn,{y(y()+1)})
output$clickSelInput <- renderText({paste('Select Input clicks =',x())}) # removed the -1 from x()
output$clickAddBtn <- renderText({paste('Add Button clicks =',y())})
}
shinyApp(ui, server)
And here's the "larger App" where this functionality matters:
library(dplyr)
library(rhandsontable)
library(shiny)
rowNames1 <- c("A", "B", "C", "Sum")
DF1 <- data.frame(row.names = rowNames1, "Col 1" = c(1, 1, 0, 2), check.names = FALSE)
ui <- fluidPage(br(),
rHandsontableOutput('hottable1'),br(),
actionButton("addCol1", "Add column 1"),br(),
h5(strong("Select column to delete:")),
uiOutput("delCol1"), hr(),
textOutput("clickSelInput"),
textOutput("clickAddBtn"),
)
server <- function(input, output) {
x = reactiveVal(0)
y = reactiveVal(0)
uiTbl1 <- reactiveVal(DF1)
observeEvent(input$hottable1, {uiTbl1(hot_to_r(input$hottable1))})
output$hottable1 <- renderRHandsontable({
rhandsontable(uiTbl1(),rowHeaderWidth = 100, useTypes = TRUE)%>%
hot_context_menu(allowRowEdit = FALSE, allowColEdit = FALSE)
})
observeEvent(input$addCol1, {
newCol <- data.frame(c(1,1,0,2))
names(newCol) <- paste("Col", ncol(hot_to_r(input$hottable1)) + 1)
uiTbl1(cbind(uiTbl1(), newCol))
})
observeEvent(input$delCol1, {
tmp <- uiTbl1()
delCol <- input$delCol1
tmp <- tmp[ , !(names(tmp) %in% delCol), drop = FALSE]
newNames <- sprintf("Col %d",seq(1:ncol(tmp)))
names(tmp) <- newNames
uiTbl1(tmp)
})
output$delCol1 <-
renderUI(
selectInput(
"delCol1",
label = NULL,
choices = colnames(hot_to_r(input$hottable1)),
selected = "",
multiple = TRUE)
)
observeEvent(input$delCol1,{x(x()+1)})
observeEvent(input$addCol1,{y(y()+1)})
output$clickSelInput <- renderText({paste('Select Input clicks =',x())})
output$clickAddBtn <- renderText({paste('Add Button clicks =',y())})
}
shinyApp(ui,server)

Converting Reactive Data Table to Kable in Shiny

I am currently building a budgeting shiny application that prompts users to enter information of their past transactions such as: Amount, Type, and Description. I would like to have this information displayed in a Kable styled table in a seperate tab whenever a user hits submit, however, when I do this I get the following message and the table does not display:
"Warning: Error in as.data.frame.default: cannot coerce class ‘c("kableExtra", "knitr_kable")’ to a data.frame"
Here is what I have coded so far:
# Libraries
library(shiny)
library(ggplot2)
library(shinythemes)
library(DT)
library(kableExtra)
ui <- fluidPage(
theme = shinytheme("spacelab"),
## Application Title
titlePanel("2021 Budgeting & Finances"),
tags$em("By:"),
tags$hr(),
navbarPage("", id = "Budget",
tabPanel("Data Entry",
div(class = "outer",
# Sidebar Layout
sidebarLayout(
sidebarPanel(
selectInput("Name",
label = "Name:",
choices = c("","Jack", "Jill")),
selectInput("Bucket",
label = "Item Bucket:",
choices = c("","Essential", "Non-Essential", "Savings", "Rent/Bills", "Trip", "Other")),
textInput("Item",
label = "Item Name:",
placeholder = "Ex: McDonald's"),
shinyWidgets::numericInputIcon("Amount",
"Amount:",
value = 0,
step = 0.01,
min = 0,
max = 1000000,
icon = list(icon("dollar"), NULL)),
dateInput("Date",
label = "Date",
value = Sys.Date(),
min = "2021-05-01",
max = "2022-12-31",
format = "M-d-yyyy"),
actionButton("Submit", "Submit", class = "btn btn-primary"),
downloadButton("Download", "Download")),
# Show a plot of the generated distribution
mainPanel(
dataTableOutput("PreviewTable")
)
)
)
),
tabPanel("Monthly Budget",
tableOutput("MonthlyTable")
),
tabPanel("Budget to Date",
tableOutput("YearTable")
)
)
)
server <- function(input, output, session) {
## SAVE DATA
# Set Up Empty DF
df <- tibble("Name" = character(),
"Date" = character(),
"Category" = character(),
"Amount" = numeric(),
"Description" = character())
# DF is made reactive so we can add new lines
ReactiveDf <- reactiveVal(value = df)
# Add inputs as new data (lines)
observeEvent(input$Submit, {
if (input$Bucket == "" | input$Amount == 0 |
is.na(input$Amount)) {
return(NULL)
}
else {
# New lines are packaged together in a DF
new_lines <- data.frame(Name = as.character(input$Name),
Date = as.character(input$Date),
Category = input$Bucket,
Amount = as.character(input$Amount),
Description = as.character(input$Item))
# change df globally
df <<- rbind(df, new_lines)
# ensure amount is numeric
df <<- df %>%
mutate("Amount" = as.numeric(Amount))
# Update reactive values
ReactiveDf(df)
#clear out original inputs now that they are written to df
updateSelectInput(session, inputId = "Name", selected = "")
updateSelectInput(session, inputId = "Bucket", selected = "")
updateNumericInput(session, inputId = "Amount", value = 0)
updateTextInput(session, inputId = "Item", value = "")
}
})
## Preview Table
output$PreviewTable <- renderTable({
ReactiveDf()
})
## MONTHLY TABLE
output$MonthlyTable <- renderTable({
ReactiveDf() %>%
kbl()
})
## YEAR TO DATE TABLE
output$YearTable <- renderTable({
ReactiveDf()
})
}
# Run the application
shinyApp(ui = ui, server = server)
Ideally what I would like to have is a table preview on the main page where the user enters their information that updates once the user submits their data. Then, I would like the month tab to populate with only the data relating to the current month and the year tab to have all information for the current year. However the biggest issue currently is that the kable table is not displaying. Any help is greatly appreciated!
Kable is plain Html so it doesn't require special render functions. This should work.
## MONTHLY TABLE
output$MonthlyTable <- function(){
ReactiveDf() %>%
kable("html") %>%
kable_styling("striped", full_width = TRUE)
}

Use Shiny ActionButton to select all rows or add all rows to selection in current view with filtering in a DT datatable

I have been trying to create ActionButtons to allow a user to 'Select all rows in view' in a reactive, filtering datatable.
Currently the button does this using tableid_rows_current; however, I also want to add in a table proxy so that it doesn't reset to the first page of results if you're on another page, but I can't figure out the syntax after much googling (see attempts commented out in code). Also if you manually select some rows, it no longer works.
Another ActionButton that allows a user to 'add all rows in view to selection'. That is to add all current rows in view to your previous selection. This one I'm not even sure where to start, so any ideas are appreciated.
(Not included here, but I do have functioning 'clear selection' and 'clear filter' buttons already, if anyone is interested)
Minimum reproducible code below. The app is meant to display the images for the selected rows, but not a big deal here that you won't have actual images displaying.
library(DT)
library(shiny)
dat <- data.frame(
type = c("car", "truck", "scooter", "bike"),
frontimage = c("carf.jpg", "truckf.jpg", "scooterf.jpg", "bikef")
)
# ----UI----
ui <- fluidPage(
titlePanel("Buttons 'select all' and 'add to select'"),
mainPanel(
DTOutput("table"),
actionButton("select_all_current", "Select All Rows in View"),
actionButton("add_to_selection", "Add All Rows in View to Selection"),
uiOutput("img1")
)
)
# ----Server----
server = function(input, output, session){
# Action button to select all rows in current view
var <- reactiveValues()
tableProxy <- dataTableProxy('table')
observeEvent(input$select_all_current, {
print("select_all_current")
# tableProxy %>% selectRows(1:nrow(input$table_rows_current))
# var$selected <- tableProxy %>% input$table_rows_current
tableProxy <- #I want the table proxy to be whatever the current selection and filters are and the current page view to stay the same after selecting
var$selected <- input$table_rows_current
})
# Action button to add all rows in current view to previous selection
observeEvent(input$add_to_selection, {
print("select_all_current")
})
# Data table with filtering
output$table = DT::renderDT({
datatable(dat, filter = list(position = "top", clear = FALSE),
selection = list(target = 'row', selected = var$selected),
options = list(
autowidth = TRUE,
pageLength = 2,
lengthMenu = c(2, 4)
))
})
# Reactive call that only renders images for selected rows
df <- reactive({
dat[input[["table_rows_selected"]], ]
})
# Front image output
output$img1 = renderUI({
imgfr <- lapply(df()$frontimage, function(file){
tags$div(
tags$img(src=file, width="100%", height="100%")
)
})
do.call(tagList, imgfr)
})
}
# ----APP----
# Run the application
shinyApp(ui, server)
Does this do what you're looking for?
library(DT)
library(shiny)
dat <- data.frame(
type = c("car", "truck", "scooter", "bike"),
frontimage = c("carf.jpg", "truckf.jpg", "scooterf.jpg", "bikef")
)
# ----UI----
ui <- fluidPage(
titlePanel("Buttons 'select all' and 'add to select'"),
mainPanel(
DTOutput("table"),
actionButton("select_all_current", "Select All Rows in View"),
actionButton("add_to_selection", "Add All Rows in View to Selection"),
uiOutput("img1")
)
)
# ----Server----
server = function(input, output, session){
# Action button to select all rows in current view
var <- reactiveValues()
tableProxy <- dataTableProxy('table')
observeEvent(input$select_all_current, {
print("select_all_current")
# tableProxy %>% selectRows(1:nrow(input$table_rows_current))
# var$selected <- tableProxy %>% input$table_rows_current
# tableProxy <- #I want the table proxy to be whatever the current selection and filters are and the current page view to stay the same after selecting
# var$selected <- input$table_rows_current
selectRows(proxy = tableProxy,
selected = input$table_rows_current)
})
# Action button to add all rows in current view to previous selection
observeEvent(input$add_to_selection, {
print("select_all_current")
selectRows(proxy = tableProxy,
selected = c(input$table_rows_selected, input$table_rows_current))
})
# Data table with filtering
output$table = DT::renderDT({
datatable(dat, filter = list(position = "top", clear = FALSE),
selection = list(target = 'row'),#, selected = var$selected),
options = list(
autowidth = TRUE,
pageLength = 2,
lengthMenu = c(2, 4)
))
})
# Reactive call that only renders images for selected rows
df <- reactive({
dat[input[["table_rows_selected"]], ]
})
# Front image output
output$img1 = renderUI({
imgfr <- lapply(df()$frontimage, function(file){
tags$div(
tags$img(src=file, width="100%", height="100%")
)
})
do.call(tagList, imgfr)
})
}
# ----APP----
# Run the application
shinyApp(ui, server)

Dynamically update position of sliderInput in Shiny

I have the following code to dynamically make either Check Boxes or Sliders.
server <- shinyServer(function(input, output, session) {
# define the data frame to use
dat <- mtcars
dat <- rownames_to_column(dat, "car")
# name of availale data frame
varNames <- names(dat)
# define defaul values as the first value in each column
defaultValues <- as.numeric(dat[1,])
# store the selected variable in a reactive variable
# dynamically creates a set of sliders
output$controls <- renderUI({
div(
fluidRow(
column(9, uiOutput("rangeUI"))
)
)
})
output$rangeUI <- renderUI({
lapply(1:length(varNames), function(k) {
fluidRow(
column(12,
if (is_character(dat[1, k])) {
# a slider range will created only is the variable is selected
checkboxGroupInput(paste0("slider_", varNames[k]), label = varNames[k], choices = unique(dat[[k]]), selected = NULL,
inline = FALSE, width = NULL, choiceNames = NULL, choiceValues = NULL)
} else {
# otherwise uses single value with a default value
sliderInput(paste0("slider_", varNames[k]), label = varNames[k],
min = 0, max = 100, value = defaultValues[k])
}
)
)
})
})
The issue I am running into is that I would like to display the sliders and check boxes side by side until they hit the screen width and then start a new row. Currently, they are all in one column.
Is there a good way to dynamically adjust offset to accomplish this, maybe something like this?
column(12, offset = match(k, colnames(dat)), # then lead into the if else statement
Any other suggestions on building the UI are welcome.
Try to put the fluidRow outside the lapply and change the size of the column from 12 to maybe 3, otherwise you are creating multiple rows with only one column, instead on one row with multiple columns.
Below is your code modified, maybe it could help you.
library(shiny)
library(tibble)
ui <- fluidPage(
uiOutput("controls")
)
server <- shinyServer(function(input, output, session) {
# define the data frame to use
dat <- mtcars
dat <- rownames_to_column(dat, "car")
# name of availale data frame
varNames <- names(dat)
# define defaul values as the first value in each column
defaultValues <- as.numeric(dat[1,])
# store the selected variable in a reactive variable
# dynamically creates a set of sliders
output$controls <- renderUI({
fluidRow(
column(offset = 3, 9, uiOutput("rangeUI"))
)
})
# to test that a dynamically created input works with an observer
observeEvent(input$slider_mpg, {
cat("slider_mpg:", input$slider_mpg, "\n")
})
output$rangeUI <- renderUI({
fluidRow(
lapply(1:length(varNames), function(k) {
column(3,
if (is.character(dat[1, k])) {
# a slider range will created only is the variable is selected
checkboxGroupInput(paste0("slider_", varNames[k]), label = varNames[k], choices = unique(dat[[k]]), selected = NULL,
inline = FALSE, width = NULL, choiceNames = NULL, choiceValues = NULL)
} else {
# otherwise uses single value with a default value
sliderInput(paste0("slider_", varNames[k]), label = varNames[k],
min = 0, max = 100, value = defaultValues[k])
}
)
})
)
})
})
shinyApp(ui = ui, server = server)
Update:
You can get the values of dynamically created inputs by using an action button as is explained here or get them automatically by using the solution explained here.

Bootstrap Modal Multiple Conditions R Shiny

I need to only display a BS modal when a button is pressed and and a condition on a variable is met.
This is a simple app that demonstrates what the challenge is. I need to display a BS modal when num_rows >= 500, and the submit button is fired, not just when the submit button is fired.
I am aware this could be done with a conditionalPanel using input.slider as one of the conditions, but in my real project it is much more complicated than this, and the BS modal/conditional panel needs to depend on both a button (user input) and a variable assigned in the server.
library(shiny)
library(shinyBS)
data = matrix(rnorm(1000*10, 0, 1), nrow = 1000)
ui <- fluidPage(
fluidRow(
column(width = 4,
sliderInput("slider", "Choose Number of Rows to Display", 0, 1000, value = NULL),
submitButton('Submit'),
bsModal("modalExample", "Yes/No", "submit", size = "small", wellPanel(
p(div(HTML("<strong>Warning: </strong> you have chosen to display a large
number of rows. Are you sure you want to proceed?"))),
actionButton("no_button", "Yes"),
actionButton("yes_button", "No")
))
),
column(width = 8,
tableOutput('data')
)
)
)
server <- shinyServer(function(input, output, server){
observe({
num_rows <- input$slider
if(num_rows >= 500){
#
# ACTIVATE MODAL PANEL
#
observeEvent(input$no_button, {
# Do not show table
})
observeEvent(input$yes_button, {
output$table <- renderTable(data)
})
} else{ # Display table normally if number of rows is less than 500
output$table <- renderTable(data)
}
})
})
shinyApp(ui, server)
Have a look at the following code. I disabled the action button if num_rows<500 with the package shinyjs. If num_rows>=500 the action button becomes available to trigger the popup. To update the number of rows selected with the slider you'll have to press the submit button every time. Hope this helps or gets you some ideas. For now I have not implemented your warning message (that did not work for me). Another issue: the slider and display for the pop up only work towards increasing number of rows, not decreasing afterwards. If you find a solution for that, pls share =)
library(shiny)
library(shinyBS)
library(shinyjs)
data = matrix(rnorm(1000*10, 0, 1), nrow = 1000)
data1=data[(1:500),]
head(data)
ui <- fluidPage(
fluidRow(
column(width = 4,
sliderInput("slider", "Choose Number of Rows to Display", 0, 1000, value = NULL),
submitButton('Submit'),
actionButton('Show','Show'),
useShinyjs(),
bsModal("modalExample",'Yes/No','Show', size = "large",tableOutput("tab")
# wellPanel(
# p(div(HTML("<strong>Warning: </strong> you have chosen to display a large
# number of rows. Are you sure you want to proceed?")
# )))
)),
column(width = 8,tableOutput('table'))))
server <- function(input, output)({
observe({
num_rows = input$slider
if(num_rows<500 &num_rows!=0) {
shinyjs::disable('Show')
output$table <- renderTable({
data = data1[(1:num_rows),]
print(head(data1))
data})
}else{
shinyjs::enable('Show')
output$tab = renderTable({
data = data[(1:num_rows),]
data}) }
})
})
shinyApp(ui, server)

Resources