Adobe Spectrum 2 web components with LiveView

Yes, this is true not only for web components, but for all DOM changes manipulated by JavaScript. Because this is how LiveView works.

For example, for the following LiveView, if we assign the initial count to 0, and increment the count when “inc” event is triggered,

def render(assigns) do
~H"""
<button id="counter" phx-click="inc"><%= @count %></button>
"""
end

First, the LiveView mounts, and sent the initial rendered html:

<button id="counter" phx-click="inc">0</button>

Then, if you use client side JavaScript to make changes to the DOM node, like document.querySelector("#counter").dataset.count = "0", the current rendered DOM node in your browser will change to:

<button id="counter" phx-click="inc" data-count="0">0</button>

Then, if you click the button and send an “inc” event to the server, the server side received the event and update the new assigned count to 1, the LiveView rerenders on the server, getting the following HTML:

<button id="counter" phx-click="inc">1</button>

And then it is sent to the browser, going through a DOM patch process. So LiveView is not aware of your previous client side changes, your previous changes get lost.

After JS commands were added to LiveView, you can use JS command to set attributes. Those attributes set by JS Command will be persisted through DOM Patch. Here is a simple explanation on how it works: How to turn my client-side DOM manipulations into DOM-patch aware manipulations? - #3 by TunkShif

Another way is to use LiveView hooks, you’re supposed to synchronize these changes in the updated() callback.

Also, you can fully ignore LiveView DOM Patch on a specific element by adding phx-update="ignore" to it, as described in DOM patching & temporary assigns — Phoenix LiveView v0.20.3

And LiveView DOM patch is implemented using morphdom lib, there’s a lower level morphdom callback option that you can set when initializing LiveSocket. Like this example showing in JavaScript interoperability — Phoenix LiveView v0.20.3 ,

For example, the following option could be used to guarantee that some attributes set on the client-side are kept intact:

onBeforeElUpdated(from, to){
  for (const attr of from.attributes){
    if (attr.name.startsWith("data-js-")){
      to.setAttribute(attr.name, attr.value);
    }
  }
}

Also, this is how AlpineJS integration achieved

2 Likes