R Shiny嵌套表格子行:复选框与YN列双向同步实现求助
问题解决:DT嵌套表格子行复选框与YN列双向同步
问题说明
需要实现:
- 嵌套表格子行加载时,复选框值与对应YN列数值一致
- 用户点击/取消复选框时,YN列同步更新
- 父行不显示复选框
现有代码可运行,但子行复选框点击后YN列无法更新,以下是修正方案:
问题根源
- 复选框ID冲突:所有子行复选框使用同一前缀
check,无法区分所属子表格 - 表格对象错误:原回调操作的是父表格而非动态生成的子表格
- 事件绑定时机错误:页面加载时绑定事件,但子表格是点击父行后才生成,导致事件无法绑定到动态元素
修正后的完整代码
library(DT) library(purrr) ### 构造主表和子表数据 ### subdat <- data.frame( Gene_SUB=c("MUTYH","AR"), Location_SUB=c("chr1:45797228","chr2:45797228"), Exon_SUB=c(NA,23), HGVS_p_SUB=c("NP_001121897.1:p.(Gly396Asp)","NP_001121897.1:p.(Gly396Asp)"), stopA=c(45797278,114925456), YN = c(FALSE, FALSE), stringsAsFactors = FALSE ) maindat <- data.frame( Gene=c("MUTYH","AR"), Location=c("chr1:45797228","chr2:45797228"), Exon=c(NA,23), HGVS_p=c("NP_001121897.1:p.(Gly396Asp)","NP_001121897.1:p.(Gly396Asp)"), stopA=c(45797278,114925456), stringsAsFactors = FALSE ) # 生成带唯一ID的复选框:check_父行标识_子行索引 shinyCheckbox <- function(parentId, values) { inputs <- character(length(values)) for(i in seq_along(inputs)) { inputs[i] <- as.character( checkboxInput(paste0("check_", parentId, "_", i), label = NULL, value = values[i], width = "20px") ) } inputs } # 按stopA拆分子表,为每个子表生成带唯一ID的复选框 fs <- split(subdat, factor(subdat$stopA, levels = unique(subdat$stopA))) subdats <- lapply(names(fs), function(id) { sub <- fs[[id]] sub$check <- shinyCheckbox(id, sub$YN) transpose(sub) }) oplus <- sapply(subdats, function(x) if(length(x)) "⊕" else "") Dat <- cbind(" " = oplus, maindat, details = I(subdats)) ### 修正后的回调函数 ### callback = JS( "table.column(1).nodes().to$().css({cursor: 'pointer'});", "// 生成子表格唯一ID", "var childId = function(d){", " var tail = d.slice(2, d.length - 1);", " return 'child_' + tail.join('_').replace(/[\\s|\\.|'|,|\\(|\\)]/g, '_');", "};", "// 构造子表格HTML结构", "var format = function (d) {", " if (d != null) {", " var id = childId(d);", " 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')){", " for(var j=0; j<dat.length; j++){", " $('td:eq('+j+')', row).css('background-color', 'papayawhip');", " }", " } else {", " for(var j=0; j<dat.length; j++){", " $('td:eq('+j+')', row).css('background-color', 'lemonchiffon');", " }", " }", "};", "// 子表格表头样式回调", "var headerCallback = function(thead, data, start, end, display){", " $('th', thead).css({", " 'border-top': '3px solid indigo',", " 'color': 'indigo',", " 'background-color': '#fadadd'", " });", "};", "// 初始化子表格", "var format_datatable = function (d) {", " 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);", " 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: '_all', className: 'dt-center'}]", " });", "};", "// 父行点击展开/收起子表格", "table.on('click', 'td.details-control', function () {", " var td = $(this),", " row = table.row(td.closest('tr'));", " if (row.child.isShown()) {", " row.child.hide();", " td.html('⊕');", " } else {", " row.child(format(row.data())).show();", " td.html('⊖');", " format_datatable(row.data());", " }", "});", "// 事件委托监听动态生成的复选框点击", "$(document).on('click', '[id^=check_]', function(){", " var id = this.getAttribute('id');", " // 解析父行标识和子行索引", " var match = id.match(/check_(.*)_(\\d+)/);", " var parentId = match[1];", " var childRowIdx = parseInt(match[2]) - 1;", " // 获取当前复选框状态", " var isChecked = $(this).prop('checked');", " // 找到对应的子表格", " var childTableId = 'child_' + parentId;", " var subtable = $('#' + childTableId).DataTable();", " // 更新YN列(假设YN列是第5列,从0开始计数)", " subtable.cell(childRowIdx, 5).data(isChecked).draw();", "});" ) ### 渲染表格 ### datatable(Dat, callback = callback, escape = FALSE, options = list( columnDefs = list( list(visible = FALSE, targets = ncol(Dat)), list(orderable = FALSE, className = 'details-control', targets = 1), list(className = "dt-center", targets = "_all") ) ) )
关键修改点
- 唯一复选框ID:修改
shinyCheckbox函数,生成包含父行标识和子行索引的唯一ID,避免冲突 - 事件委托绑定:使用
$(document).on('click', '[id^=check_]', ...)监听动态生成的复选框,确保事件能触发 - 定位子表格:通过ID解析找到对应的子表格实例,更新子表格的YN列,而非父表格
- 列索引确认:确保YN列的索引正确(代码中假设是第5列,从0开始计数,可根据实际列位置调整)
内容的提问来源于stack exchange,提问作者user3781528
相关产品推荐
相关产品推荐

