I am trying to update the choices of a selectizeInput based on the current selected choices. Here is my attempt (causes loop):
library(shiny)
run_ui <- function() {
ui <- selectizeInput('words', 'Search words:', choices = NULL, selected = NULL, multiple = TRUE, options = NULL)
server <- function(input, output, session) {
# change 'Search words' ----
observeEvent(input$words, {
# handle no words (reset everything)
if (is.null(input$words)) {
cowords <- letters
} else {
# update cowords (choices for selectizeInput)
cowords <- unique(c(input$words, sample(letters, 5)))
}
# update UI
print('updating')
updateSelectizeInput(session, 'words', choices = cowords, selected = input$words, server = TRUE)
}, ignoreNULL = FALSE)
}
runGadget(shinyApp(ui, server), viewer = browserViewer())
}
run_ui()
How can I achieve this?
If you want to stick to server = TRUE, it's maybe not a trivial problem.
One possible work-around could be to debounce the input that you are observing, and then check and only update in case there is a change. This could look as follows - I added some print statements such that you can better follow what's happening.
library(shiny)
run_ui <- function() {
ui <- selectizeInput('words', 'Search words:', choices = NULL, selected = NULL, multiple = TRUE, options = NULL)
server <- function(input, output, session) {
val <- "a"
pasteCollPlus <- function(...) {
paste(..., collapse = "+")
}
wordSelect <- debounce(reactive({input$words}), millis = 50)
# change 'Search words' ----
observeEvent(wordSelect(), {
# handle no words (reset everything)
if (is.null(input$words)) {
cowords <- letters
} else {
# update cowords (choices for selectizeInput)
cowords <- unique(c(input$words, sample(letters, 5)))
}
if (isTRUE(pasteCollPlus(val) == pasteCollPlus(input$words))) {
print(paste("No update - val is", pasteCollPlus(val)))
} else {
# update UI
print(paste("updating selection to", pasteCollPlus(input$words)))
print(paste("val is", pasteCollPlus(val)))
val <<- input$words
updateSelectizeInput(session, 'words', choices = cowords, selected = input$words, server = TRUE)
}
}, ignoreNULL = FALSE)
}
runGadget(shinyApp(ui, server), viewer = browserViewer())
}
run_ui()
Edit
Another work-around would be to handle the bouncing pattern explicitly, in order to block it. This is maybe even less elegant, but could be more robust for more involved / complex cases (apps). An example for this follows:
library(shiny)
run_ui <- function() {
ui <- selectizeInput('words', 'Search words:', choices = NULL, selected = NULL, multiple = TRUE, options = NULL)
server <- function(input, output, session) {
val <- "a"
newVal <- NULL
pasteCollPlus <- function(...) {
paste(..., collapse = "+")
}
# change 'Search words' ----
observeEvent(input$words, {
# handle no words (reset everything)
if (is.null(input$words)) {
cowords <- letters
} else {
# update cowords (choices for selectizeInput)
cowords <- unique(c(input$words, sample(letters, 5)))
}
if (isTRUE(pasteCollPlus(val) == pasteCollPlus(input$words))) {
print(paste("No update - val is", pasteCollPlus(val)))
val <<- newVal
} else {
# update UI
print(paste("updating selection to", pasteCollPlus(input$words)))
print(paste("val is", pasteCollPlus(val)))
print(paste("newVal is", pasteCollPlus(newVal)))
val <<- NULL
newVal <<- input$words
updateSelectizeInput(session, 'words', choices = cowords, selected = input$words, server = TRUE)
}
}, ignoreNULL = FALSE)
}
runGadget(shinyApp(ui, server), viewer = browserViewer())
}
run_ui()
Do you need to use server-side selectize? If not, then your code would work fine as-is by simply removing that part.
library(shiny)
run_ui <- function() {
ui <- selectizeInput('words', 'Search words:', choices = NULL, selected = NULL, multiple = TRUE, options = NULL)
server <- function(input, output, session) {
# change 'Search words' ----
observeEvent(input$words, {
# handle no words (reset everything)
if (is.null(input$words)) {
cowords <- letters
} else {
# update cowords (choices for selectizeInput)
cowords <- unique(c(input$words, sample(letters, 5)))
}
# update UI
print('updating')
updateSelectizeInput(session, 'words', choices = cowords, selected = input$words)
}, ignoreNULL = FALSE)
}
runGadget(shinyApp(ui, server), viewer = browserViewer())
}
run_ui()
The following solution simply updates the entire object through renderUI and re-draws it, rather than passing back an update via updateSelectizeInput(). This does allow choices to be fully managed on the server-side. A downside is that it fires with each change event, which means that the multiple=TRUE is moot since the object redraws with each change. If multiples are critical, I think the updateSelectizeInput() approach or any other solution that updates onChange, would run into the same issue. To allow multiple choices, the event would need to move to onBlur or a mouseout event. Otherwise, the event trigger doesn't know if a user intends to select only one choice and fire; or wait for the user to make multiple choices before firing. However, blur or mouseout might make it behave strangely from the user's perspective. A button forcing the update action would resolve this. Keeping the update based on the first select, solution as follows:
library(shiny)
run_ui <- function() {
ui <- uiOutput(outputId="select_words")
server <- function(input, output, session) {
# change 'Search words' ----
output$select_words <- renderUI({
cowords <- letters
if (!is.null(input$words)) cowords <- unique(c(input$words, sample(letters, 5)))
print(paste("Updating words: ",paste0(cowords,collapse=",")))
return (tagList(selectizeInput('words', 'Search words:', choices = cowords, selected = input$words, multiple = TRUE, options = NULL)))
})
}
runGadget(shinyApp(ui, server), viewer = browserViewer())
}
run_ui()
Related
I really had trouble finding a title for this question, hope it helps.
I have a fairly complex app for which I'm having trouble resetting an output after an actionButton ("Confirm" on this example) triggers the re-evaluation of a reactiveValues number that feeds a reactive table.
This causes that the selected table only renders once and no matter how many times the table that feeds it changes, it keeps showing the same result as the first time it was rendered.
It will be easy for you to see what I mean from this example. Believe me, it is the minimax from the one I'm coming from:
library(shiny)
library(DT)
ui <- fluidPage(
DTOutput("table"),
actionButton("checkvalues", "Check")
)
server <- function(input, output, session) {
primedata <- reactiveValues(data = NULL)
primedata$data <- as.numeric(Sys.time()) %% 10000
tabledata <- reactive({
data <- data.frame(rep(primedata$data, 5))
for (i in 1:5) {
data$V1[i] <- as.character(selectInput(paste0("sel", i), "",
choices = c("None selected" = 0,
"Icecream", "Donut"),
selected = 0, width = "120px"))
}
return(data)
})
output$table <- renderDataTable( #Generar tabla
tabledata(), filter = 'top', escape = FALSE, selection = 'none', server = FALSE,
callback = JS("table.rows().every(function(i, tab, row) {
var $this = $(this.node());
$this.attr('id', this.data()[0]);
$this.addClass('shiny-input-container');
});
Shiny.unbindAll(table.table().node());
Shiny.bindAll(table.table().node());")
)
# helper function for reading inputs in DT
shinyValue = function(id, len) {
unlist(lapply(seq_len(len), function(i) {
value = input[[paste0(id, i)]]
if (is.null(value)) NA else value
}))
}
observeEvent(input$checkvalues, {
datos <- tabledata()
selected <- cbind(datos, data.frame(N = shinyValue("sel", nrow(datos))))
selected <- selected %>% group_by(N) %>% summarise("see" = n())
showModal(modalDialog(
title = HTML('<h3 style="text-align:center;">Problem: this table will keep showing the same results as the first one presented</h3>'),
renderDT(datatable(selected, options = list(dom = 't', ordering = F))),
footer = actionButton("Confirm", "Confirm")))
})
observeEvent(input$Confirm, {
primedata$data <- as.numeric(Sys.time()) %% 10000
removeModal()
})
}
shinyApp(ui, server)
When you change primedata$data (by clicking on the Confirm button) this re-renders the table, and you have to unbind before:
ui <- fluidPage(
tags$head(tags$script(
HTML(
"Shiny.addCustomMessageHandler('unbindDT', function(id) {
var $table = $('#'+id).find('table');
if($table.length > 0){
Shiny.unbindAll($table.DataTable().table().node());
}
})")
)),
DTOutput("table"),
actionButton("checkvalues", "Check")
)
observeEvent(input$Confirm, {
session$sendCustomMessage("unbindDT", "table")
primedata$data <- as.numeric(Sys.time()) %% 10000
removeModal()
})
I have a leaflet map & datatable in a shiny app and have various input boxes to select what is being mapped.
Currently the data is processed on the server based on a set of shiny inputs, and that data is passed to both leaflet and datatable.
I'd also like to have a button on the datatable (or read double clicks on the datatable) and update a shiny input (i.e., call shiny::updateSelectizeInput) based on the users interaction with the datatable.
minimal code example:
if (interactive()) {
library(shiny)
library(DT)
shinyApp(
ui = fluidPage(
selectInput("species_selection", "Select species",
choices = c("all", as.character(iris$Species)))
, dataTableOutput("dt")
)
, server = function(input, output) {
output$dt <- renderDataTable({
if ( input$species_selection != "all" ) {
for_table <- iris %>%
filter(Species == input$species_selection)
} else {
for_table <- iris
}
for_table
# but also you can click a button or double-click a row on this datatable
# to update input$species_selection above
})
}
)
}
I'm aware there's no reason for this in this minimal example but I do want to do so for in the context of my larger app.
I've seen examples (for example, superzip) where buttons on the datatable are linked to html, and I know the datatable shiny tutorials tell you how to catch selected rows with an observer. Catching the selected rows is my backup plan but I would prefer a button on the row or a double-click.
Sure, but its a bit fiddly. I used mtcars as it has more variety:
library(shiny)
library(DT)
shinyApp(
#UI
ui <- fluidPage(
selectInput('carb_selection', 'Select carb', choices = c('all', as.character(mtcars$carb))),
DT::dataTableOutput('dt'),
),
#Server
server <- function(input, output, session) {
#Function to create buttons
shinyInput <- function(FUN, len, id, ...) {
inputs <- character(len)
for (i in seq_len(len)) {
inputs[i] <- as.character(FUN(paste0(id, i), ...))
}
inputs
}
#Add buttons to the mtcars dataframe
mtcars_btn <- reactiveValues(
data = data.frame(
mtcars,
carb_selector = shinyInput(actionButton, nrow(mtcars), 'button_', label = "Select", onclick = 'Shiny.onInputChange(\"select_button\", this.id)'),
stringsAsFactors = FALSE
)
)
#Output datatable
output$dt <- DT::renderDataTable(
if (input$carb_selection == 'all'){
DT::datatable(mtcars_btn$data, escape = FALSE, selection = 'none', options = list(searching = FALSE, ordering = FALSE))
} else {
DT::datatable(mtcars_btn$data[mtcars_btn$data$carb == input$carb_selection, ], escape = FALSE, selection = 'none', options = list(searching = FALSE, ordering = FALSE))
}
)
#Observe a button being clicked
observeEvent(input$select_button, {
carb_selected <- mtcars_btn$data[as.numeric(strsplit(input$select_button, "_")[[1]][2]),]$carb
print(paste0('clicked on ', carb_selected))
updateSelectInput(session, 'carb_selection', selected = carb_selected)
})
}
)
Note that you may wish to switch between local and server processing when using large dataframes.
Currently, I have my shiny app in condition such that I am able to enter information in the sidebar panel, and able to display the output in the mainpanel. Next, I would like to create a login screen using shinyauthr library, such that user 1 should only able to see the side bar panel information, and the Output table should only be displayed when user2 login. For this,I am trying to follow the code mentioned on the main page of shinyauthr. My issue is that whenever I try to hide mainpanel using ## tag. Below error will get display.
Note: I am new to Shiny, provide explanation with external link or code
"Error in sidebarLayout(sidebarPanel(div(id = "form", textInput("name", :
argument "mainPanel" is missing, with no default"
Code that takes in UserInput and display in Mainpanel:
#Storing data on Local Machine
library(shiny)
library(ggplot2)
outputDir <- "responses"
# Define the fields we want to save from the form
fields <- c("name", "address","used_shiny", "r_num_years","select")
#Which fields are mandatory
fieldsMandatory<-c("name","address")
labelMandatory <- function(label) {
tagList(
label,
span("*", class = "mandatory_star")
)
}
appCSS <-
".mandatory_star { color: red; }
#error { color: red; }"
saveData <- function(input) {
# put variables in a data frame
data <- data.frame(matrix(nrow=1,ncol=0))
for (x in fields) {
var <- input[[x]]
if (length(var) > 1 ) {
# handles lists from checkboxGroup and multiple Select
data[[x]] <- list(var)
} else {
# all other data types
data[[x]] <- var
}
}
data$submit_time <- date()
# Create a unique file name
fileName <- sprintf(
"%s_%s.rds",
as.integer(Sys.time()),
digest::digest(data)
)
# Write the file to the local system
saveRDS(
object = data,
file = file.path(outputDir, fileName)
)
}
loadData <- function() {
# read all the files into a list
files <- list.files(outputDir, full.names = TRUE)
if (length(files) == 0) {
# create empty data frame with correct columns
field_list <- c(fields, "submit_time")
data <- data.frame(matrix(ncol = length(field_list), nrow = 0))
names(data) <- field_list
} else {
data <- lapply(files, function(x) readRDS(x))
# Concatenate all data together into one data.frame
data <- do.call(rbind, data)
}
data
}
deleteData <- function() {
# Read all the files into a list
files <- list.files(outputDir, full.names = TRUE)
lapply(files, file.remove)
}
resetForm <- function(session) {
# reset values
updateTextInput(session, "name", value = "")
updateTextInput(session, "address", value = "")
updateCheckboxInput(session, "used_shiny", value = FALSE)
updateSliderInput(session, "r_num_years", value = 0)
updateSelectInput(session,"select",selected = 'NULL')
}
ui <- fluidPage(
shinyjs::useShinyjs(),
shinyjs::inlineCSS(appCSS),
# App title ----
titlePanel("Data Collection & Feedback"),
# Sidebar layout with input and output definitions ----
sidebarLayout(
# Sidebar panel for inputs ----
sidebarPanel(
div(id='form',
textInput("name", labelMandatory("Name"), ""),
textInput("address",labelMandatory('address'),""),
checkboxInput("used_shiny", "I've built a Shiny app before", FALSE),
sliderInput("r_num_years", "Number of years using R",
0, 10, 0, ticks = FALSE),
selectInput("select","select",choices = c('a','e','i')),
actionButton("submit", "Submit",class='btn-primary'),
actionButton("clear", "Clear Form"),
downloadButton("downloadData", "Download"),
actionButton("delete", "Delete All Data"),
shinyjs::hidden(
span(id = "submit_msg", "Submitting..."),
div(id = "error",
div(br(), tags$b("Error: "), span(id = "error_msg"))
)
)
),
shinyjs::hidden(
div(
id = "thankyou_msg",
h3("Thanks, your response was submitted successfully!"),
actionLink("submit_another", "Submit another response")
)
)
),
# Main panel for displaying outputs ----
mainPanel(
dataTableOutput("responses")
)
)
)
server = function(input, output, session) {
# Enable the Submit button when all mandatory fields are filled out
observe({
mandatoryFilled <-
vapply(fieldsMandatory,
function(x) {
!is.null(input[[x]]) && input[[x]] != ""
},
logical(1))
mandatoryFilled <- all(mandatoryFilled)
shinyjs::toggleState(id = "submit", condition = mandatoryFilled)
})
# When the Submit button is clicked, save the form data
observeEvent(input$submit, {
#saveData(input)
#resetForm(session)
shinyjs::disable("submit")
shinyjs::show("submit_msg")
shinyjs::hide("error")
tryCatch({
saveData(input)
shinyjs::reset("form")
shinyjs::hide("form")
shinyjs::show("thankyou_msg")
},
error = function(err) {
shinyjs::html("error_msg", err$message)
shinyjs::show(id = "error", anim = TRUE, animType = "fade")
},
finally = {
shinyjs::enable("submit")
shinyjs::hide("submit_msg")
})
})
observeEvent(input$submit_another, {
shinyjs::show("form")
shinyjs::hide("thankyou_msg")
})
observeEvent(input$clear, {
resetForm(session)
})
# When the Delete button is clicked, delete all of the saved data files
observeEvent(input$delete, {
deleteData()
})
# Show the previous responses in a reactive table ----
output$responses <- renderDataTable({
# update with current response when Submit or Delete are clicked
input$submit
input$delete
loadData()
})
# Downloadable csv of selected dataset ----
output$downloadData <- downloadHandler(
filename= "data.csv",
content = function(file) {
write.csv(loadData(), file, row.names = FALSE, quote= TRUE)
}
)
}
shinyApp(ui, server)
Use a req() condition to only render the responses table in the mainPanel if the logged in user is user2.
# Show the previous responses in a reactive table ----
output$responses <- renderDataTable({
# only render table if user2 is logged in
req(credentials()$info$user == "user2")
# update with current response when Submit or Delete are clicked
input$submit
input$delete
loadData()
})
Check out the package readme for a full app example that uses req() to conditionally show a table based on a login condition.
I have the following sample app and I need to be able to switch inputs for multiple_choice_1_source OR multiple_choice_2_type without breaking the app and hiding submit_request_button_ui and ColnamesInput when the inputs from change. Basically, the user should be able to revise the inputs after clicking the "submit" button and the app should reset to its previous state.
What I have tried:
shinyjs() - this just hides and does not clear the inputs. This means that once I press the submit_request_button then any change made to multiple_choice_2_type is still processed and reacted upon. In the actual app, I have the submit tied to very large tables. I want to prevent the fetch for
selected_data() from re-running and clear and hide the elements that were created in the first two choices.
reactive - I tried to make the observers listen to some reactive triggers that take dependencies from more than one input. I used user_input_rv to store the values etc but this fails as the observers are triggered more than once, so when I click the submit button, the if statements within the reactive({}) are triggered twice, essentially downloading each dataset more than once. Also it fails.
isolate - I have not been able to make this work. I tried multiple combinations of isolate with no success.
library(shiny)
library(tidyverse)
ui <- fluidPage(
selectizeInput(inputId ='multiple_choice_1_source',
choices = c("db1","db2","db3","db4"), # like this because we want the selected to be blank on initialisation
label = "1. Select source",
multiple = FALSE,
size = 10,
width = '100%'
)
,uiOutput(outputId="multiple_choice_2_type_ui")
,uiOutput(outputId="submit_request_button_ui")
,uiOutput(outputId="ColnamesInput")
)
server <- function(input, output)
{
user_input_rv = reactiveValues(
source_picked = NULL,
last_used_source = NULL,
type_picked = NULL,
series_picked = NULL,
last_used_series = NULL,
selected_data = NULL,
final_selection = NULL
)
observeEvent(input$multiple_choice_1_source, {
user_input_rv$source_picked <- input$multiple_choice_1_source
#change data loaded under type picked.
user_input_rv$type_picked <-
if ( input$multiple_choice_1_source == "db1"){ paste0(colnames(mtcars))
} else if ( input$multiple_choice_1_source == "db2"){ paste0(colnames(diamonds))
} else if ( input$multiple_choice_1_source == "db3"){ NULL
} else if ( input$multiple_choice_1_source == "db4"){ NULL
}
output$multiple_choice_2_type_ui <- renderUI({
selectizeInput( inputId = 'multiple_choice_2_type',
choices = paste(user_input_rv$type_picked),
label= "2. Select type",
multiple = TRUE,
size = 10,
width = '100%',
options = list( placeholder = 'Type',
maxItems =1
)
)
})
}) #first observeEvent for source type and data load.
observeEvent(input$multiple_choice_2_type,{
output$submit_request_button_ui <- renderUI({
actionButton(
inputId = "submit_request_button",
label = " Get data "
)
})
})#second observeEvent for submit_request_button_ui
observeEvent(input$submit_request_button, {
selected_data <- reactive({
if( input$multiple_choice_1_source =="db1"){
mtcars
} else if ( input$multiple_choice_1_source == "db1") {
diamonds
} else if ( input$multiple_choice_1_source == "db3") { NULL
} else if ( input$multiple_choice_1_source == "db4"){ NULL
}
})
user_input_rv$series_picked <- input$multiple_choice_2_type
user_input_rv$selected_data <- selected_data()
min_cols <- as.integer(1) # default 1
max_cols <- as.integer(length(colnames(selected_data())))
#print(max_cols)
#this renderUI creates the right-hand side column of the app COLUMNS
output$ColnamesInput <- renderUI({
lapply(min_cols:max_cols, function(z) {
column(width = 3,
offset = 0,
selectInput( inputId = paste0("cols","_",z),
label = paste(input$multiple_choice_2_type,": ",colnames(selected_data())[z]),
choices = unique(selected_data()[[z]]),
multiple = TRUE
) #selectizeInput
)
})#lapply inner
}) #renderUI for columns
}) #third observeEvent for data selection and customisation
}
shinyApp(ui = ui, server = server)
Here is a the code in which I have removed the reactive expression from and used a local variable selected_data instead.
observeEvent(input$submit_request_button, {
# selected_data <- reactive({
# browser()
selected_data <- NULL
if( input$multiple_choice_1_source =="db1"){
selected_data <- mtcars
} else if ( input$multiple_choice_1_source == "db1") {
selected_data <- diamonds
} else if ( input$multiple_choice_1_source == "db3") { selected_data <- NULL
} else if ( input$multiple_choice_1_source == "db4"){selected_data <- NULL
}
# })
user_input_rv$series_picked <- isolate(input$multiple_choice_2_type)
user_input_rv$selected_data <- selected_data
min_cols <- as.integer(1) # default 1
max_cols <- as.integer(length(colnames(selected_data)))
#print(max_cols)
#this renderUI creates the right-hand side column of the app COLUMNS
output$ColnamesInput <- renderUI({
lapply(min_cols:max_cols, function(z) {
column(width = 3,
offset = 0,
selectInput( inputId = paste0("cols","_",z),
label = paste(isolate(input$multiple_choice_2_type),": ",colnames(selected_data)[z]),
choices = unique(selected_data[[z]]),
multiple = TRUE
) #selectizeInput
)
})#lapply inner
}) #renderUI for columns
}) #third observeEvent for data selection and customisation
Now when you change the select input options the ColnamesInput do not get triggered. It gets triggered only after you click the submit button.
[EDIT]:
Might not be the best method, but I think I am able to achieve what you wanted. Also, I have taken the liberty on using the reactiveValue that was already defined in your server. Have a look at the modified server code below:
server <- function(input, output)
{
user_input_rv = reactiveValues(
source_picked = NULL,
last_used_source = NULL,
type_picked = NULL,
series_picked = NULL,
last_used_series = NULL,
selected_data = NULL,
final_selection = NULL
)
observeEvent(input$multiple_choice_1_source, {
user_input_rv$source_picked <- input$multiple_choice_1_source
###Start: To check if the source changed#########
if(!is.null(user_input_rv$last_used_source))
{
if(user_input_rv$last_used_source != user_input_rv$source_picked)
{
shinyjs::hide("ColnamesInput")
user_input_rv$last_used_source = user_input_rv$source_picked
}
}else
{
user_input_rv$last_used_source = user_input_rv$source_picked
}
###End: To check if the source changed#########
#change data loaded under type picked.
user_input_rv$type_picked <-
if ( input$multiple_choice_1_source == "db1"){ paste0(colnames(mtcars))
} else if ( input$multiple_choice_1_source == "db2"){ paste0(colnames(diamonds))
} else if ( input$multiple_choice_1_source == "db3"){ NULL
} else if ( input$multiple_choice_1_source == "db4"){ NULL
}
output$multiple_choice_2_type_ui <- renderUI({
selectizeInput( inputId = 'multiple_choice_2_type',
choices = paste(user_input_rv$type_picked),
label= "2. Select type",
multiple = TRUE,
size = 10,
width = '100%',
options = list( placeholder = 'Type',
maxItems =1
)
)
})
}) #first observeEvent for source type and data load.
observeEvent(input$multiple_choice_2_type,{
###Start: To check if the series changed#########
user_input_rv$series_picked <- input$multiple_choice_2_type
if(!is.null(user_input_rv$last_used_series))
{
if(user_input_rv$last_used_series != user_input_rv$series_picked)
{
shinyjs::hide("ColnamesInput")
user_input_rv$last_used_series = user_input_rv$series_picked
}
}else
{
user_input_rv$last_used_series = user_input_rv$series_picked
}
###End: To check if the series changed#########
output$submit_request_button_ui <- renderUI({
actionButton(
inputId = "submit_request_button",
label = " Get data "
)
})
})#second observeEvent for submit_request_button_ui
observeEvent(input$submit_request_button, {
# selected_data <- reactive({
# browser()
shinyjs::show("ColnamesInput")
selected_data <- NULL
if( input$multiple_choice_1_source =="db1"){
selected_data <- mtcars
} else if ( input$multiple_choice_1_source == "db1") {
selected_data <- diamonds
} else if ( input$multiple_choice_1_source == "db3") { selected_data <- NULL
} else if ( input$multiple_choice_1_source == "db4"){selected_data <- NULL
}
# })
user_input_rv$series_picked <- isolate(input$multiple_choice_2_type)
user_input_rv$selected_data <- selected_data
min_cols <- as.integer(1) # default 1
max_cols <- as.integer(length(colnames(selected_data)))
#print(max_cols)
#this renderUI creates the right-hand side column of the app COLUMNS
output$ColnamesInput <- renderUI({
lapply(min_cols:max_cols, function(z) {
column(width = 3,
offset = 0,
selectInput( inputId = paste0("cols","_",z),
label = paste(isolate(input$multiple_choice_2_type),": ",colnames(selected_data)[z]),
choices = unique(selected_data[[z]]),
multiple = TRUE
) #selectizeInput
)
})#lapply inner
}) #renderUI for columns
}) #third observeEvent for data selection and customisation
}
Hope it helps!
I would like to have a working example similar to this:
https://demo.shinyapps.io/029-row-selection/
I tried the example in my Shiny server running Shiny Server v1.1.0.10000, packageVersion: 0.10.0 and Node.js v0.10.21, but it is not working even if I load the js and css files from the website. It simply does not select rows from the table:
# ui.R
library(shiny)
shinyUI(fluidPage(
title = 'Row selection in DataTables',
tagList(
singleton(tags$head(tags$script(src='//cdn.datatables.net/1.10.2/js/jquery.dataTables.js',type='text/javascript'))),
singleton(tags$head(tags$script(src='//cdn.datatables.net/1.10.2/css/jquery.dataTables.min.css',type='text/css')))
),
sidebarLayout(
sidebarPanel(textOutput('rows_out')),
mainPanel(dataTableOutput('tbl')),
position = 'right'
)
))
# server.R
library(shiny)
shinyServer(function(input, output) {
output$tbl <- renderDataTable(
mtcars,
options = list(pageLength = 10),
callback = "function(table) {
table.on('click.dt', 'tr', function() {
$(this).toggleClass('selected');
Shiny.onInputChange('rows',
table.rows('.selected').indexes().toArray());
});
}"
)
output$rows_out <- renderText({
paste(c('You selected these rows on the page:', input$rows),
collapse = ' ')
})
})
I then tried to do this from a different example that was using radio buttons to re-sort the rows.
In my modified example, I want to produce a list of ids from the selected checkbox buttons of the dataTables table shown in the webpage. E.g., selecting some rows from the first 5, I want my textbox to be: 1,3,4 corresponding to the mymtcars$id column I added to mtcars. I then plan to link an action to the values of the textbox.
I have it almost there in this example, but checking the boxes does not update the list in the textbox. Differently to the example shinyapp, I would like my checkboxes to keep the selection status if the table is resorted. This may be the tricky part, and I am not sure how to do it. I would also like to add a "Select/Unselect all" textbox on the top left corner of the table, that selects/unselects all boxes in the table. Any ideas?
# server.R
library(shiny)
mymtcars = mtcars
mymtcars$id = 1:nrow(mtcars)
shinyServer(function(input, output, session) {
rowSelect <- reactive({
if (is.null(input[["row"]])) {
paste(sort(unique(rep(0,nrow(mymtcars)))),sep=',')
} else {
paste(sort(unique(input[["row"]])),sep=',')
}
})
observe({
updateTextInput(session, "collection_txt",
value = rowSelect()
,label = "Foo:"
)
})
# sorted columns are colored now because CSS are attached to them
output$mytable = renderDataTable({
addCheckboxButtons <- paste0('<input type="checkbox" name="row', mymtcars$id, '" value="', mymtcars$id, '">',"")
#Display table with checkbox buttons
cbind(Pick=addCheckboxButtons, mymtcars[, input$show_vars, drop=FALSE])
}, options = list(bSortClasses = TRUE, aLengthMenu = c(5, 25, 50), iDisplayLength = 25))
})
# ui.R
library(shiny)
mymtcars = mtcars
mymtcars$id = 1:nrow(mtcars)
shinyUI(pageWithSidebar(
headerPanel('Examples of DataTables'),
sidebarPanel(
checkboxGroupInput('show_vars', 'Columns to show:', names(mymtcars),
selected = names(mymtcars))
),
mainPanel(
dataTableOutput("mytable")
,textInput("collection_txt",label="Foo")
)
)
)
For the first problem you need the dev version of shiny and htmltools >= 0.2.6 installed:
# devtools::install_github("rstudio/htmltools")
# devtools::install_github("rstudio/shiny")
library(shiny)
runApp(list(ui = fluidPage(
title = 'Row selection in DataTables',
sidebarLayout(
sidebarPanel(textOutput('rows_out')),
mainPanel(dataTableOutput('tbl')),
position = 'right'
)
)
, server = function(input, output) {
output$tbl <- renderDataTable(
mtcars,
options = list(pageLength = 10),
callback = "function(table) {
table.on('click.dt', 'tr', function() {
$(this).toggleClass('selected');
Shiny.onInputChange('rows',
table.rows('.selected').indexes().toArray());
});
}"
)
output$rows_out <- renderText({
paste(c('You selected these rows on the page:', input$rows),
collapse = ' ')
})
}
)
)
for your second example:
library(shiny)
mymtcars = mtcars
mymtcars$id = 1:nrow(mtcars)
runApp(
list(ui = pageWithSidebar(
headerPanel('Examples of DataTables'),
sidebarPanel(
checkboxGroupInput('show_vars', 'Columns to show:', names(mymtcars),
selected = names(mymtcars))
,textInput("collection_txt",label="Foo")
),
mainPanel(
dataTableOutput("mytable")
)
)
, server = function(input, output, session) {
rowSelect <- reactive({
paste(sort(unique(input[["rows"]])),sep=',')
})
observe({
updateTextInput(session, "collection_txt", value = rowSelect() ,label = "Foo:" )
})
output$mytable = renderDataTable({
addCheckboxButtons <- paste0('<input type="checkbox" name="row', mymtcars$id, '" value="', mymtcars$id, '">',"")
#Display table with checkbox buttons
cbind(Pick=addCheckboxButtons, mymtcars[, input$show_vars, drop=FALSE])
}, options = list(orderClasses = TRUE, lengthMenu = c(5, 25, 50), pageLength = 25)
, callback = "function(table) {
table.on('change.dt', 'tr td input:checkbox', function() {
setTimeout(function () {
Shiny.onInputChange('rows', $(this).add('tr td input:checkbox:checked').parent().siblings(':last-child').map(function() {
return $(this).text();
}).get())
}, 10);
});
}")
}
)
)
This answer has been rendered broken in shiny 0.11.1, but can easily be fixed. Here is the update that did it (link):
Added an escape argument to renderDataTable() to escape the HTML entities
in the data table for security reasons. This might break tables from previous
versions of shiny that use raw HTML in the table content, and the old behavior
can be brought back by escape = FALSE if you are aware of the security
implications. (#627)
Thus, to make the previous solutions work, one must specify escape = FALSE as an option to renderDataTable().
I have made an alternative for check boxes in tables based on the previous Answer code and some tweaking of the JQuery / JavaScript.
For anyone who prefers actual data over row numbers i wrote this code that extracts data from the table and shows that as selection. You can deselect by clicking again. It builds on the former Answers that were very helpful to me (THANKS) so i want to share this as well.
It needs a session object to keep the vector alive (scoping). Actually you can get whatever information you want from the table, just dive into JQuery and change the $row.find('td:nth-child(2)') (number is the column number).I needed the info from the Second column but it is up to you. Selection colors is a bit odd if you also change the visible column amount.... selection colors tend to disappear...
I hope this is helpful, works for me (needs to be optimized but no time for that now)
output$tbl <- renderDataTable(
mtcars,
options = list(pageLength = 6),
callback = "function(table) {
table.on('click.dt', 'tr', function() {
if ( $(this).hasClass('selected') ) {
$(this).removeClass('selected');
} else {
table.$('tr.selected').removeClass('selected');
$(this).addClass('selected');
}
var $row = $(this).closest('tr'),
$tdsROW = $row.find('td'),
$tdsUSER = $row.find('td:nth-child(2)');
$.each($tdsROW, function() {
console.log($(this).text());
});
Shiny.onInputChange('rows',table.rows('.selected').indexes().toArray());
Shiny.onInputChange('CELLselected',$tdsUSER.text());
Shiny.onInputChange('ROWselected',$(this).text());
});
}"
)
output$rows_out <- renderUI({
infoROW <- input$rows
if(length(input$CELLselected)>0){
if(input$CELLselected %in% session$SelectedCell){
session$SelectedCell <- session$SelectedCell[session$SelectedCell != input$CELLselected]
}else{
session$SelectedCell <- append(session$SelectedCell,input$CELLselected)
}
}
htmlTXT <- ""
if(length(session$SelectedCell)>0){
for(i in 1:length(session$SelectedCell)){
htmlTXT <- paste(htmlTXT,session$SelectedCell[i],sep="<br/>")
}
}else{htmlTXT <- "please select from the table"}
HTML(htmlTXT)
})
The answers above are outdated. I received error "Error in datatable: The 'callback' argument only accept a value returned from JS()".
Instead, This one works for me.