I would like to build an app that is made up of several conditionalPanels. The first panel shows a data table when a reactive value is equal to 0. The other two panels show up when the reacitve value is 1. This happens when one double clicks on the data table.
However, I would like to have an additional condition for the last two panels. One of the panels has to show up when a specified boolean column takes FALSE for the double clicked row, the other when it is TRUE. As I am not familiar with javascript, I am failing here unfortunately.
The code looks like this:
library(shiny)
library(data.table)
library(DT)
set.seed(42)
A <- rep(0, 10)
for (i in seq_len(10)) {
random_length <- sample(1:10, 1)
random_letters <- sample(letters, random_length)
A[i] <- paste0(random_letters, collapse = "")
}
B <- sample(c(TRUE, FALSE), 10, replace = TRUE)
dt <- data.table(A, B)
ui <- fluidPage(
conditionalPanel(
condition = "output.doubleClickOutput == 0",
dataTableOutput("tableId")
),
conditionalPanel(
condition = "output.doubleClickOutput == 1",
actionButton("buttonId1",
label = "Back 1")
),
conditionalPanel(
condition = "output.doubleClickOutput == 1",
actionButton("buttonId2",
label = "Back 2")
),
)
server <- function(input, output, session) {
observeEvent(input$doubleClickData, {
doubleClickIndicator$doubleClick <- 1
})
observeEvent(input$buttonId1, {
doubleClickIndicator$doubleClick <- 0
})
observeEvent(input$buttonId2, {
doubleClickIndicator$doubleClick <- 0
})
output$doubleClickOutput <- reactive({
doubleClickIndicator$doubleClick
})
outputOptions(output,
name = "doubleClickOutput",
suspendWhenHidden = FALSE)
doubleClickIndicator <- reactiveValues(doubleClick = 0)
output$tableId <- renderDataTable(
dt,
options = list(columnDefs = list(list(
targets = 2,
render = JS(
"function(data, type, row, meta) {",
" if(type === 'display'){",
" return data ? '<input type=\"checkbox\" disabled checked/>' : '<input type=\"checkbox\" disabled/>';",
" }",
" return data;",
"}"
)
))),
callback = JS(
"table.on('dblclick', 'td', ",
"function() {",
"var row = table.cell(this).index().row;",
"Shiny.setInputValue('doubleClickData', {row});",
"}",
");"
)
)
}
shinyApp(ui, server)
Everything works fine except for:
conditionalPanel(
condition = "output.doubleClickOutput == 1",
actionButton("buttonId1",
label = "Back 1")
),
conditionalPanel(
condition = "output.doubleClickOutput == 1",
actionButton("buttonId2",
label = "Back 2")
),
It should be something like this
conditionalPanel(
condition = "output.doubleClickOutput == 1 &&
dt[name='B'].rows(input$doubleClickData) == false",
actionButton("buttonId1",
label = "Back 1")
),
conditionalPanel(
condition = "output.doubleClickOutput == 1 &&
dt[name='B'].rows(input$doubleClickData) == true",
actionButton("buttonId2",
label = "Back 2")
),
but with correct javascript. I appreciate any help.
Not sure I understand everything, but what about this:
library(shiny)
library(data.table) # why do you use data.table?
library(DT)
set.seed(42)
A <- rep(0, 10)
for (i in seq_len(10)) {
random_length <- sample(1:10, 1)
random_letters <- sample(letters, random_length)
A[i] <- paste0(random_letters, collapse = "")
}
B <- sample(c(TRUE, FALSE), 10, replace = TRUE)
dt <- data.table(A, B)
ui <- fluidPage(
conditionalPanel(
condition = "output.doubleClickOutput == 0",
dataTableOutput("tableId")
),
conditionalPanel(
condition =
"output.doubleClickOutput == 1 && input.doubleClickData == false",
actionButton("buttonId1",
label = "Back 1")
),
conditionalPanel(
condition =
"output.doubleClickOutput == 1 && input.doubleClickData == true",
actionButton("buttonId2",
label = "Back 2")
),
)
server <- function(input, output, session) {
doubleClickIndicator <- reactiveValues(doubleClick = 0)
observeEvent(input[["doubleClickData"]], {
doubleClickIndicator[["doubleClick"]] <- 1
})
observeEvent(input[["buttonId1"]], {
doubleClickIndicator[["doubleClick"]] <- 0
})
observeEvent(input[["buttonId2"]], {
doubleClickIndicator[["doubleClick"]] <- 0
})
output[["doubleClickOutput"]] <- reactive({
doubleClickIndicator[["doubleClick"]]
})
outputOptions(
output,
name = "doubleClickOutput",
suspendWhenHidden = FALSE
)
output[["tableId"]] <- renderDataTable(
dt,
options = list(
columnDefs = list(
list(
targets = 2,
render = JS(
"function(data, type, row, meta) {",
" if(type === 'display'){",
" return data ? '<input type=\"checkbox\" disabled checked/>' : '<input type=\"checkbox\" disabled/>';",
" }",
" return data;",
"}"
)
)
)
),
callback = JS(
"table.on('dblclick', 'td:nth-child(3)', function() {",
" var cell = table.cell(this);",
" Shiny.setInputValue('doubleClickData', cell.data(), {priority: 'event'});",
"});"
)
)
observe({ # (test)
print(input[["doubleClickData"]])
})
}
shinyApp(ui, server)
Note that you have to click a little next to the checkboxes, not on the checkboxes.
EDIT
To get the desired event by clicking anywhere on a row, replace the callback with this one:
callback = JS(
"table.on('dblclick', 'tr', function() {",
" var rowIndex = table.row(this).index();",
" var bool = table.cell(rowIndex, 2).data();",
" Shiny.setInputValue('doubleClickData', bool, {priority: 'event'});",
"});"
)
I am trying to add totals to a data table footer. Using code from different sources, I wrote the following application using Shiny. The problem is, when I run it, the following message appears:
"Processing ..."
and stays there forever.
My guess is the JS() code, but cannot debug that.
library(shiny)
library(DT)
library(htmltools)
ui <- fluidPage(
fluidRow(
column(9, DT::dataTableOutput('withtotal'))
)
)
server <- function(input, output, session) {
# server-side processing
mtcars2 = mtcars[, 1:8]
#sketch <- htmltools::withTags(table(tableHeader(mtcars2), tableFooter(mtcars2)))
sketch = htmltools::withTags(table(tableFooter(c("",0,0,0,0,0,0,0))))
opts <- list( footerCallback = JS("function ( row, data, start, end, display ) {",
"var api = this.api();",
"var intVal = function ( i ) {",
"return typeof i === 'string' ?",
"i.replace(/[\\$,]/g, '')*1 :",
"typeof i === 'number' ?",
"i : 0;",
"};",
"if (api.column(COLNUMBER).data().length){",
"var total = api",
".column( COLNUMBER )",
".data()",
".reduce( function (a, b) {",
"return intVal(a) + intVal(b);",
"} ) }",
"else{ total = 0};",
"if (api.column(COLNUMBER).data().length){",
"var pageTotal = api",
".column( COLNUMBER, { page: 'current'} )",
".data()",
".reduce( function (a, b) {",
" return intVal(a) + intVal(b);",
"} ) }",
"else{ pageTotal = 0};",
"$( api.column(COLNUMBER).footer() ).html(",
"'$'+pageTotal",
");",
"}"))
output$withtotal = DT::renderDataTable(DT::datatable(mtcars2,container = sketch, options = opts))
}
options(shiny.error = browser)
# Run the application
shinyApp(ui = ui, server = server)
This is a version without using Shiny. I used the same JavaScript as #gscott above, but this is for a standalone table. I used this in a RMarkdown document. The key to this, which I struggled with, is the container argument, which adds the footer to the table.
library(htmlwidgets)
library(DT)
library(htmltools)
sketch <- htmltools::withTags(table(
tableHeader(colnames(mtcars)),
tableFooter(c(0,0,0,0,0,0,0,0,0,0,0,0))
))
jsCode <- "function(row, data, start, end, display) {
var api = this.api(), data;
total = api.column(7, {page: 'current'}).data().reduce( function(a, b) { return a +
b}, 0);
total2 = api.column(6, {page: 'current'}).data().reduce( function(a, b) { return a
+ b}, 0);
total3 = api.column(2, {page: 'current'}).data().reduce( function(a, b) { return a
+ b}, 0);
$( api.column(7).footer() ).html('Total: ' + total.toFixed(2));
$( api.column(6).footer() ).html('Total: ' + total2.toFixed(2));
$( api.column(2).footer() ).html('Total: ' + total3.toFixed(2))
}"
DT::datatable(mtcars, container = sketch, options=list(scrollY=300, scrollX=TRUE, scroller=TRUE, footerCallback = JS(jsCode)))
Looks like you're using the same example program I did in shiny DataTables footer Callback sums
so my solution to the footer callback issue is below. This is the easiest that I've found to manipulate on a column by column basis.
library(shiny)
library(DT)
ui <- fluidPage(
title = 'Select Table Rows',
hr(),
h1('A Server-side Table'),
fluidRow(
column(9, DT::dataTableOutput('x3'))
)
)
server <- function(input, output, session) {
# server-side processing
mtcars2 = mtcars[, 1:8]
sketch <- htmltools::withTags(table(
class = "display",
style = "bootstrap",
tableHeader(colnames(mtcars2)),
tableFooter(colnames(mtcars2))
))
output$x3 = DT::renderDataTable(DT::datatable(mtcars2,
container = sketch,
extensions = 'Buttons',
options = list(
scrollX = TRUE,
scrollY = TRUE,
pageLength = 10,
order = list(list(1, 'asc')),
dom = 'Blrtip',
buttons = c('copy', 'csv', 'excel', 'pdf', 'print'),
footerCallback = JS(
"function( tfoot, data, start, end, display ) {",
"var api = this.api(), data;",
"total = api.column( 1, { page: 'current'} ).data().reduce( function ( a, b ) {return a + b;} )",
"total1 = api.column( 2, { page: 'current'} ).data().reduce( function ( a, b ) {return a + b;} )",
"total2 = api.column( 3, { page: 'current'} ).data().reduce( function ( a, b ) {return a + b;} )",
"total3 = api.column( 4, { page: 'current'} ).data().reduce( function ( a, b ) {return a + b;} )",
"total4 = api.column( 5, { page: 'current'} ).data().reduce( function ( a, b ) {return a + b;} )",
"total5 = api.column( 6, { page: 'current'} ).data().reduce( function ( a, b ) {return a + b;} )",
"total6 = api.column( 7, { page: 'current'} ).data().reduce( function ( a, b ) {return a + b;} )",
"total7 = api.column( 8, { page: 'current'} ).data().reduce( function ( a, b ) {return a + b;} )",
"$( api.column( 1 ).footer() ).html(total.toFixed(2));
$( api.column( 2 ).footer() ).html(total1.toFixed(2));
$( api.column( 3 ).footer() ).html(total2.toFixed(2));
$( api.column( 4 ).footer() ).html(total3.toFixed(2));
$( api.column( 5 ).footer() ).html(total4.toFixed(2));
$( api.column( 6 ).footer() ).html(total5.toFixed(2));
$( api.column( 7 ).footer() ).html(total6.toFixed(2));
$( api.column( 8 ).footer() ).html(total7.toFixed(2));",
"}"
))
))
}
shinyApp(ui = ui, server = server)
I managed to solve the same problem with the answers above, and the following is my implementation to reduce the amount of repeated JavaScript codes.
I looped through the columns using columns().eq(0).each(), then aggregate the data, and add the result to the footer.
library(shiny)
library(DT)
ui <- fluidPage(
title = 'Select Table Rows', hr(), h1('A Server-side Table'),
fluidRow(
column(9, DT::dataTableOutput('x3'))
)
)
server <- function(input, output, session) {
mtcars2 = mtcars[, 1:8]
sketch <- htmltools::withTags(table(
class = "display", style = "bootstrap",
tableHeader(colnames(mtcars2)),
tableFooter(colnames(mtcars2))
))
output$x3 = DT::renderDataTable(DT::datatable(mtcars2,
container = sketch,
extensions = 'Buttons',
options = list(
scrollX = TRUE, scrollY = TRUE,
pageLength = 10, order = list(list(1, 'asc')),
dom = 'Blrtip', buttons = c('copy', 'csv', 'excel', 'pdf', 'print'),
footerCallback = JS(
"function( tfoot, data, start, end, display ) {",
"var api = this.api(), data;",
"api.columns().eq(0).each( function(index) {",
"var col = api.column(index);",
"if(index == 0) return $(api.column(index).footer()).html('Total')",
"var data = col.data();",
"total = data.reduce( function(a, b) { return a + b }, 0 );",
"$( api.column(index).footer() ).html(total.toFixed(2));",
"})",
"}"
))
))
}
Good evening.
I had some problems combining two DataTables functions (see title).
This (FooterCallback) to display the sum of a column with numeric values:
$(document).ready(function() {
$('#pr_table').DataTable( {
"footerCallback": function ( row, data, start, end, display ) {
var api = this.api(), data;
// Remove the formatting to get integer data for summation
var intVal = function ( i ) {
return typeof i === 'string' ?
i.replace(/[\$,]/g, '')*1 :
typeof i === 'number' ?
i : 0;
};
// Total over all pages
total = api
.column( 4 )
.data()
.reduce( function (a, b) {
return intVal(a) + intVal(b);
}, 0 );
// Total over this page
pageTotal = api
.column( 4, { page: 'current'} )
.data()
.reduce( function (a, b) {
return intVal(a) + intVal(b);
}, 0 );
// Update footer
$( api.column( 4 ).footer() ).html(
'cub.m'+pageTotal +' ( cub.m'+ total +' total)'
);
}
} );
} );
With this (select filtering):
$(document).ready(function() {
$('#pr_table').DataTable( {
"initComplete": function () {
var api = this.api();
api.columns().indexes().flatten().each( function ( i ) {
var column = api.column( i );
var select = $('<select><option value=""></option></select>')
.appendTo( $(column.header()).empty() )
.on( 'change', function () {
var val = $.fn.dataTable.util.escapeRegex(
$(this).val()
);
column
.search( val ? '^'+val+'$' : '', true, false )
.draw();
} );
column.data().unique().sort().each( function ( d, j ) {
select.append( '<option value="'+d+'">'+d+'</option>' )
} );
} );
}
} );
} );
If I use only one of the functions in my script, everything is working just fine. But trying to combine them results in DataTables failing to initialize at all (just raw html table shows up).
I am relatively unfamiliar with javascript this advanced, so it would help me if someone would give me an example of the script with a little explanation how to use multiple functions in one initialization.
I want to remove the columns which have total = 0.
So I've tried in different ways.
First, I assigned ID to all columns, for example; every <td> is of column will have their ID eg: First columns <td ID = 'col_1'> , second column all <td ID = 'col_2'> etc. And then in when footer callback I've tried to remove if this column total is ZERO then this $("col_"+i).remove(); this code removed table headers only so I've tried again with $("col_"+i).empty() but again it's just empty. <th> only
Then I've tried to hide the columns by creating dynamic but I don't get any values.
"footerCallback": function ( row, data, start, end, display )
{
var api = this.api(), data;
var intVal = function ( i ) { return typeof i === 'string' ? i.replace(/[\$,]/g, '')*1 : typeof i === 'number' ? i : 0;};
var col_gonna_invis = '[';
for(i=1;i<length_of_coloumns;i++)
{
total_salary = api.column( i ).data().reduce( function (a, b) {return intVal(a) + intVal(b);},0 );
$('#total_cont_'+i).html(total_salary);
if(total_salary == 0)
{
col_gonna_invis += '{"targets": [ '+i+' ], "visible": false, "searchable": false },';
}
}
col_gonna_invis += ']';alert(col_gonna_invis);
},
"aoColumnDefs": col_gonna_invis;
Please someone help me fix this issue or please someone tell me how to hide or remove columns which footer total is 0.
Thank you in advance.
I will suggest you use the visible() API method along with the sum() plugin :
Enhance the API with a column().sum() method :
jQuery.fn.dataTable.Api.register( 'sum()', function ( ) {
return this.flatten().reduce( function ( a, b ) {
if ( typeof a === 'string' ) {
a = a.replace(/[^\d.-]/g, '') * 1;
}
if ( typeof b === 'string' ) {
b = b.replace(/[^\d.-]/g, '') * 1;
}
return a + b;
}, 0 );
} );
now, in initComplete() you can very easy hide columns which have a total or sum() of 0 :
var table = $('#example').dataTable({
//...
initComplete : function() {
var api = this.api(),
colCount = api.row(0).data().length;
for (var i=0; i<colCount; i++) {
if (api.column(i).data().sum() == 0) {
api.column(i).visible(false);
}
}
}
});
demo -> http://jsfiddle.net/qer7e5os/