glin / reactable

Interactive data tables for R
https://glin.github.io/reactable
Other
613 stars 79 forks source link

Enable Search in all Rows of Nested Table #225

Open JohChrist opened 2 years ago

JohChrist commented 2 years ago

I built a nested table with the great R package reactable and want to search in all rows including the second level that is not visible at first. This is the code:

a <- c("x", "a", 5)
b <- c("x", "b", 3)
c <- c("y", "c", 6)
d <- c("y", "d", 1)
e <- c("x", "e", 8)
f <- c("y", "f", 2)
x <- c("x", "x", 4)
y <- c("y", "y", 7)

df <- rbind(a,b,c,d,e,f,x,y)

cols <- c("group", "product", "value")

colnames(df) <- cols

df <- as.data.frame(df)

df$value <- as.numeric(df$value)

y <- df[df$product == "y" | df$product == "x",]

x <- df[!(df$product == "y" | df$product == "x"),]

library(reactable)

reactable(
  data = y,
  searchable = TRUE,
  columns = list(
    product = colDef(show = FALSE),
    group = colDef(show = FALSE)
  ),
  details = function(index) { 
    nest = x[x$group == y$product[index], ]
    reactable(data = nest,
              columns    = list(
                group   = colDef(show = FALSE),
                product   = colDef(show = FALSE)
              )
    )
  }  
)

How can I solve this?

glin commented 2 years ago

Nested tables are completely independent widgets from the main table, so you'll need some custom code to link them together. You have a few options here:

  1. Use Crosstalk to link the multiple table widgets together. Up until recently, Crosstalk was the only way to achieve this, and you can check out examples/tips from past discussions: https://github.com/glin/reactable/issues/57, https://github.com/glin/reactable/issues/144
  2. Use the new JavaScript API to create a custom search input that searches all tables at the same time. This will probably be simpler to implement than the Crosstalk way. Here's a quick example of it - I added an elementId to every table, and made a text input that calls Reactable.setSearch() on both the main + nested tables. Try searching for "y":
    
    a <- c("x", "a", 5)
    b <- c("x", "b", 3)
    c <- c("y", "c", 6)
    d <- c("y", "d", 1)
    e <- c("x", "e", 8)
    f <- c("y", "f", 2)
    x <- c("x", "x", 4)
    y <- c("y", "y", 7)

df <- rbind(a,b,c,d,e,f,x,y)

cols <- c("group", "product", "value")

colnames(df) <- cols

df <- as.data.frame(df)

df$value <- as.numeric(df$value)

y <- df[df$product == "y" | df$product == "x",]

x <- df[!(df$product == "y" | df$product == "x"),]

library(reactable) library(htmltools)

htmltools::browsable( tagList( tags$input( type = "search", placeholder = "Search...", oninput = " Reactable.setSearch('main-table', this.value) Reactable.setSearch('nested-table-1', this.value) Reactable.setSearch('nested-table-2', this.value) " ), reactable( data = y, columns = list( product = colDef(show = FALSE), group = colDef(show = FALSE) ), details = function(index) { nest = x[x$group == y$product[index], ] reactable(data = nest, columns = list( group = colDef(show = FALSE), product = colDef(show = FALSE) ), elementId = paste0("nested-table-", index) ) }, elementId = "main-table" ) ) )


3. Use a grouped table rather than nested tables, so the nested rows are all part of the same table. This may not be feasible for your data, but it would be the one way to get built-in support for searching nested rows.
dysonsphere-startmail commented 2 years ago

I am working on creating a list of research publications wherein clicking on the row will expand reveal details (full author list and abstract). This package is working great for that so far, but I would like to be able to have a general search for the whole table (authors, titles, abstract), which would show and expand any rows that have the search term in them (main and nested). The current code I have allows only for the main table data to be searched (Year, Corresponding Author, Title, and Link). I can't seem to get this to work with Crosstalk filter, or with Shiny (From the examples in #57 and #144). Crosstalk will not dig into the nested table for details, and I don't seem to have a handle on how to get search inputs to provide output in Shiny. If you have a simple solution to this let me know, otherwise I will keep trying with Shiny, as that seems to be the more promising option.


library(readr)
library(dplyr)
library(reactable)
library(htmltools)

exampleList  <- data.frame(Year                   = c("2021", "2022", "2022", "2019"),
                           'Coressponding Author' = c("Dr X", "Dr Y", "Dr Y", "Dr X"),
                           Title                  = c("Example Title 1", "Example Title 2", "Example Title 3", "Example Title 4"),
                           Journal                = c("Journal of Examples","Journal of Stuff","Journal of Anecdotes","Journal of Examples"),
                           Link                   = c("www.1.com", "www.2.com","www.3.com","www.4.com"))
