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 am developing a R Shiny application involving Twitter data fetching. As the process could last some time I would like to indicate that the application is busy doing something, so the user doesn't thing the page is frozen.
In my case, I store some reactive values this way:
rv <- reactiveValues()
rv$analysisStarted <- FALSE
rv$analysisAvailable <- FALSE
Then, in UI, the user must press an actionButton in order to start processing. Then, I would like to indicate somewhere the Server is working...
observeEvent(input$analysisButton, {
rv$analysisStarted <- TRUE
rv$analysisAvailable <- FALSE
#Processing Twitter info...
rv$followersAnalysisStarted <- FALSE
rv$followersAnalysisAvailable <- TRUE
})
If, in UI.r, I place a textOutput and create the corresponding output method this way, it does NOT work:
output$text <- renderText({
if (rv$analysisStarted) {
"Server is working..."
} else if (rv$analysisAvailable) {
"Your report is ready :) "
} else {
"Enter the data to search and press analysisButton"
}
})
What I have noticed is when the analysis begins, the label changes to a gray color, but it doesn't update the text until the process is over.
What should be the proper coding of this feature?
Is it possible to redraw the text output within observeEvent?
Is it possible with the raw shiny library or requires shinyjs, which I am also using?
Any help would be grateful.
In your case, it would be helpful a progressbar to check the state. This element would be appreciate by your users to indicate the state. Unfortunately, the progress bar would have to code before the functionality. It is a trick, you see a progress bar but this object stops when the function starts.
Here is the tutorial: http://shiny.rstudio.com/gallery/progress-bar-example.html
From my point of view, the progress bar is the best way to inform the web users of the website state but it is not completely perfect. Further you can change the style of the bar with CSS to customize and select your own colors, size...
I'm still fairly new to ASP.Net, so forgive me if this is a stupid question.
On page load I'm displaying a progress meter after which I do a post back in order to handle the actual loading of the page. During the post back, based on certain criteria I'm disabling certain links on the page. However, the links won't disable. I noticed that if I force the links to disable the first time in (through debug) that the links disable just fine. However, I don't have the data I need at that time in order to make the decision to disable.
Code Behind
If (Not IsCallback) Then
pnlLoading.Visible = True
pnlQuote1.Visible = False
Else
pnlLoading.Visible = False
pnlQuote1.Visible = True
<Load data from DB and web service>
<Build page>
If (<Some Criteria>) Then
somelink.Disable = True
End If
End If
JavaScript
if (document.getElementById('pnlQuote1') === null) {
ob_post.post(null, 'PerformRating', ratingResult);
}
ob_post.post is an obout js function that does a normal postback and then follows up with a call to the server method named by the second param. then followed by the call to a JavaScript method named by the third param. The first parameter is the page to post back to. A value of null posts back to the current page.
The post back is working fine. All methods are called in the correct order. The code that gives me trouble is under the code behind in bold. (somelink.disabled = True does not actually disable the link) Again, if I debug and force the disabling of the link to happen the first time in, it disables. Does anyone know what I might do to get around this?
Thanks,
GRB
Your code example is using the IsCallBack check, while the question text talks about the IsPostback Check. I'd verify that you're using Page.IsPostBack in your code to turn off the links.