javascript - 将选中的图标添加到 DT shiny 中的选定行

标签 javascript css shiny dt

我在 Shiny 的应用程序中有一个 DT 表,其背景颜色设置为匹配特定值。我还使用表中的选定行来控制应用程序的其他部分。现在我的问题是让哪些行被选中变得显而易见。

通常在表格中选中的行会改变背景颜色,但我没有这个选项,因为我已经设置了背景颜色并且不想改变它。更改选定行的前景颜色(字体颜色)不是最佳选择,因为这不明显且不直观。

现在我正在让选定的行与未选定的行具有不同的不透明度,这在一定程度上起作用但仍然不是最佳的。

一种方法是向选定的行添加一些选中的图标。请注意,我不想要真正的复选框输入,因为这会导致用户单击复选框,而我认为单击行进行选择更容易。

有一些示例可以在 DT 表中显示 html 内容,但这意味着通过行选择动态更改表内容,这对我的应用程序来说是 Not Acceptable ,因为每次表内容更改都会触发表刷新,从而重置行选择并进入一个循环。

我认为应该可以使用 js 更改选定行的 css 类,从而为它们添加选中的图标。我看到了this question这有点相似,但是这个例子对我来说很难理解。

更新:This answer @Stéphane Laurent 准确地解决了我的问题。我之前进行了广泛的搜索,但没有找到。

更新 2:我的用例更复杂,我在采用这种方法时遇到了问题。我需要 2 个控制表,我正在根据单选按钮控件切换它们。通过表格的动态呈现,排除的状态在每次切换时都会重置。以前我用的DT行选择没有这个问题。

看下面的例子,排除表1中的一些行,切换到表2,再切换回来,排除状态恢复。

library(shiny)
library(DT)
# DT checked js ----
rowNames <- FALSE # whether to show row names in the table
colIndex <- as.integer(rowNames)
# making variants since we have two table. not worth a function since only two instances. main changes are function name and shiny input id excludedRows
callback1 <- c(
  sprintf("table.on('click', 'td:nth-child(%d)', function(){", colIndex+1),
  "  var td = this;",
  "  var cell = table.cell(td);",
  "  if(cell.data() === 'ok'){",
  "    cell.data('remove');",
  "  } else {",
  "    cell.data('ok');",
  "  }",
  "  var $row = $(td).closest('tr');",
  "  $row.toggleClass('excluded');",
  "  var excludedRows = [];",
  "  table.$('tr').each(function(i, row){",
  "    if($(this).hasClass('excluded')){",
  "      excludedRows.push(parseInt($(row).attr('id').split('_')[1]));",
  "    }",
  "  });",
  "  Shiny.setInputValue('excludedRows1', excludedRows);",
  "})"
)
callback2 <- c(
  sprintf("table.on('click', 'td:nth-child(%d)', function(){", colIndex+1),
  "  var td = this;",
  "  var cell = table.cell(td);",
  "  if(cell.data() === 'ok'){",
  "    cell.data('remove');",
  "  } else {",
  "    cell.data('ok');",
  "  }",
  "  var $row = $(td).closest('tr');",
  "  $row.toggleClass('excluded');",
  "  var excludedRows = [];",
  "  table.$('tr').each(function(i, row){",
  "    if($(this).hasClass('excluded')){",
  "      excludedRows.push(parseInt($(row).attr('id').split('_')[1]));",
  "    }",
  "  });",
  "  Shiny.setInputValue('excludedRows2', excludedRows);",
  "})"
)
# for select all, not using it now
# restore <- c(
#   "function(e, table, node, config) {",
#   "  table.$('tr').removeClass('excluded').each(function(){",
#   sprintf("    var td = $(this).find('td').eq(%d)[0];", colIndex), 
#   "    var cell = table.cell(td);", 
#   "    cell.data('ok');",
#   "  });",
#   "  Shiny.setInputValue('excludedRows', null);",
#   "}"
# )

