R Shiny picking and storing favorites from checked dataTables - r

Background
I'm trying to create a multi-table Shiny app in which you can pick your favorite rows in each of several tables, through checkboxes. These should then be stored across sessions, and rendered in an additional "Favorites" table. Unfortunately my understanding of JavaScript appears too limited to achieve this.
Objectives
Pick favorites by checking rows
The value to store should be read from the checkbox value field
Multiple tables should work independently of each other
Picked favorites should be stored between sessions by writing to .Rds file or similar
What I've done so far
For a single table, the basic setup works well as described here: RStudio Shiny list from checking rows in dataTables
In extending this to multiple tables, separated in different tabs, these do not seem to act independently. Example: If I select row 1 from table 1, and then row 2 from table 2 - the rendering for table 2 will show both row 1 and 2 as selected. If I now press the "Save 2" button, it will save three records: row 1 (table1) and row 1+2 (table2).
In table 3, I've managed to return the value of the checkbox (ID column no longer needs to be printed in actual table), but now I can only select one row.
EDIT:
The callback is now working, collecting the values of the checkboxes and working independently of each other. Still, saving is not working as expected. This is likely a Shiny/reactivity issue?
app.R
mymtcars1 = mtcars
mymtcars2 = mtcars
mymtcars3 = mtcars
mymtcars1$id = 1:nrow(mtcars)
mymtcars2$id = 1:nrow(mtcars)
mymtcars3$id = 1:nrow(mtcars)
server <- function(input, output, session) {
rowSelect1 <- reactive({
paste(sort(unique(input[["rows1"]])),sep=',')
})
rowSelect2 <- reactive({
paste(sort(unique(input[["rows2"]])),sep=',')
})
rowSelect3 <- reactive({
paste(sort(unique(input[["rows3"]])),sep=',')
})
observe({
output$favorites_table1 <- renderText(rowSelect1())
output$favorites_table2 <- renderText(rowSelect2())
output$favorites_table3 <- renderText(rowSelect3())
})
output$mytable1 = renderDataTable({
mymtcars <- mymtcars1
addCheckboxButtons <- paste0('<input id="table1" type="checkbox" name="row', mymtcars$id, '" value="op', mymtcars$id, '">',"")
#Display table with checkbox buttons
cbind(Pick=addCheckboxButtons, mymtcars)
}, options = list(orderClasses = TRUE, lengthMenu = c(5, 25, 50), pageLength = 25),
escape = FALSE,
callback = "function(table) {
table.on('change.dt', '#table1:checkbox', function() {
setTimeout(function () {
Shiny.onInputChange('rows1', $('#table1:checked').map(function() {
return $(this).val();
}).get())
}, 10);
});
}")
output$mytable2 = renderDataTable({
mymtcars <- mymtcars2
addCheckboxButtons <- paste0('<input id="table2" type="checkbox" name="row', mymtcars$id, '" value="val', mymtcars$id, '">',"")
#Display table with checkbox buttons
cbind(Pick=addCheckboxButtons, mymtcars)
}, options = list(orderClasses = TRUE, lengthMenu = c(5, 25, 50), pageLength = 25),
escape = FALSE,
callback = "function(table) {
table.on('change.dt', '#table2:checkbox', function() {
setTimeout(function () {
Shiny.onInputChange('rows2', $('#table2:checked').map(function() {
return $(this).val();
}).get())
}, 10);
});
}")
output$mytable3 = renderDataTable({
mymtcars <- mymtcars3
addCheckboxButtons <- paste0('<input id="table3" type="checkbox" name="row', mymtcars$id, '" value="', mymtcars$id, '">',"")
#Display table with checkbox buttons
cbind(Pick=addCheckboxButtons, mymtcars[,-ncol(mymtcars)])
}, options = list(orderClasses = TRUE, lengthMenu = c(5, 25, 50), pageLength = 25),
escape = FALSE,
callback = "function(table) {
table.on('change.dt', '#table3:checkbox', function() {
setTimeout(function () {
Shiny.onInputChange('rows3', $('#table3:checked').map(function() {
return $(this).val();
}).get())
}, 10);
});
}")
favorites <- reactive({
input$send_table1
input$send_table2
input$send_table3
if(file.exists("favorites.Rds")) {
old_favorites <- readRDS("favorites.Rds")
} else {
old_favorites <- data.frame()
}
isolate({
new_favorites <- data.frame("Table"=character(0), "Key"=character(0))
if(length(input$rows1>0)) new_favorites <- rbind(new_favorites, data.frame("Table"="Table1","Key"=input$rows1))
if(length(input$rows2>0)) new_favorites <- rbind(new_favorites, data.frame("Table"="Table2","Key"=input$rows2))
if(length(input$rows3>0)) new_favorites <- rbind(new_favorites, data.frame("Table"="Table3","Key"=input$rows3))
if(nrow(new_favorites)>0){
saveRDS(new_favorites, "favorites.Rds")
new_favorites
} else {
old_favorites
}
})
})
output$favorites_table <- renderDataTable({
validate(
need(nrow(favorites())>0, paste0("No favorites stored"))
)
favorites()
})
}
ui <- shinyUI(
pageWithSidebar(
headerPanel('Examples of DataTables'),
sidebarPanel(
inputPanel(
h5("Selected (table 1)"),br(),
verbatimTextOutput("favorites_table1"),
actionButton(inputId = "send_table1", "Save 1", class="btn-mini")
),
inputPanel(
h5("Selected (table 2)"),br(),
verbatimTextOutput("favorites_table2"),
actionButton(inputId = "send_table2", "Save 2", class="btn-mini")
),
inputPanel(
h5("Selected (table 3)"),br(),
verbatimTextOutput("favorites_table3"),
actionButton(inputId = "send_table3", "Save 3", class="btn-mini")
)
),
mainPanel(
tabsetPanel(
tabPanel("Table1",
dataTableOutput("mytable1")
),
tabPanel("Table2",
dataTableOutput("mytable2")
),
tabPanel("Table3",
dataTableOutput("mytable3")
),
tabPanel("Favorites",
dataTableOutput("favorites_table")
)
)
)
)
)
shinyApp(ui = ui, server = server)

