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

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>
解决方案

问题根源

  1. 脚本执行时机错误:window.addEventListener('load')在页面初始加载完成后触发,但表格是通过renderUI动态渲染的,初始加载时表格尚未存在,导致脚本找不到目标元素。
  2. 缺失核心CSS样式:拆分表格时移除了原HTML中定义单元格样式的<style>部分,这是橙字黑底效果的关键。
  3. 缺少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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 10:33:10