render <- c(
  'function(data, type, row, meta){',
  '  if(type === "display"){',
  '    var color = data === "ok" ? "#027eac" : "gray";',
  '    return "<span style=\\\"color:" + color +',
  '           "; font-size:18px\\\"><i class=\\\"glyphicon glyphicon-" +', 
  '           data + "\\\"></i></span>";',
  '  } else {',
  '    return data;',
  '  }',
  '}'
)
# test app ----
ui <- fluidPage(
  tags$head(
    tags$style(HTML(
      ".excluded { color: gray; font-style: italic; }"
    ))
  ),
  fluidRow(
    column(
      6, 
      tags$label("Excluded rows Table 1"),
      verbatimTextOutput("excludedRows1"),
      tags$label("Excluded rows Table 2"),
      verbatimTextOutput("excludedRows2")
    ),
    column(
      6, 
      tags$label("Included rows"),
      verbatimTextOutput("includedRows1"),
      verbatimTextOutput("includedRows2")
    )
  ),
  br(),
  radioButtons("select_table", label = "Select table", choices = c("1", "2"), inline = TRUE),
  uiOutput("control_table_ui")
  # tabBox(tabPanel("1", DTOutput("mytable1")),
  #        tabPanel("2", DTOutput("mytable2")))


)
server <- function(input, output,session) {
    output$control_table_ui <- renderUI({
    if (input$select_table == "1") {
      column(12, offset = 0, DTOutput("mytable1"))
    } else {
      column(12, offset = 0, DTOutput("mytable2"))
    }
  })

  dt <- cbind(On = "ok", mtcars[1:6,], id = paste0("row_",1:6))
    row_colors <- rep(c("red", "blue", "green"), 2)
    names(row_colors) <- dt$id
  output[["mytable1"]] <- renderDT({
    datatable(dt, caption = "table 1",
              rownames = rowNames, extensions = c("Select"), 
              selection = "none", callback = JS(callback1),
              options = list(
                # pageLength = 3,
                sort = FALSE,
                rowId = JS(sprintf("function(data){return data[%d];}", 
                                   ncol(dt)-1+colIndex)), 
                columnDefs = list(
                  list(visible = FALSE, targets = ncol(dt)-1+colIndex),
                  list(className = "dt-center", targets = "_all"),
                  list(className = "notselectable", targets = colIndex),
                  list(targets = colIndex, render = JS(render)) 
                ),
                dom = "t",
                # buttons = list(list(
                #                  extend = "collection",
                #                  text = 'Select All', 
                #                  action = JS(restore)
                #                )
                # ),
                select = list(style = "single", selector = "td:not(.notselectable)")
                # select = list(style = 'os', # set 'os' select style so that ctrl/shift + click in enabled
                #               items = 'row') # items can be cell, row or column
              )
    ) %>% 
      formatStyle("id", target = "row",
                 backgroundColor = styleEqual(dt$id, row_colors))
  }, server = FALSE)
      output[["mytable2"]] <- renderDT({
    datatable(dt, caption = "table 2",
              rownames = rowNames, extensions = c("Select"), 
              selection = "none", callback = JS(callback2),
              options = list(
                # pageLength = 3,
                rowId = JS(sprintf("function(data){return data[%d];}", 
                                   ncol(dt)-1+colIndex)), 
                columnDefs = list(
                  list(visible = FALSE, targets = ncol(dt)-1+colIndex),
                  list(className = "dt-center", targets = "_all"),
                  list(className = "notselectable", targets = colIndex),
                  list(targets = colIndex, render = JS(render)) 
                ),
                dom = "t",
                # buttons = list(list(
                #                  extend = "collection",
                #                  text = 'Select All', 
                #                  action = JS(restore)
                #                )
                # ),
                select = list(style = "single", selector = "td:not(.notselectable)")
              )
    ) %>% 
      formatStyle("id", target = "row",
                 backgroundColor = styleEqual(dt$id, row_colors))
  }, server = FALSE)
    output$excludedRows1 <- renderPrint({
      input[["excludedRows1"]]
    })
    output$excludedRows2 <- renderPrint({
      input[["excludedRows2"]]
    })
    output$includedRows1 <- renderPrint({
      setdiff(1:nrow(dt), input[["excludedRows1"]])
    })

}
shinyApp(ui, server)

更新 3:根据@Stéphane Laurent 的建议,使用 conditionalPanel 解决了问题。虽然它比 renderUI 慢一点,但它可以工作。

最佳答案

感谢@StéphaneLaurent 的回答,这是一个很棒的基于 js 的解决方案,解决了我 95% 的需求。但是我需要一个按钮来清除所有选择并且由于我的 js 技能有限而无法编写那个按钮。我还忘记了重要的 server=FALSE 参数,因此遇到了排序丢失选择的问题。因此,我切换回原来的行选择机制。

我曾经尝试通过行选择来修改表格,但这会触发 react 性事件循环。后来我意识到我只需要改变 View ,而不是底层数据,并且改变 View 是可以通过纯 css 规则实现的。

正在检查 the great example here , more icons 示例可以根据复选框的选择显示不同的图标。通过检查 css 规则,我发现两个图标一直都在那里,只是 css 规则因选择状态而异。

所以我想出了这个方案,它使用了DT中内置的行选择和一些css规则,这样你仍然拥有DT中行选择控制的所有功能而不需要js代码,一切都由css实现.

library(shiny)
library(DT)
library(data.table)
ui <- fluidPage(
    tags$head(
    tags$style(HTML("
.selected .table-icon-yes {
    opacity: 1;
    display: inline-block;
    color: #3c763d;
}
.table-icon-yes {
    opacity: 0;
    display: none;
}
.selected .table-icon-no {
    opacity: 0;
    display: none;
}
.table-icon-no {
    opacity: 1;
    display: inline-block;
    color: #999;
}
    "))
  ),
  DTOutput("table")
)

icon_col <- tagList(span(class = "table-icon-yes", icon("ok", lib = "glyphicon")),
                    span(class = "table-icon-no", icon("remove", lib = "glyphicon")))

server <- function(input, output, session) {
 output$table <- renderDT({
   dt <- data.table(iris)
   dt[, Selected := as.character(icon_col)]
   setcolorder(dt, c(ncol(dt), 1:(ncol(dt) - 1)))
   datatable(dt, escape = FALSE)
 }) 
}

shinyApp(ui = ui, server = server)

关于javascript - 将选中的图标添加到 DT shiny 中的选定行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57007362/

相关文章:

javascript - 有没有办法向 jQuery 添加平滑/缓和过渡效果?

jQuery 悬停只在 div 的左侧工作

rCharts 不会在 Shiny 的应用程序中渲染绘图

css - 滚动条未显示在 Shiny 中

JavaScript 事件未注册

javascript - 带有 native react 的监听器上的 Firebase

javascript - 使用 Jquery 和 cookie.split 从 cookie 中删除一个值

javascript - 在 chrome 中工作的边界框高度在 Jsfiddle 中不起作用

r - 在 R 中使用值作为逻辑运算符

javascript - 如何为表行结果的每个按钮添加事件监听器?