I've got a nested DataTable
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 Spot:30(%)
and the Spot:15(%)
columns of the child table.
There is another column, Mix(%)
, where the user is able to go in and edit the numbers. When the user edits this column the numbers in the Spot:30(%)
and the Spot:15(%)
columns are suppose to be updated accordingly.
The equations are:
Spot:30(%) = (Mix(%) * slider_value1)/100
Spot:15(%) = (Mix(%) * slider_value2)/100
Is there a way to use slider values from the Shiny app in the JS callback
script to update the Spot:30(%)
and the Spot:15(%)
columns when the Mix(%)
column is edited by the user??
I've attempted to try a solution like this one, example1, as well as trying to follow this, communicating with shiny via javascript, but can't seem to wrap my head around this.
Data
Parentstructure(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")Childstructure(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
# The datatable callbackparentRows <- 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', '#F5F2F2');"," var api = this.api();"," 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 < 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","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('⊕');"," } 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('⊖');"," format_datatable(row.data(), childId, rowIdx);"," }else{"," row.child(true);"," td.html('⊖');"," }"," }","});")# Module to create the nested structure of the tableNestedData <- 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)) "⊕" 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 <- 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) })