Taking LiveView's JS commands for a spin
LiveView 0.17.0 introduced JS commands.
I’m excited about them because they allow us to express JavaScript within our LiveView code. And that makes it easier to create certain types of interactions.
The LiveView docs use a modal dialog as an example. Before LiveView 0.17, if we wanted to hide an open modal dialog, we had one of two options:
- Do it with LiveView with a round trip to the server, or
- Do it outside of LiveView, using JavaScript directly.
The first option allowed us to use Phoenix LiveView’s primitives (such as
phx-click
), but it came at a cost. A user closing a modal dialog had to wait
for the browser to communicate with the server so that the state changed and
LiveView re-rendered the page.
That’s less than ideal, and therefore, most production apps probably used JavaScript Hooks. But that’s not without issues – in those scenarios, we need to make sure our LiveView re-rendering doesn’t clobber our JavaScript operations.
By contrast, JS
commands are DOM patch aware, meaning:
operations applied by the JS APIs will stick to elements across patches from the server
So, I wanted to take a quick look at using the JS
commands in a tabs example.
Switching tabs
Say we have a couple of tabs that toggle whether we show:
- a form to compose a message, or
- a preview of the message to be sent
Switching between tabs should not require a round trip to our LiveView process.
It’s something we can do purely with JavaScript. Let’s use LiveView.JS
.
We first define two buttons that will be tabs. Inside our render/1
function,
we have the following buttons:
<button id="tab-1" aria-controls="panel-1" role="tab" type="button">Write</button>
<button id="tab-2" aria-controls="panel-2" role="tab" type="button">Preview</button>
We also need to have content inside two panels:
<div id="panel-1" aria-labelledby="tab-1" role="tabpanel" tabindex="0">
# form here
</div>
<div id="panel-2" aria-labelledby="tab-2" role="tabpanel" tabindex="0">
# preview here
</div>
Now, we can add a phx-click
that uses JS
commands to toggle the panels.
Instead of putting the JS
commands inline, we’ll extract a function called
show_panel/1
:
<button phx-click={show_panel("panel-1")} id="tab-1" aria-controls="panel-1" role="tab" type="button">Write</button>
<button phx-click={show_panel("panel-2")} id="tab-2" aria-controls="panel-2" role="tab" type="button">Preview</button>
Now to the fun part. Let’s define that show_panel/1
function. I have two
CSS classes defined to help me with this:
hidden
to hide the panel, andactive
to style the tab with a selected state.
So, when we click on a tab, we want to set it as active
and set the opposite
content as hidden
. We also want to remove the active
class from the
unselected tab and remove the hidden
class from the content we’ll show.
To do that, we can use JS.add_class/3
and JS.remove_class/3
:
def show_panel("panel-1") do
%JS{}
# make tab 1 active
|> JS.add_class("active", to: "#tab-1")
# make tab 2 inactive
|> JS.remove_class("active, to: "#tab-2")
# show panel 1
|> JS.remove_class("hidden", to: "#panel-1")
# hide panel 2
|> JS.add_class("hidden", to: "#panel-2")
end
def show_panel("panel-2") do
%JS{}
|> JS.add_class("active", to: "#tab-2")
|> JS.remove_class("active", to: "#tab-1")
|> JS.remove_class("hidden", to: "#panel-2")
|> JS.add_class("hidden", to: "#panel-1")
end
And voilà! 🎉
There’s a lot more we could do. I’m particularly excited that JS
commands
support transitions. For more, take a look at the LiveView.JS
module
documentation.