-
Notifications
You must be signed in to change notification settings - Fork 2
Handle search/hash URL components and custom app locations #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
I'm not sure where these were used but they caused issues when trying to install via remotes::install_github()
when redirecting into the shiny app or when triggering a page change from within the app
Bumps roxygen2 to 7.1.0
Shiny apps are often publicly hosted in sub-directories. Specifying app_path ensures that blaze() parses the browser's URL path in relation to the path where the app is hosted.
getPath() always includes the leading slash, so observePath() adds the slash if missing. This also fixes an issue with routes when app_path is set
And also store pathname and uri in history state so that we can easily fall back to window.location.pathname if event.state is null. This catches the case when shiny::updateQueryString() is used to update the query string because the JS for that function sets the history stack item state to `null`.
| UseMethod("as_paths", x) | ||
| } | ||
|
|
||
| #' @export |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to keep as_path and its implementations internal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This exports the S3 methods but not the generic, following the advice in R Packages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see the second bullet, thank you for the link. I'm not wholly convinced and the wording is rather vague. Do you know anymore about why this is good practice?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As of R 4.0 it's actually required. The #' @export tag on S3 methods doesn't export the method, it registers the method with S3method() in NAMESPACE and this step is now required, even for internal generics, for the methods to be found.
This thread has a bit more discussion: r-lib/devtools#2293
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the discussion link. I had not known the change would affect internal generics in this way.
inst/www/js/blaze.js
Outdated
|
|
||
| window.addEventListener("popstate", function(event) { | ||
| sendState(event.state || "/"); | ||
| sendState(event.state.replace(/[?#].+$/, '') || "/"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What was the motivation for dropping the search and hash components?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think maybe this was resolved but IIRC the goal here is to ignore the URL hash component
| var pathURI = function(redirect, {params, hash} = getURLComponents()) { | ||
| if (!redirect || redirect == window.location.pathname) { | ||
| return false; | ||
| } | ||
| if (params.toString()) redirect = redirect + '?' + params; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because params and hash are never specified in the calls to pathURI() I would prefer the arguments are removed and the variables assigned in the body of the function.
| var pathURI = function(redirect, {params, hash} = getURLComponents()) { | |
| if (!redirect || redirect == window.location.pathname) { | |
| return false; | |
| } | |
| if (params.toString()) redirect = redirect + '?' + params; | |
| var pathURI = function(redirect) { | |
| if (!redirect || redirect == window.location.pathname) { | |
| return false | |
| } | |
| let {params, hash} = getURLComponents(); | |
| if (params.toString()) { | |
| redirect += `?${ params }`; | |
| } | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense. I think I had used at one point and then refactored but kept it around in case we'd want to provide params or hash. Which clearly isn't necessary anymore.
| @@ -1,3 +1,3 @@ | |||
| .onLoad <- function(pkg, lib) { | |||
| .onLoad <- function(lib, pkg) { | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another one for a cleanup pull request.
| } | ||
|
|
||
| #' @rdname observePath | ||
| #' @export |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realize the function does nothing at the moment. This is a change I especially want to exclude from a feature pull request.
| var uri = target.getAttribute("href"); | ||
|
|
||
| if (uri !== window.location.pathname) { | ||
| sendState(uri); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the motivation for no longer sending the path in this situation when a link is clicked?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure this was just a bit of clean up so that sendState() is always called by _path()
| on.exit(setwd(old)) | ||
|
|
||
| if (!is.null(app_path)) { | ||
| .globals$app_path <- if (!is.null(app_path)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer not to use this sort of short-hand.
| #' | ||
| #' @export | ||
| pushPath <- function(path, session = getDefaultReactiveDomain()) { | ||
| pushPath <- function(path, mode = c("push", "replace"), session = getDefaultReactiveDomain()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to not support replace. The replace behaviour feels unexpected or non-standard. What's the motivation for including a replace option?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was primarily for parity with mode in shiny::updateQueryString(). Secondarily, it's conceivable that an app dev might want to use replace to update the URL during some initialization process. That was my use case, it was possible to land on one page with a set of inputs that would redirect to another page and I didn't want the first state to be on the history stack.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, thank you for outlining a use case. I might introduce a second function then replacePath().
|
There's a lot of content here, so thank you for the time and energy you put in. I think it's worth breaking this pull request into separate PRs. I know there are some additions I have no reservations adding and other pieces I'd like to talk further about. |
|
Hi Nate, good to hear from you! I saw that you've already started incorporating some of the ideas from here into blaze. That sounds great to me, feel free to use whatever you want from this PR. I left comments on the questions above and I'll be happy to answer anything else that comes up. |
Hi @nteetor!
I discovered this package about a month ago and have really been enjoying it. You did some awesome work getting it set up and proving the feasibility of working around shiny's URL routing. Very cool stuff!
I've added a few features that I thought I'd share with you if you want to merge back in. I'd be happy to do any follow up if you have code review feedback.
Hopefully my commit history tells a reasonable story, but in a nutshell:
I fixed installation from GitHub via remotes as the
inst/tmpfiles were breaking installI updated the JS to transparently modify just the URL path without touching the query or hash strings. This means you can go to
<app>/<page>?id=99and the?id=99will be preserved through the various redirects.Shiny apps often live at paths other than the root directory, so I added
app_pathtopaths()that lets the user declare the path where their app is running. This makes it possible to deploy on shinyapps, e.g.gadenbuie.shinyapps.io/blaze_app/<path>but does require the user declareapp_path = "blaze_app"at runtime.Finally, I gave
pushPath()a mode argument, likeshiny::updateQueryString()except with the default being"push". This makes it easier to redirect from/to an initial page, e.g./first, without having the first/in the history stack. Oh and I also updated the JavaScript to return the URL pathname if the history state isn't written by the previous pusher.