selectizeInput: allowing one element per group - r

I have a selectizeInput with some grouped elements with multiple selection. Is there an elegant way (e.g. using the options argument) of allowing just one element per group, so that a whole group will discarded (or disabled) when an element of this specific group is selected?
So far I tried it programmatically, but than the dropdown menu of the selectizeInput will be closed when updating the selectizeInput.
Minimal example:
library(shiny)
ui <- fluidPage(
selectizeInput("selInput", "Default",
choices=list(g1 = c(A="A",B="B"),
g2 = c(C="C",D="D")),
multiple=T),
selectizeInput("oneElementPerGroup", "One element per group",
choices=list(g1 = c(A="A",B="B"),
g2 = c(C="C",D="D")),
multiple=T)
)
server <- function(session, input, output) {
#Removes the corresponding groups of selected items
observeEvent(input$oneElementPerGroup, ignoreNULL = F, {
plusChoice <- input$oneElementPerGroup
names(plusChoice) <- input$oneElementPerGroup
choices <- list(g1 = c(A="A",B="B"),
g2 = c(C="C",D="D"))
if(any(input$oneElementPerGroup %in% c("A", "B"))){
choices[["g1"]] <- NULL
}
if(any(input$oneElementPerGroup %in% c("C", "D"))){
choices[["g2"]] <- NULL
}
choices$we <- plusChoice
updateSelectizeInput(session,"oneElementPerGroup",
choices = choices,
selected=input$oneElementPerGroup)
})
}
shinyApp(ui = ui, server = server)

You can use pickerInput from {shinyWidgets}. Then we can add a little javascript to do what you want. No server code is needed, very simple. Read more about the data-max-options option: https://developer.snapappointments.com/bootstrap-select/options/.
We need to add the limit to each group, not an overall limit, so we can't add it through the options argument in pickerInput, have to do it in raw HTML or use some js code to inject like what I do.
Be sure your inputId="pick" matches the id in the script #pick. Rename pick to whatever you want.
ui <- fluidPage(
shinyWidgets::pickerInput(
inputId = "pick", label = "Selected",
choices =list(g1 = c(A="A",B="B"), g2 = c(C="C",D="D")),
multiple = TRUE
),
tags$script(
'
$(function(){
$("#pick optgroup").attr("data-max-options", "1");
})
'
)
)
server <- function(input, output, session){}
shinyApp(ui, server)
updates:
If you need to update, we need to run the script again but from server. We can send js by using {shinyjs}. Imagine an observer triggers the update event.
library(shinyjs)
ui <- fluidPage(
useShinyjs(),
shinyWidgets::pickerInput(
inputId = "pick", label = "Selected",
choices =NULL,
multiple = TRUE
)
)
server <- function(input, output, session){
observe({
shinyWidgets::updatePickerInput(session, "pick", choices = list(g1 = c(A="A",B="B"), g2 = c(C="C",D="D")))
observeEvent(once = TRUE, reactiveValuesToList(session$input), {
runjs('$("#pick optgroup").attr("data-max-options", "1");')
}, ignoreInit = TRUE)
})
}
shinyApp(ui, server)

Related

Updating pickerInput based on radiobutton

