Join the Shiny Community every month at Shiny Gatherings

Introducing Autocomplete Search to shiny.fluent’s Dropdown Component


The introduction of shiny.react has been a significant milestone in R Shiny’s evolution, acting as a bridge to React.js and enabling the incorporation of React components within Shiny applications. shiny.fluent, leveraging shiny.react, integrates Microsoft’s Fluent UI components into Shiny, broadening the scope for UI design and functionality.

In a recent one-hour hackathon, we set out to explore the potential of shiny.react and shiny.fluent in delivering a customized user interface of a Shiny application. Our primary focus was integrating an autocomplete search feature into the Dropdown element of shiny.fluent.

In this article, we will explore the outcome of our hackathon and how you can implement it in your projects.

Table of Contents


The Challenge

The Dropdown component, a part of Fluent UI exposed by the shiny.fluent package, stands out for its functionality and reliability. Yet, when users encounter long lists of options, navigating through them can be less than optimal.

To enhance this experience, we aimed to integrate an autocomplete search box within the Dropdown.This feature is designed to streamline the process, allowing users to quickly search and filter through the extensive list of options.

It’s noteworthy that shiny.fluent also exposes Fluent UI’s ComboBox, a component with built-in autocomplete functionality. However, our focus was different; we wanted to specifically enhance the Dropdown component by adding a search box that not only allows for searching but also filters the available options in real-time.

Interested in enhancing the user experience of your Shiny apps while optimizing handling of large datasets? Explore how our latest reactable.extras release can help you.

Leveraging shiny.react for Seamless Integration

Our breakthrough came when we discovered a React code snippet that perfectly suited our needs. This code provided a foundation for adding an autocomplete search functionality to the dropdown menu. The key task was adapting this React code to work seamlessly within our Shiny application.

Here’s how we started by creating a basic Dropdown with a variety of fruit and vegetable options:


box::use(
shiny[div, moduleServer, NS, observeEvent],
shiny.fluent[JS, fluentPage, Dropdown.shinyInput],
)

#' @export
ui <- function(id) {
ns <- NS(id)
fluentPage(
  div(
    style = "height: 100%; width: 50%; margin:auto",
    Dropdown.shinyInput(
      inputId = ns("fruit"),
      label = "Searchable Fruit Selector",
      multiSelect = TRUE,
      placeholder = "Fruit/Vegetable",
      options = list(
        list(key = "apple", text = "Apple"),
        list(key = "banana", text = "Banana"),
        list(key = "orange", text = "Orange"),
        list(key = "grape", text = "Grape"),
        list(key = "broccoli", text = "Broccoli"),
        list(key = "carrot", text = "Carrot"),
        list(key = "lettuce", text = "Lettuce")
       )
     )
   )
 )
}

#' @export
server <- function(id) {
moduleServer(id, function(input, output, session) {
  observeEvent(input$fruit, {
    print(input$fruit)
  })
})
}

Searchable Fruit Selector

We then added a filtering header using the SearchBox in the options list:


DropdownMenuItemType <- function(type) { # nolint
 JS(paste0("jsmodule['@fluentui/react'].DropdownMenuItemType."), type)
}
// Add __FilterHeader__ to Dropdown’s options
ui <- function(id) {
 ns <- NS(id)
 fluentPage(
   div(
     style = "height: 100%; width: 50%; margin:auto",
     Dropdown.shinyInput(
       ...,
       options = list(
         list(key = "__FilterHeader__", text = "-", itemType = DropdownMenuItemType("Header")),
        ...
        )
      )
    )
  )
}

Finally, thanks to shiny.fluent exposing Fluent UI’s properties, we could customize our Dropdown with the onRenderOption prop.

This allowed us to tailor a custom renderer for the dropdown options:


render_search_box <- JS(paste("(option) => {
  // if option is not the header, simply return option label
  if (option.key !== '__FilterHeader__') {
    return option.text;
  }

  // handle onChange event
  const onChange = (event, newValue) => {
    // get the typed key in lowercase
    const query = newValue.toLocaleLowerCase();

    // get the checkbox labels
    const checkboxLabels = document.querySelectorAll(
      'div.ms-Checkbox .ms-Checkbox-label'
    );

    // filter options according to search
    checkboxLabels.forEach(label => {
      const text = label.innerText.replace('\\n', '').replace('', '').toLocaleLowerCase();

      // if the label of the option does not start with the typed search, we hide it
      if (query === '' || text.startsWith(query)) {
        label.parentElement.style.display = 'flex';
      } else {
        label.parentElement.style.display = 'none';
      }
    });
  };

  // finally, create the react element
  const props = { placeholder: 'Start typing', underlined: true, onChange };
  const element = React.createElement(jsmodule['@fluentui/react'].SearchBox, props)
  return element;
}"))

// Add onRenderOption prop
ui <- function(id) {
 ns <- NS(id)
 fluentPage(
   div(
     style = "height: 100%; width: 50%; margin:auto",
     Dropdown.shinyInput(
       ...,
       onRenderOption = render_search_box
      )
    )
  )
}

If you’re keen on exploring the project’s implementation in detail, you can find the complete code available in the Gist below:

Conclusion

As our one-hour hackathon concludes, we’ve successfully integrated an autocomplete search feature into the shiny.fluent Dropdown component, demonstrating a practical application of shiny.react and shiny.fluent in enhancing Shiny UIs.

Looking ahead, we see opportunities to refine this feature, especially in terms of accessibility and user experience. For instance, improving keyboard interactions, such as enabling more intuitive navigation and selection within the dropdown, could be a valuable enhancement.

Did you find this helpful? Leave us a comment and join us for R Shiny Trivia Night at our next Shiny Gathering! Test your knowledge, meet fellow enthusiasts, and enjoy a night of fun and networking. Secure your spot for this exciting trivia event today!