R datatable rowCallback with DT - javascript

I am trying to perform two distinct formatting operations on a datatable object using the DT and magrittr packages. One uses the helper function formatRound() and the other is passed in as JavaScript to a rowCallback option in the datatable function.
When I run either of the formatting operations individually the datatable renders with the expected formatting. However, when I run both together the datatable renders blank but I do not get an error.
This code shows the behavior I am describing.
library(magrittr)
library(DT)
df = data.frame(matrix(rnorm(20), nrow=10))
datatable(
data = df
) %>%
formatRound(c("X1", "X2"), 1)
#table renders as expected
datatable(
data = df,
options = list(
rowCallback = JS("
function( row, data, index ) {
if ( index > 2 ) {
$(row).css('background-color', '#EDEDED');
}
else if ( index > 0 ) {
$(row).css('background-color', '#DEDEDE');
}
else {
$(row).css('background-color', '#D3D3D3');
}
}"
)
)
)
#table renders as expected
datatable(
data = df,
options = list(
rowCallback = JS("
function( row, data, index ) {
if ( index > 2 ) {
$(row).css('background-color', '#EDEDED');
}
else if ( index > 0 ) {
$(row).css('background-color', '#DEDEDE');
}
else {
$(row).css('background-color', '#D3D3D3');
}
}"
)
)
) %>%
formatRound(c("X1", "X2"), 1)
#table renders as blank but with no error returned

If you have a look at the JS function of your third attempt in the browser's JS console(click on "Inspect Element option in browser"), it shows up an error saying that 'var' is unidentified because it is outside the scope of the JS function:
(
var d = parseFloat(data[1]); $(this.api().cell(row, 1).node()).html(isNaN(d) ? '' : d.toFixed(1));
var d = parseFloat(data[2]); $(this.api().cell(row, 2).node()).html(isNaN(d) ? '' : d.toFixed(1));
function( row, data, index ) {
if ( index > 2 ) {
$(row).css('background-color', '#EDEDED');
}
else if ( index > 0 ) {
$(row).css('background-color', '#DEDEDE');
}
else {
$(row).css('background-color', '#D3D3D3');
}
})
If you put those two lines inside the JS function, it works perfectly.
You can find the updated code below.
datatable(
data = df,
options = list(
rowCallback = JS("
function( row, data, index ) {
var d = parseFloat(data[1]);
$(this.api().cell(row, 1).node()).html(isNaN(d) ? '' : d.toFixed(1));
var d = parseFloat(data[2]);
$(this.api().cell(row, 2).node()).html(isNaN(d) ? '' : d.toFixed(1));
if ( index > 2 ) {
$(row).css('background-color', '#EDEDED');
}
else if ( index > 0 ) {
$(row).css('background-color', '#DEDEDE');
}
else {
$(row).css('background-color', '#D3D3D3');
}
}"
)
)
)

Related

How to build conditionaPanel on condition of data table entry that was chosen via double click?

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'});",
"});"
)

How To Add totals to a DT::datatable?

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));",
"})",
"}"
))
))
}

DataTables combine FooterCallback and select filtering

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.

How to remove columns in JQuery Datatables?

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/

ColVis - DataTable HTML sourced

I have a problem with my DataTable. I want to use ColVis plug-in.
My Table is initialised by HTML table - DOM Object.
var colvis = new $.fn.dataTable.ColVis( oTable );
$( colvis.button() ).insertAfter('div.info');
This is how I creating the button, but the button isn't visible. I downloaded full js file (not minimalized) and have following error:
"Uncaught TypeError: Cannot read property 'length' of undefined"
in this function:
"_fnAddButtons": function ()
{
var
nButton,
columns = this.s.dt.aoColumns;
if ( $.inArray( 'all', this.s.aiExclude ) === -1 ) {
for ( var i=0, iLen=columns.length ; i<iLen ; i++ )
{
if ( $.inArray( i, this.s.aiExclude ) === -1 )
{
nButton = this._fnDomColumnButton( i );
nButton.__columnIdx = i;
this.dom.buttons.push( nButton );
}
}
}
if ( this.s.order === 'alpha' ) {
this.dom.buttons.sort( function ( a, b ) {
var titleA = columns[ a.__columnIdx ].sTitle;
var titleB = columns[ b.__columnIdx ].sTitle;
return titleA === titleB ?
0 :
titleA < titleB ?
-1 :
1;
} );
}
if ( this.s.restore )
{
nButton = this._fnDomRestoreButton();
nButton.className += " ColVis_Restore";
this.dom.buttons.push( nButton );
}
if ( this.s.showAll )
{
nButton = this._fnDomShowAllButton();
nButton.className += " ColVis_ShowAll";
this.dom.buttons.push( nButton );
}
$(this.dom.collection).append( this.dom.buttons );
},
he tells that the
columns = this.s.dt.aoColumns;
is undefined.
Can someone help me?

Categories

Resources