Background
I am currently building a „Data-Logger“ - App using R Shiny. I do have an REST - API, which returns a value, that changes over time. My goal is to create an Shiny App, in which an user can click on an actionbutton to start writing the values fetched from the api periodically (e.g. every 60 seconds) to a dataframe. The logging of the data also should be stopped, when the user clicks on another actionbutton.
Problem
My problem is writing a function that starts executing when a button is pressed, executes periodically after that and stops executing when another button is pressed.
Previous Ideas
I previously tried using invalidateLater(), but i could not achieve what i desire.
Can you guys help me out with a clever thought or idea?
Thanks in advance!
This should show how it works. invalidateLater() is the right choice. The start/stop buttons change a reactive expression that determines whether polling is on or off. That way, the reactive RestPoll expression gets notified every time it gets switched on/off and, of course, after 500 ms as long as Running() == TRUE.
library(shiny)
ui <- fluidPage(
actionButton("btnStart", "Start"),
actionButton("btnStop", "Stop"),
textOutput("outTime")
)
server <- function(input, output, session) {
Running <- reactiveVal(FALSE)
observeEvent(input$btnStart, {
Running(TRUE)
})
observeEvent(input$btnStop, {
Running(FALSE)
})
RestPoll <- reactive({
if (Running()) # IS called every time `Running` changes
invalidateLater(500, session)
# Add any REST calls here, process the results
return(Sys.time()) # deliver results
})
output$outTime <- renderText({
req(RestPoll())
})
}
shinyApp(ui, server)
You could also do it with a reactiveTimer but that would also poll and use resources when polling is not required.
I'm building a Shiny dashboard to show a large amount of data. People access the dashboard through a separate login page (non-Shiny) that sits in front, at which point a JWT is generated and put in a cookie. I've managed to read in the cookie in Shiny and parse the data, saving the token in a variable called userPermissions. Now, however, I'd like to show/hide tabs in the dashboard depending on the user permissions.
For example: I have data on both managers and assistants. I want to show the manager data to any user with userPermissions == 'manager', and the assistant data to anyone with userPermissions == assistant.
I thought the best way would be to use conditionalPanel() – here's a (simplified) reproducible example:
library(shiny)
# UI file
ui <- fluidPage(
# JS to read cookie -- simplified to just return value!
tags$head(tags$script(
HTML('
Shiny.addCustomMessageHandler("goReadTheCookie", function (message) {
Shiny.onInputChange("cookie", "manager");
})
')
)
# Title
,titlePanel('Test')
# Navbar
,navbarPage(
id="navbar"
,conditionalPanel(condition = "userPermissions() == 'manager'",
mainPanel(textOutput('Manager data')))
,conditionalPanel(condition = "userPermissions() == 'assistant'",
mainPanel(textOutput('Assistant data')))
)
))
# Server file
server <- shinyServer(function(input, output,session){
## Grab permissions from Cookie
# Prepare output
userPermissions <- reactiveVal("")
# Tell JS to return cookie
session$sendCustomMessage(type="goReadTheCookie", message=list(name="cookie_name"))
# Listen for cookie
observeEvent(input$cookie,{
## -- Bunch of code omitted for sake of brevity -- ##
userPermissions("manager")
})
})
# Run app
shinyApp(ui=ui, server=server)
The problem with this code is that, in a browser console, I get the error Can't find variable: userPermissions.
I guess what's going on here is that the entire ui is executed, before JS can grab and return the cookie. What would be the best way to solve this?
Or, maybe I'm going about this the wrong way. I obviously need to verify the cookie server-side (i.e., in R) not to divulge the secret; and preferably this check, and the hiding/showing is completed at the very start of the Shiny application (userPermissions won't change during the session). Maybe there's a different (& better) solution to get to that point?
Any help would be very much appreciated!
In the end I found that the function appendTab does exactly what I was looking for. This needs to be run in server.R though, within the function to look for the cookie (otherwise userPermissions indeed doesn't exist). I could then do:
appendTab("navbar" # the id of the navigation bar created in ui.R
,tabPanel("tab name"
,mainPanel(...))
)
where tabPanel(...) could be anything you'd normally put in ui.R.
The added benefit here is that hidden tabs are also not available in the HTML source, as they're never even passed from the server to the client!
I have a shiny app with many inputs & a scoring function. There is no action button per se. Any input change triggers a re-score.
Sometimes a user has changed the input but it takes the score some time to update. This may mislead the user in associating the new state with the old score.
I want to fix this by having some display cue e.g. a "Score is being computed" message or gray out UI etc. dynamically whenever the score shown is stale.
How do I achieve this? Any thoughts?
fn_run<-reactive({
scorer(inputs......)
})
output$score<-renderPrint(cat(fn_run()$score))
sidebarPanel(title="Scoring Outputs",width = 3,
h3(textOutput("title")),
h3(textOutput("score"),style="color:blue"),
)
Mostly the delay is discernible when I publish the app to shinyapps.io and not palpable on local shiny runs.
You can do this with some CSS and the shinyjs package.
The following blog has what I think you want:
http://deanattali.com/blog/advanced-shiny-tips/
The relevant code is found here:
https://github.com/daattali/advanced-shiny/blob/master/loading-screen/app.R
This will show you how to shade the whole screen whilst the app is loading.
I found the timevis package to be very useful for displaying time periods, and it is working as expected to display my data.
I have used the addCustomTime() function to add a draggable vertical line, which also works great, however, I find that I can't get the current value of that line.
The underlying js widget exposes functions to interact with the elements, but I haven't been able to access them successfully. I have attempted to use shinyjs to catch the events emitted by the timeline, but I wasn't able to get that to work either.
I am fairly comfortable in R and shiny, but very much a newb in terms of the js, so I suspect I am just missing a trick on this.
In my ui.R, I create a timevisOutput object:
fluidPage(
timevisOutput("timeline")
)
I am trying to listen to the "timechanged" event: http://visjs.org/docs/timeline/#Events
When I add listeners with shinyjs in my server.R, I am able to see "mouseenter" events, but the "timechanged" handler doesn't seem to fire:
onevent("mouseenter", "timeline", print("timeline: mouseenter"))
onevent("timechanged", "timeline", print("Saw timechanged!"))
The visjs documentation has this snippet on "how to listen for a select event":
timeline.on('select', function (properties) {
alert('selected items: ' + properties.items);
});
I tried adding that in a shinyjs::extendShinyjs() call, but that doesn't work either. I added an example to listen for keydown events that did work:
$(document).keypress(function(e) { alert('Key pressed: ' + e.which); });
So that makes me think that I'm not referencing the timeline object correctly. The $(document) in there makes me think that I'm not aware of a way to properly get at the timeline element.
Since I can see mouseenter events for the timeline, but can't see its timechanged events, I think I need the .on() call, however, I don't think I'm referencing the timeline element properly/.
After #timelyportfolio's first response:
That was a great write up, thanks! I was able to confirm what the event looks; I have already been able to listen to the _selected event, but it's nice to see.
I am trying to get a custom time value, which uses a draggable line in the chart, it's added like this:
addCustomTime("mytimevis", Sys.Date(), "CustomTimeId")
The doc for the underlying javascript widget (http://visjs.org/docs/timeline), shows an event ("timechanged"), and a method ("getCustomTime()") to get access to its value, but I don't see the event in the trace, nor can I figure out how to make the method call work.
The "timechanged" event does not show up in the trace, which makes me think I need to make the .on() call from the widget's doc in order to enable that event:
timeline.on('select', function (properties) {
alert('selected items: ' + properties.items);
});
I have been working to get that method call to work, since ideally I'd like to capture its change, and also if I can get that working, I should be able to get the getCustomTime() method working as well!
assumption
I will answer assuming that you would like to get the data in R. If I assumed incorrectly, let me know and I will adjust.
message with shiny.trace
If you are not aware, one trick I often use is options(shiny.trace=TRUE) which will fill your screen with all the messages sent over the websocket. Here is what I see when I run the following code.
library(timevis)
data <- data.frame(
id = 1:4,
content = c("Item one" , "Item two" ,"Ranged item", "Item four"),
start = c("2016-01-10", "2016-01-11", "2016-01-20", "2016-02-14 15:00:00"),
end = c(NA , NA, "2016-02-04", NA)
)
tv <- timevis(data)
# now let's see what messages we get in Shiny
library(shiny)
options(shiny.trace=TRUE)
ui <- timevisOutput("mytimevis")
server <- function(input,output,session) {
output$mytimevis <- renderTimevis({tv})
}
shinyApp(ui,server)
I highlighted the messages pertaining to select. This tells us we can observe or react to mytimevis_selected.
observe select event
Now, let's turn off options(shiny.trace=FALSE) and print in the R console when we receive a selected message.
options(shiny.trace=FALSE)
ui <- timevisOutput("mytimevis")
server <- function(input,output,session) {
output$mytimevis <- renderTimevis({tv})
observeEvent(
input$mytimevis_selected,
{
print(input$mytimevis_selected)
}
)
}
shinyApp(ui,server)
add timechanged handler
Based on comments and re-reading your question, I now understand that you would like to add an event handler for timechanged. I think this code will help you over the hump.
library(timevis)
library(shiny)
library(htmltools)
library(htmlwidgets)
library(magrittr)
tv <- timevis() %>%
addCustomTime(Sys.Date() - 1, "yesterday") %>%
# add an event handler since this is not one
# timevis provides
htmlwidgets::onRender(
"
function(el,x) {
// this will be our instance
var tv = this.timeline;
tv.on('timechanged', function(id, time) {
//uncomment debugger if you want to stop here
//debugger;
Shiny.onInputChange(el.id + '_timechanged', {id:id, time:time})
});
}
"
)
shinyApp(
ui = fluidPage(
timevisOutput("timeline"),
actionButton("btn", "Add time bar 24 hours ago")
),
server = function(input, output) {
output$timeline <- renderTimevis(
tv
)
observeEvent(input$timeline_timechanged, {
str(input$timeline_timechanged)
})
}
)
follow up
Please let me know if I headed down the wrong path or if none of this makes any sense. I feel your pain, and troubleshooting this blend of technology can be very tricky.
(Disclaimer: I wrote the timevis package)
First of all: Kenton's answer is excellent, and OP's attempts were great as well.
This is more of an informational post.
Why what you tried didn't work
The reason onevent("timechanged", ...) doesn't work is because the onevent() function works for any standard javascript events, but it doesn't work for specific events that different plugins provide.
To bind a custom event such as that one, you'd have write some short custom javascript code. So looking at the visjs documentation and finding the timeline.on('select', ...) code is the correct path, but (as you suspected) you weren't referencing the timeline object correctly.
I made sure to export the actual underlying object just for this reason, so that people like you could manipulate it in its raw format. To access the timeline object from timevis, you use $(id)[0].widget.timeline. Since the id of your timeline is timeline and you wanted to call the on() function, then you can do something like $("#timeline")[0].widget.timeline.on('select', ...).
Why this functionality doesn't exist
When I looked at the documentation of visjs I decided to have 4 events that the widget would export to shiny automatically whenever they changed: the data, the selected items, the IDs of all items, and the currently visible time window. I decided not to use the on('timechanged') event because it behaved differently from all the rest: it would only return one item at a time.
What do I mean by that? For example, input$timeline_data always returns the entire data in this moment; input$timeline_select always tells you what are all the selected items at this moment; input$timeline_timechanged would get fired every time a different vertical bar gets dragged, and therefore will contain only the last dragged bar, not all of them, and it wouldn't give you a way to ask for the time in one specific bar. Does that make sense?
Of course it's possible to write some code that keeps track of all the bars, but that gets very messy very quickly (for example, it'd have to make sure to get updated when a bar is added/removed). So I chose to not go down that rabbithole.
I have a very large application with many actionButtons triggering events. I use the following logic for all my actionButtons in server.R:
observe({
if(input$myButton==0) return(NULL)
isolate({
# Code goes here
})
})
At some stage recently, the code within the isolate({}) now triggers twice whenever I click the actionButton.
It happens on all buttons within my project - and I can't think of how to debug it. Can anyone offer advice on debugging or what to look for?
For debugging purposes you could use reactive log visualizer and showcase mode:
Start with a fresh R session and run the command options(shiny.reactlog=TRUE)
Then run your app in a show case mode: runApp("yourApp", display.mode = "showcase")
At any time you can hit Ctrl+F3 (or for Mac users, Command+F3) in your web browser to launch the reactive log visualization.