Hi all,
in a live view, I am doing something like this. My goal is to render a table with the last 5 events:
def handle_info(e, socket) do
{:noreply,
socket
|> push_event("highlight", %{id: "SomeID"})
|> assign(
:events,
[e | socket.assigns.events] |> Enum.take(5)
}
end
The template is something like
...
<tbody id="SomeID">
<%= for {user, action, time} <- @events do %>
<tr>
<td><%= user %></td>
<td><%= action %></td>
<td><%= time |> Timex.from_now(@locale) %></td>
</tr>
<% end %>
</tbody>
...
To highlight the added row I am pushing the event highlight
and in javascript I do:
window.addEventListener("phx:highlight", (e) => {
const el = document.getElementById(e.detail.id);
el.classList.remove("highlight");
el.offsetWidth;
el.classList.add("highlight");
});
And the related css is:
@keyframes highlight {
from {
background-color: yellow;
}
to {
background-color: transparent;
}
}
#SomeID.highlight > tr:first-of-type {
animation: highlight ease 0.5s;
}
This is working ok,
I am wondering if it is possible to achieve the same result without the javascript part.
No real reason, just wanted to flex the “no javascript” trend…
Thank you
It should be enough to push a row with the CSS class that includes the animation.
Now, the hard part would be to make sure that already added rows don’t trigger that animation… 
umm what do you mean by “push a row with the CSS class” ?
Thank yoy
If you just add a row with that class, it should be enough becuase it will appear in the document, and the browser will trigger the animation.
The problem is: the browser will probably not be able to tell which row is old, and which is new (especially if LiveView reshuffles the rows when it sends updates). So you’d probably have to do this:
- all old rows are rendered without the class
- all new rows are rendered with the highlight class
Repeat on next update. But figuring this out properly might be a challenge.
You can do it with a transition and attribute selectors. Generic example:
Somewhere in you .ex
:
socket
|> assign(:highlighted_id, "some_id")
In the markup:
<ul>
<li :for={option <- options} class="list_item" mod-highlight={@highlighted_id == option.id}>
<%= option.text %>
</li>
</ul>
In the css:
.list_item {
background-color: transparent;
transition: background-color 0.5s;
}
.list_item[mod-highlight] {
background-color: yellow;
}
mod-*
is just the way I choose to call these “modifier” attributes. It can be almost anything really.
If you want a quick transition in response to a user action, you will need to use JS.set_attribute
to control mod-highlight
, otherwise it will look laggy. If you go down that road, be advised that once it is changed by the set_attribute
, mod-highlight
will not be updated by socket changes anymore.
3 Likes