Timothy Mcwilliams
Timothy Mcwilliams

Reputation: 316

Parent/Child table refreshes after hiding/re-expanding

I have a nested DataTable in my Shiny app. The child table is viewed by clicking on an expand button. Once expanded, the user can edit a few columns. After editing the columns if the user wanted to hide the child table and move onto the next parent row the data in both the first parent row and child table refreshes to the original values.

The issue here is that I need to user to be able to edit the child table, hide the child table, and those edited values are saved so when they go to expand the child table again those edited values are there.

I've tired to use observeEvent() and isolate() but those don't seem to have the functionality that I am looking for.

Data

Parent

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")

Child

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

Code

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

Any help is greatly appreciated!! This is by far the most advanced Shiny app I have worked on and I may be way over my head on this.

Upvotes: 1

Views: 329

Answers (1)

St&#233;phane Laurent
St&#233;phane Laurent

Reputation: 84599

Seems to work like this. I modified the last part of the JS callback:

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

Previously it recreated the child table each time one displayed it.

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

Upvotes: 1

Related Questions