R/shiny change tab and input on dynamically generated link clicked - r

I am trying to add dynamically generated links to a datatable of variable length. Clicking the link should switch the focus to the details tab. At the same time, the select input should be updated to the car brand that was clicked, so that the information on the details tab is updated. I prepared a minimal example with actionLinks. However, I couldn't figure out how to make the links do what I want.
library(shiny)
library(htmlwidgets)
library(tibble)
library(DT)
ui <- fluidPage(
title = "Examples of DataTables",
sidebarLayout(
sidebarPanel(
selectInput("car", h3("Car"),choices = rownames(mtcars))
),
mainPanel(
tabsetPanel(id = "dataset",
tabPanel("Cars", DT::dataTableOutput("mytable1")),
tabPanel("Details", DT::dataTableOutput("mytable2"))))))
server <- function(input, output) {
shinyInput = function(FUN, len, id, labels, ...) {
inputs = NULL
for (i in seq_len(len)) {
inputs[i] = as.character(FUN(paste0(id, i), label = labels[i], ...))
}
return(inputs)
}
output$mytable1 <- DT::renderDataTable({
cars <- mtcars %>% rownames_to_column() %>% select(rowname, mpg, cyl)
cars$rowname <- shinyInput(actionLink, nrow(cars), "link_", labels = cars$rowname)
DT::datatable(cars, rownames = FALSE, escape = FALSE)
})
output$mytable2 <- DT::renderDataTable(DT::datatable(mtcars[input$car,]))
}
shinyApp(ui, server)
Any help would be appreciated. Thanks!

I found a solution using the onclick function of the button and JavaScript statements. To open the tab, simulate a click on it and to change the drop down menu use selectize.
shinyInput = function(FUN, len, id, labels, ...) {
inputs = NULL
for (i in seq_len(len)) {
inputs[i] = as.character(FUN(paste0(id, i), label = labels[i],
onclick = paste0('$("#dataset li a")[1].click();$("#car")[0].selectize.setValue("',labels[i],'")')))
}
return(inputs)
}

Related

R Shiny DT rendering shinyinputs breaks the width definition

