Add conditional functionality to some JS module functions

Newer versions of LiveView added new powerful functions to the JS module like toggle_attribute and toggle_class. These are great because they allow us to create more complex JS interactions without having to fallback to a hook.

I do still think that some options are missing in JS that would make our lives easier. Mainly, I think we should have conditional functions for things like exec, focus, focus_first, navigate, patch, pop_focus, push and push_focus (maybe some other functions as-well?).

What I mean by “conditional” is having something similar to what toggle_attribute and toggle_class does. I will use push as an example.

Let’s say I want to create a dropdown button, to do that I would use phx-click={JS.toggle_class("hidden", "block", to: dropdown_menu)} in the button to show the dropdown menu and phx-click-away={JS.add_class("hidden")} in the dropdown menu itself.

That works great until I want to make it a little more complex, let’s say I want to push an event to the server only when the dropdown menu is open. I can’t just change the button to phx-click={JS.toggle_class("hidden", "block", to: dropdown_menu) |> JS.push("load_menu_stuff")} because if I click in the button to open the dropdown and then click on it again to close it, it will call the "load_menu_stuff" event even though I’m actually closing the dropdown in this case.

So, what I propose is to either create a new version of these functions, ex. conditional_push or expand the opts argument already in place in the existing functions.

Here are some ideas on how to handle it:

# This means that the push will only happen if the target element has the class `block`
JS.conditional_push({:class, "block"}, "load_menu_stuff")
    
# This means that the push will only happen if the target element has the attribute `open="true"`
JS.conditional_push({:attribute, "open", "true"}, "load_menu_stuff")

or

# This means that the push will only happen if the target element has the class `block`
JS.push("load_menu_stuff", condition: [class: "block"])
    
# This means that the push will only happen if the target element has the attribute `open="true"`
JS.push("load_menu_stuff", condition: [attribute: {"open", "true"}])

I didn’t do a deep dive on how the current LV JS code works, but I believe that these would work pretty similar to the way toggle_attribute and toggle_class works (in the sense on how they verify its condition), so maybe this is not that hard to implement.