Arbitrary JavaScript on LiveView update

I’m about to use LiveView for the first time. I think I get how it works when updating the DOM, hiding/changing controls, etc. but is it possible to run arbitrary JavaScript when a new message is received?

We’re building a document conversion service. When the page loads, I set the title to “Loading…” Eventually, our converter returns an actual document title, at which point I want to run document.title = title or something equivalent to include the actual document’s title in the page. Titles are generated in the layout’s view, so aren’t in the LiveView itself.

Maybe there’s a better way to set the title, but is it possible to run other client-side code when certain messages are received? We have some complex client-side state that doesn’t always translate to DOM updates–libraries that need to be called on the client whenever new content is received, for instance. I think hooks may help here, but I’m not clear how, nor am I clear how to use specific hooks on specific pages rather than globally.

Thanks.

Hi @ndarilek,

Hooks are the way to go, and they can (and actually have to be) linked to specific elements in your LiveView.

The docs cover this:
https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#module-js-interop-and-client-controlled-dom

But you are probably better watching the video of when it was announced to get an overview:

Edit: I haven’t figured out a way to nicely manage different hooks for different pages/views yet (I’m not really that up to speed with JS - that’s why I’m here!!) - I’m sure someone else can help with that.

Currently, hooks only respond to mounted (when element under view is added and LV has finished mounting) or updated (when the DOM has been updated). It seems like you have neither and need state available client side.

One thing I would like to explore is a meta tags solution for LV, especially as it relates to live_link. Effectively, we need to propagate changes server side to the meta tags client side and I don’t think we have a solution yet.

Hi @ndarilek,
Thinking about it some more, you could create a hidden element with a phx-hook and push the messages to that, e.g.

Template (leex) - spot to push messages content, plus JS hook

  <div hidden 
    id="msg-thing" 
    data-messages="<%= Jason.encode!(@messages) %>" 
    phx-hook="MessageChange" />

Live controller

  # Assumes messages published to a PubSub topic the LiveView subscribes to
  # Also assumes an empty list of messages is assigned to the socket on mount...
  def handle_info({:msg_added, %Message{}=msg}, socket) do
    messages = [msg | socket.assigns.messages]
    {:noreply, assign(socket, messages: messages)}
  end

JS

Hooks.MessageChange = {
  mount() { this.handleMessages(); },
  updated() { this.handleMessages(); },
  
  handleMessages() {
    var messages = JSON.parse(this.el.dataset.messages);
    // Do what you need to with messages
  }
}

I mentioned in another post about knockout js it’s super easy to use an a way to sprinkle extra ui js onto server rendered templates. I often handle default messages with server side code with it. .

You bind your live view data to a knockout model, then use that to handle things like the foreach for data display. I had created a git repo showing knockout rendering when the websocket updates.

https://elixirforum.com/t/using-knockout-js-to-bind-phoenix-websockets-to-the-ui/27145/3

In generally i only need loading text etc on big initial data loads,

I would say that in almost any templating language, you can have 2 elements, one shown if data is null… showing loading, and another to render the data if present.

Or if a div has a data bind on it, if you add default text inside that element, that will be displayed if no data is present to replace it.

In live view the websocket traffic is generally so fast and the ui update happens instantly on receiving that data, showing loading is sort of pointless after page load. I see more too adding things like ‘is typing’