Javascript 更新子表时更新父表

Javascript 更新子表时更新父表,javascript,html,r,shiny,dt,Javascript,Html,R,Shiny,Dt,我的闪亮应用程序中有一个嵌套表。子表允许用户进入并手动编辑几列中的值(Share(%),Spot:30(%),和Spot:15(%))。更新这些值时,总计行将更新,列总CPP($)和总CPM($)也将更新 在父项表中,当更新子项值时,父项总CPP($)和总CPM($)也应更新。父表总CPP($)和总CPM($)是子表总CPP($)和总CPM($)的加权平均值,其中权重为份额(%) 我的问题是,我似乎不知道如何实现这一点。我是否需要在JavaScript回调端或R服务器端执行此操作 父表和子表

我的闪亮应用程序中有一个嵌套表。子表允许用户进入并手动编辑几列中的值(
Share(%)
Spot:30(%)
,和
Spot:15(%)
)。更新这些值时,总计行将更新,列
总CPP($)
总CPM($)
也将更新

父项
表中,当更新
子项
值时,
父项
总CPP($)
总CPM($)
也应更新。
父表
总CPP($)
总CPM($)
子表
总CPP($)
总CPM($)
的加权平均值,其中权重为
份额(%)

我的问题是,我似乎不知道如何实现这一点。我是否需要在
JavaScript
回调端或
R
服务器端执行此操作

父表和子表

子表数据:

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 CPM` = c("$0", "$0", "$0", "$0", "$0", "$0", "$0", 
"$0")), .Names = c("Daypart", "Share (%)", "Spot:30 (%)", "Spot:15 (%)", 
"Gross CPM"), row.names = c(NA, -8L), class = "data.frame")
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")
父表数据:

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 CPM` = c("$0", "$0", "$0", "$0", "$0", "$0", "$0", 
"$0")), .Names = c("Daypart", "Share (%)", "Spot:30 (%)", "Spot:15 (%)", 
"Gross CPM"), row.names = c(NA, -8L), class = "data.frame")
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")
代码

# The callback to format the datatable
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'",
  "  });",
  "};",
  "",
  "// footer callback to display the totals",
  "var footerCallback = function(tfoot, data, start, end, display){",
  "  $('th', tfoot).css('background-color', '#F4F1F1');",
  "  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);",
  "  })",
  "}",
  "",
  "// 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,",
  "                 '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',",
  "    columns: [1,2,3],",
  "    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);",
  "  }",
  "});")

## Server.R
# Bind the market level and mix breakout data together for the final table
market_mix_table <- reactive({
  markets <- market_costings_gross_net()
  mix_breakout <- daypart_break_out()

  # Need to use replicate() on mix_breakout_table for cases when there is an arbitrary number of rows in markets 
  n <-  nrow(markets)
  children_list <- replicate(n, mix_breakout, simplify = FALSE)
  # Make the dataframe
  # This must be met length(children) == nrow(dat)
  Dat <- NestedData(
    dat = markets,
    children = children_list
  )
  return(Dat)
})
# Render the table
output$daypartTable <- DT::renderDataTable({
  # 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,
      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")
        )
      )
    )
  # Some faancy Java magic
  path <- getwd()
  dep <- htmltools::htmlDependency(
    "CellEdit", "1.0.19", path, 
    script = "dataTables.cellEdit.js", stylesheet = "dataTables.cellEdit.css")
  table$dependencies <- c(table$dependencies, list(dep))
  return(table)
})

听起来不错。我将计算结果放入页脚回调中

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

## 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

df_child确切的公式是什么
GrossCPP(家长)=TotalShare/100*TotalGrossCPP(孩子)+(1-TotalShare/100)*TotalGrossCPM(孩子)
?@StéphaneLaurent,很抱歉公式不清楚。有两个公式;一个用于
总CPP(父项)
和一个用于
总CPP(父项)
<代码>总CPP(父项)=∑(日部分份额/100)*总CPP(子项)
Gross CPM(parent)=∑(daypart share/100)*Gross CPM(child)
我尝试了您的解决方案,当我去扩展子表时,它会短暂地显示出来,但很快就会隐藏起来,而我没有点击
-
按钮来隐藏它。@TimothyMcwilliams您复制粘贴了我的代码(并更改了
路径
变量)了吗?没有变化?我复制的唯一代码是
回调
块。我想知道为什么这对我来说不起作用。我已经把它隔离到新计算的位置,我所做的只是注释掉新的块,子表保持不变。你认为这是压痕问题吗?@TimothyMcwilliams不,压痕没有效果。如果你复制我所有的代码,它能工作吗?