Ok, so this is a working solution now - for anyone else interested.
It will read the value of the checkbox, and send it to the favorites table on click.
app.R
mymtcars1 = mtcars
mymtcars2 = mtcars
mymtcars3 = mtcars
mymtcars1$id = 1:nrow(mtcars)
mymtcars2$id = 1:nrow(mtcars)
mymtcars3$id = 1:nrow(mtcars)
server <- function(input, output, session) {
rowSelect1 <- reactive({
if(!is.null(input[["rows1"]])) paste(sort(unique(input[["rows1"]])),sep=',')
})
rowSelect2 <- reactive({
if(!is.null(input[["rows2"]])) paste(sort(unique(input[["rows2"]])),sep=',')
})
rowSelect3 <- reactive({
if(!is.null(input[["rows3"]])) paste(sort(unique(input[["rows3"]])),sep=',')
})
output$favorites_table1 <- renderText(rowSelect1())
output$favorites_table2 <- renderText(rowSelect2())
output$favorites_table3 <- renderText(rowSelect3())
output$mytable1 = renderDataTable({
mymtcars <- mymtcars1
addCheckboxButtons <- paste0('<input id="table1" type="checkbox" name="row', mymtcars$id, '" value="op', mymtcars$id, '">',"")
#Display table with checkbox buttons
cbind(Pick=addCheckboxButtons, mymtcars)
}, options = list(orderClasses = TRUE, lengthMenu = c(5, 25, 50), pageLength = 25),
escape = FALSE,
callback = "function(table) {
table.on('change.dt', '#table1:checkbox', function() {
setTimeout(function () {
Shiny.onInputChange('rows1', $('#table1:checked').map(function() {
return $(this).val();
}).get())
}, 10);
});
}")
output$mytable2 = renderDataTable({
mymtcars <- mymtcars2
addCheckboxButtons <- paste0('<input id="table2" type="checkbox" name="row', mymtcars$id, '" value="val', mymtcars$id, '">',"")
#Display table with checkbox buttons
cbind(Pick=addCheckboxButtons, mymtcars)
}, options = list(orderClasses = TRUE, lengthMenu = c(5, 25, 50), pageLength = 25),
escape = FALSE,
callback = "function(table) {
table.on('change.dt', '#table2:checkbox', function() {
setTimeout(function () {
Shiny.onInputChange('rows2', $('#table2:checked').map(function() {
return $(this).val();
}).get())
}, 10);
});
}")
output$mytable3 = renderDataTable({
mymtcars <- mymtcars3
addCheckboxButtons <- paste0('<input id="table3" type="checkbox" name="row', mymtcars$id, '" value="', mymtcars$id, '">',"")
#Display table with checkbox buttons
cbind(Pick=addCheckboxButtons, mymtcars[,-ncol(mymtcars)])
}, options = list(orderClasses = TRUE, lengthMenu = c(5, 25, 50), pageLength = 25),
escape = FALSE,
callback = "function(table) {
table.on('change.dt', '#table3:checkbox', function() {
setTimeout(function () {
Shiny.onInputChange('rows3', $('#table3:checked').map(function() {
return $(this).val();
}).get())
}, 10);
});
}")
store_favorites <- function(rds="favorites.Rds", inputidx, name){
if(file.exists(rds)) favorites <- readRDS(rds) else favorites <- data.frame("Table"=character(0), "Key"=character(0))
if(length(input[[inputidx]])>0) {
new_favorites <- unique(rbind(favorites, data.frame("Table"=name,"Key"=input[[inputidx]])))
saveRDS(new_favorites, rds)
new_favorites
} else {
favorites
}
}
favorites1 <- reactive({
input$send_table1
isolate({store_favorites(inputidx="rows1", name="Table1")})
})
favorites2 <- reactive({
input$send_table2
isolate({store_favorites(inputidx="rows2", name="Table2")})
})
favorites3 <- reactive({
input$send_table3
isolate({store_favorites(inputidx="rows3", name="Table3")})
})
output$favorites_table <- renderDataTable({
# Re-evaluate favorites each time one of the buttons are pressed
input$send_table1
input$send_table2
input$send_table3
isolate({
#Unneccessary to bind the same table 3 times, then unique - but this works
all_favs <- unique(rbind(favorites1(),favorites2(),favorites3()))
})
validate(
need(nrow(all_favs)>0, paste0("No favorites stored"))
)
all_favs
})
}
ui <- shinyUI(
pageWithSidebar(
headerPanel('Examples of DataTables'),
sidebarPanel(
inputPanel(
h5("Selected (table 1)"),br(),
verbatimTextOutput("favorites_table1"),
actionButton(inputId = "send_table1", "Save 1", class="btn-mini")
),
inputPanel(
h5("Selected (table 2)"),br(),
verbatimTextOutput("favorites_table2"),
actionButton(inputId = "send_table2", "Save 2", class="btn-mini")
),
inputPanel(
h5("Selected (table 3)"),br(),
verbatimTextOutput("favorites_table3"),
actionButton(inputId = "send_table3", "Save 3", class="btn-mini")
)
),
mainPanel(
tabsetPanel(
tabPanel("Table1",
dataTableOutput("mytable1")
),
tabPanel("Table2",
dataTableOutput("mytable2")
),
tabPanel("Table3",
dataTableOutput("mytable3")
),
tabPanel("Favorites",
dataTableOutput("favorites_table")
)
)
)
)
)
shinyApp(ui = ui, server = server)

