You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

请求协助实现R Shiny中带两层子行的NestedTable嵌套表格

Hey there! Let's fix your nested DT table issue so you can get that two-level expandable functionality working properly. Your current code only handles one level of expansion, and there are a few syntax issues in the JS callback that are holding it back. Here's the revised code with explanations of the key changes:

library(shiny)
library(shinydashboard)
library(tidyverse)
library(DT)
library(data.table)

ui <- fluidPage(fluidRow(DT::dataTableOutput(width = "100%", "table")))

server <- function(input, output) {
  output$table = DT::renderDataTable({
    # Sample data (fixed row length mismatch)
    DATA <- data.table(Team = c(3,3, 4, 4, 39, 41, 43),
                      Workdone = c(2,2, 1, 1, 1, 1, 1),
                      Q1 = c(0,0, 0, 0, 0, 0, 0),
                      Q2 = c(1, 1, 1, 1,1,1,1),
                      Q3 = c(0,0, 0, 0, 0, 0,0),
                      Name = c("A", "B", "C", "D", "E", "F", "G"),
                      Number = c(1, 1, 1, 1,1,1,1),
                      q1 = c(0,0,0,0,0,0,0),
                      q2 = c(100, 100, 100, 100, 100, 100, 100),
                      q3 = c(0,0,0,0,0,0,0),
                      Data = c("2020","2021", "2011", "2021", "2011", "2015", "2022"),
                      Type = c("Normal","Normal","Normal","Normal","Normal","Normal","Normal"),
                      ID = c(0,0, 0, 0, 0, 0,0),
                      ID2 = c("A", "B", "C", "D", "E", "F", "G"),
                      Channel = c(1, 1, 1, 1,1,1,1),
                      Topic1 = c(0,0,0,0,0,0,0),
                      Topic2 = c(100, 100, 100, 100, 100, 100, 100),
                      Topic3 = c(0,0,0,0,0,0,0))
    
    # Create third-level data (child of child)
    DATA3 <- DATA[, c("Topic1", "Topic2", "Topic3", "ID2", "Channel")]
    DATA3 <- DATA3[, list(sub_data = list(.SD)), by = list(ID2, Channel)]
    DATA3 <- cbind(' ' = '&#9658;', DATA3)
    
    # Create second-level data (child of parent)
    DATA2 <- DATA[, c(6:17)]
    DATA2 <- DATA2[, list(cars = list(DATA3)), by = list(Name, Number, q1, q2, q3)]
    DATA2 <- cbind(' ' = '&#9658;', DATA2)
    
    # Create parent-level data
    DATA <- DATA[, list(cars = list(DATA2)), by = list(Team, Workdone, Q1, Q2, Q3)]
    DATA <- cbind(' ' = '&#9658;', DATA)
    
    callback <- DT::JS(
      "// Handle both parent and child table clicks",
      "var childId = function(d, level) {",
      "  var tail = d.slice(2, d.length - 1);",
      "  return 'child_' + level + '_' + tail.join('_').replace(/[\\s|\\.|'|,|\\(|\\)]/g, '_');",
      "};",
      "",
      "var format = function(d, level) {",
      "  if (d != null) {",
      "    var id = childId(d, level);",
      "    var html = '<table class=\"display compact\" id=\"' + id + '\"><thead><tr>';",
      "    for (var key in d[d.length-1][0]) {",
      "      html += '<th>' + key + '</th>';",
      "    }",
      "    html += '</tr></thead></table>';",
      "    return html;",
      "  } else {",
      "    return '';",
      "  }",
      "};",
      "",
      "var rowCallback = function(row, dat, displayNum, index) {",
      "  if($(row).hasClass('odd')) {",
      "    $('td', row).css('background-color', 'white');",
      "  } else {",
      "    $('td', row).css('background-color', 'white');",
      "  }",
      "};",
      "",
      "var headerCallback = function(thead, data, start, end, display) {",
      "  $('th', thead).css({",
      "    'border-top': '2px solid black',",
      "    'color': 'black',",
      "    'background-color': '#ffffff'",
      "  });",
      "};",
      "",
      "var format_datatable = function(d, level) {",
      "  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(d, level);",
      "  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,",
      "    'columnDefs': [",
      "      {targets: 0, className: 'details-control', orderable: false},",
      "      {targets: 3, width: '50px'},",
      "      {targets: 4, width: '50px'},",
      "      {targets: 5, width: '50px'},",
      "      {targets: 1, width: '100px'},",
      "      {targets: 2, width: '75px'},",
      "      {targets: 3, width: '60px'},",
      "      {targets: 1, className: 'dt-right'}",
      "    ]",
      "  });",
      "",
      "  // Add click handler for second-level tables",
      "  if (level === 1) {",
      "    subtable.on('click', 'td.details-control', function() {",
      "      var td = $(this);",
      "      var row = subtable.row(td.closest('tr'));",
      "      if (row.child.isShown()) {",
      "        row.child.hide();",
      "        td.html('&#9658;');",
      "      } else {",
      "        row.child(format(row.data(), 2)).show();",
      "        td.html('&CircleMinus;');",
      "        format_datatable(row.data(), 2);",
      "      }",
      "    });",
      "  }",
      "};",
      "",
      "// Initialize parent table click handler",
      "table.column(1).nodes().to$().css({cursor: 'pointer'});",
      "table.on('click', 'td.details-control', function() {",
      "  var td = $(this);",
      "  var row = table.row(td.closest('tr'));",
      "  if (row.child.isShown()) {",
      "    row.child.hide();",
      "    td.html('&#9658;');",
      "  } else {",
      "    row.child(format(row.data(), 1)).show();",
      "    td.html('&CircleMinus;');",
      "    format_datatable(row.data(), 1);",
      "  }",
      "});"
    )
    
    datatable(
      DATA, escape = FALSE, # Allow HTML buttons to render
      options = list(
        searching = FALSE,
        dom = 't',
        pageLength = 30,
        columnDefs = list(
          list(className = 'details-center', targets = c(2:6)),
          list(orderable = FALSE, className = 'details-control', targets = 1)
        )
      ),
      callback = DT::JS(callback)
    )
  }, server = FALSE)
}

shinyApp(ui = ui, server = server)

Key Fixes & Improvements:

  • Three-Level Data Nesting: Created DATA3 as the deepest level of data, nested it inside DATA2 (first child), then nested DATA2 inside the parent DATA to support two levels of expansion.
  • Unique Child Table IDs: Added a level parameter to the childId function to prevent DOM conflicts between parent and child tables.
  • Dual-Level Click Handling: Extended the JS callback to handle clicks on both parent and first-child tables, triggering expansion for the next level each time.
  • Fixed Data & Syntax Issues: Corrected row length mismatches in sample data and fixed missing commas in JS columnDefs that caused silent failures.
  • Proper Button Rendering: Set escape = FALSE so expand/collapse buttons render as clickable icons instead of raw HTML text.
  • Consistent Styling: Applied uniform row and header styling across all table levels for a polished look.

Now when you run this code, clicking the expand button on a parent row will show the first-level child table, and clicking the expand button on any child row will reveal the second-level sub-table. Collapsing works in reverse too.

内容的提问来源于stack exchange,提问作者Omer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 17:42:39