Using Shiny inputs inside a DataTable output - r

I would like to dynamically create a series of input widgets to use in each row of data table. I am successfully able to display such a list of inputs in the table, however I'm having trouble accessing the value of these dynamic inputs.
ui.R
library(shiny)
ui <- fluidPage(
fluidRow(
radioButtons('original','Normal Radio Button',c('1','2','3','4','5')),
DT::dataTableOutput("table")
)
)
server.R
library(DT)
multipleRadio <- function(FUN, id_nums, id_base, label, choices, ...) {
inputs <- 1:length(id_nums)
for (i in 1:length(inputs)) {
inputs[i] <- as.character(FUN(paste0(id_base, id_nums[i]),label, choices, ...))
}
return(inputs)
}
radio_inputs <- multipleRadio(radioButtons,
as.character(1:3),
'input_',
'Radio Button',
c('1','2','3','4','5'),
inline = TRUE)
output_table <- data.frame(id = c(1,2,3),
name=c('Item 1','Item 2','Item 3'),
select = radio_inputs)
server <- function(input, output, session) {
observe({
print(paste('original: ',input$original))
print(paste('input 1: ',input$input_1))
print(paste('input 2: ',input$input_2))
print(paste('input 3: ',input$input_3))
})
output$table <- renderDataTable({
datatable(output_table,rownames= FALSE,escape = FALSE,selection='single',
options = list(paging = FALSE,ordering=FALSE,searching=FALSE))
})
}
I define a function which generates multiple radioButton inputs and converts them into their HTML representation using as.character. This generates a series of inputs whose ids are "input_1", "input_2", and "input_3." I fill a column of the output table with the radio inputs. The display of the radioButtons works as expected. I see one in each row. However, input$input_1,input$input_2, and input$input_3 don't seem to exist and there is no response to clicking on these buttons. Any tips on what's going wrong here would be greatly appreciated!
Edit:
I found a solution here:
http://www.stackoverflow.red/questions/32993257/shiny-datatables-with-interactive-elements
Using the Shiny.bindAll function when rendering the datatable appears to convert the HTML inputs into Shiny input objects.
output$table <- renderDataTable({
datatable(output_table,rownames= FALSE,escape = FALSE,selection='single',
options = list(paging = FALSE,ordering=FALSE,searching=FALSE,
preDrawCallback=JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback=JS('function() { Shiny.bindAll(this.api().table().node()); } ')))
})

A correct shiny input object is a shiny.tag object, which you cannot put into a data.frame. If you do so, you'll get the following error message:
Error in as.data.frame.default(x[[i]], optional = TRUE,
stringsAsFactors = stringsAsFactors) : cannot coerce class
""shiny.tag"" to a data.frame
In your example, the radio_inputs object you get is in fact a list of character, which is pure HTML code. Thus you still get the UI, but they no longer work as shiny inputs.
I guess the only way is to use a pure HTML table if you want radio buttons or any other shiny input objects inside a table.

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 pass a R Shiny reactive expression as a default argument in a custom function