I am trying to render different shinyinputs (in the example below I have checkboxes, but I am also rendering dropdowns) in a datatable on DT with R Shiny, using the shinyInput function below.
It works great, I was able to render all the components that I wanted inside the cells.
Unfortunately now I am trying to make the whole table readable and I am facing this issue.
Without the checkboxes the table is rendered properly and the column width are taken from the coldef, where I have a list of lists containing targets and widths.
As soon as I include checkboxes or any other shiny component, the columndef is not working anymore, not only for the columns containing checkboxes but for ALL of the columns, it just seems that the columndef is not present.
I trying solving my way around and I am not sure if this is a bug or if there even is any workaround for this issue. I spent so much time on this table that I would feel quite bad dropping it just because it's looking so bad with the checkboxes column rendered with 300px width.
In the example below you can keep or drop the variable newvar from the dataframe to see the behaviour changing on the inclusion of checkboxes, even though the first 3 columns aren't changing.
library(DT)
ui <- basicPage(
h2("The mtcars data"),
DT::dataTableOutput("mytable")
)
server <- function(input, output) {
shinyInput <- function(FUN, len, id, ...) {
inputs <- character(len)
for (i in seq_len(len)) {
inputs[i] <- as.character(FUN(paste0(id, i), ...))
}
inputs
}
mtcarsx <- data.frame(mtcars, newvar=paste0(shinyInput(checkboxInput,nrow(mtcars),"mychbx",label="",value=FALSE,width=NULL)))
colDef <- list(
list(
targets=0,
width="150px"
),
list(
targets=1,
width="300px"
),
list(
targets=2,
width="500px"
)
)
output$mytable = DT::renderDataTable({
DT::datatable(mtcarsx,
escape = FALSE,
selection = 'none',
rownames = FALSE,
options = list(searching = FALSE,
ordering = FALSE,
columnDefs = colDef,
autoWidth = FALSE
))
})
}
shinyApp(ui, server)
I used the information from #K-Rhode from this answer: https://stackoverflow.com/a/49513444/4375992
From what I can tell, your primary issue is that the column width of the checkbox is too wide, yes? Well this should do it. Add a classname to the columnDefs for the checkbox column, then in css adjust the width of that class
library(DT)
library(shiny)
ui <- basicPage(
h2("The mtcars data"),
DT::dataTableOutput("mytable"),
tags$head( #CSS added to shrink the column with
tags$style('td.small .shiny-input-container{width:auto;}
td.small{width:30px;}
')
)
)
server <- function(input, output) {
shinyInput <- function(FUN, len, id, ...) {
inputs <- character(len)
for (i in seq_len(len)) {
inputs[i] <- as.character(FUN(paste0(id, i), ...))
}
inputs
}
mtcarsx <- data.frame(mtcars, newvar=paste0(shinyInput(checkboxInput,nrow(mtcars),"mychbx",label=NULL,value=FALSE,width=NULL)))
colDef <- list(
list(
targets=0,
width="150px"
),
list(
targets=1,
width="300px"
),
list(
targets=2,
width="500px"
),
list(
targets = 11,
className = "small" #Class name added so we can adjust the width of the checkbox element above in CSS
)
)
output$mytable = DT::renderDataTable({
DT::datatable(mtcarsx,
escape = FALSE,
selection = 'none',
rownames = FALSE,
options = list(searching = FALSE,
ordering = FALSE,
columnDefs = colDef,
autoWidth = FALSE
))
})
}
shinyApp(ui, server)

Update shiny input based on datatable settings

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.

Change renderTable title in shiny?

I've managed build a simple shiny app that takes user input from a pre-defined list and passes this input as a vector to a function, then outputs the result of that function (here I've replaced that function with print).
library(shiny)
library(shinythemes)
server <- function(input, output) {
LIST_OF_STUFF = c("A", "B", "C", "D")
other_select <- function(inputId) {
reactive({
select_ids <- grep("^select_\\d+$", names(input), value = T)
other_select_ids <- setdiff(select_ids, inputId)
purrr::map(other_select_ids, purrr::partial(`[[`, input))
})
}
render_select <- function(i, label = "Enter selections") {
renderUI({
this_id <- paste0("select_", i)
this_input <- isolate(input[[this_id]])
selected_elsewhere <- unlist(other_select(this_id)())
available_choices <- setdiff(LIST_OF_STUFF, selected_elsewhere)
selectInput(inputId = this_id, label = label, choices = available_choices,
selected = this_input, multiple = TRUE)
})
}
output$select_1 <- render_select(1)
output$selected_var <- renderTable({
as.data.frame(print(input$select_1))
})
}
ui <- fluidPage(theme = "united",
titlePanel("Title"),
mainPanel(img(src = 'testimage.png', align = "right")),
uiOutput("select_1"),
tableOutput("selected_var"))
shinyApp(ui, server)
A few questions: The resulting table has the title "print(input$select_1)" -- how can I customize this?
I'd like to apply a theme to add some color to the app, but it doesn't seem to show up. How can I make the background or header bar colored?
The results table currently prints immediately upon user selection, but I'd like it to wait until the user is finished selecting input. How can I do this?
This is my first time using shiny or making any sort of interactive application, so forgive me if these are trivial questions. Thanks!
Data frame output
To display a custom name you could add a variable name to your data frame:
output$selected_var <- renderTable({
data.frame(selections = isolate(input$select_1))
})
App customization
Since it's a web app, you can customize (almost) any element of your app. You just have to target the elements that you want to modify, for example if you want to modify the color of the background and the color of the header, you can add custom CSS within your code:
tags$head(
tags$style(
HTML("h2 {
color: red;
}
body {
background-color: grey;
}")
)
)
Delay
To wait for the user to finish the selection, I would suggest you to add an actionButton that the user will have to press to render the table. One way to do this is to use an observeEvent and to isolate the input selection.
All in all
All in all, you could have an app that looks like this:
library(shiny)
library(shinythemes)
server <- function(input, output) {
LIST_OF_STUFF = c("A", "B", "C", "D")
other_select <- function(inputId) {
reactive({
select_ids <- grep("^select_\\d+$", names(input), value = T)
other_select_ids <- setdiff(select_ids, inputId)
purrr::map(other_select_ids, purrr::partial(`[[`, input))
})
}
render_select <- function(i, label = "Enter selections") {
renderUI({
this_id <- paste0("select_", i)
this_input <- isolate(input[[this_id]])
selected_elsewhere <- unlist(other_select(this_id)())
available_choices <- setdiff(LIST_OF_STUFF, selected_elsewhere)
selectInput(inputId = this_id, label = label, choices = available_choices,
selected = this_input, multiple = TRUE)
})
}
output$select_1 <- render_select(1)
observeEvent(input$run, {
output$selected_var <- renderTable({
data.frame(selections = isolate(input$select_1))
})
})
}
ui <- fluidPage(theme = "united",
titlePanel("Title"),
tags$head(
tags$style(
HTML("h2 {
color: red;
}
body {
background-color: grey;
}")
)
),
mainPanel(img(src = 'testimage.png', align = "right")),
uiOutput("select_1"),
actionButton("run", "Run"),
tableOutput("selected_var"))
shinyApp(ui, server)

Reactive buttons in reactively updating data table

