Shiny : Search Bar with Suggestions

Deepanshu Bhalla 1 Comment , ,
Search Bar is a common feature available in any popular website or app. Most of the websites show suggestions in real time as soon as user starts typing in the search bar. It improves user experience significantly and user is able to find the content he/she wants to see in the website. Recently it's become common ask to have this similar functionality in shiny dashboard. In shiny there are many workarounds available for search bar but no straightforward method to implement it.
Search Bar (Single Selection)
Incase you want user not to select more than one value. It is to prevent multiple selection from dropdown. You can use multiple = FALSE for selection of single item. Option maxItems = '1' removes dropdown icon which is by default shown in the dropdown. To make selectizeInput behave like a search bar, we have used javascript mentioned in onDropdownOpen and onType options.
search bar with live suggestions in shiny
In the choices argument we have used concatenation of two random letters of alphabet. For example `AB`. Since it is random, it's not necessary `AB` exists in the dropdown :-( You can remove the selected value via backspace.

library(shiny)

ui <- fluidPage(
  title = "Search Bar",
  fluidRow(
    selectizeInput(
      inputId = "searchme", 
      label = "Search Bar",
      multiple = FALSE,
      choices = c("Search Bar" = "", paste0(LETTERS,sample(LETTERS, 26))),
      options = list(
        create = FALSE,
        placeholder = "Search Me",
        maxItems = '1',
        onDropdownOpen = I("function($dropdown) {if (!this.lastQuery.length) {this.close(); this.settings.openOnFocus = false;}}"),
        onType = I("function (str) {if (str === \"\") {this.close();}}")
      )
    ))
)

server <- function(input, output, session) {
  
  # Show Selected Value in Console
  observe({
    print(input$searchme)
  })
  
}

shinyApp(ui, server)

Search Bar (Multiple Selection)
In this section we are showing how to select more than one value from dropdown. To make multiple section working, you need to set multiple = TRUE along with the javascript mentioned in onItemAdd option.

library(shiny)

ui <- fluidPage(
  title = "Search Bar",
  fluidRow(
    selectizeInput(
      inputId = "searchme", 
      label = "Search Bar",
      multiple = TRUE,
      choices = paste0(LETTERS,sample(LETTERS, 26)),
      options = list(
        create = FALSE,
        placeholder = "Search Me",
        onDropdownOpen = I("function($dropdown) {if (!this.lastQuery.length) {this.close(); this.settings.openOnFocus = false;}}"),
        onType = I("function (str) {if (str === \"\") {this.close();}}"),
        onItemAdd = I("function() {this.close();}")
      )
    ))
)

server <- function(input, output, session) {
  
  # Show Selected Value in Console
  observe({
    print(input$searchme)
  })
  
}

shinyApp(ui, server)

Add icon in Search Bar
If you want to add search icon to improve appearence of the search box, you can accomplish it via CSS styling. We are using Glyphicons icons which comes with bootstrap so you don't need to add additional library for icons.

library(shiny)

