我的应用程序中呈现了一个嵌套的数据表。数据表中已预先填充了数据,但是,我收到用户的请求,要求他们必须能够编辑子行中的数据。我的数据表如下。例如,用户可能需要进入并将Share (%)
的{{1}}更改。在更改该值时,还需要更新父行。
我要解决的问题是,当我单击以编辑子表中的单个单元格时,整个表消失了,并显示一个文本框。
问题
代码
Daytime
子表结构(# 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")
)
)
)
})
):
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 $-
):
df1
答案 0 :(得分:1)
我们可以借助CellEdit plugin(MIT许可证)来做到这一点。
下面是一个示例。有关带有父行/子行的DT
数据表的说明,请参见this post。
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
可以设置“确认”和“取消”按钮的样式。创建文件dataTables.cellEdit.css
,并将此内容放入其中:
.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;
}
在callback
中,替换
"table.MakeCellsEditable({",
" onUpdate: onUpdate,",
" confirmationButton: true",
"});",
使用
"table.MakeCellsEditable({",
" onUpdate: onUpdate,",
" inputCss: 'my-input-class',",
" confirmationButton: {",
" confirmCss: 'my-confirm-class',",
" cancelCss: 'my-cancel-class'",
" }",
"});",
并替换
" subtable.MakeCellsEditable({",
" onUpdate: onUpdate,",
" confirmationButton: true",
" });",
使用
" subtable.MakeCellsEditable({",
" onUpdate: onUpdate,",
" inputCss: 'my-input-class',",
" confirmationButton: {",
" confirmCss: 'my-confirm-class',",
" cancelCss: 'my-cancel-class'",
" }",
" });",
最后,做
## 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