rstudio / DT

R Interface to the jQuery Plug-in DataTables
https://rstudio.github.io/DT/
Other
599 stars 181 forks source link

In Shiny DT the focus is lost when there is lot of columns with ScrollX=TRUE #769

Open philibe opened 4 years ago

philibe commented 4 years ago

Summary

scrollX=TRUE seems bugged, despite of overflow-x: auto works in pure css : the focus of the column is lost and the scroll go back to left

I've not seen this bug immediately, but for my users it's very irritating, it's why I put scrollX=FALSE and .datatables .dataTables_wrapper {overflow-x: auto;}, from the advice of "my" web designer @pelemele01, for my 42 DT::datatable() in 13 pages of my application.

Here is the stackoverflow link of the question with 3 workarounds.

Here is a reprex

library(shiny)
library(DT)
app<-
  shinyApp(
    ui = basicPage(

      fluidPage(
        h3("Home"),
        fluidRow(
          column (12,
                  div(DT::dataTableOutput('outblabla'),
                      style = "font-size:80%;white-space: nowrap;width:1500px")
          )
        )
      )

    ),

    server = function(input, output) {

      blabla <-  reactive({
        test<-data.frame(
          matrix (rep(c(c(999.2,2), 1200), 4000), nrow = 40, ncol = 30)
        )
        colnames(test) <-  paste("aaaa_bbbb_ccccc_ddddd_eeee_fffff", 1:30)

        return( test
        )

      })

      output$outblabla<- DT::renderDataTable(
        DT::datatable(blabla(),
                      style = "bootstrap",   class = "compact", filter='top',
                      selection = c("single"),
                      options = list(
                        bSortClasses = TRUE,iDisplayLength = 10,   width = "100%",
                        scrollX=TRUE,
                        autoWidth = TRUE
                      )
        )
      )
    }
  )
shiny::runApp(app)

xfun::session_info('DT')

R version 3.6.1 (2019-07-05)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 16.04.6 LTS, RStudio 1.2.1322

Locale:
  LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8       
  LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
  LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C              
  LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

Package version:
  assertthat_0.2.1   BH_1.72.0.3        cli_2.0.1          colorspace_1.4.1  
  crayon_1.3.4       crosstalk_1.0.0    digest_0.6.23      DT_0.12.1         
  ellipsis_0.3.0     fansi_0.4.1        farver_2.0.3       fastmap_1.0.1     
  ggplot2_3.2.1      glue_1.3.1         graphics_3.6.1     grDevices_3.6.1   
  grid_3.6.1         gtable_0.3.0       htmltools_0.4.0    htmlwidgets_1.5.1 
  httpuv_1.5.2       jsonlite_1.6.1     labeling_0.3       later_1.0.0       
  lattice_0.20.38    lazyeval_0.2.2     lifecycle_0.1.0    magrittr_1.5      
  MASS_7.3.51.4      Matrix_1.2.17      methods_3.6.1      mgcv_1.8.29       
  mime_0.9           munsell_0.5.0      nlme_3.1.141       pillar_1.4.3      
  pkgconfig_2.0.3    plyr_1.8.5         promises_1.1.0     R6_2.4.1          
  RColorBrewer_1.1.2 Rcpp_1.0.3         reshape2_1.4.3     rlang_0.4.4       
  scales_1.1.0       shiny_1.4.0        sourcetools_0.1.7  splines_3.6.1     
  stats_3.6.1        stringi_1.4.5      stringr_1.4.0      tibble_2.1.3      
  tools_3.6.1        utf8_1.1.4         utils_3.6.1        vctrs_0.2.2       
  viridisLite_0.3.0  withr_2.1.2        xtable_1.8.4       yaml_2.2.1        

By filing an issue to this repo, I promise that

I understand that my issue may be closed if I don't fulfill my promises.

zkhan12 commented 4 years ago

I also have this issue. Setting manual column width on DT using autowidth and columndef options as described in #29 is not possible without setting scrollX = TRUE

hugo-pa commented 4 years ago

For what it's worth, I recently encountered this issue as well and, after a bit of experimentation, discovered that a pretty robust solution can be obtained using the DataTables dom option along with some styling inspired by a comment yihui made on a somewhat similar issue. While the suggestion made by yihui also fixes the issue, the dom option was more appealing for my use case as it restricts the scrollable area to the table itself (as opposed to the table and the table controls).

I have created a reprex demonstrating the solution, based on the original reprex:

suppressPackageStartupMessages({
  library(DT)
  library(shiny)
})

