I want to create a table in RShiny with numericInput so that user-supplied values can be used immediately. I followed the code HERE, but as the variables (car models) changes, it stops printing the new values. It works fine until the user changes the input.
Here is the code:
library(shiny)
library(DT)
library(tidyverse)
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());
}
})")
)),
title = 'selectInput or numericInput column in a table',
sidebarLayout(
sidebarPanel(
selectizeInput(inputId = "cars", label = "Car model", choices = rownames(mtcars), selected = rownames(mtcars)[1:6], multiple = T )
),
mainPanel(
DT::dataTableOutput('carTable'),
verbatimTextOutput('price')
)
)
)
server <- function(input, output, session) {
rvar <- reactiveValues(
DF = mtcars
)
observeEvent(input$cars,{
for (i in 1:nrow(rvar$DF)) {
rvar$DF$price[i] <- as.character(numericInput(paste0("price", i), "", 0, width = "100px"))
}
rvar$data <- rvar$DF[rownames(mtcars) %in% input$cars, ] %>% select(-price)
})
output$carTable = DT::renderDT({
data <- rvar$DF[rownames(mtcars) %in% input$cars, ] %>% mutate(carmodel = input$cars) %>% relocate(carmodel)
datatable(
data, escape = FALSE, selection = 'none',
options = list(
dom = 't',
paging = FALSE,
ordering = FALSE,
preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); }')
),
rownames = FALSE
)
}, server = FALSE)
output$price = renderPrint({
str(sapply(1:nrow(rvar$data), function(i) input[[paste0("price", i)]]))
})
observe({
updatedPrice <- sapply(1:nrow(rvar$DF), function(i) input[[paste0("price", i)]]) %>% Reduce(c,.)
if(is.null(updatedPrice) | length(updatedPrice) != nrow(rvar$data)){
updatedPrice <- 0
}
isolate({
rvar$data$price <- updatedPrice
})
print(sapply(1:nrow(rvar$data), function(i) input[[paste0("price", i)]]) %>% Reduce(c,.))
print(rvar$data)
})
observeEvent(input$cars, {
session$sendCustomMessage("unbindDT", "carTable")
})
}
shinyApp(ui, server)
Works like this. It took me several trials and I don't exactly remember what were the problems...
library(shiny)
library(DT)
library(tidyverse)
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());
}
})")
)),
title = "selectInput or numericInput column in a table",
sidebarLayout(
sidebarPanel(
selectizeInput(
inputId = "cars", label = "Car model",
choices = rownames(mtcars), selected = rownames(mtcars)[1:6],
multiple = TRUE
)
),
mainPanel(
DTOutput("carTable"),
verbatimTextOutput("price")
)
)
)
server <- function(input, output, session) {
rvar <- reactiveValues(
DF = mtcars
)
observeEvent(input$cars, {
rvar$DF <- rvar$DF[rownames(mtcars) %in% input$cars, ]
for(i in 1:nrow(rvar$DF)) {
rvar$DF$price[i] <-
as.character(numericInput(paste0("price", i), "", 0, width = "100px"))
}
rvar$data <- rvar$DF %>% select(-price)
rvar$DTdata <- rvar$DF %>%
mutate(carmodel = input$cars) %>%
relocate(carmodel)
session$sendCustomMessage("unbindDT", "carTable")
})
output$carTable <- renderDT({
data <- rvar$DTdata
datatable(
data,
escape = FALSE, selection = "none",
options = list(
dom = "t",
paging = FALSE,
ordering = FALSE,
preDrawCallback =
JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback =
JS('function() { Shiny.bindAll(this.api().table().node()); }')
),
rownames = FALSE
)
},
server = FALSE
)
output$price <- renderPrint({
str(sapply(1:nrow(rvar$data), function(i) input[[paste0("price", i)]]))
})
observe({
updatedPrice <-
sapply(1:nrow(rvar$DF), function(i) input[[paste0("price", i)]]) %>%
Reduce(c, .)
if(is.null(updatedPrice) || length(updatedPrice) != nrow(rvar$data)) {
updatedPrice <- 0
}
isolate({
rvar$data$price <- updatedPrice
})
print(
sapply(1:nrow(rvar$data), function(i) input[[paste0("price", i)]]) %>%
Reduce(c, .)
)
print(rvar$data)
})
}
shinyApp(ui, server)
Related
When I click on the Action Button, I would like to clear everything: both the output and the selections in the picketInput() (input$engine and input$cylinder in the code below). For consistency if I can do it with shinyWidget's actionBttn, that will be great as well.
library(shiny)
library(shinyWidgets)
df <- mtcars
ui <- fluidPage(
sidebarPanel(
pickerInput("engine", "Select engine:", choices = unique(df$vs),
options = list(
`actions-box` = TRUE),
multiple = TRUE
),
pickerInput("cylinder", "Select cylinder:", choices = unique(df$cyl),
options = list(
`actions-box` = TRUE),
multiple = TRUE
),
actionButton("reset", "Clear Selection"),
),
mainPanel(
textOutput("results")
)
)
server <- function(input, output, session) {
data <- reactiveValues()
observeEvent(input$cylinder, {
tmp <- df
tmp1 <- tmp[tmp$vs %in% input$engine, ]
tmp2 <- tmp1[tmp1$cyl %in% input$cylinder, ]
data$tmp2 <- tmp2
})
output$results <- renderText({
if(is.null(data$tmp2)) return()
print(row.names(data$tmp2))
})
observeEvent(input$reset, {
updatePickerInput(session, "engine", NULL)
updatePickerInput(session, "cylinder", NULL)
data$tmp2 <- NULL
})
}
shinyApp(ui = ui, server = server)
You'll have to respect the order of updatePickerInput's parameters or name them. Your above approach would have updated the label.
Please see ?updatePickerInput and check the following:
library(shiny)
library(shinyWidgets)
library(datasets)
DF <- mtcars
ui <- fluidPage(
sidebarPanel(
pickerInput("engine", "Select engine:", choices = unique(DF$vs),
options = list(
`actions-box` = TRUE),
multiple = TRUE
),
pickerInput("cylinder", "Select cylinder:", choices = unique(DF$cyl),
options = list(
`actions-box` = TRUE),
multiple = TRUE
),
actionBttn("reset", "Clear Selection"),
),
mainPanel(
textOutput("results")
)
)
server <- function(input, output, session) {
data <- reactiveValues()
observeEvent(input$cylinder, {
tmp <- DF
tmp1 <- tmp[tmp$vs %in% input$engine, ]
tmp2 <- tmp1[tmp1$cyl %in% input$cylinder, ]
data$tmp2 <- tmp2
})
output$results <- renderText({
req(data$tmp2)
row.names(data$tmp2)
})
observeEvent(input$reset, {
updatePickerInput(session, inputId = "engine", selected = "")
updatePickerInput(session, inputId = "cylinder", selected = "")
data$tmp2 <- NULL
})
}
shinyApp(ui = ui, server = server)
My issue is when showing my new modal. As soon as a user clicks on the modal, it disappears.
Is there a way to resolve this? I have tried to call all functions shown in the code below.
header <- dashboardHeader(title = "Example")
body <- dashboardBody(mainPanel(DT::dataTableOutput("mytable"),
uiOutput("sd")))
ui <- dashboardPage(header, sidebar, body, skin = "red")
server = function(input, output, session)
{
mymtcars <<- mtcars
mymtcars$id <<- 1:nrow(mtcars)
output$sd <- renderUI({
lapply(seq_len(nrow(mymtcars)),
function(i)
{
bsModal(paste0("myModal", i), "Your plot", paste0("btn", i), size = "large",
dataTableOutput(paste0("plot", i)))
})
})
iris$new <- paste0("<a href=\'https://r-studio.vip.ebay.com/shiny/map-analytics/qa/app_topics/',\ target='_blank'>Click Here for Reviews</a>")
output$mytable = DT::renderDataTable({
btns <- shinyInput(actionButton, nrow(mymtcars), "btn", label = "Show modal")
DT::datatable(cbind(Pick = btns, mymtcars), escape = F,
options = list(orderClasses = TRUE,
preDrawCallback = JS("function() {
Shiny.unbindAll(this.api().table().node()); }"),
drawCallback = JS("function() {
Shiny.bindAll(this.api().table().node()); } ")))
},server = FALSE)
lapply(seq_len(nrow(mymtcars)), function(i)
{
output[[paste0("plot", i)]] <- renderDataTable(datatable(iris,escape = F, rownames = F))
observeEvent(input[[paste0("btn", i)]], {
toggleModal(session, paste0("myModal", i), "open")
print("Pressed")
})
})
}
runApp(list(ui = ui, server = server))
I would like to obtain the row number and choice selected each time an input is changed in one of the selectInput. The following is a test code. So in short if I change the species in row three, using observeEvent I would like the output to tell me what row was it in and what was picked.
Is there a way of doing this.
library(shiny)
library(DT)
ui <- fluidPage(
DT::dataTableOutput('foo'),
textOutput("text")
)
server <- function(input, output, session) {
data <- head(iris, 5)
for (i in 1:nrow(data)) {
data$species_selector[i] <- as.character(selectInput(paste0("change", i), label = paste0("change", i), choices = unique(iris$Species), width = "100px"))
}
output$foo = DT::renderDataTable(
data, escape = FALSE, selection = 'none', server = FALSE,
options = list(dom = 't', paging = FALSE, ordering = FALSE))
observeEvent$...
}
shinyApp(ui, server)
First, you have to use these options preDrawCallback and drawCallback, otherwise Shiny is not aware of the selectors:
output[["foo"]] <- renderDT(
data, escape = FALSE, selection = 'none', server = FALSE,
options = list(
dom = 't',
paging = FALSE,
ordering = FALSE,
preDrawCallback = JS(
"function() { Shiny.unbindAll(this.api().table().node()); }"
),
drawCallback = JS(
"function() { Shiny.bindAll(this.api().table().node()); }"
)
)
)
Now, you can use two reactive values to store the row and the species:
row <- reactiveVal()
species <- reactiveVal()
And then, define an observer for each row:
lapply(1:nrow(data), function(i){
selector <- paste0("change", i)
observeEvent(input[[selector]], {
row(i)
species(input[[selector]])
})
})
Full app:
library(shiny)
library(DT)
ui <- fluidPage(
br(),
DTOutput('foo'),
br(),
wellPanel(
textOutput("text")
)
)
server <- function(input, output, session) {
data <- head(iris, 5)
data$species_selector <- vapply(1:nrow(data), function(i){
as.character(selectInput(
paste0("change", i),
label = paste0("change", i),
choices = unique(iris$Species),
width = "100px"
))
}, character(1))
output[["foo"]] <- renderDT(
data, escape = FALSE, selection = 'none', server = FALSE,
options = list(
dom = 't',
paging = FALSE,
ordering = FALSE,
preDrawCallback = JS(
"function() { Shiny.unbindAll(this.api().table().node()); }"
),
drawCallback = JS(
"function() { Shiny.bindAll(this.api().table().node()); }"
)
)
)
row <- reactiveVal()
species <- reactiveVal()
lapply(1:nrow(data), function(i){
selector <- paste0("change", i)
observeEvent(input[[selector]], {
row(i)
species(input[[selector]])
})
})
output[["text"]] <- renderText({
sprintf("Row %d --- Species %s", row(), species())
})
}
shinyApp(ui, server)
I'm trying to create a datatable with Shiny input elements (checkboxInput or textInput). This works well until I update the datatable. If I add more rows with more input elements, only the new elements work. I thought the table would be recreated every time I update it and the ids would be associated with the new input elements. The code example below illustrates the problem. It creates a table with one row first. If I then create a table with two rows using the dropdown on the left, I can only read the values of the second row in the output table. Any change to the inputs of the first row has no impact on the ouput table.
library(DT)
library(shiny)
server <- function(input, output) {
updateTable <- reactive({
num <- as.integer(input$num)
df <- data.frame(check = unlist(lapply(1:num, function(i) as.character(checkboxInput(paste0("check_", i), label = paste0("check", i), value = 0)))),
text = unlist(lapply(1:num, function(i) as.character(textInput(paste0("text_",i), label = paste0("text", i), value = "")))))
})
output$input_ui <- DT::renderDataTable(
updateTable(),
server = FALSE, escape = FALSE, selection = 'none',
options = list(
dom = 't', paging = FALSE, ordering = FALSE,lengthChange = TRUE,
preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ')
)
)
output$table <- renderTable({
num <- as.integer(input$num)
data.frame(lapply(1:num, function(i) {
paste(input[[paste0("check_", i)]], input[[paste0("text_",i)]], sep = " : ")
}))
})
}
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectInput("num", "select number of inputs", choices = seq(1,10,1))
),
mainPanel(
DT::dataTableOutput("input_ui"),
tableOutput("table")
)
)
)
shinyApp(ui = ui, server = server)
A possible solution is provided here:
https://groups.google.com/d/msg/shiny-discuss/ZUMBGGl1sss/7sdRQecLBAAJ
As far as I understand, it allows to "force" a complete unbind of all checkbox/textinpts before redrawing the table thanks to the use of:
session$sendCustomMessage('unbind-DT', 'input_ui')
. I do not pretend to really understsand it, but apparently it works. See below for a possible implementation.
library(shiny)
library(DT)
server <- function(input, output,session) {
updateTable <- reactive({
num <- as.integer(input$num)
session$sendCustomMessage('unbind-DT', 'input_ui')
df <- data.frame(
check = unlist(lapply(1:num, function(i) as.character(checkboxInput(paste0("check_", i), label = paste0("check", i), value = 0)))),
text = unlist(lapply(1:num, function(i) as.character(textInput(paste0("text_",i), label = paste0("text", i), value = "")))))
tbl <- DT::datatable(df, escape = FALSE,
selection = "none",
options = list(
dom = 't', paging = FALSE, ordering = FALSE,lengthChange = TRUE,
preDrawCallback = JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { Shiny.bindAll(this.api().table().node()); } ')
))
})
output$input_ui <- DT::renderDataTable(
updateTable(),
server = FALSE
)
output$table <- renderTable({
num <- as.integer(input$num)
data.frame(lapply(1:num, function(i) {
paste(input[[paste0("check_", i)]], input[[paste0("text_",i)]], sep = " : ")
}))
})
}
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
selectInput("num", "select number of inputs", choices = seq(1,10,1))
),
mainPanel(
DT::dataTableOutput("input_ui"),
tags$script(HTML(
"Shiny.addCustomMessageHandler('unbind-DT', function(id) {
Shiny.unbindAll($('#'+id).find('table').DataTable().table().node());
})")),
tableOutput("table")
)
)
)
shinyApp(ui = ui, server = server)
HTH!
I would like to add a new category at the beginning which will select the columns for the table. I can not combine variables with other elements in an application. Could someone explain to me what I'm doing wrong? As you can see on the graphics program does not work well.
My code:
library(shiny)
data <- data.frame(
Category1 = rep(letters[1:3], each = 15),
Info = paste("Text info", 1:45),
Category2 = sample(letters[15:20], 45, replace = T),
Size = sample(1:100, 45),
MoreStuff = paste("More Stuff", 1:45)
)
ui <- fluidPage(titlePanel("Test Explorer"),
sidebarLayout(
sidebarPanel(
selectizeInput(
"show_vars",
"Columns to show:",
choices = colnames(data),
multiple = TRUE,
selected = c("Category1", "Info", "Category2")
),
uiOutput("category1"),
uiOutput("category2"),
uiOutput("sizeslider")
),
mainPanel(tableOutput("table"))
))
server <- function(input, output, session) {
data2 <- reactive({
req(input$table)
if (input$table == "All") {
return(data)
}
data[, names(data) %in% input$show_vars]
})
output$category1 <- renderUI({
selectizeInput('cat1',
'Choose Cat 1',
choices = c("All", sort(as.character(
unique(data$Category1)
))),
selected = "All")
})
df_subset <- eventReactive(input$cat1, {
if (input$cat1 == "All") {
df_subset <- data
}
else{
df_subset <- data[data$Category1 == input$cat1, ]
}
})
df_subset1 <- reactive({
if (is.null(input$cat2)) {
df_subset()
} else {
df_subset()[df_subset()$Category2 %in% input$cat2, ]
}
})
output$category2 <- renderUI({
selectizeInput(
'cat2',
'Choose Cat 2 (optional):',
choices = sort(as.character(unique(
df_subset()$Category2
))),
multiple = TRUE,
options = NULL
)
})
output$sizeslider <- renderUI({
sliderInput(
"size",
label = "Size Range",
min = min(df_subset1()$Size),
max = max(df_subset1()$Size),
value = c(min(df_subset1()$Size), max(df_subset1()$Size))
)
})
df_subset2 <- reactive({
if (is.null(input$size)) {
df_subset1()
} else {
df_subset1()[df_subset1()$Size >= input$size[1] &
df_subset1()$Size <= input$size[2], ]
}
})
output$table <- renderTable({
df_subset2()
})
}
shinyApp(ui, server)
You have few problems with your code
You stored that reactive value of the columns selection in data2(), and displaying table df_subset2(). As with your code, the columns change when you add columns and select Cat1 dropdown, since its values are dependent on the data.react.
Avoid using generic names like data to store data. Sometimes it interfere with R base names
You need to use ObserveEvent and eventReactive, when you expect the change on UI to reflect
Below is what I fixed, you can change accordingly.
Added a submit button
Wrapped the input selections code into an ObserveEvent
By this, your data is displayed only when you click the submit button.
Here is the code.
library(shiny)
data.input <- data.frame(
Category1 = rep(letters[1:3], each = 15),
Info = paste("Text info", 1:45),
Category2 = sample(letters[15:20], 45, replace = T),
Size = sample(1:100, 45),
MoreStuff = paste("More Stuff", 1:45)
)
ui.r
ui <- fluidPage(titlePanel("Test Explorer"),
sidebarLayout(
sidebarPanel(
selectizeInput(
"show_vars",
"Columns to show:",
choices = colnames(data.input),
multiple = TRUE,
selected = c("Category1", "Info", "Category2")
),
actionButton("button", "An action button"),
uiOutput("category1"),
uiOutput("category2"),
uiOutput("sizeslider")
),
mainPanel(tableOutput("table"))
))
server.r
server <- function(input, output, session) {
data.react <- eventReactive(input$button, {
data.input[, input$show_vars]
})
observeEvent(input$button, {
output$category1 <- renderUI({
data.sel <- data.react()
selectizeInput('cat1',
'Choose Cat 1',
choices = c("All", sort(as.character(
unique(data.sel$Category1)
))),
selected = "All")
})
df_subset <- eventReactive(input$cat1, {
data.sel <- data.react()
if (input$cat1 == "All") {
data.sel
}
else{
data.sel[data.sel$Category1 == input$cat1,]
}
})
output$category2 <- renderUI({
selectizeInput(
'cat2',
'Choose Cat 2 (optional):',
choices = sort(as.character(unique(
df_subset()$Category2
))),
multiple = TRUE,
options = NULL
)
})
df_subset1 <- reactive({
if (is.null(input$cat2)) {
df_subset()
} else {
df_subset()[df_subset()$Category2 %in% input$cat2,]
}
})
output$sizeslider <- renderUI({
sliderInput(
"size",
label = "Size Range",
min = min(data.input$Size),
max = max(data.input$Size),
value = c(min(data.input$Size), max(data.input$Size))
)
})
df_subset2 <- reactive({
if (is.null(input$size)) {
df_subset1()
} else {
df_subset1()[data.input$Size >= input$size[1] &
data.input$Size <= input$size[2],]
}
})
output$table <- renderTable({
df_subset2()
})
})
}
shinyApp(ui, server)