I want to display a table in Shiny with renderDataTable() function. For each row I want to create 2 checkboxes. I'm using checkboxInput() function to create those checkboxes.
The checkboxes are created, but when I'm trying to read them with input$checkbox_id, I get NULL.
The same trick works using renderTable(), but my client wants the extra features of DT table (sorting, filtering).
If I inspect the HTML generated, I see that renderTable() inserts and extra class="shiny-bound-input" into the tag. renderDataTable() does not.
library(shiny)
shinyApp(
ui = fluidPage(
fluidRow(
column(12,dataTableOutput('dataTableOutput')),
column(12,tableOutput('tableOutput')),
actionButton('printCheckStatus','printCheckStatus')
)
),
server = function(input, output) {
df1 <- data.frame(CheckBoxColumn=as.character(checkboxInput("id_1",NULL)))
df2 <- data.frame(CheckBoxColumn=as.character(checkboxInput("id_2",NULL)))
output$dataTableOutput <- renderDataTable(df1,escape = FALSE)
output$tableOutput <- renderTable(df2, sanitize.text.function = function(x) x)
observeEvent(input$printCheckStatus, {print(input$id_1);print(input$id_2)})
}
)
The code generates a button and two tables each containing one checkbox.
When I click the button I get NULL and FALSE in the console. FALSE is correct, because the second checkbox is unchecked. I get NULL because input$id_1 does not exist in the Shiny server session.
I expect FALSE and FALSE in the console log.
You can use the DT package (based on this):
library(shiny)
library(DT)
shinyApp(
ui = fluidPage(
fluidRow(
column(12,dataTableOutput('dataTableOutput')),
column(12,tableOutput('tableOutput')),
actionButton('printCheckStatus','printCheckStatus')
)
),
server = function(input, output) {
df1 <- data.frame(CheckBoxColumn=as.character(checkboxInput("id_1",NULL)))
df2 <- data.frame(CheckBoxColumn=as.character(checkboxInput("id_2",NULL)))
output$dataTableOutput <- renderDataTable(df1,escape = FALSE, server = FALSE,
callback = JS("table.cells().every(function(i, tab, cell) {
var $this = $(this.node());
$this.attr('id', this.data()[0]);
$this.addClass('shiny-input-checkbox');
});
Shiny.unbindAll(table.table().node());
Shiny.bindAll(table.table().node());"))
output$tableOutput <- renderTable(df2, sanitize.text.function = function(x) x)
observeEvent(input$printCheckStatus, {print(input$id_1);print(input$id_2)})
}
)
Related
I'm trying to make an app where users can edit some tables and run a calculation, and using DT. Is there a way to just read in what's currently in a DT table? This would simplify things a lot for me. All the solutions I've been able to find involve detecting when the table is edited, and then updating the data accordingly. This seems clunky and also might cause problems for my use case later.
Here's an example: after editing the data zTable, I'd like something that just returns what is now in zTable after clicking the calculate button aside from just watching every edit and updating z$data.
library(shiny)
library(DT)
ui <- fluidPage(
DT::dataTableOutput("zTable"),
actionButton("calcButton","Calculate!")
)
server <- function(input, output) {
z<-reactiveValues(data={data.frame(x=c(0,1),
y=c(0,1))
})
output$zTable <- DT::renderDT(z$data,editable=T)
observeEvent(input$calcButton,{
print(z$data)
})
observeEvent(input$zTable_cell_edit, {
info = input$zTable_cell_edit
z$data[as.numeric(info$row),as.numeric(info$col)] <- as.numeric(info$value)
})
}
shinyApp(ui = ui, server = server)
You can do as follows. There's a problem with the current version of DT: when you edit a numeric cell, the new value is stored as a string instead of a number. I've just done a pull request which fixes this issue. With the next version of DT the .map(Number) in the JavaScript callback will not be needed anymore. If you are ok to adopt my solution, tell me if you want to use it with non-numeric cells, and I'll have to improve the code in order to handle this situation. Or you can install my fork of DT in which I fixed the issue: remotes::install_github("stla/DT#numericvalue").
library(shiny)
library(DT)
callback <- c(
'$("#show").on("click", function(){',
' var headers = Array.from(table.columns().header()).map(x => x.innerText);',
' var arrayOfColumns = Array.from(table.columns().data());',
' var rownames = arrayOfColumns[0]',
' headers.shift(); arrayOfColumns.shift();',
' var entries = headers.map((h, i) => [h, arrayOfColumns[i].map(Number)]);',
' var columns = Object.fromEntries(entries);',
' Shiny.setInputValue(',
' "tabledata", {rownames: rownames, columns: columns}, {priority: "event"}',
' );',
'});'
)
ui <- fluidPage(
br(),
DTOutput("dtable"),
br(),
tags$h3("Edit a cell and click"),
actionButton("show", "Print data")
)
server <- function(input, output) {
dat <- data.frame(x=c(0,1),
y=c(0,1))
output[["dtable"]] <- renderDT({
datatable(
dat,
editable = TRUE,
callback = JS(callback)
)
}, server = FALSE)
observeEvent(input[["tabledata"]], {
columns <- lapply(input[["tabledata"]][["columns"]], unlist)
df <- as.data.frame(columns)
rownames(df) <- input[["tabledata"]][["rownames"]]
print(df)
})
}
shinyApp(ui = ui, server = server)
You can rely on JavaScript to get the data via the DataTable api:
library(shiny)
library(DT)
library(shinyjs)
ui <- fluidPage(
useShinyjs(),
DT::dataTableOutput("zTable"),
actionButton("calcButton","Calculate!")
)
server <- function(input, output) {
z <- reactiveValues(data = data.frame(x = 0:1, y = 0:1))
output$zTable <- renderDT(z$data, editable = TRUE)
observeEvent(input$calcButton, {
runjs('Shiny.setInputValue("mydata", $("#zTable table").DataTable().rows().data())')
})
observeEvent(input$mydata, {
dat <- req(input$mydata)
## remove chunk from .data()
dat[c("length", "selector", "ajax", "context")] <- NULL
print(do.call(rbind, dat))
})
}
shinyApp(ui = ui, server = server)
However, as you need to some data wrangling to back-transfrom the data, I am not sure whether this is eventually such a good idea.
What is your general issue with the _cell_edit approach? (which I would prefer because no need to additional data wrangling other than storing it in the right spot?
My R Shiny app has text and a datatable. When I click a datatable row, the data changes and the table is updated using a datatable proxy so that the table page doesn't change. Also the text updates to show how many rows were clicked.
The problem is that when the text updates, it also updates the table which resets to page 1, ruining the point of using the datatable proxy.
Here is a working example. Run it with and without the last code line commented (the code line starting with v$selected_count <- ...)
library(shiny)
library(DT)
shinyApp(
ui = fluidPage(
uiOutput("app_ui")
)
, server = function(input, output){
v <- reactiveValues(
selected_count = 0
, data = iris
)
output$app_ui <- renderUI({
tagList(
h5(v$selected_count) # Want this to update without affecting the datatable
, DTOutput('tbl')
)
})
output$tbl = renderDT({
datatable(iris)
})
observeEvent(
input$tbl_rows_selected
, {
i <- input$tbl_rows_selected
v$data[i, "Species"] <- ""
dataTableProxy("tbl") %>% replaceData(v$data, resetPaging = FALSE, clearSelection = FALSE)
v$selected_count <- v$selected_count + 1 # Comment this line to see the difference to the datatable
}
)
}
)
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
I have the following very simple app
funSolver <- function(server,currencyList,tenorList,tailList) {
return(do.call(rbind,lapply(currencyList,function(ccy) {
return(do.call(rbind,lapply(tenorList,function(tenor) {
return(do.call(rbind,lapply(tailList,function(myTail) {
a <- 0
b <- 10
d <- 25
e <- 35
return(data.frame(ccy=ccy,tenor=tenor,tail=myTail,a=a,b=b,d=d,e=e))
})))
})))
})))
}
ui <- fluidPage(
titlePanel("Carry Selector"),
sidebarPanel(
selectInput(inputId = 'serverListInput',label = 'Server', choices=as.list(paste("adsg-",c("l01","ln01","l02","ln02"),sep="")),multiple = FALSE,selectize = TRUE),
selectInput(inputId = 'currencyListInput',label = 'blabla', choices=list("blabla1","blabla2","blabla3"),multiple = TRUE,selectize = TRUE),
fluidRow(
column(6,selectInput(inputId = 'tenorListInput',label = 'Tenors', choices=as.list(c("All",paste(c(1,3,6),"m",sep=""),paste(c(seq(1,9),seq(10,30,5)),"y",sep=""))),multiple = TRUE,selectize = TRUE)),
column(6,selectInput(inputId = 'tailListInput',label = 'Tails', choices=as.list(c("All",paste(c(1,2,3,5,7,10,15,20),"y",sep=""))),multiple = TRUE,selectize = TRUE))
),
actionButton(inputId = 'launchCalcButton',label = 'Launch Calc')
),
mainPanel(
fluidRow(
column(12,dataTableOutput(outputId = 'table'))
)
)
)
server <- function(input,output){
observeEvent(input$launchCalcButton, {
output$table <- renderDataTable({
datatable(funSolver(input$serverListInput,input$currencyListInput,input$tenorListInput,input$tailListInput))
})
})
}
app = shinyApp(ui,server)
runApp(app,port=3250,host='0.0.0.0')
You choose a few parameters and click on the button to show the table generated by the funSolver function. I have wrapped the button call into an observeEvent so the table is generated only when you click. I do not understand why, if you change parameters after the button is first clicked, the table gets updated even though I have not clicked the button.
Q1 : Is that behavior expected ?
Q2 : If it is, how to do what I want to do within a shiny app (table refreshed only when the button is clicked on, not when the parameters are updated) ?
All help appreciated
You added the render function inside the observe event part which means it will run each time the dependencies change within this event. Looking at ?observeEvent gives you a very nice method to get what you want:
EDIT
I played a bit with the selectinput and button options and found out it is actually due to the multiple=TRUE argument (I believe). For some reason, this argument overrules the input button dependency in observeEvent. The version below with eventReactive however does work:
server <- function(input,output){
df = eventReactive(input$launchCalcButton, {
data.table(funSolver(input$serverListInput,input$currencyListInput,input$tenorListInput,input$tailListInput))
})
output$table <- renderDataTable({
df()
})
}
I reproduced an example shiny app written by Yihui Xie (https://yihui.shinyapps.io/DT-rows/). The app uses DT::renderDataTable() which allows a row selection.
Everything works perfectly fine. I was however wondering if it's possible to reset the row selection (i.e. undo the click selection) ? I already tried it with an action button to reset s = input$x3_rows_selected (see script below).
With my current script,s = input$x3_rows_selected does indeed get emptied, I can however not refill it. Also the selected rows are still clicked (shaded)
Does anyone has an idea? Is there an option within DT::renderDataTable() to reset the selection? Or does anyone has an idea for a workaround?
Thank you!
Example form https://yihui.shinyapps.io/DT-rows/) with my modification (action button):
server.R
library(shiny)
library(DT)
shinyServer(function(input, output, session) {
# you must include row names for server-side tables
# to be able to get the row
# indices of the selected rows
mtcars2 = mtcars[, 1:8]
output$x3 = DT::renderDataTable(mtcars2, rownames = TRUE, server = TRUE)
# print the selected indices
selection <- reactive({
if (input$resetSelection)
vector() else input$x3_rows_selected
})
output$x4 = renderPrint({
if (length(selection())) {
cat("These rows were selected:\n\n")
output <- selection()
cat(output, sep = "\n")
}
})
})
ui.R
library(shiny)
shinyUI(
fluidPage(
title = 'Select Table Rows',
h1('A Server-side Table'),
fluidRow(
column(9, DT::dataTableOutput('x3')),
column(3, verbatimTextOutput('x4'),
actionButton('resetSelection',
label = "Click to reset row selection"
) # end of action button
) #end of column
)))
In the current development version of DT (>= 0.1.16), you can use the method selectRows() to clear selections. Please see the section "Manipulate An Existing DataTables Instance" in the documentation.
Here is a possible solution, maybe not the best but it works. It is based on re-create the datatable each time the action button is clicked, so the selected rows are removed.
library(shiny)
library(DT)
runApp(list(
server = function(input, output, session) {
mtcars2 = mtcars[, 1:8]
output$x3 = DT::renderDataTable({
# to create a new datatable each time the reset button is clicked
input$resetSelection
mtcars2
}, rownames = TRUE, server = TRUE
)
# print the selected indices
selection <- reactive ({
input$x3_rows_selected
})
output$x4 = renderPrint({
if (length(selection())) {
cat('These rows were selected:\n\n')
output <- selection()
cat(output, sep = '\n')
}
})
},
ui = shinyUI(fluidPage(
title = 'Select Table Rows',
h1('A Server-side Table'),
fluidRow(
column(9, DT::dataTableOutput('x3')),
column(3, verbatimTextOutput('x4'),
actionButton( 'resetSelection',label = "Click to reset row selection")
) #end of column
)
))
))