title: Using values from slider in javascript DataTables calculations
body: <p>I've got a nested <code>DataTable</code> in my shiny app that is made from the data below. There are two sliders that I have which make up a percentage of 100. If one slider is 50 the other sider is 50. These two numbers from the two sliders help make up the <code>Spot:30(%)</code> and the <code>Spot:15(%)</code> columns of the child table. </p>
  
  <p>There is another column, <code>Mix(%)</code>, where the user is able to go in and edit the numbers. When the user edits this column the numbers in the <code>Spot:30(%)</code> and the <code>Spot:15(%)</code> columns are suppose to be updated accordingly.</p>
  
  <p>The equations are:</p>
  
  <p><code>Spot:30(%) = (Mix(%) * slider_value1)/100</code></p>
  
  <p><code>Spot:15(%) = (Mix(%) * slider_value2)/100</code></p>
  
  <p>Is there a way to use slider values from the Shiny app in the <code>JS callback</code> script to update the <code>Spot:30(%)</code> and the <code>Spot:15(%)</code> columns when the <code>Mix(%)</code> column is edited by the user??</p>
  
  <p>I've attempted to try a solution like this one, <a href="https://www.r-bloggers.com/edit-datatables-in-r-shiny-app/" rel="nofollow noreferrer">example1</a>, as well as trying to follow this, <a href="https://shiny.rstudio.com/articles/communicating-with-js.html" rel="nofollow noreferrer">communicating with shiny via javascript</a>, but can't seem to wrap my head around this.</p>
  
  <p><strong>Data</strong></p>
  
  <pre><code>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` = c("$0", "$0"), `Historical Composite Gross CPM` = c("$0", 
      "$0")), .Names = c("Market", "Gross CPP", "Gross CPM", "Historical Composite Gross CPP", 
  "Historical Composite Gross CPM"), 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", "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_cross_screen", "Daytime", "Early Fringe", 
  "Early Morning", "Early News", "Late Fringe", "Late News", "Prime Access", 
  "Prime Time", "tv_2", "tv_cross_screen"), `Mix (%)` = c(15, 10, 
  15, 10, 5, 5, 10, 10, 0, 0, 15, 10, 15, 10, 5, 5, 10, 10, 0, 
  0), `Spot:30 (%)` = c(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
  0, 0, 0, 0, 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), `Gross CPP ($)` = c(18, 
  18, 16, 23, 24, 40, 26, 44, 0, 0, 77, 71, 61, 78, 109, 145, 93, 
  213, 0, 0), `Gross CPM ($)` = c(0, 0, 0, 0, 0, 0, 0, 0, 23, 13, 
  0, 0, 0, 0, 0, 0, 0, 0, 23, 13), `Historical Override CPP ($)` = c(18, 
  18, 16, 23, 24, 40, 26, 44, 0, 0, 77, 71, 61, 78, 109, 145, 93, 
  213, 0, 0), `Historical Override CPM ($)` = c(0, 0, 0, 0, 0, 
  0, 0, 0, 23, 13, 0, 0, 0, 0, 0, 0, 0, 0, 23, 13)), .Names = c("Market", 
  "Daypart", "Mix (%)", "Spot:30 (%)", "Spot:15 (%)", "Gross CPP ($)", 
  "Gross CPM ($)", "Historical Override CPP ($)", "Historical Override CPM ($)"
  ), class = "data.frame", row.names = c(NA, -20L))
  </code></pre>
  
  <p><strong>EDIT</strong></p>
  
  <p>For some reason when I move the slider the Shiny Server observes the event and prints out the new slider value, but that new slider value doesn't get passed to the JS callback which in turn doesn't update the DataTable. The data table stays at the <code>var slider = 50</code> and doesn't update. </p>
  
  <p><strong>Code</strong></p>
  
  <pre><code># The datatable callback
  parentRows &lt;- which(Dat[,1] != "")
  callback_js = JS(
    "var tv_spots30 = 50;",
    "Shiny.addCustomMessageHandler('sliderValue1', function(value1){",
    "  tv_spots30 = value1;",
    "  $('#' + children[0]).DataTable().draw()",
    "  $('#' + children[1]).DataTable().draw();",
    "});",
    "var spots15 = 50;",
    "Shiny.addCustomMessageHandler('sliderValue2', function(value2){",
    "  spots15 = value2;",
    "  $('#' + children[0]).DataTable().draw();",
    "  $('#' + children[1]).DataTable().draw();",
    "});",
    "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 &lt; nrows; ++i){",
    "  if(parentRows.indexOf(i) &gt; -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 = ",
    "      '&lt;table class=\"display compact hover\" ' + ",
    "      'style=\"padding-left: 30px;\" id=\"' + childId + '\"&gt;&lt;thead&gt;&lt;tr&gt;';",
    "    for(var key in d[d.length-1][0]){",
    "      html += '&lt;th&gt;' + key + '&lt;/th&gt;';",
    "    }",
    "    html += '&lt;/tr&gt;&lt;/thead&gt;&lt;tfoot&gt;&lt;tr&gt;'",
    "    for(var key in d[d.length-1][0]){",
    "      html += '&lt;th&gt;&lt;/th&gt;';",
    "    }",
    "    return html + '&lt;/tr&gt;&lt;/tfoot&gt;&lt;/table&gt;';",
    "  } 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', '#F5F2F2');",
    "    var api = this.api();",
    "    var col_mix = api.column(2).data();",
    "    for(let i = 0; i &lt; col_mix.length; i++){",
    "      api.cell(i,3).data(parseFloat(col_mix[i])*tv_spots30/100);",
    "      api.cell(i,4).data(parseFloat(col_mix[i])*spots15/100);",
    "    }",
    "    api.columns().eq(0).each(function(index){",
    "      if(index == 0) return $(api.column(index).footer()).html('Mix Total');",
    "      var coldata = api.column(index).data();",
    "      var total = coldata",
    "          .reduce(function(a, b){return parseFloat(a) + parseFloat(b)}, 0);",
    "      if(index == 3 || index == 4 ||index == 5 || index == 6 || index==7 || index==8) {",
    "        $(api.column(index).footer()).html('');",
    "      } else {",
    "        $(api.column(index).footer()).html(total);",
    "      }",
    "      if(total == 100) {",
    "        $(api.column(index).footer()).css({'color': 'green'});",
    "      } else {",
    "        $(api.column(index).footer()).css({'color': 'red'});",
    "      }",
    "    })",
    "  // update the parent row",
    "    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 &lt; 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 &lt; 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 &gt; 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 &gt; 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",
    "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('&amp;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('&amp;CircleMinus;');",
    "      format_datatable(row.data(), childId, rowIdx);",
    "    }else{",
    "      row.child(true);",
    "      td.html('&amp;CircleMinus;');",
    "    }",
    "  }",
    "});")
  
  # Module to create the nested structure of the table
  NestedData &lt;- function(dat, children){
    stopifnot(length(children) == nrow(dat))
    g &lt;- function(d){
      if(is.data.frame(d)){
        purrr::transpose(d)
      }else{
        purrr::transpose(NestedData(d[[1]], children = d$children))
      }
    }
    subdats &lt;- lapply(children, g)
    oplus &lt;- sapply(subdats, function(x) if(length(x)) "&amp;oplus;" else "")
    cbind(" " = oplus, dat, "_details" = I(subdats), stringsAsFactors = FALSE)
  }
  
  
  # shiny
      # Bind the market level and mix breakout data together for the final table
      market_mix_table &lt;- reactive({
        markets &lt;- market_costings_gross_net()
        mix_breakout &lt;- mix_breakout_digital_elements()
        # Make the dataframe
        # This must be met length(children) == nrow(dat)
        Dat &lt;- NestedData(
          dat = markets,
          children = split(mix_breakout, mix_breakout$Market)
        )
        return(Dat)
      })
  
      # Render the table
      output$daypartTable &lt;- DT::renderDataTable({
        Server = FALSE
        # Whether to show row names (set TRUE or FALSE)
        rowNames &lt;- FALSE
        colIdx &lt;- as.integer(rowNames)
        # The data
        Dat &lt;- market_mix_table()
        # Table
        table &lt;- 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 &lt;- getwd()
        # Call the html tools deps (js &amp; css files in this directory)
        dep &lt;- htmltools::htmlDependency(
          "CellEdit", "1.0.19", path,
          script = "dataTables.cellEdit.js", stylesheet = "dataTables.cellEdit.css")
        table$dependencies &lt;- c(table$dependencies, list(dep))
        # server = FALSE
        return(table)
      })
  
  observeEvent(input[["tv_spots30"]], {
    print("30%")
    print(input[["tv_spots30"]])
    session$sendCustomMessage("sliderValue1", input[["tv_spots30"]])
  })
  
  observeEvent(input[["spots15"]], {
    print("15%")
    print(input[["spots15"]])
    session$sendCustomMessage("sliderValue2", input[["spots15"]])
  })
  
  # ui
            sliderInput("tv_spots30",
                        "Spots 30 (%):",
                        min = 0,
                        max = 100,
                        value = 50,
                        step = 5
            ),
            sliderInput("spots15",
                        "Spots 15 (%):",
                        min = 0,
                        max = 100,
                        value = 50,
                        step = 5
            ),
  </code></pre>