My question is an extension of this question:
R Shiny: Handle Action Buttons in Data Table
I am trying to add reactive buttons to a data table that is generated reactively.
Basically, my table is subsetted from a dataframe based on a search term entered by the user. I'd like to have buttons in the subsetted and displayed table, but instead of the buttons appearing as in the linked question, I get HTML code for them.
Here's the server code:
server = function(input, output, session) {
table<-reactive({
filter(evidence_test,grepl(input$search,evidence_abstract,ignore.case=TRUE))[,c(input$show_vars)]
})
shinyInput <- function(FUN, len, id, ...) {
inputs <- character(len)
for (i in seq_len(len)) {
inputs[i] <- as.character(FUN(paste0(id, i), ...))
}
inputs
}
df<-reactive({reactiveValues(
data=data.frame(
table(),
Actions = shinyInput(actionButton, nrow(table()), 'button_', label = "Fire", onclick = 'Shiny.onInputChange(\"select_button\", this.id)' ),
stringsAsFactors = FALSE
)
)
})
observeEvent(input$select_button, {
selectedRow <- as.numeric(strsplit(input$select_button, "_")[[1]][2])
})
output$tbl <- DT::renderDataTable({
df()$data
});
output$myText <- renderText({
colnames(df$data)
})
}
And here's the UI code:
ui = fluidPage(
headerPanel("Search for article terms"),
sidebarPanel(
textInput(inputId="search",value="kras",label="Search for a term", width=400),
checkboxGroupInput(inputId='show_vars', label='Columns to show:', dbListFields(database,"evidence_test"),
selected = c("evidence_title","evidence_abstract","evidence_score","evidence_priority"))
),
mainPanel(
DT::dataTableOutput("tbl")
)
)
Thanks for the help.

Showing and hiding inputs based on checkboxGroupInput

My shiny app begins with a checkboxGroupInput which contains the names of three companies: A, B and C. It also has 3 hidden numeric inputs, each corresponding to a company. Potential investors may select the name of the company they wish to invest in and specifiy the amount they are willing to invest. When the name of a company is checked, the corresponding numeric input shows up. Also, when the company name is unchecked, the numeric input disappears.
The checkboxGroupInput is called company. The 3 numericInput fields are respectively called amountA, amountB and amountC and are all generated inside a uiOutput. They are hidden with the hidden function of shinyjs.
library(shiny)
library(shinyjs)
library(magrittr)
ui <- fluidPage(
useShinyjs(),
checkboxGroupInput(inputId = "company", label = "Select a company", choices = LETTERS[1:3]),
uiOutput(outputId = "amounts")
)
server <- function(input, output){
company_names <- LETTERS[1:3]
num_ids <- paste0("amount", LETTERS[1:3])
output$amounts <- renderUI({
num_inputs <- lapply(1:3, function(i){
numericInput(inputId = num_ids[i], label = paste0("Investment in ", company_names[i]), value = 0, min = 0, max = 5000)
}) %>% tagList
shinyjs::hidden(num_inputs)
})
observeEvent(eventExpr = input$company, handlerExpr = {
if(length(input$company) == 0){
for(i in num_ids){
shinyjs::hide(id = i)
}
} else {
for(i in input$company){
shinyjs::toggle(id = paste0("amount", i), condition = input$company)
}
}
})
}
shinyApp(ui = ui, server = server)
The problem with my app is that the intended dynamics between the checkboxGroupInput and the numericInput fields are not working as intended. For instance, once a numericInput is shown, it cannot be hidden anymore by unchecking the boxes. How can I handle this?
The code pasted above is fully functional. Thank you very much.
I fixed your code by explicitly show/hide the numericInput when the corresponding check box is selected/unselected. Also I change the observeEvent with observe to make sure that the observer reacts when none of the check boxes are selected.
library(shiny)
library(shinyjs)
library(magrittr)
ui <- fluidPage(
useShinyjs(),
checkboxGroupInput(inputId = "company", label = "Select a company", choices = LETTERS[1:3]),
uiOutput(outputId = "amounts")
)
server <- function(input, output){
company_names <- LETTERS[1:3]
num_ids <- paste0("amount", LETTERS[1:3])
output$amounts <- renderUI({
num_inputs <- lapply(1:3, function(i){
numericInput(inputId = num_ids[i], label = paste0("Investment in ", company_names[i]), value = 0, min = 0, max = 5000)
}) %>% tagList
shinyjs::hidden(num_inputs)
})
observe({
for(i in company_names){
if (i %in% input$company) {
shinyjs::show(id = paste0("amount", i))
} else {
shinyjs::hide(id = paste0("amount", i))
}
}
})
}
shinyApp(ui = ui, server = server)

Resources