I am trying to pass input$myInput text field to a custom function as a predefined argument. This is important to control the functions arguments in future for documentation (roxygen2).
Here is a minimal working example:
The app schould bind the input field to mtcars dataset and apply the custom function my_DTarguments with two arguments (1. data, 2. input):
library(shiny)
library(DT)
#function
my_DTarguments <- function(data, input=input$myInput) {
DT::datatable(
rownames = FALSE,
data,
extensions = 'Buttons',
options = list(
dom = 'frtipB',
buttons = list(
list(
extend = 'csv',
filename = paste0("mydesiredname-", "cyl-", input, "-", Sys.Date())
),
)
)
)
}
shinyApp(
ui <- fluidPage(
textInput(inputId = "myInput", label = "My Input", placeholder = "a"),
DT::dataTableOutput("table")
),
server <- function(input, output) {
# combine mtcars with input field
mtcars1 <- reactive({
cbind(mtcars, input$myInput)
})
# apply function to mtcars1
output$table <- DT::renderDataTable(
my_DTarguments(mtcars1())
)
})
}
)
The last error after many tries is : promise already under evaluation: recursive default argument reference or earlier problems?
The thing with reactive expressions is that Shiny adds them to the reactive chain and then evaluates all reactive expressions in the chain one by one. We do not know the order in which Shiny does that.
In your case, Shiny tries to evaluate renderDataTable and by doing so calls my_DTarguments(). However, myInput has not been evaluated, yet, hence the error.
When you use a reactive expressions as a default function argument, you should always add a in a req(...) call (example: req(input$myInput)). But you still have to add input$myInput in the call to my_DTarguments() from renderDataTable. But you'll get an empty column, if input$myInput is still empty.
Alternatively, you can make sure that input$myInput is truthy before calling my_DTarguments. In this case, the table will only be shown once input$myInput is not empty and Shiny has already evaluated it.
Personally I think the secnond is the cleaner approach. I recommend that we do not use reactive expressions as default arguments for functions, in general. Not only does it violate the idea of a default argument when the caller has to add it anyway. The way reactive expressions work they are only available in the runtime environment as needed. A default argument, however, should be available anytime a function is called. That is like adding a predetermined breaking point in our code ... which - of course - we don't want.
library(shiny)
library(DT)
#function
my_DTarguments <- function(data, input=req(input$myInput)) {
DT::datatable(
rownames = FALSE,
data,
extensions = 'Buttons',
options = list(
dom = 'frtipB',
buttons = list(
#list(
extend = 'csv',
filename = paste0("mydesiredname-", "cyl-", input, "-", Sys.Date())
#),
)
)
)
}
shinyApp(
ui <- fluidPage(
textInput(inputId = "myInput", label = "My Input", placeholder = "a"),
DT::dataTableOutput("table")
),
server <- function(input, output) {
# combine mtcars with input field
mtcars1 <- reactive({
cbind(mtcars, input$myInput)
})
# apply function to mtcars1
output$table <- DT::renderDataTable({
my_DTarguments(mtcars1())
# Alternative approach: call req() here
# my_DTarguments(mtcars1(), req(input$myInput))
})
}
)
By the way, there seems to be an issue with the buttons option. I commented 2 lines out, so that the code runs.

Row-slider for the rendered table made using R - DataTable for ShinyApp

I have to display a large data frame on shiny-mainPanel(). I am using library("DT") for the purpose with renderDT({}) and datatable(df,rownames = FALSE). The rows in the data frame have very long string values which are distorting the shape of the rendered table on the mainPanel.
Here is the distorted-table with all the columns
Here is the clean-table with fewer columns
I want the display all the columns just like displayed in the clean-table. I am trying to make a slider for gliding through the rows but couldn't find any in-built option for datatable()
function on the UI
mainPanel(DT::dataTableOutput("table"))
function on the Server
output$table <- renderDT({datatable(df,rownames = FALSE)})
You can set the option scrollX = TRUE in Datatable.
All the options of the Datatable javascript library, in particular scrollX, can be set in Shiny using the òptions parameter.
Try :
ui <- fluidPage(
title = 'DataTable Options',
tabPanel('Display length', DT::dataTableOutput('ex1'))
)
server <- function(input, output) {
# A lot of columns
df <- cbind(head(mtcars),head(iris))
output$ex1 <- DT::renderDataTable(
DT::datatable(df, options = list(scrollX = T))
)
}
shinyApp(server = server,ui)

Interactive Column/Table Updates with textInput in R Shiny

