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, and
  • active 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.

Want my latest thoughts, posts, and projects in your inbox?

    I will never send you spam. Unsubscribe any time.