【问题标题】:Parent/Child table refreshes after hiding/re-expanding隐藏/重新展开后父/子表刷新
【发布时间】:2020-07-13 12:39:31
【问题描述】:

我的 Shiny 应用中有一个嵌套的 DataTable。单击展开按钮可查看子表。展开后,用户可以编辑几列。如果用户想要隐藏子表并移动到下一个父行,则在编辑列后,第一个父行和子表中的数据都会刷新为原始值。

这里的问题是我需要用户能够编辑子表,隐藏子表,并且保存那些编辑过的值,所以当他们再次展开子表时,那些编辑过的值就在那里。

我已经厌倦了使用 observeEvent()isolate(),但它们似乎没有我想要的功能。

数据

父母

structure(list(Market = c("ABILENE-SWEETWATER", "ALBANY-SCHENECTADY-TROY, NY"
), `Gross CPP` = c("$1.94", "$7.89"), `Gross CPM` = c("$1.02", 
"$0.82"), `Historical Composite Gross CPP (if applicable)` = c("$0", 
"$0"), `Historical Composite Gross CPM (if applicable)` = c("$0", 
"$0")), .Names = c("Market", "Gross CPP", "Gross CPM", "Historical Composite Gross CPP (if applicable)", 
"Historical Composite Gross CPM (if applicable)"), row.names = c(NA, 
-2L), class = "data.frame")

孩子

structure(list(Market = c("ABILENE-SWEETWATER", "ABILENE-SWEETWATER", 
"ABILENE-SWEETWATER", "ABILENE-SWEETWATER", "ABILENE-SWEETWATER", 
"ABILENE-SWEETWATER", "ABILENE-SWEETWATER", "ABILENE-SWEETWATER", 
"ABILENE-SWEETWATER", "ABILENE-SWEETWATER", "ABILENE-SWEETWATER", 
"ALBANY-SCHENECTADY-TROY, NY", "ALBANY-SCHENECTADY-TROY, NY", 
"ALBANY-SCHENECTADY-TROY, NY", "ALBANY-SCHENECTADY-TROY, NY", 
"ALBANY-SCHENECTADY-TROY, NY", "ALBANY-SCHENECTADY-TROY, NY", 
"ALBANY-SCHENECTADY-TROY, NY", "ALBANY-SCHENECTADY-TROY, NY", 
"ALBANY-SCHENECTADY-TROY, NY", "ALBANY-SCHENECTADY-TROY, NY", 
"ALBANY-SCHENECTADY-TROY, NY"), Daypart = c("Daytime", "Early Fringe", 
"Early Morning", "Early News", "Late Fringe", "Late News", "Prime Access", 
"Prime Time", "tv_2", "tv_3", "tv_cross_screen", "Daytime", "Early Fringe", 
"Early Morning", "Early News", "Late Fringe", "Late News", "Prime Access", 
"Prime Time", "tv_2", "tv_3", "tv_cross_screen"), `Mix (%)` = c(15, 
10, 15, 10, 5, 5, 10, 10, 0, 0, 0, 15, 10, 15, 10, 5, 5, 10, 
10, 0, 0, 0), `Spot:30 (%)` = c(15, 10, 15, 10, 5, 5, 10, 10, 
0, 0, 0, 15, 10, 15, 10, 5, 5, 10, 10, 0, 0, 0), `Spot:15 (%)` = c(0, 
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
), `Gross CPP ($)` = c(18, 18, 16, 23, 24, 40, 26, 44, 0, 0, 
0, 77, 71, 61, 78, 109, 145, 93, 213, 0, 0, 0), `Gross CPM ($)` = c(1.57, 
1.57, 1.39, 2, 2.09, 3.49, 2.27, 3.83, 23, 21, 13, 6.71, 6.19, 
5.32, 6.8, 9.5, 12.63, 8.1, 18.56, 23, 21, 13), `Historical Composite CPP ($)` = c(0, 
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
), `Historical Composite CPM ($)` = c(0, 0, 0, 0, 0, 0, 0, 0, 
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)), .Names = c("Market", 
"Daypart", "Mix (%)", "Spot:30 (%)", "Spot:15 (%)", "Gross CPP ($)", 
"Gross CPM ($)", "Historical Composite CPP ($)", "Historical Composite CPM ($)"
), class = "data.frame", row.names = c(NA, -22L))