Related

Shiny DT datatable selectInput with reactive data

I recently asked a similar question (Shiny DT datatable input reactivity after table is reloaded). My issue was getting a selectInput in a DT datatable to work correctly after the table is reloaded. The solution worked, which was to use javascript to unbind before reloading the table. However, that example used a static dataframe. When the input data in the datatable are reactive, it doesn't work. In the example below, when the user clicks "Update data" the first time to load data, the selectInput works correctly and input$id1 responds to the user selection. However, when the user clicks "Update data" again to update the reactive data, the input$id no longer responds to the user selection. I've seen two potential approaches to address the issue. One is using dataTableProxy() and replaceData(), and the other is renaming the selectInput ids each time the reactive data are updated. I was wondering if I can avoid those two approaches and get this example working with minimal changes.
require(shiny)
require(DT)
shinyApp(
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());
}
})")
)),
actionButton(inputId = "update", label = "Update data"),
uiOutput("resettable_table")
),
server = function(input, output, session) {
rv <- reactiveValues(
times = 1,
mydata = NULL
)
observeEvent(input$update, {
session$sendCustomMessage("unbindDT", "mytable")
rv$times <- rv$times + 1
rv$mydata <- data.frame(
Col1 = as.character(selectInput(
inputId = "id1",
label = NULL,
choices = paste0(letters, input$update),
selected = paste0(letters, input$update)[1],
))
)
})
output$mytable <- DT::renderDataTable({
req(rv$mydata)
DT::datatable(
data = rv$mydata,
escape = F,
selection = "none",
options = list(
preDrawCallback = JS('function(){Shiny.unbindAll(this.api().table().node());}'),
drawCallback = JS('function(){Shiny.bindAll(this.api().table().node());}')
)
)
}, server = F)
output$resettable_table <- renderUI({
req(rv$times)
div(
id = paste0("mydiv", rv$times),
DT::dataTableOutput("mytable")
)
})
observe({
if(is.null(input$id1)) {
print("input$id1 is NULL")
} else {
print(paste(c("input$id1:", input$id1)))
}
})
}
)
Update
Thanks to #StephaneLaurent for pointing out that the reactive counter keeping track of the number of times the data were reloaded was causing the issue. It wasn't actually necessary to put the DT datatable inside a div with an id that updated each time. Here is working code:
require(shiny)
require(DT)
shinyApp(
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());
}
})")
)),
actionButton(inputId = "update", label = "Update data"),
DT::dataTableOutput("mytable")
),
server = function(input, output, session) {
rv <- reactiveValues(mydata = NULL)
observeEvent(input$update, {
session$sendCustomMessage("unbindDT", "mytable")
rv$mydata <- data.frame(
Col1 = as.character(selectInput(
inputId = "id1",
label = NULL,
choices = paste0(letters, input$update),
selected = paste0(letters, input$update)[1],
))
)
})
output$mytable <- DT::renderDataTable({
req(rv$mydata)
DT::datatable(
data = rv$mydata,
escape = F,
selection = "none",
options = list(
preDrawCallback = JS('function(){Shiny.unbindAll(this.api().table().node());}'),
drawCallback = JS('function(){Shiny.bindAll(this.api().table().node());}')
)
)
}, server = F)
observe({
if(is.null(input$id1)) {
print("input$id1 is NULL")
} else {
print(paste(c("input$id1:", input$id1)))
}
})
}
)
The problem is caused by the presence of rv$times in the renderUI. The simplest way to make this app work is to get rid of this renderUI.
However, for fun, and in order to understand what happens, I did the app below which works with the renderUI and which shows what happens. The key point was to remove the id1 element when the table is consecutively rendered two times, before the second rendering. To do so, I use a JavaScript counter i.
library(shiny)
library(DT)
shinyApp(
ui = fluidPage(
tags$head(tags$script(
HTML("var i = 1;")
)),
actionButton(inputId = "update", label = "Update data"),
uiOutput("resettable_table")
),
server = function(input, output, session) {
rv <- reactiveValues(
times = 1,
mydata = NULL
)
observeEvent(input$update, {
rv$times <- rv$times + 1
rv$mydata <- data.frame(
Col1 = as.character(selectInput(
inputId = "id1",
label = NULL,
choices = paste0(letters, input$update),
selected = paste0(letters, input$update)[1],
))
)
})
output$mytable <- DT::renderDataTable({
req(rv$mydata)
DT::datatable(
data = rv$mydata,
escape = F,
selection = "none",
options = list(
initComplete = JS('function(settings) { alert("initComplete - incrementing i"); i++; alert("i = " + i)}'),
preDrawCallback = JS('function() { alert("preDrawCallback triggered - unbinding"); Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = JS('function() { alert("drawCallback triggered - i = " + i); if(i===2) {alert("removing id1 and resetting i to 0"); $("#id1").remove(); i=0;} Shiny.bindAll(this.api().table().node());}')
)
)
}, server = F)
output$resettable_table <- renderUI({
div(
id = paste0("mydiv", rv$times),
tags$p(paste0("mydiv", rv$times)),
DT::dataTableOutput("mytable")
)
})
observe({
if(is.null(input$id1)) {
print("input$id1 is NULL")
} else {
print(paste(c("input$id1:", input$id1)))
}
})
}
)

DT Filtering not returning row number

In the code below, I am saving user selected rows into CSV file. It is working perfectly fine. Problem arises if I add filter='top', selection = 'multiple' in datatable() function then it stops returning row value (row position). The user-defined function rowSelect() returns row value.
See the complete code below -
library(shiny)
library(RODBC)
library(DT)
library(shinyalert)
mydata = mtcars
mydata$id = 1:nrow(mydata)
d = data.frame(stringsAsFactors = F)
runApp(
list(ui = pageWithSidebar(
headerPanel('Examples of Table'),
sidebarPanel(
textInput("collection_txt",label="RowIndex")
,br(),useShinyalert(),
actionButton("run", "Write Data"),
br(),
p("Writeback with every user input. CSV file gets saved on your working directory!")),
mainPanel(
DT::dataTableOutput("mytable")
))
, server = function(input, output, session) {
shinyInput <- function(FUN,id,num,...) {
inputs <- character(num)
for (i in seq_len(num)) {
inputs[i] <- as.character(FUN(paste0(id,i),label=NULL,...))
}
inputs
}
rowSelect <- reactive({
rows=names(input)[grepl(pattern = "srows_",names(input))]
paste(unlist(lapply(rows,function(i){
if(input[[i]]==T){
return(substr(i,gregexpr(pattern = "_",i)[[1]]+1,nchar(i)))
}
})))
})
observe({
updateTextInput(session, "collection_txt", value = rowSelect() ,label = "RowIndex:" )
})
df_subset <- reactive({
d = data.frame(n = rowSelect(), stringsAsFactors = F)
return(d)
})
observeEvent(input$run, {write.csv(mydata[as.numeric(df_subset()$n),], file = "Writeback.csv" , row.names=F)
shinyalert(title = "Task Completed!", type = "success")})
output$mytable = DT::renderDataTable({
DT::datatable(cbind(Flag=paste0('<input type="checkbox" id="srows_', mydata$id, '" value="', mydata$id, '">',""),
mydata), extensions = 'Buttons', options = list(orderClasses = TRUE,
pageLength = 5, lengthChange = FALSE, dom = 'Bfrtip',
buttons = c('copy', 'csv', 'excel'),
drawCallback= JS(
'function(settings) {
Shiny.unbindAll(this.api().table().node());
Shiny.bindAll(this.api().table().node());}')
),escape=F)
}
)
}), launch.browser = T
)
Any help would be highly appreciated!

Multiple javascript in datatable

Is there any way to define multiple javascripts in DT Shiny? I have dropdowns at each row of table. I want some dropdowns to be selected as 'Prevention' by default? I want to define the following JS code in DT. This code sets 5th drop down (from top) to be assigned as 'Prevention'. But it's not working.
function setSelectedIndex(s, valsearch)
for (i = 0; i< s.options.length; i++)
{
if (s.options[i].value == valsearch)
{
s.options[i].selected = true;
break;
}
}
return;}
setSelectedIndex(document.getElementById('selecter_5'),'Prevention');
The code below is the main Shiny code I am using -
library(shiny)
library(DT)
mymtcars = mtcars
mymtcars$id = 1:nrow(mtcars)
runApp(list(
ui = basicPage(
h2('The mtcars data'),
DT::dataTableOutput('mytable'),
h2("Selected"),
tableOutput("checked")
)
,
server = function(input, output) {
# datatable with checkbox
output$mytable = DT::renderDataTable({
#Display table with select
DT::datatable(cbind(Pick=paste0('
<select id="selecter_', mymtcars$id, '">
<option disabled selected>Choose Status</option>
<option>Non-Fraud</option>
<option>Fraud</option>
<option>Prevention</option>
</select>',""), mymtcars),
extensions = c('Scroller'),
filter = 'top',
selection=list(mode = 'multiple'),
options = list(
scrollX = TRUE,
scroller = TRUE,
scrollY = "400px",
orderClasses = TRUE,
pageLength = 50,
fixedHeader = TRUE,
dom = 'Bfrtip',
drawCallback= JS(
'function(settings) {
Shiny.bindAll(this.api().table().node());}'
)), escape=F) }, server = T)
# helper function for reading checkbox
shinyValue = function(id, len) {
unlist(lapply(seq_len(len), function(i) {
value = input[[paste0(id, i)]]
if (is.null(value)) NA else value
}))
}
# output read checkboxes
output$checked <- renderTable({
data.frame(selected=shinyValue("selecter_",nrow(mtcars)))
})
}))

Shiny: keep boxes checked on datatable after changing inputs

I want to implement checkboxes into my shiny app; however, I'm facing two problems:
After I reorder columns, any checks on the datatable disappear (e.g., try to order table by mpg)
After I remove column, any checks on the datatable disappear (e.g., unchecking boxes from Columns to show:)
Here's my dummy example (it's a modified version of code from this SO answer):
library(shiny)
TABLE = mtcars
TABLE$id = 1:nrow(mtcars)
APP <- list()
APP$ui <- pageWithSidebar(
headerPanel(NULL),
sidebarPanel(
checkboxGroupInput("show_vars", "Columns to show:",
names(TABLE), selected = names(TABLE))
),
mainPanel(
dataTableOutput("resultTABLE")
)
)
APP$server <- function(input, output, session) {
output$resultTABLE = renderDataTable({
addCheckboxButtons <- paste0('<input type="checkbox" name="row',
TABLE$id, '" value="', TABLE$id, '">',"")
cbind(Pick = addCheckboxButtons, TABLE[, input$show_vars, drop = FALSE])
}, escape = FALSE)
}
runApp(APP)
APP works, but for the full implementation I need to solve problems 1 and 2.
Based on the SO answer provided in your question:
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))
),
mainPanel(
dataTableOutput("mytable")
)
)
, server = function(input, output, session) {
strd<-reactiveValues(tr=0, slrows=character(length=nrow(mymtcars)))
#preserve selected rows in a reactive element
rowSelect <- reactive({
input$rows
})
# use reactive value that's equal to 'checked' parameter for html code
observe({
strd$slrows<-ifelse(mymtcars$id %in% as.numeric(rowSelect()),'checked','' )
})
#use observer for column checkboxinput to detect first run
observeEvent(input$show_vars, {
strd$tr<-strd$tr+1
print(strd$tr)
}, ignoreNULL = TRUE)
output$mytable = renderDataTable({
#if first run - nothing is checked
if (strd$tr==1){
addCheckboxButtons <- paste0('<input type="checkbox" name="row', mymtcars$id, '" value="', mymtcars$id, '" >',"")
} else{
# add 'checked' parameter for html depending if id is present in selected rows reactive value
addCheckboxButtons <- paste0('<input type="checkbox" name="row', mymtcars$id, '" value="', mymtcars$id,'" ',
strd$slrows,'>',"")
}
#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),
escape=FALSE, 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);
});
}")
}
)
)
Similar, but DT approach: (a bit more efficient as you don't create input for each row and as a consequence it won't recreate table for each reactive values trigger (that's is columns and rows ticks). It recreates table only in column reactive value trigger. You can also use colvis in buttons extension in order to get along with pure DT solution
library(shiny)
library(DT)
mymtcars<-mtcars
shinyApp(
ui = pageWithSidebar(
headerPanel('Examples of DataTables'),
sidebarPanel(
checkboxGroupInput('show_vars', 'Columns to show:', names(mymtcars),
selected = names(mymtcars))
),
mainPanel(
verbatimTextOutput("selrows"),
DT::dataTableOutput("mytable")
)
),
server = function(input, output) {
strd<-reactiveValues(tr=0, slrows=c(0,0))
observe({
if(strd$tr==1){
strd$slrows<-0
} else strd$slrows<-input$mytable_rows_selected
})
rowSelect <- reactive({
input$mytable_rows_selected
})
observeEvent(input$show_vars, {
strd$tr<-strd$tr+1
print(strd$tr)
}, ignoreNULL = TRUE)
output$mytable = DT::renderDataTable({
datatable(mymtcars[, input$show_vars, drop=F], rownames=FALSE,options = list(pageLength = 10),
selection = list(mode='multiple', target='row',
selected = strd$slrows) )
}
)
output$selrows<-renderPrint({
input$mytable_rows_selected
})
}
)

RStudio Shiny list from checking rows in dataTables

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.

Resources