ui <- fluidPage(
  title = "Search Bar",
  fluidRow(
    tags$style(HTML("
    
.selectize-input.items.not-full.has-options:before {
 content: '\\e003';
 font-family: \"Glyphicons Halflings\";
 line-height: 2;
 display: block;
 position: absolute;
 top: 0;
 left: 0;
 padding: 0 4px;
 font-weight:900;
 }
  
  .selectize-input.items.not-full.has-options {
    padding-left: 24px;
  }
 
 .selectize-input.items.not-full.has-options.has-items {
    padding-left: 0px;
 }
 
  .selectize-input.items.not-full.has-options .item:first-child {
      margin-left: 20px;
 }
 
")),
    selectizeInput(
      inputId = "searchme", 
      label = "Search Bar",
      multiple = T,
      choices = c("SBI Cap", "SBI Technology",  "Kotak Technology"),
      options = list(
        create = FALSE,
        placeholder = "Search Me",
        onDropdownOpen = I("function($dropdown) {if (!this.lastQuery.length) {this.close(); this.settings.openOnFocus = false;}}"),
        onType = I("function (str) {if (str === \"\") {this.close();}}"),
        onItemAdd = I("function() {this.close();}")
      )
    ))
)

server <- function(input, output, session) {
  
  # Show Selected Value in Console
  observe({
    print(input$searchme)
  })
  
}

shinyApp(ui, server)

Add image in Search Bar
To add custom image, you can change CSS from the previous section to the CSS shown below.


.selectize-input.items.not-full.has-options:before {
content:'';
background: url('https://cdn.iconscout.com/icon/free/png-256/google-1772223-1507807.png'); /*url of image*/
height: 16px; /*height of image*/
width: 16px;  /*width of image*/
position: absolute;
 display: block;
 position: absolute;
 left: 0;
 background-size: 16px 16px;
 background-repeat: no-repeat;
 margin-left: 3px;
}

.selectize-input.dropdown-active:before {
    top: 0;
    margin-top: 6px;
 }
  
  .selectize-input.items.not-full.has-options {
    padding-left: 24px;
  }
 
 .selectize-input.items.not-full.has-options.has-items {
    padding-left: 0px;
 }
 
  .selectize-input.items.not-full.has-options .item:first-child {
      margin-left: 20px;
 }


Search Bar using Shiny Widgets
shinyWidgets is a great package for widgets implemented for shiny. It offers several controls for interactivity. It has searchInput() widget for search bar but that does not allow suggestions as user enters text. We can customise pickerInput widget but it has limitation of not allowing to remove text via backspace but it shows Clear Text button on top of dropdown to clear selection.

This implementation is a bit messy as compared to above selectizeInput. It requires changes via javascript and CSS. `live-search` = TRUE allows live suggestion in dropdown box.


library(shiny)
library(shinyWidgets)

ui <- fluidPage(
  title = "Search Bar",
  fluidRow(
    tags$script(HTML(
      "var CONTROL_INTERVAL = setInterval(function(){ 
          if($('#searchme').length > 0 ){
                $('#searchme').on('show.bs.select', function(){
                    var input = $('.bootstrap-select:has(select[id=\"searchme\"]) .bs-searchbox > input');
                    var opts = $(this).parent().find('div[role=\"listbox\"] ul li');
                    var opts0 = $(this).parent().find('div[role=\"listbox\"]');
                    opts.hide();
                    opts0.hide();
                    
                    input.on('input', function(e){
                        if ($(this).val() !== \"\") {opts.show(); opts0.show();}
                        else {opts.hide(); opts0.hide();}
                    });
                });
                
  clearInterval(CONTROL_INTERVAL);
  }}, 200);
    "
    )),
    
    tags$style(HTML(".bs-select-all {display: none;}
        .bs-deselect-all {width: 100% !important;}")),
    
    pickerInput(
      inputId = "searchme",
      label = "Search Bar",
      choices = paste0(LETTERS,sample(LETTERS, 26)),
      multiple = TRUE,
      options = pickerOptions(title = "Search Me",
                     `live-search` = TRUE,
                     actionsBox = TRUE,
                     deselectAllText = "Clear Search")
    )
))

server <- function(input, output, session) {
  
  # Show Selected Value in Console
  observe({
    print(input$searchme)
  })
  
}

shinyApp(ui, server)

Search Bar with large option lists
selectize is slow when you have large number of values. In the example below we are using dqshiny library which is very efficient.

library(shiny)
library(dqshiny)

# create 100K random words
opts <- sapply(1:100000, function(i) paste0(sample(letters, 7), collapse=""))

ui <- fluidPage(
  fontawesome::fa_html_dependency(),
  
  tags$style("input{font-family:'Font Awesome\ 5 Free';font-weight: 900;}
  
input:placeholder-shown#auto2{
  background-image: url('https://cdn.iconscout.com/icon/free/png-256/google-1772223-1507807.png');
  text-indent: 20px;
  background-size: 16px 16px;
  background-repeat: no-repeat;
  background-position: 8px 8px;
}

input#auto2:focus{ background-image:none; text-indent: 0px;}"),
  
  fluidRow(
    column(3,
           autocomplete_input("auto1", "Unnamed:", opts, 
                              placeholder = " Search Value",
                              max_options = 1000),
           autocomplete_input("auto2", "Named:", 
                              placeholder = "Search Value",
                              max_options = 1000,
                              structure(opts, names = opts[order(opts)]))
    ), column(3,
              tags$label("Value:"), verbatimTextOutput("val1", placeholder = TRUE),
              tags$label("Value:"), verbatimTextOutput("val2", placeholder = TRUE)
    )
  )
)


server <-  function(input, output) {
  output$val1 <- renderText(as.character(input$auto1))
  output$val2 <- renderText(as.character(input$auto2))
}


shinyApp(ui, server)

Related Posts
Spread the Word!
Share
About Author:
Deepanshu Bhalla

Deepanshu founded ListenData with a simple objective - Make analytics easy to understand and follow. He has over 10 years of experience in data science. During his tenure, he worked with global clients in various domains like Banking, Insurance, Private Equity, Telecom and HR.

1 Response to "Shiny : Search Bar with Suggestions"
Next → ← Prev