代码

# The datatable callback
parentRows <- which(Dat[,1] != "")
callback_js = JS(
  "function onUpdate(updatedCell, updatedRow, oldValue) {};",
  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><tfoot><tr>'",
  "    for(var key in d[d.length-1][0]){",
  "      html += '<th></th>';",
  "    }",
  "    return html + '</tr></tfoot></table>';",
  "  } 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', 'lightgreen');",
  "    }, function() {",
  "      $(this).css('background-color', 'white');",
  "    });",
  "  } else {",
  "    $(row).css('background-color', 'white');",
  "    $(row).hover(function(){",
  "      $(this).css('background-color', 'lightblue');",
  "    }, 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 green',",
  "    'color': 'black',",
  "    'background-color': 'white'",
  "  });",
  "};",
  "",
  "// make the datatable",
  "var format_datatable = function(d, childId, rowIdx){",
  "  // footer callback to display the totals",
  "  // and update the parent row",
  "  var footerCallback = function(tfoot, data, start, end, display){",
  "    $('th', tfoot).css('background-color', 'white');",
  "    var api = this.api();",
  "    api.columns().eq(0).each(function(index){",
  "      if(index == 0) return $(api.column(index).footer()).html('Total');",
  "      var coldata = api.column(index).data();",
  "      var total = coldata",
  "          .reduce(function(a, b){return parseFloat(a) + parseFloat(b)}, 0);",
  "      if(index == 5 || index == 6 || index==7 || index==8) {",
  "        $(api.column(index).footer()).html((total / coldata.count()).toFixed(2));",
  "      } else {",
  "        $(api.column(index).footer()).html(total);",
  "     }",
  "    })",
  "    var col_share = api.column(2).data();",
  "    var col_CPP = api.column(5).data();",
  "    var col_CPM = api.column(6).data();",
  "    var col_Historical_CPP = api.column(7).data();",
  "    var col_Historical_CPM = api.column(8).data();",
  "    var CPP = 0, CPM = 0, Historical_CPP = 0, Historical_CPM = 0;",
  "    for(var i = 0; i < col_share.length; i++){",
  "      CPP += (parseFloat(col_share[i])*parseFloat(col_CPP[i]).toFixed(2));",
  "      CPM += (parseFloat(col_share[i])*parseFloat(col_CPM[i]).toFixed(2));",
  "      Historical_CPP += (parseFloat(col_share[i])*parseFloat(col_Historical_CPP[i]).toFixed(2));",
  "      Historical_CPM += (parseFloat(col_share[i])*parseFloat(col_Historical_CPM[i]).toFixed(2));",
  "    }",
  "    table.cell(rowIdx, j0+2).data((CPP/100).toFixed(2));",
  "    table.cell(rowIdx, j0+3).data((CPM/100).toFixed(2));",
  "    table.cell(rowIdx, j0+4).data((Historical_CPP/100).toFixed(2));",
  "    table.cell(rowIdx, j0+5).data((Historical_CPM/100).toFixed(2));",
  "  }",
  "  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': true,",
  "                 'scrollX': false,",
  "                 'scrollY': false,",
  "                 'searching': false,",
  "                 'sortClasses': false,",
  "                 'pageLength': 50,",
  "                 'rowCallback': rowCallback,",
  "                 'headerCallback': headerCallback,",
  "                 'footerCallback': footerCallback,",
  "                 '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': true,",
  "            'scrollX': false,",
  "            'scrollY': false,",
  "            'searching': false,",
  "            'sortClasses': false,",
  "            'pageLength': 50,",
  "            'rowCallback': rowCallback,",
  "            'headerCallback': headerCallback,",
  "            'footerCallback': footerCallback,",
  "            '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,",
  "    inputCss: 'my-input-class',",
  "    columns: [2, 7, 8],",
  "    confirmationButton: {",
  "      confirmCss: 'my-confirm-class',",
  "      cancelCss: 'my-cancel-class'",
  "    }",
  "  });",
  "};",
  "",
  "// 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, rowIdx);",
  "  }",
  "});")

