I'd like to know when a user clicks on an image in a Shiny app. My full app displays images in a kind of "Photo Hunt" game and I want to capture how long it takes the user to click on a relevant place in each image. However, I'm having a hard time capturing client-side time, which is important because server-side times can be delayed for various reasons. I think I would need something like the following javascript, but I do not know how to integrate it in the app.
- Find the element on the page
var image = document.getElementById('img');
- Listen for the click and capture the time
addEventListener("click", true,
click_time = new Date().getTime();)
- Send the captured time to shiny server with something like:
Shiny.onInputChange("new_click_time",click_time);
- Listen in shiny to get the click_time
observeEvent(input$new_click_time,{
# This should be the time the user clicked on the image:
input$new_click_time
})
Here's an example shiny app that gets server-time, but not client-time:
library(shiny)
library(shinyjs)
options(digits.secs = 3) # Modify default global time to show milleseconds
shinyApp(
ui = fluidPage(
useShinyjs(),
sidebarLayout(
sidebarPanel(
p("When clicking on the histogram, I'd like to capture the client-side computer's time"),
actionButton(inputId="new_image", label= "New Image")
),
mainPanel(
imageOutput("img", click = "photo_click"),
textOutput("client_time"),
textOutput("server_time")
)
)
),
server = function(input, output) {
output$img <- renderImage({
input$new_image
outfile <- tempfile(fileext='.png')
png(outfile, width=400, height=400)
hist(rnorm(100))
dev.off()
list(src = outfile,
alt = "This is alternate text")
},
deleteFile = TRUE)
output$server_time <- renderText({
req(input$photo_click)
server.time <- as.character(strptime(Sys.time(), "%Y-%m-%d %H:%M:%OS")) # Time with milliseconds
paste("SERVER TIME:", server.time)
})
output$client_time <- renderText({
# cat("\nI'd like to capture click-time, possibly here -- the time on the client's machine when a click is made")
req(input$photo_click)
client.time <- "???" # Time with milliseconds
paste("CLIENT TIME:", client.time)
})
}
)
CodePudding user response:
You could use the javascript function Shiny.setInputValue
supplied by Shiny to the client. Example:
library(shiny)
ui <- shiny::fluidPage(
img(id = 'trigger_image',
src = 'notfound.jpg',
height = '100px',
width = '100px'
),
tags$script('
document.getElementById("trigger_image").onclick = function(){
var the_time = new Date().getTime();
// set input$client_time to the_time:
Shiny.setInputValue("client_time", the_time)}
')
)
server <- function(input, output) {
observeEvent(input$client_time,{
## do stuff with input$client_time
})
}
shinyApp(ui, server)
Note that Javascript getTime
returns milliseconds elapsed since 1970/1/1.
Shiny: Communicating with Javascript
CodePudding user response:
The below seems to capture the client's time, but I don't see how it can be correct, because the client's time is AFTER the server's time.
library(shiny)
library(shinyjs)
options(digits.secs = 3) # Modify and save default global time options
click.time.image.js <- "
document.getElementById('img').onclick = function(){
var the_time = new Date().getTime();
console.log(the_time);
// set input$client_time to the_time:
Shiny.setInputValue('client_time', the_time)
}
"
shinyApp(
ui = fluidPage(
useShinyjs(),
sidebarLayout(
sidebarPanel(
p("When clicking on the histogram, I'd like to capture the client-side computer's time"),
actionButton(inputId="new_image", label= "New Image")
),
mainPanel(
imageOutput("img", click = "photo_click"),
textOutput("client_time"),
textOutput("server_time")
)
)
),
server = function(input, output) {
output$img <- renderImage({
input$new_image
outfile <- tempfile(fileext='.png')
png(outfile, width=400, height=400)
hist(rnorm(100))
dev.off()
shinyjs::runjs(click.time.image.js)
list(src = outfile,
alt = "This is alternate text")
},
deleteFile = TRUE)
output$server_time <- renderText({
req(input$photo_click)
server.time <- as.character(strptime(Sys.time(), "%Y-%m-%d %H:%M:%OS")) # Time with milliseconds
paste("SERVER TIME:", server.time)
})
output$client_time <- renderText({
req(input$photo_click)
req(input$client_time)
client.time <- input$client_time # Time with milliseconds
client.time <- as.POSIXct(client.time/1000, origin="1970-01-01")
paste("CLIENTs TIME:", client.time)
})
}
)