SaladUI - Implement avatar component for Phoenix LiveView
Tags: elixir, phoenix, liveview
Published: 2024-05-27 14:55:31

This post is written after I implement Avatar component for [SaladUI component library](http://localhost:4000/welcome) for Phoenix Liveview.

An avatar component is quite simple, but I want to enhance it with fallback avatar text. The template structure is as following:

```html
<.avatar>
  <.avatar_image src="./my-profile-img.jpg"></.avatar_image>
  <.avatar_fallback class="bg-primary text-white">CN</.avatar_fallback>
<.avatar>
```

Here is an implementation of avatar component in html

```html
<span class="relative rounded-full overflow-hidden w-10 h-10">
    <img class="aspect-square w-full h-full" src="https://github.com/shadcn.png">
    <span class="flex rounded-full bg-primary text-white items-center justify-center w-full h-full">CN</span>
</span>
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3001dp2sgguqeyxk2irh.png)

It's the happy case, the image exist, and the fallback element is push down and hidden due to class `overflow-hidden`.

But when the image doesn't exist, the broken image is displayed.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bejgjom7ydyyd8l81brw.png)

Hmm, the image should be hidden if it does not exist or got error while loading. Fortunately, `img` provide `onerror` event.

```html
<span class="relative rounded-full overflow-hidden w-10 h-10">
    <img class="aspect-square w-full h-full" src="badimage.png" onerror="this.style.display='none'">
    <span class="flex rounded-full bg-primary text-white items-center justify-center w-full h-full">CN</span>
</span>
```

Guest what. Nothing changed, the broken image is still visible.
It took me a while to discover the reason. `onerror="this.style.display='none'"` change attribute on client side, when Phoenix LiveView update, it patch the html and remove the `display` style value. So just add `phx-update="ignore"` and you got the error image hidden.

```html
<span class="relative rounded-full overflow-hidden w-10 h-10">
    <img class="aspect-square w-full h-full" src="https://github.com/shadcn.png" onerror="this.style.display='none'" phx-update="ignore">
    <span class="flex rounded-full bg-primary text-white items-center justify-center w-full h-full">CN</span>
</span>
```

And it works as expected


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kmb3oe3r18foh1gf8z4x.png)


**Now wrap up the component**

```elixir
defmodule SaladUI.Avatar do
  @moduledoc false
  use Phoenix.Component

  attr(:class, :string, default: nil)
  attr(:rest, :global)

  def avatar(assigns) do
    ~H"""
    <span class={classes(["relative h-10 w-10 overflow-hidden rounded-full", @class])} {@rest}>
      <%= render_slot(@inner_block) %>
    </span>
    """
  end

  attr(:class, :string, default: nil)
  attr(:rest, :global)

  def avatar_image(assigns) do
    ~H"""
    <img
      class={classes(["aspect-square h-full w-full", @class])}
      {@rest}
      phx-update="ignore"
      style="display:none"
      onload="this.style.display=''"
    />
    """
  end

  attr(:class, :string, default: nil)
  attr(:rest, :global)
  slot(:inner_block, required: false)

  def avatar_fallback(assigns) do
    ~H"""
    <span
      class={
        classes(["flex h-full w-full items-center justify-center rounded-full bg-muted", @class])
      }
      {@rest}
    >
      <%= render_slot(@inner_block) %>
    </span>
    """
  end
end
```

You may notice that I use `onload` event instead of `onerror`:
```
style="display:none"
onload="this.style.display=''"
```
Using `onerror` browser waits until the image loading complete to decide if there is any error then trigger the event. This cause the white avatar while loading image. Using `onload` event to show image when the loading process complete, so by default if the image loading is slow, the fallback avatar still displays.


If you want to learn more, then visit my github repo [https://github.com/bluzky/salad_ui](https://github.com/bluzky/salad_ui).

Thanks for reading.