ui <- fluidPage(
  tags$head(
    tags$style("
      .datatables-scroll {
        overflow-x: auto;
        width: 100%;
        max-height: 500px;
      }
      .datatables-scroll .dataTable thead tr th,
      .datatables-scroll thead tr td {
        position: sticky;
        background-color: #FFFFFF;
      }
      .datatables-scroll thead tr th {
        top: 0;
      }
      .datatables-scroll thead tr td {
        top: 2.45em;
      }
    ")
  ),
  h3("Home"),
  fluidRow(
    column(12,
      div(style = "font-size:80%; white-space: nowrap; width:1500px;",
        DTOutput("outblabla")
      )
    )
  )
)

server <- function(input, output, session) {
  blabla <-  reactive({
    test <- data.frame(
      matrix(
        data = rep.int(c(c(999.2,2), 1200), 4000),
        nrow = 40,
        ncol = 30
      )
    )
    colnames(test) <- paste0("aaaa_bbbb_ccccc_ddddd_eeee_fffff_", 1:30)

    test
  })

  output$outblabla<- renderDT(
    datatable(blabla(),
      options = list(
        dom = "<lf<\"datatables-scroll\"t>ipr>",
        pageLength = 50
      ),
      class = "compact",
      rownames = FALSE,
      filter = "top",
      style = "bootstrap",
      selection = "single"
    )
  )
}

shinyApp(ui, server)

Created on 2020-06-05 by the reprex package (v0.3.0)

For the table creation, the line of interest is:

        dom = "<lf<\"datatables-scroll\"t>ipr>",

This takes advantage of the dom option's markup features to create the following DOM structure:

<div>
  { length }
  { filter }
  <div class="datatables-scroll">
    { table }
  </div>
  { info }
  { paging }
  { processing }
</div>

Then, the included CSS sets the overflow-x property to enable scrolling.

In addition, I set the default page length to 50 and specified a max-height for the table container to demonstrate the ability to preserve positioning of the column headers and filter inputs in the presence of vertical scrolling. This is achieved by setting the position property of the th and td elements containing the table headers and filter inputs, respectively, to sticky (however, note that this does not appear to be supported by Internet Explorer).

Hope this helps!

TenshiChiyo commented 3 years ago

Can someone help me with column width? If I use the wonderful colution autoWidth = TRUE, columnDefs = list(list(className = 'dt-center',width = '200px', targets = "_all")) doesn't work anymore and I canot find any solution to change it either individually or for all columns

Thanks in advance

egglesworth commented 1 year ago

@hugo-pa This worked great for me thanks! However I had a button to export the datatable to Excel, which is removed when applying this fix - is it possible to keep this button and still fix the issue with scrolling back?

egglesworth commented 1 year ago

@hugo-pa This worked great for me thanks! However I had a button to export the datatable to Excel, which is removed when applying this fix - is it possible to keep this button and still fix the issue with scrolling back?

Managed to fix this by adding a B to the dom options: dom = "B<lf<\"datatables-scroll\"t>ipr>"

NicholasSievert commented 1 year ago

Thanks so much for the information above, it was incredibly helpful.

The solution provided by @hugo-pa works well but one issue remains that I haven't been able to resolve.

When using the proposed solution the entire table scrolls which causes the column names to be out of view. Is there any way to implement the proposed solution while keeping the column names visible (Freeze or Sticky the column names while scrolling the remainder of the rows)?

I have tested in both Chrome and Edge browsers.

View scrolled to top: Top

View after scrolling down (note row values shown rather than column names) Scroll

CGlemser commented 1 year ago

Reviving this thread, since I struggle with the same issues - are there any plans to include hugo-pa's solution in a DT release (or something else that fixes the issue)? I have the same problems when using scrollX and filters together on wide data sets and his solution seems to fix the problem.

My use case: We have a wrapper function around the DT::datatable function with many of our default settings already set, and would like to fix this issue for all of our user, as well. However I am struggling with including and applying the CSS code from within my function (includeCSS doesn't seem to work from within a function when knitting R Markdowns).

hugo-pa commented 1 year ago

@NicholasSievert the behaviour you observed is caused by an oversight in the reprex I shared in my earlier comment. In particular, when column ordering is enabled, the ruleset for the column headers in the default DataTables styling has higher specificity than the custom ruleset that is added by the application.

Thus, if you modify the following ruleset from the reprex:

      .datatables-scroll .dataTable thead tr th,
      .datatables-scroll thead tr td {
        position: sticky;
        background-color: #FFFFFF;
      }

to include an additional selector for headers with ordering enabled:

      .datatables-scroll .dataTable thead tr th,
      .datatables-scroll .dataTable thead tr th.sorting, /* New selector */
      .datatables-scroll thead tr td {
        position: sticky;
        background-color: #FFFFFF;
      }

then the column names will remain visible.

Reproducing your screenshots in Chrome:

NicholasSievert commented 1 year ago

Thank you Hugo, your updated code resolved the issue I was having. I really appreciate your help!

bathyscapher commented 1 year ago

In case it might be helpful to someone: here is a version of @hugo-pa's code with the first column fixed to the left margin and a footer callback:

suppressPackageStartupMessages({
  library(DT)
  library(shiny)
})

ui <- fluidPage(
  tags$head(
    tags$style("
      .datatables-scroll {
        overflow-x: auto;
        width: 100%;
        max-height: 500px;
      }
      .datatables-scroll .dataTable thead tr th,
      .datatables-scroll .dataTable tfoot tr th,
      .datatables-scroll .dataTable thead tr th.sorting,
      .datatables-scroll thead tr td {
        position: sticky;
        background-color: #FFFFFF;
      }
      .datatables-scroll th:first-of-type {
        z-index: 2 !important;
      }
      .datatables-scroll thead td:first-of-type {
        z-index: 2 !important;
        left: 0;
        position: sticky;
      }
      .datatables-scroll thead tr th {
        top: 0;
      }
      .datatables-scroll thead tr td {
        top: 2.45em;
      }
      .datatables-scroll tfoot tr th {
        bottom: 0;
      }
      .datatables-scroll tfoot tr td {
        bottom: 2.45em;
      }
    "),
  ),
  h3("Home"),
  fluidRow(column(12,
                  div(DTOutput("outblabla"))))
)

server <- function(input, output, session) {
  blabla <- data.frame(matrix(data = sample(1000000, 1200),
                              nrow = 40,
                              ncol = 30))
  colnames(blabla) <- c("FIX", paste0("col--", 1:29))

  sketch <- htmltools::withTags(
    table(tableHeader(c("fix", paste0("col-", as.character(1:29)))),
          tableFooter(c("-", rep("x", 29))))
  )

  output$outblabla <- renderDT(
    datatable(blabla,
              container = sketch,
              rownames = FALSE,
              filter = "top",
              style = "bootstrap",
              selection = "none",
              escape = FALSE,
              plugins = c('natural'),
              extensions = c('FixedColumns', 'FixedHeader'),
              options = list(
                dom = "<lf<\"datatables-scroll\"t>ipr>",
                paging = FALSE,
                autoWidth = TRUE,
                fixedColumns = list(leftColumns = 1),
                scrollCollapse = TRUE,
                footerCallback =  JS(
                  "function( tfoot, data, start, end, display ) {",
                  "var api = this.api();",
                  "$( api.column( 1 ).footer() ).html(",
                  "api.column( 1).data().reduce( function ( a, b ) {",
                  "return a + b;",
                  "} )",
                  ");",
                  "$( api.column( 2 ).footer() ).html(",
                  "api.column( 2 ).data().reduce( function ( a, b ) {",
                  "return a + b;",
                  "} )",
                  ");",
                  "$( api.column( 3 ).footer() ).html(",
                  "api.column( 3 ).data().reduce( function ( a, b ) {",
                  "return a + b;",
                  "} )",
                  ");",
                  "$( api.column( 4 ).footer() ).html(",
                  "api.column( 4 ).data().reduce( function ( a, b ) {",
                  "return a + b;",
                  "} )",
                  ");",
                  "$( api.column( 5 ).footer() ).html(",
                  "api.column( 5 ).data().reduce( function ( a, b ) {",
                  "return a + b;",
                  "} )",
                  ");",
                  "$( api.column( 6 ).footer() ).html(",
                  "api.column( 6 ).data().reduce( function ( a, b ) {",
                  "return a + b;",
                  "} )",
                  ");",
                  "$( api.column( 7 ).footer() ).html(",
                  "api.column( 7 ).data().reduce( function ( a, b ) {",
                  "return a + b;",
                  "} )",
                  ");",
                  "$( api.column( 8 ).footer() ).html(",
                  "api.column( 8 ).data().reduce( function ( a, b ) {",
                  "return a + b;",
                  "} )",
                  ");",
                  "}"),
                pageLength = 50
              )
    )
  )
}

shinyApp(ui, server)
tcash21 commented 7 months ago

I'm having this same issue but no luck in fixing it with these workarounds. Are there plans to implement a fix in the foreseeable future? Thank you so much for all your help and for this package!

UPDATE: The reprex works as expected, but it seems as if the column names and values are of varying length it does not because as soon as I swap in my data the issue persists.