I want to be able to update the selection available in the pickerInput based upon the selection made in the radio button input. In the example below, I want the radio button "A", to give a pickerInput list of mtcars$cyl, whilst a picker input of "B" to give a picker input of mtcars$mpg
I have tried doing this using if statements, but I haven't got anywhere so far. Reproducible example below:
library(shiny)
library(leaflet)
library(shinyjs)
library(rgeos)
library(tidyverse)
library(openxlsx)
library(plotly)
library(shinyWidgets)
library(rgdal)
ui <- fluidPage(shinyjs::useShinyjs(),
fluidRow(column(
6,radioButtons("type", "Type:",
c("A" = "norm",
"B" = "unif")))),
fluidRow(column(
3,
pickerInput("regInput","Region",choices=unique(mtcars$cyl),
options = list(`actions-box` = TRUE),multiple = T,
selected = unique(mtcars$cyl)))))
server <- function (input, output, session) {
observeEvent(input$type,{
a_option <- input$regInput
if (a_option == "B") {
updatePickerInput(session = session,inputId = "regInput",
choices =unique(mtcars$mpg))}})
}
shinyApp(ui = ui, server = server)
As said in the other answer you need to observe input$type and update the picker input according to the values in it. Also, it's important to include an else statement, to be able to update the picker input when the user picks between the radio button choices multiple times. Finally, on the updatePickerInput you need to include the selected argument to maintain the behaviour as it is on the ui.
Here's the code doing what I've described above:
library(shiny)
library(shinyjs)
library(tidyverse)
library(shinyWidgets)
ui <- fluidPage(shinyjs::useShinyjs(),
fluidRow(
column(
6,
radioButtons("type",
"Type:",
c("A" = "norm",
"B" = "unif"))
)),
fluidRow(
column(
3,
pickerInput("regInput","Region",
choices=unique(mtcars$cyl),
options = list(`actions-box` = TRUE),
multiple = T,
selected = unique(mtcars$cyl))
))
)
server <- function (input, output, session) {
observeEvent(input$type,{
if (input$type == "unif") {
updatePickerInput(session = session,inputId = "regInput",
choices = unique(mtcars$mpg),
selected = unique(mtcars$mpg))
} else {
updatePickerInput(session = session,inputId = "regInput",
choices = unique(mtcars$cyl),
selected = unique(mtcars$cyl))
}
})
}
shinyApp(ui = ui, server = server)
The server has to listen to the right UI Element (ID = "type" for the radio buttons in question). Currently it observes an undefined element "dist".
Try changing
observeEvent(input$dist, { code })
to
observeEvent(input$type, { code })

Shiny R: two selectizeInput menus that need to update each other (mutually exclusive selections)

