1

This one has stumped me, and although I've looked into DataTables specific solutions (jQuery DataTables - Filter column by exact match), I haven't yet identified how to achieve a JavaScript solution.

I need a literal match for column filters in my DataTable. In the example that follows, that means returning just Mazda RX4 and not Mazda RX4 Wag. Ideally this would apply to any DT search field. Here's an MRE with the formatting I'm using for this specific table:

library(shiny)
library(DT)

ui <- fluidPage(

    fluidRow(
      dataTableOutput("dt_tab")
    )
)

server <- function(input, output) {

  output$dt_tab <- DT::renderDataTable({
    
    mtcars$car <- rownames(mtcars)

    DT::datatable(mtcars,
                  style = "bootstrap",
                  class = "display compact stripe cell-border wrap",
                  selection = "single",
                  callback=DT::JS('$(\'div.has-feedback input[type="search"]\').attr( "placeholder", "Search" )'),
                  rownames = FALSE,
                  filter = 'top',
                  extensions = 'Scroller',
                  options=list(columnDefs = list(list(visible=FALSE, targets=c(0))),
                               search = list(regex = FALSE, caseInsensitive = FALSE),
                               lengthChange = F,
                               scrollX = T,
                               scrollY = '60vh',
                               scroller = T,
                               paging = T,
                               initComplete  = JS(sprintf('function() {
                                   this.api().table().scroller.toPosition(%s);
                                   }', 0)),
                               dom = '<"top"if>rt<"bottom"lp><"clear">',
                               deferRender = TRUE,
                               language = list(searchPlaceholder = "across all columns")
                  )
    )
    
  }, server = TRUE)
  
}

shinyApp(ui = ui, server = server)

I know that a similar solution was posted here: Search Exact Match R datatable, but I can't seem to manage the right JavaScript.

Thanks for any help you can provide.

Stéphane Laurent
  • 75,186
  • 15
  • 119
  • 225

1 Answers1

3

Is it ok with the search builder?

library(DT)

dat <- data.frame(
  car = c("Mazda", "Mazda RX4", "Mazda RX4 Wag", "Ford", "Mercedes"),
  day = Sys.Date() + 0:4
)

datatable(
  dat,
  extensions = c("SearchBuilder", "DateTime"),
  options = list(
    dom = "Qlfrtip",
    searchBuilder = TRUE
  )
)

enter image description here

Note: usage in Shiny

In order to use the search builder in a Shiny app, you have to set the option server = FALSE in the renderDT function. Server-side searching is not supported.


EDIT

Here is a way without the search builder. It has an inconvenient: you have to specify the index of the column in which you want to search. That's because DataTables supports exact regex search only on a column basis.

library(DT)

dat <- data.frame(
  car = c("Mazda", "Mazda RX4", "Mazda RX4 Wag", "Ford", "Mercedes"),
  day = Sys.Date() + 0:4
)

js <- c(
  "function(settings){",
  "  var instance = settings.oInstance;",
  "  var table = instance.api();",
  "  var input = instance.parent().find('.dataTables_filter input');",
  "  input.off('keyup search input').on('keyup', function(){",
  "    var keyword = '^' + input.val() + '$';",
  "    // we search in column 1",
  "    table.column(1).search(keyword, true, false).draw();",
  "  });",
  "}"
)

datatable(
  dat,
  options = list(initComplete = JS(js))
)

EDIT

Finally, here is how to search an exact match in the column-wise filter boxes:

library(DT)

dat <- data.frame(
  car = c("Mazda", "Mazda RX4", "Mazda RX4 Wag", "Ford", "Mercedes"),
  pet = c("dog", "dog", "cat", "cat", "cat"),
  day = Sys.Date() + 0:4
)

js <- c(
  "function(settings){",
  "  var instance = settings.oInstance;",
  "  var table = instance.api();",
  "  var $inputs = instance.parent().find('.form-group input');",
  "  $inputs.off('keyup search input').on('keyup', function(){",
  "    var keyword = '^' + $(this).val() + '$';",
  "    var index = 1 + $inputs.index(this);", # add 1 for the rownames column
  "    table.column(index).search(keyword, true, false).draw();",
  "  });",
  "}"
)

datatable(
  dat, filter = "top", 
  options = list(initComplete = JS(js))
)

enter image description here


EDIT

If you delete the search with the previous solution, then it is not cancelled, you have to click the cross and this is not convenient. Here is a solution which doesn't have this inconvenient:

js <- c(
  "function(settings) {",
  "  var instance = settings.oInstance;",
  "  var table = instance.api();",
  "  var $inputs = instance.parent().find('.form-group input');",
  "  $inputs.off('keyup search input').on('keyup', function() {",
  "    var value = $(this).val();",
  "    if(value !== '') {",
  "      var keyword = '^' + value + '$';",
  "      var index = 1 + $inputs.index(this);", # add one if row names
  "      var column = table.column(index);",
  "      column.search(keyword, true, false).draw();",
  "    }",
  "  });",
  "}"
)
Stéphane Laurent
  • 75,186
  • 15
  • 119
  • 225
  • The search builder is a great extension I was not aware of! I will certainly use it moving forward. But for this project's purposes I do need a literal string match in the top search filter. – Alan Farahani Sep 17 '22 at 20:02
  • @AlanFarahani I have a way which *should* work but it doesn't. I've just opened [an issue here](https://github.com/rstudio/DT/issues/1019). – Stéphane Laurent Sep 17 '22 at 20:10
  • And strangely enough, the search builder doesn't work for me in the way that you are showing. I can provide an MRE. It doesn't show the dropdown menu nor does it filter the table. – Alan Farahani Sep 17 '22 at 20:11
  • @AlanFarahani Strange. Are you using an old version of DT? – Stéphane Laurent Sep 17 '22 at 20:15
  • @ Stéphane Laurent -- interesting! I'll look forward to see what the solution is. Otherwise I still can't the search builder to work with Shiny -- either in the R Studio window or in a browser. I may open a separate post for that. – Alan Farahani Sep 17 '22 at 20:18
  • I'm using shiny 1.7.2 and DT 0.25 on R 4.2.1 patched – Alan Farahani Sep 17 '22 at 20:22
  • @AlanFarahani It seems that the method I showed in the Github issue works if one searches in a given column, but not the global search. You can try [here](https://datatables.net/examples/api/regex.html). Search e.g. ***Satou$*** (with regex on and smart off). It does not work globally, but it works if you search in the first column. – Stéphane Laurent Sep 17 '22 at 20:24
  • @AlanFarahani See my edit. And outside shiny, does it work? – Stéphane Laurent Sep 17 '22 at 20:36
  • Search builder works in R Markdown, but not in Shiny. Is that worthy of opening a bug ticket in DT? *** And the solution you provided works -- but it's still reliant on the global search bar, rather than using filter = 'top'. In my case I need literal string matches (i.e. ^+val+$) in the filters beneath the column. *** Would it be possible to target the column using something like "this.api().table().column(1).search"? – Alan Farahani Sep 17 '22 at 20:51
  • @AlanFarahani I opened a new question: . – Stéphane Laurent Sep 17 '22 at 20:54
  • Ah you want to search with the DT filters at top of the columns. I don't know... – Stéphane Laurent Sep 17 '22 at 20:55
  • @AlanFarahani I see: in **Shiny**, you have to set `server=FALSE` in `renderDT` in order for the search builder to work. – Stéphane Laurent Sep 17 '22 at 20:56
  • I found the solution. Using your code, if we change the instance.parent().find to instance.parent().find('.form-group input');", that targets the filters underneath each column. Would you mind editing your answer to reflect that? Thanks for all of your help. – Alan Farahani Sep 17 '22 at 21:00
  • @AlanFarahani Let me try and I edit. – Stéphane Laurent Sep 17 '22 at 21:01
  • @AlanFarahani But what do you write instead of `table.column(1)`? – Stéphane Laurent Sep 17 '22 at 21:02
  • I changed nothing in your original code *except* for this line: " var input = instance.parent().find('.form-group input');", Everything else is the same. However I notice if you search in the bar, then delete the search, the table shows no rows! – Alan Farahani Sep 17 '22 at 21:06
  • The regex approach which you expect to work (using start and end anchors) - I think that applies across the entire data in the row, not field-by-field. I suppose perhaps that is an optimization in how DataTables implements its global search. – andrewJames Sep 17 '22 at 21:08
  • @AlanFarahani Instead of deleting the search, you have to click the cross to cancel it. – Stéphane Laurent Sep 17 '22 at 21:16
  • @AlanFarahani Yes but this works only in the first column. I found the final solution, see my edit. – Stéphane Laurent Sep 17 '22 at 21:24
  • Amazing, I appreciate your heroic efforts! Just a note that the "cross" to cancel the search will not show up if server = T (i.e. it must = F) in *shiny*. And I suppose one way to incorporate backspace or delete is to add a conditional in the JS to not search if $(this).val() = "" or something along those lines. That's for a later time. Cheers. – Alan Farahani Sep 18 '22 at 00:06
  • @AlanFarahani I edited again. With this code, the search is cancelled when you delete it. – Stéphane Laurent Sep 18 '22 at 07:44
  • @AlanFarahani Could you please accept the answer? Otherwise your question is considered as non-answered. – Stéphane Laurent Sep 18 '22 at 07:45
  • It should have been accepted already. – Alan Farahani Sep 19 '22 at 04:10
  • And one last final note (in a very long comments section) -- if you are using shiny, you can not use this javascript with *any* kind of scrollX=T, scroller = T, or rownames = F. It works fine with pagination, though. – Alan Farahani Sep 19 '22 at 04:17
  • @AlanFarahani It should work with `rownames=F`, but you have to remove the `1+` in the js code. – Stéphane Laurent Sep 19 '22 at 07:45