exampleDetails <- data.frame(Title                = c("Example Title 1", "Example Title 2", "Example Title 3", "Example Title 4"),
                             Authors              = c("Dr A, Dr B, Dr C, and Dr X", "Dr A, Dr B, Dr C, and Dr Y","Dr A, Dr B, Dr C, and Dr Y","Dr A, Dr B, Dr C, and Dr X"),
                           Abstract               = c("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?", "At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.", "Phasellus ac tincidunt lectus. Nunc lacinia egestas ipsum. Morbi auctor ullamcorper felis quis rutrum. Ut ut consequat nunc. Nulla semper odio sit amet ligula consequat, quis pulvinar ipsum scelerisque. Aliquam viverra cursus lorem, cursus porta orci convallis in. Vivamus sit amet vestibulum magna, dapibus vehicula sem. Sed dolor metus, pellentesque vitae iaculis sit amet, pretium eget tortor. Aenean tristique quam ut ipsum auctor, eu ultrices elit vestibulum. Ut a varius neque. Integer posuere interdum aliquam. Donec cursus lorem non tincidunt sagittis. Donec sodales consectetur turpis, sed consectetur orci fermentum non. Suspendisse eu nibh vitae purus auctor semper sit amet eleifend elit. Donec tincidunt ac nibh consequat imperdiet. "))

exampleReact <- reactable(exampleList, details = function(index) {
  exampleDetails <- exampleDetails[exampleDetails$Title == exampleDetails$Title [index],]
  htmltools::div(style = "padding: 16px",
                 reactable(exampleDetails[c(2:3)],
                           onClick = "expand",
                           searchable = FALSE,
                           pagination = FALSE,
                           outlined = TRUE,
                           highlight = TRUE,
                           compact = TRUE,
                           defaultColDef = colDef(show = TRUE,
                                                  align = "left"),
                           columns = list(
                             "Authors"     = colDef(minWidth = 800),
                             "Abstract"    = colDef(minWidth = 800)
                           ))
                 )
},
searchable = TRUE,
defaultSorted = list(`Year` = "desc"),
compact = TRUE,
pagination = FALSE,
highlight = TRUE,
striped = FALSE,
borderless = TRUE,
wrap = TRUE,
fullWidth = FALSE,
onClick = "expand",
defaultColDef = colDef(align = "left",
                       footerStyle = list(fontWeight = "bold")),
columns = list(
  "Year"                 = colDef(minWidth = 100),
  "Corresponding Author" = colDef(minWidth = 300),
  "Title"                = colDef(minWidth = 600),
  "Journal"              = colDef(minWidth = 300),
  "Link"                 = colDef(minWidth = 300)
)
)
glin commented 2 years ago

@dysonsphere-startmail Are your nested tables all single row tables that are just subsets of the main row? And you want the search to apply to either the main row or nested row?

If so, there's a simple solution if you can use the development version of reactable. You can keep those nested detail columns in the main table as hidden columns, but allow them to be searched using colDef(show = FALSE, searchable = TRUE) (searchable was added recently in https://github.com/glin/reactable/issues/217). Then the built-in search input would work on those extra details even though they don't appear in the main table.

Here's a minimal example using MASS::Cars93, where the Type and AirBags columns are hidden in expandable details, but still searchable:


library(reactable)

data <- MASS::Cars93[1:5, c("Manufacturer", "Model", "Price", "MPG.city", "Type", "AirBags")]

reactable(
  data,
  columns = list(
    Type = colDef(show = FALSE, searchable = TRUE),
    AirBags = colDef(show = FALSE, searchable = TRUE)
  ),
  details = function(i) {
    reactable(
      data[i, c("Type", "AirBags")],
      bordered = TRUE,
      width = 400
    )
  },
  searchable = TRUE
)

And also, tables might be overkill for the expandable details if it's just for a single row, so I could also suggest displaying them in a list format. For example, the details in the CRAN Packages demo: https://glin.github.io/reactable/articles/cran-packages/cran-packages.html

dysonsphere-startmail commented 2 years ago

Thanks @glin . Having all the details in the same data frame and using the above code works perfectly. I will look into the CRAN Packages demo later to get more familiar with that approach. I really appreciate your work on this package and the help you give us all! Cheers.