Very new to Shiny here, I have a module like the one below where I just want 2 SelectizeInput menus with the same options each.
The trick is that they have to be mutually exclusive, so I understand I have to use updateSelectizeInput to update the selected options in one menu based on the selected options in the other.
This should work in such a way that if I select one option in one menu, it has to be removed from the selected options in the other menu, and vice versa.
I understand the moving pieces here, but I am not sure where to place them and how to finally accomplish this.
This is what I have so far:
mod_saving_side_ui <- function(id){
ns <- NS(id)
tagList(
shinyjs::useShinyjs(),
shinyalert::useShinyalert(),
uiOutput(outputId = ns("positive_markers")),
uiOutput(outputId = ns("negative_markers"))
)
}
mod_saving_side_server <- function(id, r){
moduleServer( id, function(input, output, session){
ns <- session$ns
output$positive_markers <- renderUI({
selectizeInput(inputId = ns("pos_markers"), label = "Positive:",
choices = LETTERS
selected = LETTERS[1],
multiple = TRUE)
})
output$negative_markers <- renderUI({
selectizeInput(inputId = ns("neg_markers"), label = "Negative:",
choices = LETTERS,
selected = LETTERS[2],
multiple = TRUE)
})
# add selected markers to the reactive values
observeEvent(input$pos_markers, {
r$pos_markers <- input$pos_markers
#selected_markers <- ALL EXCEPT pos_markers
#updateSelectizeInput(session, inputId = "neg_markers", selected = selected_markers)
})
observeEvent(input$neg_markers , {
r$neg_markers <- input$neg_markers
#selected_markers <- ALL EXCEPT neg_markers
#updateSelectizeInput(session, inputId = "pos_markers", selected = selected_markers)
})
})
}
Not sure if this is a standalone MWE... a side question would be how to make one with the above... Many thanks!
This should do what you asked.
I removed the extra calls to shinyjs and shinyalert and added call to library(shiny) to make it a MWE. I removed the argument r to the server call.
I've also moved the input to the UI, removed the uiOutput and renderUI as it wasn't needed in this case (I'm not sure if the are needed for other parts of your code). Then taking setdiff of the options gives you the new set to update the selectizeInput with.
I've also added code at the bottom to run and test the app.
library(shiny)
mod_saving_side_ui <- function(id){
ns <- NS(id)
tagList(
selectizeInput(inputId = ns("pos_markers"), label = "Positive:",
choices = LETTERS,
selected = LETTERS[1],
multiple = TRUE),
selectizeInput(inputId = ns("neg_markers"), label = "Negative:",
choices = LETTERS,
selected = LETTERS[2],
multiple = TRUE)
)
}
mod_saving_side_server <- function(id){
moduleServer(id, function(input, output, session){
ns <- session$ns
# add selected markers to the reactive values
observeEvent(input$neg_markers, {
selected_pos_markers <- input$pos_markers
selected_markers <- setdiff(selected_pos_markers, input$neg_markers)
updateSelectizeInput(session, inputId = "pos_markers", selected = selected_markers)
})
observeEvent(input$pos_markers , {
selected_neg_markers <- input$neg_markers
selected_markers <- setdiff(selected_neg_markers, input$pos_markers)
updateSelectizeInput(session, inputId = "neg_markers", selected = selected_markers)
})
})
}
demoApp <- function() {
ui <- fluidPage(
mod_saving_side_ui("demo")
)
server <- function(input, output, session) {
mod_saving_side_server("demo")
}
shinyApp(ui, server)
}
demoApp()

Capture selectize Input value in R shiny module

I am building a shiny app with a selectize input.
The choices in the input are dependent upon the ids in the underlying data.
In my real app, the data updates with a call to an API.
I would like the selected id choice in the selectize input to hold constant when I hit the "update data" button.
I was able to do this prior to using shiny modules. However, when I tried to transform my code to use a shiny module, it fails to hold the selected id value, and resets the selectize input each time I update the underlying data.
The following example was helpful without the module, but when I use the module it doesn't seem to work...link here
Below is a reprex. Thanks for any help.
library(shiny)
library(tidyverse)
# module UI
mymod_ui <- function(id){
ns <- NS(id)
tagList(
uiOutput(ns("ids_lookup")),
)
}
# module server
mymod_server <- function(input, output, session, data, actionb){
ns <-session$ns
ids <- reactive(
data() %>%
filter(!is.na(first_name) & !is.na(last_name) & !is.na(ages)) %>%
mutate(ids = paste(first_name, last_name, sep = " ")) %>%
select(ids)
)
output$ids_lookup <- renderUI({
selectizeInput(ns("lookup"),
label = "Enter id:",
choices = c("Type here ...", ids()), multiple = FALSE)
})
# here is where I would like to hold on to the selected ids when updating the table
# when I click the "reload_data" button I don't want the name to change
# I pass the button from the main server section into the module
current_id_selection <- reactiveVal("NULL")
observeEvent(actionb(), {
current_id_selection(ns(input$ids_lookup))
updateSelectizeInput(session,
inputId = ns("lookup"),
choices = ids(),
selected = current_id_selection())
})
}
ui <- fluidPage(
titlePanel("Test module app"),
br(),
# this button reloads the data
actionButton(
inputId = "reload_data",
label = "Reload data"
),
br(),
br(),
# have a look at the data
h4("Raw data"),
tableOutput("mytable"),
br(),
# now select a single id for further analysis in a much larger app
mymod_ui("mymod"),
)
server <- function(input, output, session) {
df <- eventReactive(input$reload_data, {
# in reality, df is a dataframe which is updated from an API call everytime you press the action button
df <- tibble(
first_name = c("john", "james", "jenny", "steph"),
last_name = c("x", "y", "z", NA),
ages = runif(4, 30, 60)
)
return(df)
}
)
output$mytable <- renderTable({
df()
})
# make the reload data button a reactive val that can be passed to the module for the selectize Input
mybutton <- reactive(input$reload_data)
callModule(mymod_server, "mymod", data = df, actionb = mybutton)
}
shinyApp(ui, server)
Just using inputId = "lookup" instead of inputId = ns("lookup") in updateSelectizeInput() will do it. Also, you had another typo in there. Try this
library(shiny)
library(tidyverse)
# module UI
mymod_ui <- function(id){
ns <- NS(id)
tagList(
uiOutput(ns("ids_lookup")),
verbatimTextOutput(ns("t1"))
)
}
# module server
mymod_server <- function(input, output, session, data, actionb){
ns <-session$ns
ids <- reactive(
data() %>%
filter(!is.na(first_name) & !is.na(last_name) & !is.na(ages)) %>%
mutate(ids = paste(first_name, last_name, sep = " ")) %>%
select(ids)
)
output$ids_lookup <- renderUI({
selectizeInput(ns("lookup"),
label = "Enter id:",
choices = c("Type here ...", ids()), multiple = FALSE)
})
# here is where I would like to hold on to the selected ids when updating the table
# when I click the "reload_data" button I don't want the name to change
# I pass the button from the main server section into the module
current_id_selection <- reactiveValues(v=NULL)
observeEvent(actionb(), {
req(input$lookup)
current_id_selection$v <- input$lookup
output$t1 <- renderPrint(paste0("Current select is ",current_id_selection$v))
updateSelectizeInput(session,
inputId = "lookup",
choices = ids(),
selected = current_id_selection$v )
})
}
ui <- fluidPage(
titlePanel("Test module app"),
br(),
# this button reloads the data
actionButton(inputId = "reload_data", label = "Reload data"
),
br(),
br(),
# have a look at the data
h4("Raw data"),
tableOutput("mytable"),
br(),
# now select a single id for further analysis in a much larger app
mymod_ui("mymod")
)
server <- function(input, output, session) {
df <- eventReactive(input$reload_data, {
# in reality, df is a dataframe which is updated from an API call everytime you press the action button
df <- tibble(
first_name = c("john", "james", "jenny", "steph"),
last_name = c("x", "y", "z", NA),
ages = runif(4, 30, 60)
)
return(df)
})
output$mytable <- renderTable({
df()
})
# make the reload data button a reactive val that can be passed to the module for the selectize Input
mybutton <- reactive(input$reload_data)
callModule(mymod_server, "mymod", data = df, actionb = mybutton)
}
shinyApp(ui, server)

Add and delete rows of DT Datatable in R Shiny

I'm trying to add a "save inputs" feature to my Shiny app where the saved inputs would be saved in a DT data table. If a user clicks an Add button, the inputs would be appended to a data table. A user then can delete a row from this data table by selecting a row and clicking the Delete button. I also need to have this table's values be saved as a global variable so it stays persistent across all sessions.
The example code is shown below. When I close the session, the table (this_table) is correctly updated, however, those changes don't appear realtime during the app. I've tried putting both of these input buttons in an eventReactive function, but this did not work when one of the buttons was selected more than once.
Any ideas?
Global table:
this_table = data.frame(bins = c(30, 50), cb = c(T, F))
Shiny app code:
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30),
checkboxInput("cb", "T/F"),
actionButton("add_btn", "Add"),
actionButton("delete_btn", "Delete")
),
mainPanel(
DTOutput("shiny_table")
)
)
)
server <- function(input, output) {
observeEvent(input$add_btn, {
t = rbind(data.frame(bins = input$bins,
cb = input$cb), this_table)
this_table <<- t
})
observeEvent(input$delete_btn, {
t = this_table
print(nrow(t))
if (!is.null(input$shiny_table_rows_selected)) {
t <- t[-as.numeric(input$shiny_table_rows_selected),]
}
this_table <<- t
})
output$shiny_table <- renderDT({
datatable(this_table, selection = 'single', options = list(dom = 't'))
})
}
shinyApp(ui = ui, server = server)
You can use reactiveVal to add server side variables that are observable and mutable at the same time. The syntax for those variables is to initialize them as
rV <- reactiveValue("init_value")
and update them with
rV("new_value")
Those variables can be accessed inside reactive contexts (basically like inputs) with
rV()
The syntax is quite unusual for R and might take time to get used to, but it is definitely the recommended way to solve issues like these. You might also want to take a look at reactiveValues for a similar functionality but with a semantic closer to the R class list.
Here is how this technique can be applied to your question
library(shiny)
library(DT)
this_table = data.frame(bins = c(30, 50), cb = c(T, F))
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30),
checkboxInput("cb", "T/F"),
actionButton("add_btn", "Add"),
actionButton("delete_btn", "Delete")
),
mainPanel(
DTOutput("shiny_table")
)
)
)
server <- function(input, output) {
this_table <- reactiveVal(this_table)
observeEvent(input$add_btn, {
t = rbind(data.frame(bins = input$bins,
cb = input$cb), this_table())
this_table(t)
})
observeEvent(input$delete_btn, {
t = this_table()
print(nrow(t))
if (!is.null(input$shiny_table_rows_selected)) {
t <- t[-as.numeric(input$shiny_table_rows_selected),]
}
this_table(t)
})
output$shiny_table <- renderDT({
datatable(this_table(), selection = 'single', options = list(dom = 't'))
})
}
shinyApp(ui = ui, server = server)
Finally, I would like to add that # Vishesh Shrivastavs recommendation to use the rhandsontable package is also a viable approach, although you will definitely loose some flexibility in doing so.

