Add `:params` opt to JS.patch and JS.navigate, and opt to merge `phx-value-*`

It does. Just keep in mind that JS.patch(query: merge: lorem: "ipsum", dolor: "sit") is not valid Elixir syntax. You’d need to write JS.patch(query: [merge: [lorem: "ipsum", dolor: "sit"]]).

side note: I explored navigation guards for LiveView in the past, but the browser APIs are very lacking at the moment and there’s no good way to actually cancel a navigation caused by pressing the back/forward buttons. It needs a lot of manual state bookkeeping, so I abandoned that even though I really wanted to have that feature. The most popular use case for such guards would be to prevent navigation when there are unsaved changes, but one more catch there is that you cannot prevent a server initiated navigation on the client, because the LiveView process simply shuts down when you do `push_navigate`. So even if there were navigation guards, you’d still need to also be careful about server initiated navigations.

There might be cases where it’s brittle, but for me this is mostly about filters in tables where you’re only concerned about a single or maybe set of query parameters that are unrelated to any other filters you have.

With you example, whenever @uri_switch changes, all js_patch commands will be re-sent. If you have a lot of buttons / links on a page (like a country selector with hundreds elements that should all change the URL) this can get expensive fast.

This is pretty good and it actually benefits from something I wasn’t fully aware of: when you `push_patch` from a handle_event, the patch does not incur an additional client-server roundtrip. The LiveView just directly calls handle_params and sends two messages over the websocket: the new URL that is patched to for updating the client-side URL and the handle_event diff response.

Correct. That’s actually the reason I started with the client-side URL patching in the first place. Previously, I built links on the server, which looked something like this:

~H"""
<.link patch={~p"/my-page?#{Map.put(@params, "country", "US")}"}>United States</.link>
"""

which has the exact same issue that all the links relying on the current @params get re-sent. The big benefit of this approach (and why I still like it for use cases where you don’t render thousands of links on the page) is that you can command/alt click the link and also right click to copy the URL to send someone else, etc.

That doesn’t work with any of the client side patch or handle_event based approaches.

Note that if you do you push_navigate, the optimization I mentioned earlier does not apply. In that case, you need two round trips to perform the navigation if you use the handle_event based approach. Initiating the live navigation from the client only needs one round trip as the client directly joins the new LiveView channel. Most of the time you’d probably use patch though.


Quoting myself:

you can command/alt click the button/link and also right click to copy the URL to send someone else, etc.

Technically, you can of course make this work with more JavaScript. You hook into both the LiveView DOM patching (there’s onPatchEnd) and the popstate event and then compute the correct href value based on the current URL and set it using liveSocket.js().setAttribute. But then, this may best be explored in a separate library first.

2 Likes

That may be true for the elements outside of streams. Since we’re all in streams and streams of streams, we had the exact opposite problem - how to achieve the same effect as if it did happen, which is why all of our Switch modules operate on the presumption that the data they contain is the result of the initial page rendering only and are made immune url semantics-wise to the updates of “their” individual stream elements.

Also, the very navigation in our case is indirect, having all roads lead to “Rome” where the actual target is truly resolved and navigated to - the navigating-away “hard-coded” push (in %JS{}) being one and the same for all links on the page so we can circumvent/alleviate the issue that I reported and asked solving “long ago”: The inability to differentiate b/w transition when item is removed and transition when its container is removed · Issue #3199 · phoenixframework/phoenix_live_view · GitHub.

There’s a bit of complexity to what I did with all this, but I had no other choice given the requirements and the still present LiveView limitations.

2 Likes

Thank you for all the extra context. I also am focused primarily on the simple use-case of filtering/sorting/paginating a large data set and making any sorted/filtered/paginated view of that data linkable. I think I will focus on making that common use-case accessible through the JS.patch command in heex templates, then once I have something working, It should be easier to explore the edge cases or improve the API at that point. :slight_smile:

This still bites me every time I convert a keyword into a nested keyword. :rofl: Thanks for calling this out!

First draft if anyone wants to take a look: WIP: Relative Query Patching by GrammAcc · Pull Request #4067 · phoenixframework/phoenix_live_view · GitHub

2 Likes