How do you deal with IDs in Shiny Dropdown lists? - r

Typically in a web interface if you have a dropdown populated from a database that display's some text and you want to use that selected text in the dropdown and pass it back to a database. But a lot of times you want to pass an ID instead of the actual text displayed.
In my example below I have a global.R file that returns the data for the dropdowns. This simulates data returned from a database. For each dropdown there is a text field that is displayed in the dropdowns and an "id" field that is not displayed BUT I have to somehow access the "id" fields of the dropdowns. How is this done in Shiny?... Because the selectInputs don't allow you to store the ids so you can access them like input$DisplayName$id
In the example below I just want to print the "id" of the "DisplayName" selectInput so if "Mary" is in the input$DisplayName then "20" should be printed in the RenderText call.
Here is code to run:
require(shiny)
runApp(list(
ui = basicPage(
sidebarPanel(
selectInput("Department", "Select a department", choices = as.character(GetDepartments()$Department), selected = as.character(GetDepartments()$Department[1])),
uiOutput("DisplayName")
),
mainPanel(textOutput("Text") )
),
server = function(input, output, session) {
output$DisplayName<-renderUI({
Department <- input$Department
print(Department)
selectInput("DisplayName", 'DisplayName:', choices = as.character(GetDisplayName(Department)$DisplayName), selected =as.character(GetDisplayName(Department)$DisplayName[1] ))
})
output$Text <- renderText({
# Here I want to simulate accessing the "id" field of the input$DisplayName
#in my app I need to pass the id to a database query
#If Mary is in input$DisplayName how can I access her id of "20"?
print("in render text")
return( ??? How do I access the id = 20???)
})
}
))
Here is the global.r file that simulates code that returns stuff from a database
GetDepartments<- function(){
df<- data.frame(Department= c("Dept A", "Dept B"), id = c(1,2))
return(df)
}
GetDisplayName<- function(Dept){
if(Dept == "Dept A")
{
df<- data.frame(DisplayName= c("Bob", "Fred"), id = c(4,6))
return(df)
}else
{
df<- data.frame(DisplayName= c("George", "Mary"), id = c(10,20))
return(df)
}
}

This is very similar to your other question here. As #nrussel suggests, this is just a simple subsetting problem. Just pull up your department and index on the name. Here is a working example.
EDIT*** - make dataset reactive to avoid repetition.
As per the documentation:
Reactive expressions are a smarter than regular R functions. They cache results and only update when they become obsolete. The first time that you run a reactive expression, the expression will save its result in your computer’s memory. The next time you call the reactive expression, it can return this saved result without doing any computation (which will make your app faster). The reactive expression will use this new copy until it too becomes out of date.
runApp(list(
ui = basicPage(
sidebarPanel(
selectInput("Department", "Select a department",
choices = as.character(GetDepartments()$Department),
selected = as.character(GetDepartments()$Department[1])),
uiOutput("DisplayName")
),
mainPanel(textOutput("Text") )
),
server = function(input, output, session) {
myData <- reactive({
GetDisplayName(input$Department)
})
output$DisplayName<-renderUI({
Department <- input$Department
print(Department)
myData <- myData()
selectInput("DisplayName", 'DisplayName:', choices = as.character(myData$DisplayName),
selected =as.character(myData$DisplayName[1] ))
})
output$Text <- renderText({
print("in render text")
myData <- myData()
code <- as.character(myData[myData$DisplayName == input$DisplayName,2])
return(code)
})
}
))

Related

Shiny App: How to collect all text inputs into a data frame without listing them individually (how to index reactive values?)