R shiny: Cannot delete last value in SelectizeInput

I have a very simple question:
Why is it in the following code not possible to remove the last entered value in SelectizeInput? E.g. I entered "a" "b" & "c" and I want to delete them all afterwards from the SelectizeInput. Why is it no possible to remove the last remaing value from SelectizeInput?
library(shiny)
ui <- bootstrapPage(
textInput(inputId = "txtinput", label = "Insert label", value = "", placeholder = ""),
actionButton(inputId = "actbtn", label = "Add label"),
uiOutput('selctInput')
)
server <- function(input, output, session){
#Create reactive values
rv <- reactiveValues()
#When Button is clicked..
observeEvent(input$actbtn, {
#Save value in a reactiveValues list
rv$new.label <- input$txtinput
#Collect all entries from text input
rv$labels <- c(rv$labels, rv$new.label)
#Clear textinput
updateTextInput(session, "txtinput", value = "")
})
#When selectizeInput changes...
observeEvent(input$selctInput, {
#Why is it not possible to completely empty selectizeInput? The last value just remains.
rv$labels <- input$selctInput
print(input$selctInput)
})
#Add selectizeInput to UI
output$selctInput <- renderUI({
selectizeInput(inputId = "selctInput", label= "Reorder / delete labels",
choices = rv$labels,
selected = rv$labels, multiple=TRUE,
options = list(plugins = list('remove_button', 'drag_drop')))
})
}
shinyApp(ui, server)
You just need to add ignoreNULL = FALSE to your observeEvent so that it "reacts" to the NULL value
#When selectizeInput changes...
observeEvent(input$selctInput, {
#Why is it not possible to completely empty selectizeInput? The last value just remains.
rv$labels <- input$selctInput
print(input$selctInput)
}, ignoreNULL = FALSE)
Update:
After finding this link https://gist.github.com/pvictor/ee154cc600e82f3ed2ce0a333bc7d015 there seems to be a far simpler approach:
library(shiny)
ui <- fluidPage(
selectizeInput("select", "Select", c(),
multiple = TRUE, options = list(
'plugins' = list('remove_button'),
'create' = TRUE,
'persist' = FALSE)
),
textOutput("out")
)
server <- function(input, output){
output$out <- renderText({
input$select
})
}
shinyApp(ui, server)

Resources