UPDATE
I've gotten to what I think is the root problem. The following R Shiny App produces a UI with 2 text input boxes, as well as event observers that print messages to the console as the text changes in their respective text input boxes. The issue is that only one of these event observers works correctly, and I can't figure out why.
ui.R (shortened)
library(shiny)
library(shinydashboard)
library(DT)
library(data.table)
shinyUI(
renderUI({
fluidPage(
column(12, dataTableOutput("Main_table")),
box(textInput("TEST_BOX", label=NULL, value="TEST"))
)
})
)
server.R (shortened)
shinyServer(function(input, output) {
test <- reactiveValues()
test$data <- data.table(ID = 1, Group = 1)
output$Main_table <- renderDataTable({
datatable(data.frame(test$data,
New_Group=as.character(textInput("BOX_ID", label = NULL, value = "TEST2",
width = '100px'))), escape=F
)})
observeEvent(input$TEST_BOX, {
print("Test Box Success")
})
observeEvent(input$BOX_ID, {
print("Box ID Success")
})
})
Original Post:
I'm attempting to create a simple app in R Shiny to allow the user to interactively update the values in a column of a small table, then be able to hit a "Save Changes" button and update the table to include their selections.
I've gotten really close with the code below (I think), but for some reason the inputs cbox_1 to cbox_10 always come back as NULL.
ui.R
library(shiny)
library(shinydashboard)
library(DT)
library(data.table)
shinyUI(fluidPage(
dashboardBody(uiOutput("MainBody")
)
))
server.R
# Load libraries
library(shiny)
library(shinydashboard)
library(DT)
library(data.table)
# Define server logic
shinyServer(function(input, output) {
# Create sample data
vals <- reactiveValues()
vals$Data <- data.table(ID = 1:10, Group = 1:1)
# Create main UI with Save Changes button and additional text input box for testing.
output$MainBody <- renderUI({
fluidPage(
box(width=12,
h3(strong("Group Testing"),align="center"),
hr(),
box(textInput("test", label=NULL, value="TESTING")),
column(6, offset = 5, actionButton("save_changes","Save changes")),
column(12, dataTableOutput("Main_table"))
)
)
})
# Function to be used to create multiple text input boxes.
shinyInput = function(FUN, len, id, ...) {
inputs = character(len)
for (i in seq_len(len)) {
inputs[i] = as.character(FUN(paste0(id, i), label = NULL, value = vals$Data$Group[i], width = '100px', ...))
}
inputs
}
# Renders table to include column with text input boxes. Uses function above.
output$Main_table <- renderDataTable({
datatable(data.frame(vals$Data, New_Group=shinyInput(textInput, nrow(vals$Data),"cbox_")), options = list(dom = 't', pageLength = nrow(vals$Data), paging=FALSE, searching=FALSE), rownames=FALSE,
escape=F)
}
)
# Tests if the test input box works.
observeEvent(input$test, {
print("Success1")
})
# Tests if the first input box in the table works.
observeEvent(input$cbox_1, {
print("Success2")
})
# Tests if the Save Changes button works.
observeEvent(input$save_changes, {
print("Success3")
# Assigns the values in the input boxes (New_Group) to the existing Group column.
for (i in 1:nrow(vals$Data)) {
vals$Data$Group[i] <- eval(paste0("input$cbox_", i))
}
datatable(data.frame(vals$Data, New_Group=shinyInput(textInput, nrow(vals$Data),"cbox_")), options = list(pageLength = nrow(vals$Data), paging=FALSE, searching=FALSE), rownames=FALSE,
escape=F)
})
})
The first two observeEvents at the end of the code are solely for testing purposes. "Success2" is never printed even when the contents of the first box are changed. "Success1" is printed when the test box is changed, but I'm not sure why one works and the other doesn't. I've tried inserting a browser() statement in various places of the code to check the value of cbox_1, but it always comes back NULL. I'd also be open to alternate solutions to this problem if I'm approaching it completely wrong. Thanks.
After further research, an approach utilizing the rhandsontable package seemed like the best solution. I modeled my code after this example:
Data input via shinyTable in R shiny application
I also utilized several of the options described here:
https://jrowen.github.io/rhandsontable/#introduction

'Select All' checkbox for Shiny DT::renderDataTable