I have a tab of my app where I display a bunch of text inputs based on a three-column data frame that contains: variable_name, text_prompt, and example_data. The code below seems to work fine since it displays how I want it to. Eventually, I will be feeding it different data frames, depending on the circumstances, so I need to be able to do everything programmatically.
library(shiny)
library(tidyverse)
library(DT)
additional.data.fields <- tibble (var.name = c("project.id", "director.name"),
prompt.text = c("Enter Project ID", "Enter Director's name"),
var.value = c("e.g. 09-111", "e.g. Paul Smith"))
ui <- fluidPage(
tabsetPanel(
#Generate Input fields from dataframe
tabPanel("Input", #value = "input.2",
# Generate input fields with pmap
actionButton("submit", "Submit"),
pmap(additional.data.fields, ~textInput(..1, ..2, value = ..3)),
),
#Output data to tell if it updates with button click
tabPanel("Output", value = "output",
DT::dataTableOutput("data")
)
)
)
server <- function(input, output, session) {
# Create a reactive values object to store the input data
values <- reactiveValues()
# Set the reactive values object when the submit button is clicked
observeEvent(input$submit, {
var.names <- pull(additional.data.fields, var.name)
#THIS IS THE PART I DON'T KNOW HOW TO DO
#input.data <- ???
#I'll add dummy data so that the program loads
input.data <- tibble(var.names,
temp = 1:length(var.names))
values$data <- input.data
})
# Render the input data table
output$data <- DT::renderDataTable({
values$data
})
}
shinyApp(ui, server)
But what I want - and really have no idea how to do - is to get it back into a data frame after the user hits "submit" (I only need two columns in the subsequent data frame; I don't need the text_prompt data again.)
I know that the user input creates a list of read-only ReactiveValues called "input". But I can't figure out how to do anything with this list besides access using known names (i.e. I know that there is a variable named "project_id" which I can access using input$project_id). But what I want is not to have to write them all out, so that I can change the data used to create the input fields. So I need a way to collect them in a data frame without knowing all the individual names of the variables or even how many there are.
I figured this out on my own. You can't index reactive values with []. However, for some reason you can using [[]].
I would love to know why this is, if anyone has an answer that can help me understand why it works this way.
Here's the key bit of code that I was missing before:
input.data <- tibble (names = var.names,
values = map_chr(var.names, ~input[[.x]]))
The full code that works as I want it is pasted below. I'd still appreciate any feedback or recommendations for improvement.
library(shiny)
library(tidyverse)
library(DT)
additional.data.fields <- tibble (var.name = c("project.id", "director.name"),
prompt.text = c("Enter Project ID", "Enter Director's name"),
var.value = c("e.g. 09-111", "e.g. Paul Smith"))
ui <- fluidPage(
tabsetPanel(
#Generate Input fields from dataframe
tabPanel("Input", #value = "input.2",
# Generate input fields with pmap
actionButton("submit", "Submit"),
pmap(additional.data.fields, ~textInput(..1, ..2, value = ..3)),
),
#Output data to tell if it updates with button click
tabPanel("Output", value = "output",
DT::dataTableOutput("data")
)
)
)
server <- function(input, output, session) {
# Create a reactive values object to store the input data
values <- reactiveValues()
# Set the reactive values object when the submit button is clicked
observeEvent(input$submit, {
var.names <- pull(additional.data.fields, var.name)
input.data <- tibble (names = var.names,
values = map_chr(var.names, ~input[[.x]]))
values$data <- input.data
})
# Render the input data table
output$data <- DT::renderDataTable({
values$data
})
}
shinyApp(ui, server)

How to update a SelectizeInput depending on a textInput in Shiny

I have create one app that contains a textInput and a selectizeInput. Depending on the user's input and if the input can be found in one dataset, you will see all the possibilities according to that textInput in the selectizeInput.
In this way, if the user introduces a word that it is not in the dataset, the selectizeInput can't display any choice.
Everything works fine, but I found one problem. If the user starts writing a correct word, the user gets a dropdown list... and then, if the input is removed... the dropdown list is still there (the choices from selectizeInput are still there).
Here the code:
library(shiny)
library(dplyr)
library(stringr)
ui <- fluidPage(
textInput("my_input", "Introduce a word"),
selectizeInput(inputId = "dropdown_list", label = "Choose the variable:", choices=character(0)),
)
server <- function(input, output, session) {
my_list <- reactive({
req(input$my_input)
data <- as.data.frame(storms)
res <- subset(data, (grepl(pattern = str_to_sentence(input$my_input), data$name))) %>%
dplyr::select(name)
res <- as.factor(res$name)
return(res)
})
# This is to generate the choices (gene list) depending on the user's input.
observeEvent(input$my_input, {
updateSelectizeInput(
session = session,
inputId = "dropdown_list",
choices = my_list(), options=list(maxOptions = length(my_list())),
server = TRUE
)
})
}
shinyApp(ui, server)
Do you know how can I remove the choices from the selectizeInput if the user deletes the input?
Thanks very much in advance
Regards
The issue is the req(input$myinput). Hence, if the user deletes the input my_list() does not get updated. Instead of req you could use an if to check whether the input is equal to an empty string:
my_list <- reactive({
if (!input$my_input == "") {
data <- as.data.frame(storms)
res <- subset(data, grepl(pattern = str_to_sentence(input$my_input), data$name), name)
res <- as.factor(res$name)
return(res)
}
})

How to pass multiple values from URL to r shiny checkbox input

Please see the sample code below, what I want is just pass multiple chk values from url as shiny check box input values; it works for single value like: http://127.0.0.1:3471/?chk=1
but it does not work for multiple values like http://127.0.0.1:3471/?chk=1,2.
Thanks in advance.
library(shiny)
shinyApp(
ui = shinyUI(fluidPage(
checkboxGroupInput("chk", "Check box", c("Monthly" = 1,
"Quarterly" = 2,
"Weekly" = 3,
"Daily" = 4))
)),
server = shinyServer(function(input, output,session) {
observe({
query <- parseQueryString(session$clientData$url_search)
if (!is.null(query[['chk']])) {
print(query[['chk']])
updateTextInput(session, "chk", value = query[['chk']])
}
})
})
)
I believe that you may want to use updateCheckboxGroupInput to update your checkboxes instead of updateTextInput.
The extracted query[['chk']] will be a string separated by comma containing elements you wish to update in your checkboxes. You can use strsplit to separate them (and unlist since result would be a list).
server = shinyServer(function(input, output,session) {
observe({
query <- parseQueryString(session$clientData$url_search)
if (!is.null(query[['chk']])) {
print(query[['chk']])
updateCheckboxGroupInput(session, "chk", selected = unlist(strsplit(query[['chk']], ",")))
}
})
})

Shiny R Update a list box after confirming delete

This is probably a very basic question in regards to shiny
I have a drop down list where a user clicks a button and the dropdown menu should update with one less item
Here is a toy example
library(shiny)
library(shinythemes)
getData<- function()
{
data <- c('A','B','C','D')
return (data)
}
ui <- fluidPage(
selectInput("names", "Select Data", getData(), multiple = FALSE),
actionButton("del","delete"),
bsModal("modalnew", "Approve Data For Reporting", "del", size = "medium",
HTML("Note: Confirm Deletion"),
br(),br(),
actionButton("delyes", "Yes"),
actionButton("delno", "No")
)
)
server <- function(input, output, session) {
# Reactive part that gets the data and populates the drop down
my_sel = reactive({
mydata = getData()
})
# Confirmation Menu to Approve the upload
observeEvent(input$delyes, {
toggleModal(session, "modalnew", toggle = "close")
# Delete an Item, Update the list box with one less item
})
}
shinyApp(ui = ui, server = server)
An item should disappear from the list.
I'm basically stuck on the reactive bit i guess.
In the project I'm doing separate from the toy example, the user reviews their data by first selecting their area, if they approve it a stored procedure in MySQL moves the data to another table and the list box should update to remove the data from the list box because it no longer needs to be approved
Use reactiveValues and updateSelectInput to change the Select Input.
server <- function(input, output, session) {
# Reactive part that gets the data and populates the drop down
v <- reactiveValues(
mydata = getData()
)
# Confirmation Menu to Approve the upload
observeEvent(input$delyes, {
toggleModal(session, "modalnew", toggle = "close")
# Delete an Item, Update the list box with one less item
v$mydata <- v$mydata[!v$mydata %in% input$names]
#update the selection
updateSelectInput(session, "names",
choices = v$mydata
)
})
}

A dynamic UI with a Reactive function - How does timing work?

I am using renderUI to create a "DisplayName" input and I have a reactive function called "ReactiveTest" that uses input$DisplayName. If you run the code below you will see that the "DisplayName" drop down list calls the NameAndID() which in turn calls GetDisplayName() in the global.r file. That call populates the "DisplayName" dropdown list.
BUT when ReactiveTest() tries to access input$DisplayName it says that input$DisplayName is NULL then there seems to be a second call to ReactiveTest() and THEN it sees the input$DisplayName
so when you run the code you will see the timing looks like this:
[1] "Department dropdown list is being returned now"
[1] "Department dropdown list is being returned now"
Listening on http://155.0.0.1:555
[1] "DISPLAY NAME dropdown list is being returned now"
[1] "Now ReactiveTest() tries to access input$DisplayName"
[1] "ReactiveTest() says input$DisplayName Name is NULL" --> why is it NULL? if 2 lines above the dropdown was populated???
[1] "Now ReactiveTest() tries to access input$DisplayName"--> Where is this coming from? Shouldn't there only be 1 call to ReactiveTest()
[1] "ReactiveTest() says input$DisplayName Name is Bob"
I have 3 questions:
(1) Why does ReactiveTest() not see the input$DisplayName after the DisplayName dropdown is called?
(2) Why is there a second call to ReactiveTest()?
(3) How can I make ReactiveTest() see whats in the "DisplayName" dropdown the first call? In my real code I have to test for is.null(input$DisplayName)== TRUE and then not run some code but isn't there a better way?
Here is my code which you can run:
library(shiny)
runApp(list(
ui = basicPage(
sidebarPanel(
selectInput("Department", "Select a department",
choices = as.character(GetDepartments()$Department),
selected = as.character(GetDepartments()$Department[1])),
uiOutput("DisplayName")
),
mainPanel(textOutput("Text") )
),
server = function(input, output, session) {
NameAndID<- reactive({
GetDisplayName(input$Department)
})
output$DisplayName<-renderUI({
Department <- input$Department
selectInput("DisplayName", 'DisplayName:', choices = as.character(NameAndID()$DisplayName), selected =as.character(NameAndID()$DisplayName[1] ))
})
ReactiveTest<- reactive({
print("Now ReactiveTest() tries to access input$DisplayName")
if(is.null(input$DisplayName) == TRUE)
{
print("ReactiveTest() says input$DisplayName Name is NULL")
return("NULL")
}else
{
print(paste0("ReactiveTest() says input$DisplayName Name is ", input$DisplayName))
return(input$DisplayName)
}
})
output$Text <- renderText({
#myData <- GetDisplayName(input$Department)
#code <- as.character(myData[myData$DisplayName == input$DisplayName,2])
return(ReactiveTest())
})
}
))
global.r
GetDepartments<- function(){
df<- data.frame(Department= c("Dept A", "Dept B"), id = c(1,2))
print("Department dropdown list is being returned now")
return(df)
}
GetDisplayName<- function(Dept){
if(Dept == "Dept A")
{
df<- data.frame(DisplayName= c("Bob", "Fred"), id = c(4,6))
print("DISPLAY NAME dropdown list is being returned now")
return(df)
}else
{
df<- data.frame(DisplayName= c("George", "Mary"), id = c(10,20))
print("DISPLAY NAME dropdown list is being returned now")
return(df)
}
}
Your questions are essentially all asking the same thing, why is the function being called twice?
As I mentioned in your previous question, reactive expressions will only run again if they realize something has changed in the input. When you initialize a shiny app with a reactive ui, the values will not be initially loaded (i.e. null values). This has come up before with other shiny questions, such as this one on google groups and a similar one on SO, and the general consensus is that you should have some sort of check in your code (would love to be proven wrong). So your instinct to add a is.null check is correct. This is therefore a nice opportunity to use the validate function. Although this is initialization problem is somewhat annoying, the time taken during initialization should be insignificant.
Here is an example using validate. This however makes it very easy to provider a user friendly error message if somehow a null value is passed and anything passed the validate is not run. More straightforward, in the reactive expression, the initialization only prints and then leaves the function. This type of validation is recommended and documentation can be found here
runApp(list(
ui = basicPage(
sidebarPanel(
selectInput("Department", "Select a department",
choices = as.character(GetDepartments()$Department),
selected = as.character(GetDepartments()$Department[1])),
uiOutput("DisplayName")
),
mainPanel(textOutput("Text") )
),
server = function(input, output, session) {
NameAndID<- reactive({
GetDisplayName(input$Department)
})
output$DisplayName<-renderUI({
Department <- input$Department
selectInput("DisplayName", 'DisplayName:', choices = as.character(NameAndID()$DisplayName), selected =as.character(NameAndID()$DisplayName[1] ))
})
ReactiveTest<- reactive({
print("Now ReactiveTest() tries to access input$DisplayName")
validate(
need(!is.null(input$DisplayName), "ReactiveTest() says input$DisplayName Name is NULL")
)
print(paste0("ReactiveTest() says input$DisplayName Name is ", input$DisplayName))
return(input$DisplayName)
})
output$Text <- renderText({
return(ReactiveTest())
})
}
))

Resources