Shiny bookmarking the state in URL

Hi Tobias & Team,

first of all, thanks again for ShinyProxy – a very happy end user here :slight_smile:

I have a question similar to Pass custom arguments to the Shiny app – but not only on passing data to Shiny via URL params, but also to update the URL of the app as per the bookmarking feature of Shiny: https://shiny.rstudio.com/articles/bookmarking-state.html

It seems that the URL of the app is always static (domain/app/foobar) despite setting in the app:

onBookmarked(function(url) { updateQueryString(url) })

Do you have any suggestions on how to enable bookmarking in apps behind ShinyProxy?

Thanks,
Gergely

Hi @daroczig,

I suspect it isn’t working because ShinyProxy apps run inside an iframe. This imposes some different rules when a script tries to access the browser’s location bar.

That being said, ShinyProxy does support query strings: if you append a query string to the URL, this query string will get forwarded to the shiny app. E.g.

domain/app/foobar?arg=query_string

Regards,
Frederick

Hi Frederick,

Am LOVING shinyproxy!

On the topic of bookmarks though, it seems the query strings are being suppressed by Shiny proxy.

Am am trying with the sample from the shiny site

# Update the browser's location bar every time an input changes. This should
# not be used with enableBookmarking("server"), because that would create a
# new saved state on disk every time the user changes an input.
ui <- function(req) {
  fluidPage(
    textInput("txt", "Text"),
    checkboxInput("chk", "Checkbox")
  )
}
server <- function(input, output, session) {
  observe({
    # Trigger this observer every time an input changes
    reactiveValuesToList(input)
    session$doBookmark()
  })
  onBookmarked(function(url) {
    updateQueryString(url)
  })
}
enableBookmarking("url")
shinyApp(ui, server) 

But I don’t seem to get any query params. Running the app docker container free-standing outside of shinyproxy works fine.

Is there some configuration setting for this please?

thanks!

Dear Shinyproxy team, I’m having the same feature request. Has this been fixed, i.e. is it possible to pass parameters through the URL to the shiny app when run on a ShinyProxy server?

@fmichielssen Can you point me to the file in containerproxy or shinyproxy where I could add the JS pushState code to make changes to the iframe query string propagate to the page that contains it?

@fmichielssen friendly ping

Let’s distinguish between several querystring-related things:

  • Passing a query string from a custom-made URL into the shiny app works. I.e. the Shinyproxy index page lists links like /app/app1, but you could just as well enter the URL /app/app1?arg=value into the address bar and the arg=value query string will be visible to the shiny app.

  • The shiny bookmarking feature uses URL query strings to store state, if you use enableBookmarking("url"). Here another issue comes into play: the shiny app runs inside an iframe, so when it updates its URL, e.g. by using pushState(), the URL being updated will be the iframe URL, not the parent page URL.

To solve the iframe issue, the client should call
window.parent.history.pushState();
instead of
history.pushState();
But I guess this code is inside the shiny framework, and thus not easy to modify.

Alternatively, you could use the URL /app_direct/app1. This gives you the shiny app directly, without iframe. But then you will also not get a header bar or loading animation etc.

@hendiatris, the HTML template that renders the /app page, is here:

But I’m not sure what you would want to modify here to make the pushState call work.

In addition to the above, I think the folllowing

can be solved by manually mimicking the action of the updateQueryString function with shiny’s custom message functionality (Shiny - How to send messages from the browser to the server and back using Shiny), so e.g. in the example mentioned above one would substitute

onBookmarked(function(url) {
    updateQueryString(url)
  })

with something like

onBookmarked(function(url) {
     session$sendCustomMessage("myUpdateQueryString", url)
  })

and add the following javascript piece to the ui code:

addMessageHandler("myUpdateQueryString", function(url) {
    window.parent.history.replaceState(null, null, url);
});

(simplified version, you can also replicate shiny’s functionality with modes by copying from shiny/srcjs/shinyapp.js at 8ae31eb998c1b64d444f4b733038725162d2a8d1 · rstudio/shiny · GitHub)

@fmichielssen I get the sense that you think this shouldn’t be done, but how else can shinyproxy behave like shiny server in this use case that certainly isn’t a infinitesimally minor one? There are a lot of applications that depend on this usage. If I build an application that’s maintaining state information in the query string and I want to be able to refer to that state and perhaps pass it on to another user or bookmark it for latter, it’s hidden by the iframe. app_direct doesn’t solve the problem, and forcing the user to implement a solution in their application for shinyproxy sort-of solves it but it is a bad solution.

If I add logic to the default template, it’s only useful in the case that the default template is used – is there a way to add it at a more fundamental level?

Hi @hendiatris,

I’m not saying this is not an important item; it does make sense to me to support this.
However, I haven’t had the time yet to dive deep into this issue. Right now, it seems to me that a proper solution will have to be implemented on the javascript side in the browser. It might be a good idea to create an issue on github where we can collect relevant info and think about a solution.

For what it’s worth, I’m currently using a work-around where I pass the URL of shiny proxy as an environment variable to each container and build the URL for the user manually. This has been working well in production.

shiny::onBookmarked(function(url){
    
    # if the env variable SHINYPROXY_URL is present, use that instead of the URL
    # that shiny sees.
    # export the URL in the docker container 
    # so that bookmarkable state works for users
    shinyproxy_url <- Sys.getenv('SHINYPROXY_URL')
    
    if (!is.null(shinyproxy_url) && shinyproxy_url != ""){
        query <- basename(url) # pull out query parmaeters
        url <- file.path(shinyproxy_url, query)
    } 
    
    shiny::showBookmarkUrlModal(url)
    
})
2 Likes