# Module to create the nested structure of the table
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)
}

# Shiny App
    # Bind the market level and mix breakout data together for the final table
    market_mix_table <- reactive({
      markets <- market_costings_gross_net()
      mix_breakout <- mix_breakout_digital_elements()
      # Make the dataframe
      # This must be met length(children) == nrow(dat)
      Dat <- NestedData(
        dat = markets,
        children = split(mix_breakout, mix_breakout$Market)
      )
      return(Dat)
    })
    # Render the table
    output$daypartTable <- DT::renderDataTable({
      Server = FALSE
      # Whether to show row names (set TRUE or FALSE)
      rowNames <- FALSE
      colIdx <- as.integer(rowNames)
      # The data
      Dat <- market_mix_table()
      # Table
      table <- DT::datatable(
        Dat,
        callback = callback_js,
        rownames = rowNames,
        escape = -colIdx-1,
        style = "bootstrap4",
          options = list(
            lengthMenu = list(
              c(-1, 10, 20),
              c("All", 10, 20)
            ),
            columnDefs = list(
              list(width = '30px'),
              list(width = '100px', targets = 1),
              list(visible = FALSE, targets = ncol(Dat)-1+colIdx),
              list(orderable = FALSE, className = 'details-control', targets = colIdx),
              list(className = "dt-center", targets = "_all")
            )
          )
        )
      # Some faancy Java magic
      path <- getwd()
      # Call the html tools deps (js & css files in this directory)
      dep <- htmltools::htmlDependency(
        "CellEdit", "1.0.19", path,
        script = "dataTables.cellEdit.js", stylesheet = "dataTables.cellEdit.css")
      table$dependencies <- c(table$dependencies, list(dep))
      # server = FALSE
      return(table)
    })
    # Keep the edited cell values
    observeEvent(input$daypartTable_cell_edit, {
      expdf[input$daypartTable_cell_edit$row,input$daypartTable_cell_edit$col] <<- input$daypartTable_cell_edit$value
    })

非常感谢任何帮助!这是迄今为止我工作过的最先进的 Shiny 应用程序,我可能在这方面过头了。

