cicerone
cicerone copied to clipboard
Highlight "search" area in DataTable
This PR tries to solve #28.
Here's the example in the issue:
library(shiny)
library(DT)
guide <- Cicerone$
new()$
step(
el = "tbl",
title = "DT table",
description = "This is a DT table"
)$
step(
el ="DataTables_Table_0_filter",
"Search",
"This is search"
)
ui <- fluidPage(
use_cicerone(), # include dependencies
br(),
actionButton("guide", "Guide"),
DTOutput('tbl')
)
server <- function(input, output){
# initialise the guide
guide$init()
observeEvent(input$guide, {
guide$start()
})
output$tbl = renderDT(
iris, options = list(lengthChange = FALSE)
)
}
shinyApp(ui, server)
I think the reason why this "Search" area is not detected by driver.js
is because driver.js
makes the list of the steps before the DataTable is actually rendered, which means that the "search" area is undetected. I wrap the whole driver
creation in setTimeout
so that the DataTable is rendered first.
This isn't very clean, and the "search" area is not highlighted in some cases (if you click very quickly on the "Guide" button after having launched the app for instance). But it works in many cases, for this example at least.
Just in case it is helpful, I add below the shiny app with the "raw" driver.js
code instead of cicerone
.
shiny+driver.js
example
library(shiny) library(DT)ui <- fluidPage( shinyjs::useShinyjs(), tags$head( tags$script(src="https://unpkg.com/driver.js/dist/driver.min.js"), tags$link(rel="stylesheet", href="https://unpkg.com/driver.js/dist/driver.min.css") ), actionButton("guide", "Guide"), DTOutput('tbl'),
)
server <- function(input, output){
output$tbl = renderDT( iris, options = list(lengthChange = FALSE) )
shinyjs::runjs("
setTimeout(function(){ const driver = new Driver();
driver.defineSteps([ { element: '#tbl', popover: { title: 'Test 1', position: 'bottom' } }, { element: '#DataTables_Table_0_filter', popover: { title: 'Test 2', position: 'bottom' } } ]); let btn = document.querySelector('#guide'); btn.addEventListener('click', function(){ event.stopPropagation() driver.start(); }); }, 30)")
}
shinyApp(ui, server)
Hey Etienne,
It's a good observation that the reason this does not work is because driver.js runs before the table has rendered.
However, I don't think the solution will work in every case. It works in this one because 50 ms is enough but it could not. I'm not sure what the right solution could be but here are some ideas.
- Allow the user to
launch
after a certain element has rendered as done with {waiter} here (not ideal) - Ideally driver.js (
launch
) only kicks in only after all initial reactives have rendered, perhaps there is an available event - Worst case, let the user customise said timeout
Let me know your thoughts.
Hello John,
Actually, in the example of #28, the problem is that the driver is created as soon as the app runs. Putting guide$init()
in observeEvent
solves the problem for this app.
library(shiny)
library(DT)
guide <- Cicerone$
new()$
step(
el = "tbl",
title = "DT table",
description = "This is a DT table"
)$
step(
el ="DataTables_Table_0_filter",
"Search",
"This is search"
)
ui <- fluidPage(
use_cicerone(), # include dependencies
br(),
actionButton("guide", "Guide"),
DTOutput('tbl')
)
server <- function(input, output){
observeEvent(input$guide, {
guide$init()$start()
})
output$tbl = renderDT({
iris
}, options = list(lengthChange = FALSE))
}
shinyApp(ui, server)
But what if we want the guide to run as soon as the app launches?
It seems to me that there's no good solution that allows to have both a guide that runs when the app starts and a guide that includes an item that takes a long time to render. I think that if an item really takes a long time to render, the guide should be launched via clicking on a button because otherwise, cicerone is just waiting for the item to render and (from the point of view of the user) nothing is happening.
So, providing a timeout option doesn't seem so bad to me, as it is the developer who decides how much delay he/she is willing to give to cicerone before initiating the guide, and this delay is not necessarily noticeable by the user (in the example I used, 50ms is not perceived as a delay, but of course it was a very simple app without much to render). However, I suppose that the time to render elements differ between users according to the internet connection for instance?
In summary:
- your first two points could work if the elements that cicerone need to wait for don't take a long time to render, but otherwise it can create a long delay and hence a bad user experience.
- giving the developer a timeout choice doesn't seem so bad to me, as a very small delay can be sufficient for elements needed to render
- finally, I think that driver.js throws an error in the console if some elements in the steps don't exist when the driver is created (but need to check that). If this is the case, maybe it could be possible to put a message in the R console to tell the developer that there's a problem with that?
That's what I think (with my very limited experience in UX and web development), what do you think?
Sorry etienne, j'ai complètement oublié cette PR :(
No worries, I just wanted to clean my fork and it's not like there was much code changed on this PR ;)