I want a checkbox that selects all the rows displayed (displayed is key as this differs between the filters you have applied and the entire data table) in a standard DT::renderDataTable in Shiny.
Is there any DT extension that already does this? My coding skills are basic so I cannot write an equivalent Java or HTML code.
This is my app so far, any csv file is compatible for the select all purpose. At the moment there is a clunky way of creating another table of all the selected rows (manually selected one by one) - this is difficult when you want to select 30 animals all with the same characteristic.
library(shiny)
library(shinyjs)
library(DT)
library(dplyr)
library(data.table)
ui = pageWithSidebar(
headerPanel(""),
#This is where the full animal information file is input, as a ".txt" file.
sidebarPanel(
fileInput("ani", "Upload Animal Information File", accept = ".csv"),
br(),
numericInput("groups","Number of Ewe Groups", value = 1 ),
#This is a list of the table headers. These headers can be indivdually selected to be part of the concatenated "Unique ID" single column.
uiOutput("choose_columns"),
width = 2),
mainPanel(
DT::dataTableOutput("ani1"),
DT::dataTableOutput("selectedEwes")
))
server = function(input, output, session) {
animalinformation <- reactive({
file1 <- input$ani
if (is.null(file1))
return(NULL)
#This removes the Ewes and Status non-zero Rams from the displayed data, so that only live/at hand Rams are shown for selection.
isolate({
anifile <- read.csv(file1$datapath, header = TRUE)
anifile <- as.data.frame(anifile)
})
anifile
})
output$choose_columns <- renderUI({
if (is.null(animalinformation()))
return()
colnames <- names(animalinformation())
# Create the checkboxes and select them all by default
checkboxGroupInput("columns", "Choose Columns",
choices = colnames,
selected = colnames)
})
#This line is repsonsible for creating the table for display.
output$ani1 = DT::renderDataTable({
if (is.null(animalinformation()))
return()
if (is.null(input$columns) || !(input$columns %in% names(animalinformation()))) { return() }
{ datatable(animalinformation()[, input$columns, drop = F], filter = "top") }
})
ani1_selected <- reactive({
ids <- input$ani1_rows_selected
animalinformation()[ids,]
})
#This displays the table of selected rows from the table of Rams. This table can be downloaded or printed, or copied using the buttons that appear above the table, thanks to the 'Buttons' extension.
output$selectedEwes <- DT::renderDataTable({
datatable(
ani1_selected(),
selection = list(mode = "none"),
caption = "Copy to clipboard, download a .csv or print the following table of selected Ewes, using the above buttons.", extensions = 'Buttons', options = list(dom = 'Bfrtip', buttons = c('copy', 'csv', 'excel', 'pdf', 'print'))
)
})
}
shinyApp(ui = ui, server = server)
Any help would be much appreciated thanks.
Here is the simplest implementation I can think of. It takes advantage of the fact that DT will return the filter row indexes back to R, which is input$dt_rows_all in the below example. Moreover, it uses the DT::dataTableProxy() to control the row selection. Finally, it works in both the client mode and the server-side processing mode.
By the way, I want to mention that using javascript to mimic the selecting / deselecting events in DT won't change the related shiny binding values in R (e.g., input$dt_rows_selected). It's because DT has its own implementation of row selections (may change in the future but not yet at the time of writing). See rstudio/DT#366 if you want to know more.
library(shiny)
ui <- tagList(
DT::DTOutput("dt"),
checkboxInput("dt_sel", "sel/desel all"),
h4("selected_rows:"),
verbatimTextOutput("selected_rows", TRUE)
)
server <- function(input, output, session) {
dat <- reactive({iris})
output$dt <- DT::renderDT(dat(), server = TRUE)
dt_proxy <- DT::dataTableProxy("dt")
observeEvent(input$dt_sel, {
if (isTRUE(input$dt_sel)) {
DT::selectRows(dt_proxy, input$dt_rows_all)
} else {
DT::selectRows(dt_proxy, NULL)
}
})
output$selected_rows <- renderPrint(print(input$dt_rows_selected))
}
shiny::runApp(list(ui = ui, server = server))

Resources