shiny仪表板上设置带建议的搜索栏

319 阅读2分钟

搜索栏是任何流行的网站或应用程序中的一个常见功能。大多数网站在用户开始在搜索栏中输入信息时,就会实时显示建议。它极大地改善了用户体验,用户能够在网站上找到他/她想看到的内容。最近,在shiny仪表板上有这种类似的功能已经成为普遍的要求。在shiny中,搜索栏有很多变通方法,但没有直接的方法来实现它。

搜索栏(单选)

如果你希望用户不选择一个以上的值。这是为了防止从下拉菜单中进行多重选择。你可以使用multiple = FALSE 来选择单个项目。选项maxItems = '1' 移除默认显示在下拉菜单中的下拉图标。为了使selectizeInput 表现得像一个搜索栏,我们使用了onDropdownOpenonType 选项中提到的javascript。

choices 参数中,我们使用了两个随机字母的连接。例如 "AB"。因为它是随机的,所以`AB`不一定存在于下拉菜单中:-(你可以通过退格键删除选择的值。


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)

搜索栏(多选)

在本节中,我们将展示如何从下拉菜单中选择一个以上的值。为了使多个部分工作,你需要设置multiple = TRUE ,以及在onItemAdd 选项中提到的JavaScript。


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)

在搜索栏中添加图标

如果你想添加搜索图标来改善搜索框的外观,你可以通过CSS样式来实现。我们使用的是Glyphicons图标,这是bootstrap自带的,所以你不需要为图标添加额外的库。


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)

在搜索栏中添加图片

要添加自定义图片,你可以将上一节的CSS改为下面的CSS。



.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;
 }

使用Shiny Widgets的搜索栏

shinyWidgets 是一个为shiny实现的小工具的伟大包。它提供了几个用于交互的控件。它有 widget的搜索栏,但它不允许在用户输入文本时提供建议。我们可以定制 widget,但它有一个限制,即不允许通过退格删除文本,但它在下拉菜单的顶部显示 按钮来清除选择。searchInput() pickerInput Clear Text

与上述selectizeInput 相比,这个实现有点混乱。它需要通过javascript和CSS进行修改。`live-search` = TRUE ,允许在下拉框中进行实时建议。


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)

带有大型选项列表的搜索栏

当你有大量的值时,选择会很慢。在下面的例子中,我们使用的是dqshiny 库,它非常有效。


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)