Shiny中展示tinytable生成的JS触发CSS样式的HTML表格问题
问题描述
我想在Shiny应用中展示由tinytable包生成的HTML表格,该表格的某个单元格样式由window.addEventListener触发的JavaScript函数应用。
我尝试了以下操作:
- 将表格组件与JS脚本组件拆分;
- 通过
tags$head(tags$script(HTML(script)))将脚本传入UI; - 通过
renderUI(HTML(table))将表格传入server。
运行应用后,查看源码可见脚本已在head中,但样式未应用到表格。表格显示正常,但目标单元格的橙字黑底样式缺失。
我的R代码如下:
library("shiny") library("tinytable") # 创建带样式的HTML表格 tab <- tt(mtcars[1:5, 1:4]) %>% style_tt(1, 1, color = "orange", background = "black") %>% save_tt("html") # 拆分脚本与表格 lines <- strsplit(tab, "\n")[[1]] idx_open <- max(grep("<script>$", lines)) idx_close <- max(grep("</script>$", lines)) script <- paste(lines[(idx_open + 1):(idx_close - 1)], collapse ="\n") tab <- c(lines[1:(idx_open-1)], lines[(idx_close+1):length(lines)]) tab <- paste(tab, collapse = "\n") # 启动Shiny应用 shinyApp( ui = fluidPage( tags$head(tags$script(HTML(script))), fluidRow(column(12, h1("This is test of tiny table"), uiOutput('table')))), server = function(input, output) { output$table <- renderUI(HTML(tab)) } )
样式正确的表格效果:
tinytable生成的完整参考HTML如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>tinytable_foso3jzxac6xevph2ijf</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"> <style> .table td.tinytable_css_c4k1sy9wnv7b3ohvpz8x, .table th.tinytable_css_c4k1sy9wnv7b3ohvpz8x { border-bottom: solid 0.1em #d3d8dc; } .table td.tinytable_css_301bqkqzpns14bb4286d, .table th.tinytable_css_301bqkqzpns14bb4286d { color: orange; background-color: black; } </style> <script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script> <script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script> <script> MathJax = { tex: { inlineMath: [['$', '$'], ['\\(', '\\)']] }, svg: { fontCache: 'global' } }; </script> </head> <body> <div class="container"> <table class="table table-borderless" id="tinytable_foso3jzxac6xevph2ijf" style="width: auto; margin-left: auto; margin-right: auto;" data-quarto-disable-processing='true'> <thead> <tr> <th scope="col">mpg</th> <th scope="col">cyl</th> <th scope="col">disp</th> <th scope="col">hp</th> </tr> </thead> <tbody> <tr> <td>21.0</td> <td>6</td> <td>160</td> <td>110</td> </tr> <tr> <td>21.0</td> <td>6</td> <td>160</td> <td>110</td> </tr> <tr> <td>22.8</td> <td>4</td> <td>108</td> <td> 93</td> </tr> <tr> <td>21.4</td> <td>6</td> <td>258</td> <td>110</td> </tr> <tr> <td>18.7</td> <td>8</td> <td>360</td> <td>175</td> </tr> </tbody> </table> </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> <script> function styleCell_tinytable_a3n06841ebx0qj2p20q5(i, j, css_id) { var table = document.getElementById("tinytable_foso3jzxac6xevph2ijf"); table.rows[i].cells[j].classList.add(css_id); } function insertSpanRow(i, colspan, content) { var table = document.getElementById('tinytable_foso3jzxac6xevph2ijf'); var newRow = table.insertRow(i); var newCell = newRow.insertCell(0); newCell.setAttribute("colspan", colspan); // newCell.innerText = content; // this may be unsafe, but innerText does not interpret <br> newCell.innerHTML = content; } function spanCell_tinytable_a3n06841ebx0qj2p20q5(i, j, rowspan, colspan) { var table = document.getElementById("tinytable_foso3jzxac6xevph2ijf"); const targetRow = table.rows[i]; const targetCell = targetRow.cells[j]; for (let r = 0; r < rowspan; r++) { // Only start deleting cells to the right for the first row (r == 0) if (r === 0) { // Delete cells to the right of the target cell in the first row for (let c = colspan - 1; c > 0; c--) { if (table.rows[i + r].cells[j + c]) { table.rows[i + r].deleteCell(j + c); } } } // For rows below the first, delete starting from the target column if (r > 0) { for (let c = colspan - 1; c >= 0; c--) { if (table.rows[i + r] && table.rows[i + r].cells[j]) { table.rows[i + r].deleteCell(j); } } } } // Set rowspan and colspan of the target cell targetCell.rowSpan = rowspan; targetCell.colSpan = colspan; } window.addEventListener('load', function () { styleCell_tinytable_a3n06841ebx0qj2p20q5(0, 0, 'tinytable_css_c4k1sy9wnv7b3ohvpz8x') }) window.addEventListener('load', function () { styleCell_tinytable_a3n06841ebx0qj2p20q5(0, 1, 'tinytable_css_c4k1sy9wnv7b3ohvpz8x') }) window.addEventListener('load', function () { styleCell_tinytable_a3n06841ebx0qj2p20q5(0, 2, 'tinytable_css_c4k1sy9wnv7b3ohvpz8x') }) window.addEventListener('load', function () { styleCell_tinytable_a3n06841ebx0qj2p20q5(0, 3, 'tinytable_css_c4k1sy9wnv7b3ohvpz8x') }) window.addEventListener('load', function () { styleCell_tinytable_a3n06841ebx0qj2p20q5(1, 0, 'tinytable_css_301bqkqzpns14bb4286d') }) </script> </body> </html>
解决方案
问题根源
- 脚本执行时机错误:
window.addEventListener('load')在页面初始加载完成后触发,但表格是通过renderUI动态渲染的,初始加载时表格尚未存在,导致脚本找不到目标元素。 - 缺失核心CSS样式:拆分表格时移除了原HTML中定义单元格样式的
<style>部分,这是橙字黑底效果的关键。 - 缺少Bootstrap依赖:tinytable生成的表格依赖Bootstrap样式,原HTML包含的Bootstrap资源未在Shiny应用中引入。
修正后的代码
library("shiny") library("tinytable") # 生成带样式的表格HTML tab <- tt(mtcars[1:5, 1:4]) %>% style_tt(1, 1, color = "orange", background = "black") %>% save_tt("html") # 拆分CSS、脚本和表格主体内容 lines <- strsplit(tab, "\n")[[1]] # 提取CSS样式 css_start <- max(grep("<style>$", lines)) css_end <- max(grep("</style>$", lines)) css <- paste(lines[(css_start + 1):(css_end - 1)], collapse = "\n") # 提取脚本 script_start <- max(grep("<script>$", lines[-(1:css_end)])) + css_end script_end <- max(grep("</script>$", lines[-(1:css_end)])) + css_end script <- paste(lines[(script_start + 1):(script_end - 1)], collapse = "\n") # 提取表格HTML table_start <- max(grep("<table", lines)) table_end <- max(grep("</table>", lines)) tab_table <- paste(lines[table_start:table_end], collapse = "\n") shinyApp( ui = fluidPage( # 引入Bootstrap依赖资源 tags$link(href = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css", rel = "stylesheet"), tags$script(src = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"), # 添加表格样式定义 tags$head(tags$style(HTML(css))), fluidRow(column(12, h1("This is test of tiny table"), uiOutput('table'))), # 将脚本放在表格输出之后,确保表格渲染完成后再执行 tags$script(HTML(script)) ), server = function(input, output) { output$table <- renderUI(HTML(tab_table)) } )
关键修改说明
- 引入Bootstrap资源:添加tinytable依赖的Bootstrap CSS和JS,保证表格基础样式正常显示。
- 保留CSS样式:提取原HTML中的样式定义并添加到Shiny应用的head中,确保单元格样式规则存在。
- 调整脚本位置:将脚本从head移到表格输出组件之后,确保脚本执行时表格已完成渲染。
- 精准拆分内容:分别提取CSS、脚本和表格主体,避免遗漏任何关键资源。
内容的提问来源于stack exchange,提问作者Vincent
相关产品推荐
相关产品推荐

