I have a nested data table that is rendered in my app. The data table is pre-populated with data, however, I got a request from the users that they will need to be able to edit the data in the child rows. My data table is below. For example, the user might need to go in and change the Share (%)
for Daytime
. In changing that value it will also need to update the parent row.
The issue I am trying to get around is when I click to edit a single cell in the child table the whole table disappears and a text box shows up.
The Issue
Code
# Merge the row details
subdats <- lapply(
list(df2),
purrr::transpose
)
# Dataframe for the datatable
Dat <- cbind(
" " = "⊕",
df1,
details = I(subdats)
)
callback_js = JS(
"table.column(1).nodes().to$().css({cursor: 'pointer'});",
"",
"// make the table header of the nested table",
"var format = function(d, childId){",
" if(d != null){",
" var html = ",
" '<table class=\"display compact hover\" id=\"' + childId + '\"><thead><tr>';",
" for (var key in d[d.length-1][0]) {",
" html += '<th>' + key + '</th>';",
" }",
" html += '</tr></thead></table>'",
" return html;",
" } else {",
" return '';",
" }",
"};",
"",
"// row callback to style the rows of the child tables",
"var rowCallback = function(row, dat, displayNum, index){",
" if($(row).hasClass('odd')){",
" $(row).css('background-color', 'white');",
" $(row).hover(function(){",
" $(this).css('background-color', 'white');",
" }, function() {",
" $(this).css('background-color', 'white');",
" });",
" } else {",
" $(row).css('background-color', 'white');",
" $(row).hover(function(){",
" $(this).css('background-color', 'white');",
" }, function() {",
" $(this).css('background-color', 'white');",
" });",
" }",
"};",
"",
"// header callback to style the header of the child tables",
"var headerCallback = function(thead, data, start, end, display){",
" $('th', thead).css({",
" 'border-top': '3px solid indigo',",
" 'color': 'white',",
" 'background-color': 'white'",
" });",
"};",
"",
"// make the datatable",
"var format_datatable = function(d, childId){",
" var dataset = [];",
" var n = d.length - 1;",
" for(var i = 0; i < d[n].length; i++){",
" var datarow = $.map(d[n][i], function (value, index) {",
" return [value];",
" });",
" dataset.push(datarow);",
" }",
" var id = 'table#' + childId;",
" var subtable = $(id).DataTable({",
" 'data': dataset,",
" 'autoWidth': true,",
" 'deferRender': true,",
" 'info': false,",
" 'lengthChange': false,",
" 'ordering': d[n].length > 1,",
" 'order': [],",
" 'paging': false,",
" 'scrollX': false,",
" 'scrollY': false,",
" 'searching': false,",
" 'sortClasses': false,",
" 'rowCallback': rowCallback,",
" 'headerCallback': headerCallback,",
" 'columnDefs': [",
" {targets: -1, visible: false},",
" {targets: 0, orderable: false, className: 'details-control'},",
" {targets: '_all', className: 'dt-center'}",
" ]",
" }).column(0).nodes().to$().css({cursor: 'pointer'});",
" }",
"",
"// display the child table on click",
"table.on('click', 'td.details-control', function(){",
" var tbl = $(this).closest('table'),",
" tblId = tbl.attr('id'),",
" td = $(this),",
" row = $(tbl).DataTable().row(td.closest('tr')),",
" rowIdx = row.index();",
" if(row.child.isShown()){",
" row.child.hide();",
" td.html('⊕');",
" } else {",
" var childId = tblId + '-child-' + rowIdx;",
" row.child(format(row.data(), childId)).show();",
" td.html('⊖');",
" format_datatable(row.data(), childId);",
" }",
"});")
# Render the table
output$daypartTable <- DT::renderDataTable({
Dat <- Dat
DT::datatable(Dat, callback = callback_js, escape = -2, editable = TRUE,
options = list(
columnDefs = list(
list(visible = FALSE, targets = ncol(Dat)),
list(orderable = FALSE, className = 'details-control', targets = 1),
list(className = "dt-center", targets = "_all")
)
)
)
})
Child table structure ( df2
):
structure(list(Daypart = c("Daytime", "Early Fringe", "Early Morning",
"Early News", "Late Fringe", "Late News", "Prime Access", "Prime Time",
"Total"), `Share (%)` = c(15L, 10L, 15L, 10L, 10L, 10L, 15L,
15L, 100L), `Spot:30 (%)` = c(0, 0, 0, 0, 0, 0, 0, 0, 0), `Spot:15 (%)` = c(0,
0, 0, 0, 0, 0, 0, 0, 0), `Demo Impressions` = c("368,381", "245,588",
"368,381", "245,588", "245,588", "245,588", "368,381", "368,381",
"2,455,876"), Gross = c("$0", "$0", "$0", "$0", "$0", "$0", "$0",
"$0", "$0"), Net = c("$0", "$0", "$0", "$0", "$0", "$0", "$0",
"$0", "$0"), `Gross CPM` = c("$0", "$0", "$0", "$0", "$0", "$0",
"$0", "$0", "$-")), .Names = c("Daypart", "Share (%)", "Spot:30 (%)",
"Spot:15 (%)", "Demo Impressions", "Gross", "Net", "Gross CPM"
), row.names = c(NA, -9L), class = "data.frame")
Daypart Share (%) Spot:30 (%) Spot:15 (%) Demo Impressions Gross Net Gross CPM
1 Daytime 15 0 0 368,381 $0 $0 $0
2 Early Fringe 10 0 0 245,588 $0 $0 $0
3 Early Morning 15 0 0 368,381 $0 $0 $0
4 Early News 10 0 0 245,588 $0 $0 $0
5 Late Fringe 10 0 0 245,588 $0 $0 $0
6 Late News 10 0 0 245,588 $0 $0 $0
7 Prime Access 15 0 0 368,381 $0 $0 $0
8 Prime Time 15 0 0 368,381 $0 $0 $0
9 Total 100 0 0 2,455,876 $0 $0 $-
Parent table structure ( df1
):
structure(list(Market = "ALBANY, GA", Gross = "$0", Net = "$0",
GRP = 100, `Demo Impressions` = "238,792", `Gross CPP` = "$0",
`Gross CPM` = "$0"), .Names = c("Market", "Gross", "Net",
"GRP", "Demo Impressions", "Gross CPP", "Gross CPM"), row.names = c(NA,
-1L), class = "data.frame")
Market Gross Net GRP Demo Impressions Gross CPP Gross CPM
1 ALBANY, GA $0 $0 100 238,792 $0 $0
We can do that with the help of the CellEdit plugin (MIT license).
Below is an example. See this post for explanations on the DT
datatables with parent/child rows.
df1 <- iris[1:3,]
df2 <- cars[1:4,]
# function to make the required dataframe
NestedData <- function(dat, children){
stopifnot(length(children) == nrow(dat))
g <- function(d){
if(is.data.frame(d)){
purrr::transpose(d)
}else{
purrr::transpose(NestedData(d[[1]], children = d$children))
}
}
subdats <- lapply(children, g)
oplus <- sapply(subdats, function(x) if(length(x)) "⊕" else "")
cbind(" " = oplus, dat, "_details" = I(subdats), stringsAsFactors = FALSE)
}
# make the required dataframe
# one must have: length(children) == nrow(dat)
Dat <- NestedData(
dat = df1,
children = list(df2, df2, df2)
)
## whether to show row names (set TRUE or FALSE)
rowNames <- FALSE
colIdx <- as.integer(rowNames)
## make the callback
parentRows <- which(Dat[,1] != "")
callback = JS(
"function onUpdate(updatedCell, updatedRow, oldValue) {};",
"table.MakeCellsEditable({",
" onUpdate: onUpdate,",
" confirmationButton: true",
"});",
sprintf("var parentRows = [%s];", toString(parentRows-1)),
sprintf("var j0 = %d;", colIdx),
"var nrows = table.rows().count();",
"for(var i=0; i < nrows; ++i){",
" if(parentRows.indexOf(i) > -1){",
" table.cell(i,j0).nodes().to$().css({cursor: 'pointer'});",
" }else{",
" table.cell(i,j0).nodes().to$().removeClass('details-control');",
" }",
"}",
"",
"// make the table header of the nested table",
"var format = function(d, childId){",
" if(d != null){",
" var html = ",
" '<table class=\"display compact hover\" ' + ",
" 'style=\"padding-left: 30px;\" id=\"' + childId + '\"><thead><tr>';",
" for(var key in d[d.length-1][0]){",
" html += '<th>' + key + '</th>';",
" }",
" html += '</tr></thead></table>'",
" return html;",
" } else {",
" return '';",
" }",
"};",
"",
"// row callback to style the rows of the child tables",
"var rowCallback = function(row, dat, displayNum, index){",
" if($(row).hasClass('odd')){",
" $(row).css('background-color', 'papayawhip');",
" $(row).hover(function(){",
" $(this).css('background-color', '#E6FF99');",
" }, function() {",
" $(this).css('background-color', 'papayawhip');",
" });",
" } else {",
" $(row).css('background-color', 'lemonchiffon');",
" $(row).hover(function(){",
" $(this).css('background-color', '#DDFF75');",
" }, function() {",
" $(this).css('background-color', 'lemonchiffon');",
" });",
" }",
"};",
"",
"// header callback to style the header of the child tables",
"var headerCallback = function(thead, data, start, end, display){",
" $('th', thead).css({",
" 'border-top': '3px solid indigo',",
" 'color': 'indigo',",
" 'background-color': '#fadadd'",
" });",
"};",
"",
"// make the datatable",
"var format_datatable = function(d, childId){",
" var dataset = [];",
" var n = d.length - 1;",
" for(var i = 0; i < d[n].length; i++){",
" var datarow = $.map(d[n][i], function (value, index) {",
" return [value];",
" });",
" dataset.push(datarow);",
" }",
" var id = 'table#' + childId;",
" if (Object.keys(d[n][0]).indexOf('_details') === -1) {",
" var subtable = $(id).DataTable({",
" 'data': dataset,",
" 'autoWidth': true,",
" 'deferRender': true,",
" 'info': false,",
" 'lengthChange': false,",
" 'ordering': d[n].length > 1,",
" 'order': [],",
" 'paging': false,",
" 'scrollX': false,",
" 'scrollY': false,",
" 'searching': false,",
" 'sortClasses': false,",
" 'rowCallback': rowCallback,",
" 'headerCallback': headerCallback,",
" 'columnDefs': [{targets: '_all', className: 'dt-center'}]",
" });",
" } else {",
" var subtable = $(id).DataTable({",
" 'data': dataset,",
" 'autoWidth': true,",
" 'deferRender': true,",
" 'info': false,",
" 'lengthChange': false,",
" 'ordering': d[n].length > 1,",
" 'order': [],",
" 'paging': false,",
" 'scrollX': false,",
" 'scrollY': false,",
" 'searching': false,",
" 'sortClasses': false,",
" 'rowCallback': rowCallback,",
" 'headerCallback': headerCallback,",
" 'columnDefs': [",
" {targets: -1, visible: false},",
" {targets: 0, orderable: false, className: 'details-control'},",
" {targets: '_all', className: 'dt-center'}",
" ]",
" }).column(0).nodes().to$().css({cursor: 'pointer'});",
" }",
" subtable.MakeCellsEditable({",
" onUpdate: onUpdate,",
" confirmationButton: true",
" });",
"};",
"",
"// display the child table on click",
"table.on('click', 'td.details-control', function(){",
" var tbl = $(this).closest('table'),",
" tblId = tbl.attr('id'),",
" td = $(this),",
" row = $(tbl).DataTable().row(td.closest('tr')),",
" rowIdx = row.index();",
" if(row.child.isShown()){",
" row.child.hide();",
" td.html('⊕');",
" } else {",
" var childId = tblId + '-child-' + rowIdx;",
" row.child(format(row.data(), childId)).show();",
" td.html('⊖');",
" format_datatable(row.data(), childId);",
" }",
"});")
## the datatable
dtable <- datatable(
Dat, callback = callback, rownames = rowNames, escape = -colIdx-1,
options = list(
columnDefs = list(
list(visible = FALSE, targets = ncol(Dat)-1+colIdx),
list(orderable = FALSE, className = 'details-control', targets = colIdx),
list(className = "dt-center", targets = "_all")
)
)
)
path <- "~/Work/R/DT" # folder containing the file dataTables.cellEdit.js
dep <- htmltools::htmlDependency(
"CellEdit", "1.0.19",
path, script = "dataTables.cellEdit.js")
dtable$dependencies <- c(dtable$dependencies, list(dep))
dtable
It is possible to style the 'Confirm' and 'Cancel' buttons. Create the file dataTables.cellEdit.css
, and put this content inside:
.my-input-class {
padding: 3px 6px;
border: 1px solid #ccc;
border-radius: 4px;
}
.my-confirm-class {
padding: 3px 6px;
font-size: 12px;
color: white;
text-align: center;
vertical-align: middle;
border-radius: 4px;
background-color: #337ab7;
text-decoration: none;
}
.my-cancel-class {
padding: 3px 6px;
font-size: 12px;
color: white;
text-align: center;
vertical-align: middle;
border-radius: 4px;
background-color: #a94442;
text-decoration: none;
}
In the callback
, replace
"table.MakeCellsEditable({",
" onUpdate: onUpdate,",
" confirmationButton: true",
"});",
with
"table.MakeCellsEditable({",
" onUpdate: onUpdate,",
" inputCss: 'my-input-class',",
" confirmationButton: {",
" confirmCss: 'my-confirm-class',",
" cancelCss: 'my-cancel-class'",
" }",
"});",
and replace
" subtable.MakeCellsEditable({",
" onUpdate: onUpdate,",
" confirmationButton: true",
" });",
with
" subtable.MakeCellsEditable({",
" onUpdate: onUpdate,",
" inputCss: 'my-input-class',",
" confirmationButton: {",
" confirmCss: 'my-confirm-class',",
" cancelCss: 'my-cancel-class'",
" }",
" });",
Finally, do
## the datatable
dtable <- datatable(
Dat, callback = callback, rownames = rowNames, escape = -colIdx-1,
options = list(
columnDefs = list(
list(visible = FALSE, targets = ncol(Dat)-1+colIdx),
list(orderable = FALSE, className = 'details-control', targets = colIdx),
list(className = "dt-center", targets = "_all")
)
)
)
path <- "~/Work/R/DT" # folder containing the files dataTables.cellEdit.js
# and dataTables.cellEdit.css
dep <- htmltools::htmlDependency(
"CellEdit", "1.0.19", path,
script = "dataTables.cellEdit.js", stylesheet = "dataTables.cellEdit.css")
dtable$dependencies <- c(dtable$dependencies, list(dep))
dtable
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.