【问题讨论】:

    标签: javascript r shiny datatables dt


    【解决方案1】:

    似乎像这样工作。我修改了JS回调的最后一部分:

      "// display the child table on click",
      "var children = [];", # array to store the id's of the already created child tables
      "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;",
      "    if(children.indexOf(childId) === -1){", # this child table has not been created yet
      "      children.push(childId);",
      "      row.child(format(row.data(), childId)).show();",
      "      td.html('&CircleMinus;');",
      "      format_datatable(row.data(), childId, rowIdx);",
      "    }else{",
      "      row.child(true);",
      "      td.html('&CircleMinus;');",
      "    }",
      "  }",
      "});")
    

    以前每次显示子表时它都会重新创建它。

    library(DT)
    
    df_child <- structure(
      list(
        Daypart = c("Daytime", "Early Fringe", "Early Morning", "Early News", "Late Fringe", "Late News", "Prime Access", "Prime Time"), 
        `Share (%)` = c(15, 15, 15, 15, 10, 10, 10, 10), 
        `Spot:30 (%)` = c(0, 0, 0, 0, 0, 0, 0, 0), 
        `Spot:15 (%)` = c(0, 0, 0, 0, 0, 0, 0, 0), 
        `Gross CPP ($)` = c(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 (%)", "Gross CPP ($)", "Gross CPM ($)"), 
      row.names = c(NA, -8L), class = "data.frame")
    
    df_parent <- structure(
      list(
        Market = c("ABILENE-SWEETWATER", "ALBANY-SCHENECTADY-TROY, NY"), 
        `Gross CPP` = c("$0", "$0"), 
        `Gross CPM` = c("$0", "$0"), 
        `Historical Composite Gross CPP (if applicable)` = c("$0", "$0"), 
        `Historical Composite Gross CPM (if applicable)` = c("$0", "$0")), 
      .Names = c("Market", "Gross CPP", "Gross CPM", "Historical Composite Gross CPP (if applicable)", "Historical Composite Gross CPM (if applicable)"), 
      row.names = c(NA, -2L), class = "data.frame")
    
    # 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 = df_parent, 
      children = list(df_child, df_child)
    )
    
    ## 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,",
      "  inputCss: 'my-input-class',",
      "  confirmationButton: {",
      "    confirmCss: 'my-confirm-class',",
      "    cancelCss: 'my-cancel-class'",
      "  }",
      "});",
      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><tfoot><tr>'",
      "    for(var key in d[d.length-1][0]){",
      "      html += '<th></th>';",
      "    }",
      "    return html + '</tr></tfoot></table>';",
      "  } 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, rowIdx){",
      "  // footer callback to display the totals",
      "  // and update the parent row",
      "  var footerCallback = function(tfoot, data, start, end, display){",
      "    $('th', tfoot).css('background-color', '#fed8b1');",
      "    var api = this.api();",
      "    api.columns().eq(0).each(function(index){",
      "      if(index == 0) return $(api.column(index).footer()).html('Total');",
      "      var coldata = api.column(index).data();",
      "      var total = coldata", 
      "          .reduce(function(a, b){return parseFloat(a) + parseFloat(b)}, 0);",
      "      $(api.column(index).footer()).html(total);",
      "    })",
      "    var col_share = api.column(1).data();",
      "    var col_CPP = api.column(4).data();",
      "    var col_CPM = api.column(5).data();",
      "    var CPP = 0, CPM = 0;",
      "    for(var i = 0; i < col_share.length; i++){",
      "      CPP += parseFloat(col_share[i])*parseFloat(col_CPP[i]);",
      "      CPM += parseFloat(col_share[i])*parseFloat(col_CPM[i]);",
      "    }",
      "    table.cell(rowIdx, j0+2).data('$' + (CPP/100));",
      "    table.cell(rowIdx, j0+3).data('$' + (CPM/100)).draw();",
      "  }",
      "  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,",
      "                 'footerCallback': footerCallback,",
      "                 '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,",
      "            'footerCallback': footerCallback,",
      "            '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,",
      "    inputCss: 'my-input-class',",
      "    confirmationButton: {",
      "      confirmCss: 'my-confirm-class',",
      "      cancelCss: 'my-cancel-class'",
      "    }",
      "  });",
      "};",
      "",
      "// display the child table on click",
      "var children = [];",
      "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;",
      "    if(children.indexOf(childId) === -1){",
      "      children.push(childId);",
      "      row.child(format(row.data(), childId)).show();",
      "      td.html('&CircleMinus;');",
      "      format_datatable(row.data(), childId, rowIdx);",
      "    }else{",
      "      row.child(true);",
      "      td.html('&CircleMinus;');",
      "    }",
      "  }",
      "});")
    
    ## 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
    

    【讨论】:

    • 感谢您的帮助!这正如预期的那样工作。看来我需要学习 JavaScript,因为我需要的大部分功能都是基于 JS callback
    • 知道如果值大于 100,我将如何更改 footerCallback 总数的颜色吗??
    • @TimothyMcwilliams 是的。在$(api.column(index).footer()).html(total);行之后,做if(total &gt;= 100){$(api.column(index).footer()).css({'color': 'red'});}
    猜你喜欢
    • 1970-01-01
    • 2017-09-15
    • 2016-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多