在闪亮的数据表中手动编辑子行

时间:2020-03-13 01:07:03

标签: javascript r shiny datatables dt

我的应用程序中呈现了一个嵌套的数据表。数据表中已预先填充了数据,但是,我收到用户的请求,要求他们必须能够编辑子行中的数据。我的数据表如下。例如,用户可能需要进入并将Share (%)的{​​{1}}更改。在更改该值时,还需要更新父行。

我要解决的问题是,当我单击以编辑子表中的单个单元格时,整个表消失了,并显示一个文本框。

enter image description here

问题

enter image description here

代码

Daytime

子表结构(# Merge the row details subdats <- lapply( list(df2), purrr::transpose ) # Dataframe for the datatable Dat <- cbind( " " = "&oplus;", 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('&oplus;');", " } else {", " var childId = tblId + '-child-' + rowIdx;", " row.child(format(row.data(), childId)).show();", " td.html('&CircleMinus;');", " 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

1 个答案:

答案 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)) "&oplus;" 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('&oplus;');",
  "  } else {",
  "    var childId = tblId + '-child-' + rowIdx;",
  "    row.child(format(row.data(), childId)).show();",
  "    td.html('&CircleMinus;');",
  "    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

enter image description here

可以设置“确认”和“取消”按钮的样式。创建文件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

enter image description here