Connectivity status with Phoenix LiveView
Tags: elixir, liveview, phoenix
Published: 2024-05-27 10:14:28

Some notes on how to display - outside of a LiveView - the status of the connection by rendering images using the callback [onBeforeElUpdated](https://hexdocs.pm/phoenix_live_view/js-interop.html#client-hooks-via-phx-hook).

We define an `<img>` tag somewhere in "root.html.heex" or "app.html.heex" and want to set its `src` attribute depending upon the connection status, "online" or "offline".

```html
<img width="30px" alt="line-status" id="online-status" />
```

The connection status is accessible in the browser with the [navigator.onLine](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine) boolean.

The connection change is captured by the `window` interface events [online](https://developer.mozilla.org/en-US/docs/Web/API/Window/online_event) and [offline](https://developer.mozilla.org/en-US/docs/Web/API/Window/offline_event).

We define the callbacks to these events: we append a given image file to the `src` attribute with some styling.

```js
// /assets/js/onlineStatus.js

const domEl = document.getElementById("online-status");

const status = {
  online: { src: "/images/online.svg", bg: "lavender", opacity: 0.8 },
  offline: { src: "/images/offline.svg", bg: "tomato" },
};

const setOnline = (el, { opacity = 1, bg, src }) => {
  el.style.opacity = opacity;
  el.src = src;
  el.style.backgroundColor = bg;
};

const statusListener = () => {
  window.onoffline = () => setOnline(domEl, status.offline);
  window.ononline = () => setOnline(domEl, status.online);
};


export { statusListener };
```

We  import and use this function in our Javascript "app.js" file:

```js
// /assets/js/app.js

import {statusListener} from "./onlineStatus.js"

statusListener()
```

It remains to solve the first render problem. Indeed, unless we set a initial `src` attribute on the `<img>` element above, which we don't want, the LiveView DOM patching won't rendering anything. The snippet below doesn't work:

```js
navigator.onLine ? setOnline(domEl, status.online) : setOnline(domEl, status.offline)
```

This can be solved with the `onBeforeElUpdated` callback. It lets you perform your own DOM patching, independently from LiveView, on whatever DOM element you name `from` into a transformed DOM element named `to`. 
This callback is attached to the `dom` property of the `LiveSocket` under the key `onBeforeElUpdated`. It looks like this:

We add a say `firstRender` function in our custom JS file and add our snippet:

```js
// /assets/js/onlineStatus.js

const firstRender = (from, to) => {
  if (from.getAttribute("id") === "online-status") {
    navigator.onLine
      ? setOnline(to, status.online)
      : setOnline(to, status.offline);
  }
}

export {firstRender, statusListener }
```

and then append it to our `LiveSocket` in the "app.js": 


```js
// /assets/js/app.js
import {statusListener, firstRender} from "./onlineStatus.js"

const liveview = new LiveSocket("/live", Socket, {
  longPollFallbackMs: 2500,
  params: { _csrf_token: csrfToken },
  dom: { onBeforeElUpdated: firstRender },
  },
});

liveview.connect();
onlineStatus();
[...]
```

### Examples of SVGs

The "/priv/static/images/online.svg" file example:

```html

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path xmlns="http://www.w3.org/2000/svg" d="M 5.78125 4.1875 C 3.48125 6.0215 2 8.837 2 12 C 2 15.163 3.48125 17.9785 5.78125 19.8125 L 7.03125 18.25 C 5.19125 16.783 4 14.53 4 12 C 4 9.47 5.19125 7.217 7.03125 5.75 L 5.78125 4.1875 z M 18.25 4.1875 L 17 5.75 C 18.83 7.218 20 9.477 20 12 C 20 14.523 18.83 16.782 17 18.25 L 18.25 19.8125 C 20.538 17.9775 22 15.154 22 12 C 22 8.846 20.538 6.0215 18.25 4.1875 z M 8.28125 7.3125 C 6.90125 8.4125 6 10.102 6 12 C 6 13.898 6.90125 15.5875 8.28125 16.6875 L 9.53125 15.125 C 8.61225 14.391 8 13.265 8 12 C 8 10.735 8.61225 9.609 9.53125 8.875 L 8.28125 7.3125 z M 15.75 7.3125 L 14.5 8.90625 C 15.416 9.64025 16 10.739 16 12 C 16 13.262 15.415 14.36075 14.5 15.09375 L 15.75 16.6875 C 17.122 15.5875 18 13.892 18 12 C 18 10.108 17.123 8.4135 15.75 7.3125 z M 12 10.5 C 11.171573 10.5 10.5 11.171573 10.5 12 C 10.5 12.828427 11.171573 13.5 12 13.5 C 12.828427 13.5 13.5 12.828427 13.5 12 C 13.5 11.171573 12.828427 10.5 12 10.5 z"/>
</svg>
```

The "/priv/static/images/offline.svg" example:

```html
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path xmlns="http://www.w3.org/2000/svg" d="M 3.90625 2.28125 L 2.5 3.71875 L 4.34375 5.5625 C 2.8847559 7.2990879 2 9.55875 2 12 C 2 15.162 3.48125 17.9785 5.78125 19.8125 L 7.03125 18.25 C 5.19125 16.783 4 14.53 4 12 C 4 10.111392 4.6813379 8.3690147 5.78125 7 L 7.1875 8.40625 C 6.44465 9.4038808 6 10.663008 6 12 C 6 13.898 6.90125 15.5875 8.28125 16.6875 L 9.53125 15.09375 C 8.61225 14.35975 8 13.265 8 12 C 8 11.214008 8.2597973 10.490055 8.65625 9.875 L 10.53125 11.75 C 10.517412 11.831789 10.5 11.91425 10.5 12 C 10.5 12.828 11.172 13.5 12 13.5 C 12.08575 13.5 12.168169 13.482588 12.25 13.46875 L 20.5 21.71875 L 21.90625 20.28125 L 3.90625 2.28125 z M 18.25 4.1875 L 17 5.75 C 18.83 7.217 20 9.477 20 12 C 20 13.194 19.722 14.2945 19.25 15.3125 L 20.71875 16.8125 C 21.51475 15.3805 22 13.751 22 12 C 22 8.846 20.538 6.0215 18.25 4.1875 z M 15.75 7.3125 L 14.5 8.875 C 15.416 9.609 16 10.738 16 12 C 16 12.027 16.001 12.0355 16 12.0625 L 17.71875 13.78125 C 17.89975 13.21225 18 12.628 18 12 C 18 10.108 17.123 8.4135 15.75 7.3125 z"/>
</svg>
```