How can I use the searchPanes extension and server-side processing in my R Shiny app? - shiny

I've been able to create a working Shiny app with the datatables searchPanes extension. The problem is that my table is huge, which makes searchPanes go too slow; plus I have the cascadePanes option turned on, which slows down my table row filtering even more. Currently the data processing is done on the client side but I have read that it is more efficient on the server side, therefore I would like to change the configuration of my app to work on the server side.
This link on the datatables website explains how to configure searchPanes with server processing, but I don't know how to apply this information to my shiny application.
I attach a minimal example of my application with the iris data:
library(shiny)
library(DT)
ui <- fluidPage(
DTOutput("table")
)
server <- function(input, output) {
output$table <- renderDT({
datatable(
iris,
class = 'white-space: nowrap cell-border',
extensions = c("SearchPanes", "Select", "Buttons", "Scroller"),
selection = "none", rownames = F, filter = "none",
options = list(
select = "multiple",
scrollY = "497px", scrollCollapse = TRUE, searching = TRUE,
scrollX = FALSE, autoWidth = FALSE,
info = TRUE,
paging = TRUE,
dom = "Brtip",
buttons = list(
# searchPanes button
list(
extend="searchPanes",
config = list(cascadePanes = T,combiner="and")
),
# clear filters button
list(
extend="searchPanes",
text="clear filters",
action=JS(
"function(e, table, node, config){",
" table.searchPanes.clearSelections();",
"}"
)
),
# select all rows
list(
extend='selectAll',
className='selectAll',
text="select all",
action = JS(
"function(e, table, node, config) {",
" table.rows({ search: 'applied'}).deselect();",
" table.rows({ search: 'applied'}).select();",
"}"
)
),
# deselect all rows
"selectNone",
# page length button
"pageLength"
),
language = list(
# show number of active filters in button text
searchPanes = list(collapse = paste("Filter rows: ",'Filters Selected - %d'))
)
)
)
},server = F)
}
shinyApp(ui, server)
I have read this post where the user Kraggle uses the function dataTableProxy() from the DT package, but I don't know how to capture the button input from searchpanes since I have defined it inside a custom button.

Related

data table uiOutput disappears on redraw

