Goal: To make JS.patch and JS.navigate more interoperable with JS.push.
Scenario: Imagine making a reusable Phoenix component and you want to allow the user of your component to customize its interactive behavior. The JS module already gives us good tools for this, but if you want to allow for customized navigation events you have to use a callback function to generate the JS command (as demonstrated by the row_click attr in the default CoreComponents table.
This is different from push, which does support values, while patch and navigate don’t receive the values from phx-value or support setting their own values. This makes sense contextually as an event push is different from navigation.
With a slight modification we could allow users of .patch and .navigate to opt in to receiving the same phx-value-* as JS.push. Then in our component example above, the user of the component would have full control over the behavior without introducing additional complexity via callback functions.
Suggestion:
- Add
:paramsas a way to pass query params toJS.patchandJS.navigate - Add
:values_as_paramsto control whichphx-value-*should be included as query params. Defaults to false (or empty list?).
Examples:
cmd_1 = JS.patch("/things", values_as_params: true)
~H"""
<!--- a bunch of posts -->
<button phx-click={cmd_1} phx-value-page="2" phx-value-size="10">Next Page</button>
"""
Clicking the button would do a patch to /things?page=2&size=10.
cmd_2 = JS.patch("/things", values_as_params: [:page])
~H"""
<!--- a bunch of posts -->
<button phx-click={cmd_1} phx-value-page="2" phx-value-size="10">Next Page</button>
"""
Clicking the button would do a patch to /things?page=2.
cmd_3 = JS.patch("/things", params: %{size: 100}, values_as_params: [:page]
~H"""
<!--- a bunch of posts -->
<button phx-click={cmd_1} phx-value-page="2" phx-value-size="10">Next Page</button>
"""
Goes to /things?page=2&size=100
Using :params the user of the component can override or introduce their own params.
This would significantly simplify designing data driven components like tables. Users could easily reuse the same components for purely ephemeral uses using JS.push and more durable ones using JS.patch without having to change the component code.
I’d be happy to help work on an implementation if anyone else sees value in this.
Alternative approaches:
- Use callbacks functions to generate the JS commands, passing in whatever data is relevant (as mentioned above)
- Use
JS.dispatchalong with a custom event listener inapp.jsto implement the suggested functionality. - We could choose to support custom JS commands instead, something like
JS.customwhere the user would register custom commands similar to how hooks are defined in the socket. (Basically a more streamlined version of alternative 2 above). This approach could also allow further customizations that are out of scope for the core LiveView library like supporting path params in the Javascript commands like/things/:id
Sketch of alternative 3 above:
const commands = {
ParamPatch(view, el, href, opts) {
const finalHref = determineHref(href, opts)
# or a path param version might do
# const finalHref = subsititutePathParams("/things/:id", {id: 5}
view.liveSocket.pushHistoryPatch(finalHref, ... etc)
}
}
let liveSocket = new LiveSocket("/live", Socket, { commands, ...}
JS.custom_command("ParamPatch", opts?)






















