Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.
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) }) }) }
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!
The post appeared first on appsilon.com/blog/.
R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.