I am displaying widgets within a DT datatables in an R Shiny app.
Following a similar approach to here, widgets are inserted as text into the data table. Then using the option escape = FALSE, the text of the widgets is interpretated as HTML and the widget is displayed.
This approach has worked very well, until I came to redraw the datatable. On redraw the widgets within the table no longer appear. You can test with the following example. When "Redraw" is clicked, the UI output showing the text "here" disappears.
# UI
ui = fluidPage(
actionButton("draw", "Redraw"),
# uiOutput("ui"),
DT::dataTableOutput("classify_table")
)
# server
server = function(input, output, session) {
output$ui = renderUI({ p("here") })
output$classify_table <- DT::renderDataTable({
df = data.frame(
rows = input$draw,
UI = as.character(uiOutput("ui")),
stringsAsFactors = FALSE
)
DT::datatable(
df,
escape = FALSE,
options = list(
dom = "t",
preDrawCallback = DT::JS('function() { Shiny.unbindAll(this.api().table().node()); }'),
drawCallback = DT::JS('function() { Shiny.bindAll(this.api().table().node()); } ')
)
)
})
}
# run
shinyApp(ui, server)
My best hypothesis is that Shiny ends up with two uiOutput("ui") bound. This would mean it does not display at the uiOutput("ui") in the redrawn table.
Supporting this hypothesis: if we uncomment uiOutput("ui") from line four, then the text "here" never appears in the data table, just like after redrawing.
Contrary evidence: the whole point of the callbacks is to unregister the previous uiOutput("ui") and to reregister the new uiOutput("ui") which should prevent this cause.
Any idea what is causing this behavior and how to fix it? Even suggestions for how to better debug this behavior would be helpful.
Another possible solution is to bind the UI component to shiny directly, instead of in the callback. But I don't know how to determine the JavaScript for this.
Based on the comment from #Stéphane Laurent, I found this answer by them (search term user:1100107 [dt] unbind).
I inserted this code into the UI
tags$script(HTML(
"Shiny.addCustomMessageHandler('unbindDT', function(id) {
var $table = $('#'+id).find('table');
if($table.length > 0){
Shiny.unbindAll($table.DataTable().table().node());
}
})"
))
and this code at the start of the DT::renderDataTable({:
session$sendCustomMessage("unbindDT", "classify_table")
Note that if working in a module, we need to wrap with ns as so:
session$sendCustomMessage("unbindDT", ns("classify_table"))

How to listen to checkbox event that has been insert into ui using `insertUI` in Shiny-R

I am trying to build a simple To do list app using Shiny, the application is very simple, there's a textInput where I put things I want to do, and submit it, and it creates a checkbox. What I want to achieve is, if I check the box, there will be a text on the right side that says: "you have done XXX", the XXX is the information of the checkbox.
So far, I have managed to figure out how to insert a checkbox back into the UI however, I have problems in: writing the party of the code that once the checkbox is checked, generate a text which says " you have done XXX"
The two main difficulties is : 1. listen to the inserted UI (each checkbox needs a special id, but I can't write logic in the server components that has indeterministic checkbox id. Also, I can figure out a way to extract the content of the checkbox from the server side, the input$checkbox only gives me true or false value.
This is the code I am working on so far, I am wondering if this functionality is achievable using Shiny-R?
ui <- fluidPage(
theme = bs_theme(version = 4, bootswatch = "sketchy"),
titlePanel("A simple todo App"),
sidebarLayout(
sidebarPanel(
# Date configuration
dateInput("date", label = "Date Configuration"),
# Things to do
h3("Daily Todo", id="start", style = "color:grey", align = "center"),
checkboxInput("checkbox","Submit One Job Application"),
textInput("todo", label = "Other things to do"),
actionButton("todoBnt", label = "Submit"),
br(),
br(),
textInput("learnt", label = "Key things learnt"),
actionButton("learntBnt", label = "Submit")),
mainPanel(
h1(textOutput("timing"), align = "center"),
h1("What have I done",align = "center"),
verbatimTextOutput("value"),
h1("What have I learnt", align = "center"),
h2(textOutput("selected_var"),align = "center"),
p(textOutput("summary"),align="center"))
)
)
server <- function(input, output) {
inserted <- c()
observeEvent(input$todoBnt, {
insertUI(
selector = "#start",
where = "afterEnd",
ui = checkboxInput("chekcbox",input$todo)
)
})
output$timing <- renderText({
paste0("Today is ", as.character(input$date))
})
output$value <- renderText({ input$checkbox }) ##this gives "TRUE" value, I don't think it's right.
output$summary <- renderText({
paste0("I have learnt to ", input$learnt, "!")
})
}
shinyApp(ui = ui, server = server)
I have tried to search answer online, but I think the checkbox in Shiny-R is mainly used to filter graphs etc... So I am not sure if the function I want is actually achievable using the langague. Please help!
Instead of inserting each checkbox separately one option would be to switch to a checkboxGroupInput which you could update in your observeEvent. Doing so makes it easy to get a list of things you have done. However, doing so requires to track the things to do for which I use a reactiveVal choices which gets updated each time a new item is added to the to-do list:
library(shiny)
library(bslib)
ui <- fluidPage(
theme = bs_theme(version = 4, bootswatch = "sketchy"),
titlePanel("A simple todo App"),
sidebarLayout(
sidebarPanel(
# Date configuration
dateInput("date", label = "Date Configuration"),
# Things to do
h3("Daily Todo", id="start", style = "color:grey", align = "center"),
checkboxGroupInput("checkbox", label = "", choices = "Submit One Job Application"),
textInput("todo", label = "Other things to do"),
actionButton("todoBnt", label = "Submit"),
br(),
br(),
textInput("learnt", label = "Key things learnt"),
actionButton("learntBnt", label = "Submit")),
mainPanel(
h1(textOutput("timing"), align = "center"),
h1("What have I done",align = "center"),
verbatimTextOutput("value"),
h1("What have I learnt", align = "center"),
h2(textOutput("selected_var"),align = "center"),
p(textOutput("summary"),align="center"))
)
)
server <- function(input, output) {
choices <- reactiveVal("Submit One Job Application")
inserted <- c()
observeEvent(input$todoBnt, {
selected <- input$checkbox
choices(c(choices(), input$todo))
updateCheckboxGroupInput(inputId = "checkbox", choices = choices(), selected = selected)
updateTextInput(inputId = "todo", value = "")
})
output$timing <- renderText({
paste0("Today is ", as.character(input$date))
})
output$value <- renderText({
paste(input$checkbox, collapse = "\n")
})
output$summary <- renderText({
paste0("I have learnt to ", input$learnt, "!")
})
}
shinyApp(ui = ui, server = server)

Reload a page and switch to tab

I use the package shinyjs to allow the user to reload the page by clicking on "Reload the page" button in Tab 2 and I would like to stay on Tab 2 after reloading the page. But after realoading, the page is taking to Tab1 instead of Tab2
How can we fix it ? This is my code below :
library(shiny)
library(shinyjs)
jscode <- "shinyjs.refresh = function() { history.go(0); }"
ui <- fluidPage(
useShinyjs(),
extendShinyjs(text = jscode, functions = "refresh"),
tabsetPanel(
tabPanel("Tab 1"),
tabPanel("Tab 2", actionButton("mybutton", "Reload the page",
onclick ="javascript:window.location.reload(true)")))
)
server <- function(input, output, session) {
}
shinyApp(ui = ui, server = server)
Some help would be appreciated
You can resort to plain JavaScript. The idea is that you provide an id to tabsetPanel such that you can use updateTabsetPanel in the server.
All you have to do then is to let the reloaded page know that you want to start on the second tab. As a refresh typically resets all of the inputs and reactives (you are aware of that, right?), you cannot use reactives and you have to rely on another way of communication. The query string would be one possibility.
With these ingredients you can:
Write a simple JavaScript function that reloads the page and adds a parameter to the url.
Parse the query string in the server and if the parameter is found, react on it accordingly.
library(shiny)
js <- HTML("
function doReload(tab_index) {
let loc = window.location;
let params = new URLSearchParams(loc.search);
params.set('tab_index', tab_index);
loc.replace(loc.origin + loc.pathname + '?' + params.toString());
}")
ui <- fluidPage(
tags$head(tags$script(js, type ="text/javascript")),
tabsetPanel(
tabPanel("Tab 1", value = "tab1"),
tabPanel("Tab 2", value = "tab2",
actionButton("mybutton", "Reload the page",
onclick = "doReload('tab2')")),
id = "tabsets"
)
)
server <- function(input, output, session) {
observe({
params <- parseQueryString(session$clientData$url_search)
if ("tab_index" %in% names(params)) {
updateTabsetPanel(session, "tabsets", selected = params$tab_index)
}
})
}
shinyApp(ui = ui, server = server)

Shiny selectInput(multiple = TRUE) problem with mobile devices

I developed a dashboard to monitor covid-19 cases in Brazil using shiny and flexdashboard. It's working fine, but not on mobile devices (at least in some of them). At the sidebar there is a selectinput() with multiple=TRUE, and the problem is that every time the mobile user tries to select a value to update the plots, the screen keyboard is shown and the whole app is realoaded before the selection (for the correct usage the user needs to select the states and click on "Atualizar" button to update).
I tried to solve it by duplicating the siderbar section, and use {.no-mobile} for the first one, and {.mobile) for the second, and using multiple=FALSE in this second selectinput(). Unfortunately it won't work, and both sections were shown overlapping.
I thought about another way (and i don't know how to do it), using something like multiple=ifelse("is mobile test",FALSE,TRUE).
My questions are: Is there a way to test if the browser is mobile? Is there another approach do solve this problem?
Any help will be highly appreciated.
App link (code embed): https://costafilho.shinyapps.io/monitor_covid19/
Github project: https://github.com/sergiocostafh/monitor_covid19
Problematic line:
selectInput("estado",h3("Estados"),choices = est_nome, selected = "Sao Paulo", multiple = TRUE)
Could you try this app on your mobile:
library(shiny)
js <- '
function(){
$(".selectize-input input").attr("readonly", "readonly");
}
'
shinyApp(
ui = fluidPage(
selectizeInput("variable", "Variable:",
c("Cylinders" = "cyl",
"Transmission" = "am",
"Gears" = "gear"),
multiple = TRUE,
options = list(onInitialize = I(js))
),
tableOutput("data")
),
server = function(input, output) {
output$data <- renderTable({
mtcars[, c("mpg", input$variable), drop = FALSE]
}, rownames = TRUE)
}
)
The keyboard should not appear.

How to catch dynamic tabPanel() id in Shiny app

It is possible to use a tabPanel id into a link ? I speak of id like #tab-6584-1 or #tab-6985-1 that shows when your cursor is over a tab in a Shiny app running in a browser :
Image of dynamic tabPanel() id example with Firefox
I would like to use this to put a top left link in a Shiny app to redirect on app "home" page.
From your question, I'm not sure, but it seems like you want to mimic navigation to "subpages" in your app. If that's the case, there's a way of doing that in Shiny by reading and writing the value of the hash string at the end of the app's URL (link to gist):
library(shiny)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
tags$a("Go to Panel 1", href = "#panel1"), br(),
tags$a("Go to Panel 2", href = "#panel2"), br(),
tags$a("Go to Panel 3", href = "#panel3")
),
mainPanel(
tabsetPanel(id = "tabs",
tabPanel("Panel 1", h1("Panel 1"), value = "#panel1"),
tabPanel("Panel 2", h1("Panel 2"), value = "#panel2"),
tabPanel("Panel 3", h1("Panel 3"), value = "#panel3")
)
)
)
)
server <- function(input, output, session) {
# When we change from one `tabPanel` to another, update the URL hash
observeEvent(input$tabs, {
# No work to be done if input$tabs and the hash are already the same
if (getUrlHash() == input$tabs) return()
# The 'push' argument is necessary so that the hash change event occurs and
# so that the other observer is triggered.
updateQueryString(
paste0(getQueryString(), input$tabs),
"push"
)
# Don't run the first time so as to not generate a circular dependency
# between the two observers
}, ignoreInit = TRUE)
# When the hash changes (due to clicking on the link in the sidebar or switching
# between the `tabPanel`s), switch tabs and update an input. Note that clicking
# another `tabPanel` already switches tabs.
observeEvent(getUrlHash(), {
hash <- getUrlHash()
# No work to be done if input$tabs and the hash are already the same
if (hash == input$tabs) return()
valid <- c("#panel1", "#panel2", "#panel3")
if (hash %in% valid) {
updateTabsetPanel(session, "tabs", hash)
}
})
}
shinyApp(ui, server)