So I am trying to make a shiny app that acts as a calculator. So the basic idea is built on the DT edit function which I found here. As you can see the screenshot below once the user clicks on the save button I would like to update the values for the column TotalReach which is nothing but impressions/frequency. I was trying to do it under input$Updated_trich. But when I do it I get this error Warning: Error in function_list[[k]]: attempt to apply non-function.
What could I be doing to fix this. Below is the code
server
library(shiny)
library(shinyjs)
## shinysky is to customize buttons
library(shinysky)
library(DT)
library(data.table)
library(lubridate)
library(shinyalert)
rm(list = ls())
useShinyalert()
shinyServer(function(input, output, session){
### interactive dataset
vals_trich<-reactiveValues()
vals_trich$Data<-data.frame(Partner = c("Brand1", "Brand2","Brand3"),
Impressions = c(2000, 3000, 4000),
TotalReach = c (0, 0, .0),
Frequency = c(2, 3, 4),
Assumptions = c (.5, .5, .5),
pcReach = c (0, 0, 0),
#gg = c (.5, .5, .5),
stringsAsFactors = FALSE)
#vals_trich$Data<-readRDS("note.rds")
#### MainBody_trich is the id of DT table
output$MainBody_trich<-renderUI({
fluidPage(
hr(),
column(6,offset = 6,
HTML('<div class="btn-group" role="group" aria-label="Basic example" style = "padding:10px">'),
### tags$head() This is to change the color of "Add a new row" button
tags$head(tags$style(".butt2{background-color:#231651;} .butt2{color: #e6ebef;}")),
div(style="display:inline-block;width:30%;text-align: center;",actionButton(inputId = "Add_row_head",label = "Add", class="butt2") ),
tags$head(tags$style(".butt4{background-color:#4d1566;} .butt4{color: #e6ebef;}")),
div(style="display:inline-block;width:30%;text-align: center;",actionButton(inputId = "mod_row_head",label = "Edit", class="butt4") ),
tags$head(tags$style(".butt3{background-color:#590b25;} .butt3{color: #e6ebef;}")),
div(style="display:inline-block;width:30%;text-align: center;",actionButton(inputId = "Del_row_head",label = "Delete", class="butt3") ),
### Optional: a html button
# HTML('<input type="submit" name="Add_row_head" value="Add">'),
HTML('</div>') ),
column(12,dataTableOutput("Main_table_trich")),
tags$script("$(document).on('click', '#Main_table_trich button', function () {
Shiny.onInputChange('lastClickId',this.id);
Shiny.onInputChange('lastClick', Math.random()) });")
)
})
#### render DataTable part ####
output$Main_table_trich<-renderDataTable({
DT=vals_trich$Data
datatable(DT,selection = 'single',
escape=F) })
observeEvent(input$Add_row_head, {
### This is the pop up board for input a new row
showModal(modalDialog(title = "Add a new row",
textInput(paste0("partner", input$Add_row_head), "Partner"),
numericInput(paste0("impressions", input$Add_row_head), "Impressions",0),
numericInput(paste0("reach", input$Add_row_head), "TotalReach:",0),
numericInput(paste0("frequency", input$Add_row_head), "Frequency:",0),
numericInput(paste0("assumption", input$Add_row_head), "Assumptions:",0),
numericInput(paste0("reach_pc", input$Add_row_head), "pcReach:",0),
actionButton("go", "Add item"),
easyClose = TRUE, footer = NULL ))
})
### Add a new row to DT
observeEvent(input$go, {
new_row=data.frame(
Partner=input[[paste0("partner", input$Add_row_head)]],
Impressions=input[[paste0("impressions", input$Add_row_head)]],
TotalReach=input[[paste0("reach", input$Add_row_head)]],
Frequency=input[[paste0("frequency", input$Add_row_head)]],
Assumptions=input[[paste0("assumption", input$Add_row_head)]],
pcReach=input[[paste0("reach_pc", input$Add_row_head)]]
)
vals_trich$Data<-rbind(vals_trich$Data,new_row )
removeModal()
})
observe({
# We'll use these multiple times, so use short var names for
# convenience.
c_num <- input$control_num
# Change the value
updateNumericInput(session, "inNumber", value = c_num)
})
### save to RDS part
observeEvent(input$Updated_trich,{
print(vals_trich$Data)
calc<- vals_trich$Data
print(calc)
calc <-calc %>% (calc$TotalReach = calc$Impressions/calc$Frequency)
print(calc)
vals_trich$Data <-calc
DT=vals_trich$Data
datatable(DT,selection = 'single',
escape=F)
saveRDS(vals_trich$Data, "op.rds")
shinyalert(title = "Saved!", type = "success")
})
### delete selected rows part
### this is warning messge for deleting
observeEvent(input$Del_row_head,{
showModal(
if(length(input$Main_table_trich_rows_selected)>=1 ){
modalDialog(
title = "Warning",
paste("Are you sure delete",length(input$Main_table_trich_rows_selected),"rows?" ),
footer = tagList(
modalButton("Cancel"),
actionButton("ok", "Yes")
), easyClose = TRUE)
}else{
modalDialog(
title = "Warning",
paste("Please select row(s) that you want to delect!" ),easyClose = TRUE
)
}
)
})
### If user say OK, then delete the selected rows
observeEvent(input$ok, {
vals_trich$Data=vals_trich$Data[-input$Main_table_trich_rows_selected]
removeModal()
})
### edit button
observeEvent(input$mod_row_head,{
showModal(
if(length(input$Main_table_trich_rows_selected)>=1 ){
modalDialog(
fluidPage(
h3(strong("Modification"),align="center"),
hr(),
dataTableOutput('row_modif'),
actionButton("save_changes","Save changes"),
tags$script(HTML("$(document).on('click', '#save_changes', function () {
var list_value=[]
for (i = 0; i < $( '.new_input' ).length; i++)
{
list_value.push($( '.new_input' )[i].value)
}
Shiny.onInputChange('newValue', list_value) });")) ), size="l" )
}else{
modalDialog(
title = "Warning",
paste("Please select the row that you want to edit!" ),easyClose = TRUE
)
}
)
})
#### modify part
output$row_modif<-renderDataTable({
selected_row=input$Main_table_trich_rows_selected
old_row=vals_trich$Data[selected_row]
row_change=list()
for (i in colnames(old_row))
{
if (is.numeric(vals_trich$Data[[i]]))
{
row_change[[i]]<-paste0('<input class="new_input" value= ','"',old_row[[i]],'"',' type="number" id=new_',i,' ><br>')
}
else if( is.Date(vals_trich$Data[[i]])){
row_change[[i]]<-paste0('<input class="new_input" value= ','"',old_row[[i]],'"',' type="date" id=new_ ',i,' ><br>')
}
else
row_change[[i]]<-paste0('<input class="new_input" value= ','"',old_row[[i]],'"',' type="textarea" id=new_',i,'><br>')
}
row_change=as.data.table(row_change)
setnames(row_change,colnames(old_row))
DT=row_change
DT
},escape=F,options=list(dom='t',ordering=F,scrollX = TRUE),selection="none" )
### This is to replace the modified row to existing row
observeEvent(input$newValue,
{
newValue=lapply(input$newValue, function(col) {
if (suppressWarnings(all(!is.na(as.numeric(as.character(col)))))) {
as.numeric(as.character(col))
} else {
col
}
})
DF=data.frame(lapply(newValue, function(x) t(data.frame(x))))
colnames(DF)=colnames(vals_trich$Data)
vals_trich$Data[input$Main_table_trich_rows_selected]<-DF
}
)
### This is nothing related to DT Editor but I think it is nice to have a download function in the Shiny so user
### can download the table in csv
output$Trich_csv<- downloadHandler(
filename = function() {
paste("Trich Project-Progress", Sys.Date(), ".csv", sep="")
},
content = function(file) {
write.csv(data.frame(vals_trich$Data), file, row.names = F)
}
)
})
ui
#
# This is the user-interface definition of a Shiny web application. You can
# run the application by clicking 'Run App' above.
#
# Find out more about building applications with Shiny here:
#
# http://shiny.rstudio.com/
#
library(shiny)
library(shinyjs)
library(shinysky)
library(DT)
library(data.table)
library(lubridate)
library(shinyalert)
useShinyalert()
# Define UI for application that draws a histogram
shinyUI(fluidPage(
# Application title
titlePanel("Calculator"),
### This is to adjust the width of pop up "showmodal()" for DT modify table
tags$head(tags$style(HTML('
.modal-lg {
width: 1200px;
}
'))),
# helpText("Note: Remember to save any updates!"),
br(),
### tags$head() is to customize the download button
numericInput("inNumber", "Number input:",
min = 1, max = 330000000, value = 20000000, step = 1000000),
useShinyalert(), # Set up shinyalert
uiOutput("MainBody_trich"),actionButton(inputId = "Updated_trich",label = "Save"),
tags$head(tags$style(".butt{background-color:#230682;} .butt{color: #e6ebef;}")),br(),
downloadButton("Trich_csv", "Download in CSV", class="butt"),
))
The error appears to stem from the usage of piping in this line:
calc <-calc %>% (calc$TotalReach = calc$Impressions/calc$Frequency)
Adding library(dplyr)
to the attached libraries and changing the line to
calc <-calc %>%
mutate(TotalReach = Impressions/Frequency)
allows proper saving to occur.
Related
I am new to shiny and am currently trying to develop my first shinyapp.
This apps contains multiple actionButtons and nested observeEvents statements, which I think are the cause of my problem.
The app should allow the user to add observations of species by clicking on a add button, that updates the UI. Within each observation, more details can be asked, but I only showed the species name in the REPREX below (textinput).
Each observation can be deleted individually via a delete button.
Until here, it works! However, I also want a modal dialog to confirm the deletion when the delete button is clicked. To do this, I used a nested observeEvent and it doesn't seem to work (or maybe only for the first time). What am I doing wrong ?
Thanks in advance to anyone who tries to help me.
library(shiny)
library(random)
ui <- fluidPage(
fluidRow(br(), br(), actionButton("adder",
label = "Add an observation"),
align="center")
)
server <- function(input, output,session) {
rv <- reactiveValues()
rv$GridId_list <- c()
observeEvent(input$adder,{
# create random ID for each added species
GridId <- as.character(randomStrings(1, 10))
# store the new ID
rv$GridId_list <- c(rv$GridId_list,GridId)
# ID for the textinput
SpId <- paste(GridId, "sp", sep="_")
# ID of the button used to remove this species
removeSpeciesId <- paste(GridId,'remover', sep="_")
#Update of the UI
insertUI(
selector = '#adder',
where = "beforeBegin",
ui = tags$div(
id = GridId,
fluidRow(
column(6,
h5("Species name : "),
textInput(SpId,label = NULL)
),
column(6, align = "center",
br(),br(),
actionButton(removeSpeciesId,
label = "Delete")
)
)
)
)
# Remove an observation when the "delete" button is clicked (and after confirmation)
observeEvent(input[[removeSpeciesId]], {
#Confirmation modal
showModal(
modalDialog(
"Are you sure ?",
title = "Delete",
footer = tagList(
actionButton("cancel", "Cancel"),
actionButton("confirm", "Confirm", class = "btn btn-danger")
)
)
)
# Delete observation if user confirms
observeEvent(input$confirm, {
id_to_remove <- substring(removeSpeciesId,1, nchar(removeSpeciesId)-8)
rv$GridId_list <- rv$GridId_list[rv$GridId_list!=id_to_remove]
removeUI(selector = paste("#", id_to_remove, sep = ""))
showNotification("Observation deleted !")
removeModal()
})
# Just remove the modal if user cancels
observeEvent(input$cancel, {
removeModal()
})
})
})
}
shinyApp(ui = ui, server = server, options = list(launch.browser = T))
Referencing dynamic input id's is a pain. I find it best to add a last clicked input identifier to reference. You can add a class to those inputs to just listen to them and not others in your app:
tags$head(tags$script(HTML("$(document).on('click', '.needed', function () {
Shiny.onInputChange('last_btn',this.id);
});")))
That little piece of code will allow you to get an input$last_btn id, that you can use for your event listeners. In this case you don't need to nest your event listeners; it is better to think about the events in sequence and program those reactions. So, with some tweakings in your code, your app now looks like this:
library(shiny)
library(random)
ui <- fluidPage(
tags$head(tags$script(HTML("$(document).on('click', '.needed', function () {
Shiny.onInputChange('last_btn',this.id);
});"))),
fluidRow(br(), br(), actionButton("adder",
label = "Add an observation"),
align="center")
)
server <- function(input, output,session) {
rv <- reactiveValues()
rv$GridId_list <- c()
observeEvent(input$adder,{
# create random ID for each added species
GridId <- as.character(randomStrings(1, 10))
# store the new ID
rv$GridId_list <- c(rv$GridId_list,GridId)
# ID for the textinput
SpId <- paste(GridId, "sp", sep="_")
# ID of the button used to remove this species
removeSpeciesId <- paste(GridId,'remover', sep="_")
#Update of the UI
insertUI(
selector = '#adder',
where = "beforeBegin",
ui = tags$div(
id = GridId,
fluidRow(
column(6,
h5("Species name : "),
textInput(SpId,label = NULL)
),
column(6, align = "center",
br(),br(),
actionButton(removeSpeciesId,
label = "Delete", class="needed")
)
)
)
)
})
# Remove an observation when the "delete" button is clicked (and after confirmation)
observeEvent(input$last_btn, {
observeEvent(input[[input$last_btn]] > 0,{#We want the modal to show when any "remover" id is clicked
#Confirmation modal
showModal(
modalDialog(
"Are you sure ?",
title = "Delete",
footer = tagList(
actionButton("cancel", "Cancel"),
actionButton("confirm", "Confirm", class = "btn btn-danger")
)
)
)
})
}, ignoreNULL = TRUE, ignoreInit = TRUE)
# Delete observation if user confirms
observeEvent(input$confirm, {
#The following selector is for the parent id of the parent id of the last_btn id
removeUI(selector = paste0("div:has(>div:has(>#", input$last_btn, "))"))
showNotification("Observation deleted !")
removeModal()
})
# Just remove the modal if user cancels
observeEvent(input$cancel, {
removeModal()
})
}
shinyApp(ui = ui, server = server, options = list(launch.browser = T))
I have an app which creates boxes. Each box has a button that triggers a modal. The modal has inputs which the user changes and then a button which triggers an action based on those inputs (basically just uploading to a database). Because each box has a different specification, I wrote a module and then loop thru a list, creating a box for each element. This works fine.
However, the flow in the modal and observeEvent has a flaw: the first run thru I get the desired results, but on the second occasion in the same box (same id module), after pressing the modal button to update, it will not use the new inputs, but rather what happened in the first run. I am guessing it has something to do with the namespace/observeEvent combination as I might be triggering the event with a "stored" namespace? Would I need to somehow "flush" the namespace after every update? Anyway, any help appreciated as it gets confusing fast with all the namespace/modules combinations.
library(shiny)
library(shinyWidgets)
ui <- navbarPage(
'page', collapsible = TRUE,
tabPanel("test",
useSweetAlert(),
sidebarLayout(
sidebarPanel(),
mainPanel(
uiOutput('all_products_ui')
)
)
)) # end navbar
server <- shinyServer(function(input, output) {
list_products <- c(1,2,3,4,5)
# Now, I will create a UI for all the products
output$all_products_ui <- renderUI({
r <- tagList()
progress_move <- 0
for(k in 1:length( list_products )){
r[[k]] <- ExistingProductUI(id = k, product = list_products[[k]] )
}
r
})
# handlers duplicate a call to module depending on the id of ExistingProductUI
handlers <- list()
observe(
handlers <<- lapply(seq.int(length( list_products )),
function(i) {
callModule(ExistingProductUpdate,
id = i,
product = list_products[[i]] )
})
)
handlers
}) # end of server ----
# UI module ------------------------------------------------------
ExistingProductUI <- function(id, product){
ns <- NS(id)
box(title = as.character(p$title),
product["title"],
footer = tagList(
actionBttn(
inputId = ns("change_selected"), label = "change"),
)
)
}
# server module ------------------------------------------------------
ExistingProductUpdate <- function(input, output, session, product){
ns <- session$ns
observeEvent(input$change_selected, {
# when box button is clicked for this product (id)
# FIRST: show a modal
showModal(
modalDialog(
title = "what do you want to change?",
tagList(
radioGroupButtons(inputId = ns("change_selected_choice"), labels = "change x", choices = c(1,2,3,4)),
sliderInput(ns("change_selected_pct"), "change y:", min = -50, max = 100, value = 0, step = 5)
),
easyClose = TRUE,
footer = tagList(
actionButton(ns("change_selected_submit"), "submit!", icon = icon("check")),
modalButton("never mind")
)
)
)
# SECOND: when change_selected_submit is clicked,
observeEvent(input$change_selected_submit, {
# do some calculations with product using what I inputed in modal ---
# then, update a table ----
functionToUploadThings(product, input$change_selected_choice)
# THIRD: Close with a confirmation
sendSweetAlert(
session,
title = "Success!",
type = "success",
btn_labels = "Ok",
closeOnClickOutside = TRUE,
width = NULL
)
})
})
}
Below is a solution that works. The problem was that you nested your observeEvent in the module. I'm not entirely sure why this led to problems, some values weren't processed correctly. However, you don't need to nest the observeEvent, the second one gets also triggered by the actionButton in the modal when it is by its own. Additionally, I included a removeModal before the success notification is shown:
library(shiny)
library(shinyWidgets)
library(shinydashboard)
ui <- navbarPage(
'page', collapsible = TRUE,
tabPanel("test",
useSweetAlert(),
sidebarLayout(
sidebarPanel(),
mainPanel(
uiOutput('all_products_ui')
)
)
)) # end navbar
server <- shinyServer(function(input, output) {
list_products <- c(1,2,3,4,5)
# Now, I will create a UI for all the products
output$all_products_ui <- renderUI({
r <- tagList()
progress_move <- 0
for(k in 1:length( list_products )){
r[[k]] <- ExistingProductUI(id = k, product = list_products[[k]] )
}
r
})
# handlers duplicate a call to module depending on the id of ExistingProductUI
handlers <- list()
observe(
handlers <<- lapply(seq.int(length( list_products )),
function(i) {
callModule(ExistingProductUpdate,
id = i,
product = list_products[[i]] )
})
)
handlers
}) # end of server ----
# UI module ------------------------------------------------------
ExistingProductUI <- function(id, product){
ns <- NS(id)
box(title = as.character(product),
product,
footer = tagList(
actionBttn(
inputId = ns("change_selected"), label = "change"),
)
)
}
# server module ------------------------------------------------------
ExistingProductUpdate <- function(input, output, session, product){
ns <- session$ns
observeEvent(input$change_selected, {
# when box button is clicked for this product (id)
# FIRST: show a modal
showModal(
modalDialog(
title = "what do you want to change?",
tagList(
radioGroupButtons(inputId = ns("change_selected_choice"), label = "change x", choices = c(1,2,3,4)),
sliderInput(ns("change_selected_pct"), "change y:", min = -50, max = 100, value = 0, step = 5)
),
easyClose = TRUE,
footer = tagList(
actionButton(ns("change_selected_submit"), "submit!", icon = icon("check")),
modalButton("never mind")
)
)
)
})
# SECOND: when change_selected_submit is clicked,
observeEvent(input$change_selected_submit, {
# do some calculations with product using what I inputed in modal ---
# then, update a table ----
# functionToUploadThings(product, input$change_selected_choice)
# THIRD: Close with a confirmation
removeModal()
sendSweetAlert(
session,
title = "Success!",
type = "success",
btn_labels = "Ok",
closeOnClickOutside = TRUE,
width = NULL
)
})
}
shinyApp(ui, server)
Please note: I made some modifications to make your MWE work:
include library(shinydashboard)
p$title and product["title"] to product
change labels to label in radioGroupButtons
comment out functionToUploadThings(product, input$change_selected_choice)
Edit
I'm still not super sure what happens when nesting the observeEvents. I made a small toy example and played around with the reactlog. It seems that nesting the observers generates a new observer for button2 every time button1 is clicked. These observers are not removed and lead to unwanted behaviour. In contrast, when using separate observeEvents, the observer for button2 is only created once.
library(shiny)
library(reactlog)
ui <- fluidPage(
actionButton("button1", "click")
)
server <- function(input, output, session) {
observeEvent(input$button1, {
print("from first observer")
print(input$button2)
showModal(
modalDialog(
title = "what do you want to change?",
"some text",
easyClose = TRUE,
footer = tagList(
actionButton("button2", "submit!", icon = icon("check")),
modalButton("never mind")
)
)
)
# nested observer -> leads to remaining observers
observeEvent(input$button2, {
print("from second observer")
print(input$button2)
removeModal()
})
})
# independent observer -> generates only one observer
# observeEvent(input$button2, {
# print("from second observer")
# print(input$button2)
# removeModal()
# })
}
shinyApp(ui, server)
The app below contains a selectInput of dataset IDs and a button View details which displays a modalDialog when clicked. The modal dialog has a datatable that contains some information about the datasets in the selectInput dropdown.
Here is a screenshot of the app on startup:
Since the user can select a dataset either by selecting an option from the dropdown menu or by selecting a row in the datatable, I created a reactive value rv$selectedRow which stores the value of the selected dataset. When the modal is triggered, rv$selectedRow takes the value of input$data. When the Select button in the modal footer is clicked, rv$selectedRow takes the value of input$dfs_rows_selected and the selectInput is updated to reflect this new value. This is done by the two observeEvents in the code below.
When the user selects a row, closes the modal and opens it again, I would like the page and row of the selected dataset (input$data) to be pre-selected. I tried to achieve this using selection = list(mode = 'single', selected = rv$selectedRow) in the renderDT call. As you can see in the screenshot, row 1 should be pre-selected but it isn't. I feel like I'm missing a req() somewhere in the renderDT but I'm not sure. The value of rv$selectedRow checks out when I print it to the console, so I don't know why the selected argument of renderDT isn't working. I am also not sure how to store the page of the selected row. Any insight would be much much appreciated as I'm a little lost.
The app is as follows:
library(shiny)
library(DT)
datasets = data.frame(cbind(id = seq_len(4), name = c('iris', 'mtcars', 'satellite', 'credit')))
# UI ----------------------------------------------------------------------
ui = fluidPage(
selectInput('data', 'Select dataset:', choices = datasets$id),
actionButton('view', 'View details')
)
# SERVER ------------------------------------------------------------------
server <- shinyServer(function(input, output, session) {
rv = reactiveValues(selectedRow = NULL, selectedPage = NULL)
# Opening the modal
observeEvent(input$view, {
rv$selectedRow = req(input$data)
print(paste("selectedRow on 'View':", rv$selectedRow))
showModal(modalDialog(
title = 'Available datasets',
tags$b('Click on a row to select a dataset.'),
br(),
br(),
DT::dataTableOutput('dfs'),
easyClose = F,
footer = tagList(
modalButton('Cancel'),
bsButton('select', 'Select')
)
)
)
})
# Rendering the DT - pre-selection of row not working
output$dfs <- renderDT({
print(paste("selectedRow on 'renderDT':", rv$selectedRow))
datasets
},
options = list(
# displayStart = selectedPage,
pageLength = 2
),
filter = 'top',
selection = list(mode = 'single', selected = rv$selectedRow),
rownames = F
)
# Saving the selected row and updating the selectInput
observeEvent(input$select, {
rv$selectedRow = req(input$dfs_rows_selected)
print(paste("selectedRow on 'Select':", rv$selectedRow))
updateSelectInput(session = session, inputId = 'data', selected = datasets[rv$selectedRow, 1])
removeModal(session)
})
})
shinyApp(ui, server)
Updated code:
As per this solution and the one posted by Wilmar below, using datatable() in the renderDT seemed to fix the problem -
library(shiny)
library(DT)
datasets = data.frame(cbind(id = seq_len(4), name = c('iris', 'mtcars', 'satellite', 'credit')))
# UI ----------------------------------------------------------------------
ui = fluidPage(
selectInput('data', 'Select dataset:', choices = datasets$id),
actionButton('view', 'View details')
)
# SERVER ------------------------------------------------------------------
server <- shinyServer(function(input, output, session) {
rv = reactiveValues(selectedRow = NULL, selectedPage = NULL)
# Opening the modal
observeEvent(input$view, {
print(paste("selectedRow on 'View':", rv$selectedRow))
showModal(modalDialog(
title = 'Available datasets',
tags$b('Click on a row to select a dataset.'),
br(),
br(),
DT::dataTableOutput('dfs'),
easyClose = F,
footer = tagList(
modalButton('Cancel'),
bsButton('select', 'Select')
)
)
)
})
# Rendering the DT - pre-selection of row not working
output$dfs <- renderDataTable({
r = rv$selectedRow
print(paste("selectedRow on 'renderDT':", r))
datatable(
datasets,
options = list(
displayStart = as.numeric(r)-1,
pageLength = 2
),
filter = 'top',
selection = list(mode = 'single', selected = r),
rownames = F
)
}, server = F)
# Saving the selected row and updating the selectInput
observeEvent(input$select, {
rv$selectedRow = req(input$dfs_rows_selected)
print(paste("selectedRow on 'Select':", rv$selectedRow))
updateSelectInput(session = session, inputId = 'data', selected = datasets[rv$selectedRow, 1])
removeModal(session)
})
observe({
rv$selectedRow = input$data
})
})
shinyApp(ui, server)
I guess this is what you're looking for. Your first problem was that you had to convert rv$selectedRow to numeric. Secondly it you were re-rendering your datatable everytime you pressed the "view" button. And thirdly you didn't do anything with your selectInput ("data").
I transformed rv$selectedRow to a numeric, moved your showModal to the ui and created an observer for your selectInput. In addition, I wrapped your datafarme in the datatable function, which I think is a bit more convenient.
Working example:
library(shiny)
library(DT)
library(shinyBS)
datasets = data.frame(cbind(id = seq_len(4), name = c('iris', 'mtcars', 'satellite', 'credit')))
# UI ----------------------------------------------------------------------
ui = fluidPage(
selectInput('data', 'Select dataset:', choices = datasets$id),
actionButton('view', 'View details'),
tags$head(tags$style("#df_popup .modal-footer{ display:none}
#df_popup .modal-header .close{display:none}")),
bsModal("df_popup", title='Available datasets', trigger='view',
tags$b('Click on a row to select a dataset.'),
br(),
br(),
DT::dataTableOutput('dfs'),
column(12, align='right',
modalButton('Cancel'),
bsButton('select', 'Select')
)
)
)
# SERVER ------------------------------------------------------------------
server <- shinyServer(function(input, output, session) {
rv = reactiveValues(selectedRow = NULL, selectedPage = NULL)
# Rendering the DT - pre-selection of row not working
output$dfs <- renderDT({
print(paste("selectedRow on 'renderDT':", rv$selectedRow))
datatable(datasets, options = list(
# displayStart = selectedPage,
pageLength = 2
),
filter = 'top',
selection = list(mode = 'single', selected=c(as.numeric(rv$selectedRow))),
rownames = F)
},
)
# Saving the selected row and updating the selectInput
observeEvent(input$select, {
rv$selectedRow = req(input$dfs_rows_selected)
print(paste("selectedRow on 'Select':", rv$selectedRow))
updateSelectInput(session = session, inputId = 'data', selected = datasets[rv$selectedRow, 1])
toggleModal(session, 'df_popup')
})
observeEvent(input$data, {
rv$selectedRow = input$data
print(paste("selectedRow on 'data':", rv$selectedRow))
})
})
shinyApp(ui, server)
I'm building a form in Shiny. I'm using tabs to put information in different pages. In the code here, I'm having 3 main pages: 1. the main form to fill, 2. the "help" page 3. and the "thanks for submitting!".
The only thing is that when I submit the form, the title of one of the tab is sticking in place. I want to remove the title on one of the tab (see figures).
library(shiny)
library(shinydashboard)
# Fields definition -------------------------------------------------------
fields <- c("name", # the order here will be the same as the one that is saved in a CSV!
"title_reference",
"year_publication",
"first_author",
"journal",
"Species",
"used_shiny",
"favourite_pkg",
"os_type",
"r_num_years")
# Mandatory fields --------------------------------------------------------
fieldsMandatory <- c("name",
"favourite_pkg")
# Labeling with star for mandatory fields
labelMandatory <- function(label) {
tagList(
label,
span("*", class = "mandatory_star")
)
}
# Design ------------------------------------------------------------------
appCSS <-
".mandatory_star { color: red; }
#error { color: red; }"
# To save the Data -------------------------------------------------------
library(rdrop2)
# This is a folder that is going to be created on Dropbox
outputDir <- "responses"
epochTime <- function() {
as.integer(Sys.time())
}
humanTime <- function() {
format(Sys.time(), "%Y%m%d-%H%M%OS")
}
saveData <- function(data) {
data = c(data, timestamp = epochTime())
data <- t(data)
# Create a unique file name
fileName <- sprintf("evo_rates_form_%s_%s.csv",
humanTime(),
digest::digest(data))
# Write the data to a temporary file locally
filePath <- file.path(tempdir(),
fileName)
write.csv(data,
filePath,
row.names = FALSE,
quote = TRUE)
# Upload the file to Dropbox
drop_upload(filePath,
dest = outputDir)
}
loadData <- function() {
# Read all the files into a list
filesInfo <- drop_dir(outputDir)
filePaths <- filesInfo$path
data <- lapply(filePaths, drop_read_csv, stringsAsFactors = FALSE)
# Concatenate all data together into one data.frame
data <- do.call(rbind, data)
data
}
# ShinyAPP ----------------------------------------------------------------
# Shiny app with 3 fields that the user can submit data for
shinyApp(
ui = dashboardPage(skin = "green",
dashboardHeader(title = "Evolutionary Rates Data Collection", titleWidth = 350),
dashboardSidebar(#disable = TRUE,
width = 250,
sidebarMenu(menuItem("Menu Item"),
menuItem("Form", tabName = "form_tab", icon = icon("file-text")),
menuItem("Help", tabName = "help_tab", icon = icon("question"))
)
),
dashboardBody(
shinyjs::useShinyjs(),
shinyjs::inlineCSS(appCSS), # you need this if you want to change the "design" of you form
# titlePanel("Evolutionary Rates Form"),
# DataTables
DT::dataTableOutput("responses", # this will customize the table
width = 300),
# tags$hr(), # This is adding a horizontal rule (line)
# Form inputs -------------------------------------------------------------
tabItems(
# First tab content
tabItem(tabName = "form_tab",
h2("Main form"),
div(
id = "form",
## text input
textInput("name",
labelMandatory("Name (First and last name)"), ""),
textInput("favourite_pkg",
labelMandatory("Favourite R package")),
textInput("title_reference",
"Title of the reference"),
textInput("year_publication",
"Year of publication"),
textInput("first_author",
"First author"),
textInput("journal",
"Journal"),
textInput("Species","Species"),
## Checkbox input
checkboxInput("used_shiny",
"I've built a Shiny app in R before",
FALSE),
## Slider input
sliderInput("r_num_years",
"Number of years using R",
0, 25, 2, ticks = FALSE),
## Dropdown menu input
selectInput("os_type",
"Operating system used most frequently",
c("", "Windows", "Mac", "Linux")),
## Action button
actionButton("submit",
"Submit"),
# Submission progression bar or Error
shinyjs::hidden(
span(id = "submit_msg",
"Submitting..."),
div(id = "error",
div(br(),
tags$b("Error: "), # b tags is for bold text
span(id = "error_msg"))
) # Closing div
) # Closing shinyjs::hidden
) # Closing div
), # closing tab 1
# Second tab content
tabItem(tabName = "help_tab",
h2("Want some help or information?"))
), # Closing tabItems
# Thank you message -------------------------------------------------------
shinyjs::hidden(
div(
id = "thankyou_msg",
h2("Thanks, your response was submitted successfully!"),
actionLink("submit_another",
"Submit another response")
) # Closing div
) # Closing shinyjs::hidden
) # Closing DashboardBody
), # Closing DashboardPage
# Server ------------------------------------------------------------------
server = function(input, output, session) {
# Whenever a field is filled, aggregate all from data
formData <- reactive({
data <- sapply(fields, function(x) input[[x]])
data
})
observe({
# check if all mandatory fields have a value
mandatoryFilled <-
vapply(fieldsMandatory,
function(x) {
!is.null(input[[x]]) && input[[x]] != ""
},
logical(1))
mandatoryFilled <- all(mandatoryFilled)
# enable/disable the submit button
shinyjs::toggleState(id = "submit",
condition = mandatoryFilled)
})
# When the Submit button is clicked, save the form data (action to take when submit button is pressed)
observeEvent(input$submit, {
shinyjs::disable("submit")
shinyjs::show("submit_msg")
shinyjs::hide("error")
tryCatch({
saveData(formData())
shinyjs::reset("form")
shinyjs::hide("form")
shinyjs::show("thankyou_msg")
},
error = function(err) {
shinyjs::text("error_msg", err$message)
shinyjs::show(id = "error", anim = TRUE, animType = "fade")
},
finally = {
shinyjs::enable("submit")
shinyjs::hide("submit_msg")
})
})
# Hide the thank you message and show the form
observeEvent(input$submit_another, {
shinyjs::show("form")
shinyjs::hide("thankyou_msg")
})
}
)
If I am reading this correctly, the issue is that you never tell your app to hide it. In the ui, you have:
tabItem(tabName = "form_tab",
h2("Main form"),
div(
id = "form",
#### A bunch more stuff)
so, the h2("Main form") is not part of the div with id="form".
In the server you have
observeEvent(input$submit, {
shinyjs::disable("submit")
shinyjs::show("submit_msg")
shinyjs::hide("error")
tryCatch({
saveData(formData())
shinyjs::reset("form")
shinyjs::hide("form")
shinyjs::show("thankyou_msg")
}, #### More stuff
Which will hide "form" in the ui but that does not include the h2("Main form") so that stays visible. If you move that into the "form" div that should fix it.
I'm making an app and I need to add a button to refresh page (same function to press F5). Is there anyone can share a piece of code to implement it?
Thanks a lot!
I do have a very simple and nice solution but it won't work for a file input.
Here's a solution that'll work for all inputs except a file input:
UPDATE 2017: this solution did not work on file inputs for the first 2 years, but it does now.
library(shiny)
library(shinyjs)
runApp(shinyApp(
ui = fluidPage(
shinyjs::useShinyjs(),
div(
id = "form",
textInput("text", "Text", ""),
selectInput("select", "Select", 1:5),
actionButton("refresh", "Refresh")
)
),
server = function(input, output, session) {
observeEvent(input$refresh, {
shinyjs::reset("form")
})
}
))
When you press "Refresh", all inputs will be reset to their initial values. This is what the poster said in a comment that they actually want to do.
But file inputs are very strange and it's hard to "reset" them. See here. You could hack some JavaScript together to try to almost kind of reset an input field if you want.
However, for completeness, you can also refresh the entire page. The easiest way to do that is with session$reload(). You can also do it with {shinyjs}:
library(shiny)
library(shinyjs)
runApp(shinyApp(
ui = fluidPage(
shinyjs::useShinyjs(),
shinyjs::extendShinyjs(text = "shinyjs.refresh_page = function() { location.reload(); }", functions = "refresh_page"),
textInput("text", "Text", ""),
actionButton("refresh", "Refresh")
),
server = function(input, output, session) {
observeEvent(input$refresh, {
shinyjs::js$refresh_page()
})
}
))
Disclaimer: both these solutions use a package I wrote, shinyjs
I have a drop-down list input:
selectInput("domain", label = h4("Domain:"), choices = Domain, selected = CurrentDomain)
The choices set is based on a table in the database. It should change after I add or delete record from the table.
When I was experimenting with your reset or refresh function, the choice set could not reflect the changes and always stay the same. However, when I use the "reload" button provided by the browser, the choice set will update immediately. I am wondering whether you have a reset/refresh solution that is equivalent to the "reload" button of the browser.
I provided my code here, which will not work but will give you an idea what I want to do.
conn<-odbcDriverConnect(connString)
SystemInfo<-sqlQuery(conn, 'SELECT * FROM [DQ].[DQSystemInfo]', stringsAsFactors = FALSE)
close(conn)
Domain<-unique(SystemInfo$Domain)
Domain<-c(Domain,'NEW')
SubDomain<-unique(SystemInfo$SubDomain[SystemInfo$Domain==Domain[1]])
SubDomain<-c(SubDomain,'NEW')
CurrentDomain<-Domain[1]
CurrentSubDomain<-SubDomain[1]
SystemInfo1<-SystemInfo[SystemInfo$Domain==CurrentDomain & SystemInfo$SubDomain==CurrentSubDomain,]
jsResetCode <- "shinyjs.reset = function() {history.go(0)}"
shinyApp(
ui = fluidPage(
shinyjs::useShinyjs(),
shinyjs::extendShinyjs(text = "shinyjs.refresh = function() { location.reload(); }"),
# div(
# id = "form",
fluidRow(
column(6, selectInput("domain", label = h4("Domain:"),
choices = Domain, selected = CurrentDomain)),
column(6,uiOutput("Condition2"))
),
# fluidRow(column(2, verbatimTextOutput("value"))),
fluidRow(
column(6, uiOutput("Condition1")),
column(6,uiOutput("Condition3"))
),
extendShinyjs(text = jsResetCode),
fluidRow(
column(2, actionButton("submit", "Save", class="btn btn-primary btn-lg")),
column(2, actionButton("cancel", "Cancel", class="btn btn-primary btn-
lg")),
column(2, actionButton("delete", "Delete", class="btn btn-primary btn-lg"))
)
#)
),
server = function(input, output) {
observeEvent(input$domain, {
if (input$domain=='NEW') {
shinyjs::disable("domain")
shinyjs::disable("delete")
CurrentSubDomain<-'NEW'
output$Condition1 = renderUI({
textInput("domainT",label = "", value = "")
})
output$Condition3 = renderUI({
textInput("subdomainT", label = "",value = "")
})
})
} else {
CurrentDomain<-input$domain
SubDomain<-unique(SystemInfo$SubDomain[SystemInfo$Domain==input$domain])
SubDomain<-c(SubDomain,'NEW')}
output$Condition2 = renderUI({
selectInput("subdomain", label = h4("SubDomain:"),
choices = SubDomain, selected =CurrentSubDomain)
})
})
observeEvent(input$subdomain, {
if (input$subdomain=='NEW') {
shinyjs::disable("domain")
shinyjs::disable("subdomain")
shinyjs::disable("delete")
output$Condition3 = renderUI({
textInput("subdomainT", label = "", value = "")
})
} else {
CurrentSubDomain<-input$subdomain
conn<-odbcDriverConnect(connString)
SystemInfo<-sqlQuery(conn, 'SELECT * FROM [DQ].[DQSystemInfo]', stringsAsFactors = FALSE)
close(conn)
SystemInfo1<-SystemInfo[SystemInfo$Domain==input$domain & SystemInfo$SubDomain==input$subdomain,]
}
})
observeEvent(input$submit, {
conn<-odbcDriverConnect(connString)
DQ.DQSystemInfo<-SystemInfo[FALSE,c("Domain","SubDomain")]
DQ.DQSystemInfo[1,]<-c("","","","","","","",0,48)
DQ.DQSystemInfo$Domain<-ifelse(input$domain=='NEW',input$domainT,input$domain)
DQ.DQSystemInfo$SubDomain<-input$subdomainT
varType1 <- c("varchar(20)", "varchar(20)" )
names(varType1)<-colnames(DQ.DQSystemInfo)
sqlSave(conn, DQ.DQSystemInfo, append = TRUE, rownames = FALSE, varTypes = varType1)
close(conn)
# js$reset()
#shinyjs::reset("form")
# js$reset("form")
conn<-odbcDriverConnect(connString)
SystemInfo<-sqlQuery(conn, 'SELECT * FROM [DQ].[DQSystemInfo]', stringsAsFactors = FALSE)
close(conn)
Domain<-unique(SystemInfo$Domain)
Domain<-c(Domain,'NEW')
SubDomain<-unique(SystemInfo$SubDomain[SystemInfo$Domain==Domain[1]])
SubDomain<-c(SubDomain,'NEW')
CurrentDomain<-Domain[1]
CurrentSubDomain<-SubDomain[1]
SystemInfo1<-SystemInfo[SystemInfo$Domain==CurrentDomain & SystemInfo$SubDomain==CurrentSubDomain,]
shinyjs::js$refresh()
})
observeEvent(input$cancel, {
#js$reset()
#shinyjs::reset("form")
#js$reset("form")
shinyjs::js$refresh()
})
observeEvent(input$delete, {
conn<-odbcDriverConnect(connString)
delete.query <- paste0("DELETE DQ.DQSystemInfo WHERE Domain='",
input$domain,"' and SubDomain='",input$subdomain,"'")
sqlQuery(conn, delete.query)
close(conn)
#js$reset()
# shinyjs::reset("form")
# js$reset("form")
conn<-odbcDriverConnect(connString)
SystemInfo<-sqlQuery(conn, 'SELECT * FROM [DQ].[DQSystemInfo]', stringsAsFactors = FALSE)
close(conn)
Domain<-unique(SystemInfo$Domain)
Domain<-c(Domain,'NEW')
SubDomain<-unique(SystemInfo$SubDomain[SystemInfo$Domain==Domain[1]])
SubDomain<-c(SubDomain,'NEW')
CurrentDomain<-Domain[1]
CurrentSubDomain<-SubDomain[1]
SystemInfo1<-SystemInfo[SystemInfo$Domain==CurrentDomain & SystemInfo$SubDomain==CurrentSubDomain,]
shinyjs::js$refresh()
})
},options = list(height = 520))