Feature Request: LiveView Dynamic Favicon

Proposal

  • Support DOM patching of the HTML link element for a favicon.
  • Add the special @page_favicon assign mimicking the @page_title assign so each page can have it’s own dynamic favicon; supporting multiple tabs for an application with different ‘statuses’.

Reason
Many modern web applications have dynamic favicons. These dynamic favicons are used to notify browser users of events or statuses. For example: unread messages or a CI build status. As those use cases are quite common, providing such functionality seems to fit the ‘no Javascript’-philosophy. Additionaly it seems to align with the automagic @page_title.

Because the root layout from the Plug pipeline is rendered outside of LiveView, the contents cannot be dynamically changed. The one exception is the <title> of the HTML document. Phoenix LiveView special cases the @page_title assign to allow dynamically updating the title of the page, which is useful when using live navigation, or annotating the browser tab with a notification. For example, to update the user’s notification count in the browser’s title bar, first set the page_title assign on mount
Live layouts — Phoenix LiveView v0.20.2

Current State

  • The HTML link element for favicons is placed in layout/root_live.html.heex. No native logic is in place to update it in the DOM and there is no @page_favicon.
  • The HTML title element is placed in `layout/root_live.html.heex. Native logic is in place to update the element in the DOM when the @page_title assignment is changed.

Todo

  • get approval for feature
  • research possibilities
  • draft design
  • get approval for design
  • implement

I am willing to work on this as contribution to the community.

Links
[LiveView Dynamic Favicon?]
[https://medium.com/@alperen.talaslioglu/building-dynamic-favicon-with-javascript-223ad7999661]

14 Likes

Here is a working prototype for Dynamic Favicon. A streamlined solution that follows the style of live_title_tag would be great. I will help. :wink:

4 Likes

Here’s some rough design ideas:

Let there be a special assign @favicon_href

The live_favicon_tag would go into layout/root_live.html.heex

<%= live_favicon_tag assigns[:favicon_href] || "/images/favicon.ico" %>

The live_favicon_tag function would emit the following HTML

<link id="phx-favicon" rel="icon" type="image/x-icon" href="/images/favicon.ico"/>

Add an event listener to the client javascript

window.addEventListener("phx:change-favicon", (e) => {
  var href = e.detail.href 
  var fabtag = document.getElementById('phx-favicon') 
  fabtag.href = href
})

On the server, when the LiveView process detects that @favicon_href has changed, push an event to the client:

push_event(socket, "change-favicon", %{href: @favicon_href})}

With this approach, add-on packages (like live_dashboard) could add their own favicon images to priv/images, and then use their own favicons in their particular LiveViews.

10 Likes

Awesome job @AndyL! I am not sure if this is such a common use case to warrant inclusion on LiveView, but that’s all the pieces needed for anyone who wants to roll with it. :slight_smile:

2 Likes

It would be a great quick win!

Great example!

  • Should it support a size opt (or list of sizes and return multiple elements)?
  • SVG icons can be styled with CSS; removing the need for multiple images (in an ico container). Should it support that use case and if so, how? Blog post: Building an adaptive favicon

The other option is to keep is really simple and let a lib handle more advanced usecases :slight_smile:

Didn’t know about SVG icons - super nice! Pretty well supported on desktop browsers, less on mobile. Also learned that people sometimes use multiple favicon links, to give browsers an way to fall back to supported types.

I think there are two alternatives to attack this problem: manual coding like I did in the prototype, or editing LiveView itself. I don’t think this can be done with a Library, because there is no way for a Library to inject behavior into the LiveView JS. Is this right?

I looked in the LiveView code to understand live_title_tag - there seems to be three key areas in the code:

The code itself looks pretty straightforward - with a good amount of testing support.

Generating the appropriate favicon mime type could be done by checking the file extension of the href - this could be done in the javascript.

size opt

I’m not sure what the size opt does - I’ve always used ‘auto’ size.

The other option is to keep is really simple

If we could do something simple to streamline the 80% case, people could always fall back to a manual approach if needed.

2 Likes

Chose to keep the assign key generic; not sure of we can pass components to it (its too late).

If we can the favicon_tag function can use the (to be updated) DOM diffing too as no attribute is in the name. This allow for the simple approach by default using it as href when assigned a binary. Keeping the advanced usage of favicons to a lib which can simply pass a full icon link element. :slight_smile:

Too be continued….first let’s see if the draft PR survives the public exposure.

Won’t take long before you can play Doom in the browsers` tab bar…

4 Likes

PR looks great - beautiful screen shot!

I’m curious - did you intend that it should be possible to update multiple favicon links, or just one? If multiple: would you assign an array of hrefs to @favicon, or use some other convention?

Last night I looked at support for multiple icons

Phoenix LiveView is awesome at diffing and sending the least amount of data over the wire. I am trying to keep that in honor. As a result I have chosen to use a simple string replace.

The free schema also makes it easier to use online favicon generators that generate a bunch of PNG files for you. Just unzip and place in a subfolder of a variant. SVG scale so don’t need a subfolder. See pseudo code:

# pseudo code supports multiple schema’s
<.live_favicon href="/images/svg/@dynamic.svg" />
<.live_favicon href="/images/status/@dynamic/favicon-32x32.png" />
<.live_favicon href="/images/status/@dynamic/favicon-16x16.png" />

When a new @dynamic value is assigned, the href is split in 3 parts: prefix, @dynamic and suffix. The dynamic value is replaced with the value assigned.

static: /images/status/
dynamic: “new_message”
static: /favicon-32x32.png

Only the dynamic part is sent over the wire on socket assign updates. Just like…LiveView does already :slight_smile:

{"f": {   "dynamic": "new_message"}}

When href is defined without a dynamic, the helper assumes the user needs full control over the href and just passes it through. Thus supporting base64 encoded data and one-off use cases.

This also allow me to add ‘class’ to support those fancy dynamic SVG’s.

As you like screenshots:

1 Like

Update: the draft PR is rejected

Not sure if the presented solution really covers the use cases as mentioned above. It does cover the simple first case in the draft though, so it’s not a surprise. Will check later.

Just to keep everyone informed

To be super clear
I fully understand and respect Chris’ rejection. He just doesn’t want anyone spilling time so he was quick to respond.

The rejection
The rejection was based on the commit and example code published.

The PR did not yet include support for multi favicon links nor some other features, as it was not final code yet. I used a ‘draft PR’ which GitHub says “is meant for discussion, not review” just to make sure the ‘we are working on this idea already’ would be represented at GitHub too and discussion about code could take place there.

So Chris was right the published code at that time could be done with 10 lines of primitives and -based on that- changing Phoenix codebase should not happen.

Future
Currently I see 4 options.

  1. Write a blog post how it’s done with primitives
  2. Write a helper lib/files requiring manual changes in Javascript files.
  3. Write a lib which solely relies on Phoenix so it’s a ‘add to mix dep’ and go.
  4. Challenge the rejection with the improved ‘include in Phoenix Liveview’ code and description why Chris’ solution won’t work (enough)

When option 3 is viable, it’s probably gonna be 3.

While the code for 4 is ready for a challenge, the challenge won’t happen until the pro’s and cons of all the options are clear and comparable.

So far
So far I have the code for 2 and 4 (they share most lines). Will make an attempt at 3 this weekend, should be quite straight forward.

One way or another we will have dynamic multi-link favicons in our Phoenix Live View apps; so just subscribe to this topic, wait and relax :slight_smile:

5 Likes

I like your approach of respecting others current vision but not stop until succeed.
Remember, great innovation always starts like that as in the beginning the benefits don’t overweight complexity or not immediate convenience but as soon as from one feature you scale-up to 7 and optimise your code, then current perception will change towards your approach.
Do you remember how LiveView started? a library called Drab…that was considered by some strange :slight_smile: Well the good kind of strange but too far from complete.
I really like your idea and positive energy on it.

1 Like

I am very familiar with the concept; it has been a major factor in my largest projects.

Some of my projects really failed, never made it into a lib or became unneeded before publishing (Hex got a new dependency solver)

But the ones that became large always had (fair or unfair) criticism at the start. The moment the person I looked up to rejected the idea.

Releases4U
“Your website will remain small and will eventually disappear”. - The competitor when I launched my website.

After a year or so, we outgrew them. Moderators from their forum moved to ours because of the ‘nice atmosphere’ and the website became internationally known.

SuperRepo
“It’s technically impossible to create a repository containing all addons for XBMC (now Kodi red.). It’s a stupid plan and I don’t trust you” - One of the addon repo maintainers of Kodi

Actually I could, and it even had the first webbrowsable index of addons. It has run stable for many years, fully automized. It has been used by millions.

Fun fact: SuperRepo’s redirector/mirror manager is written in Elixir! Needing a highly scalable and stable redirector I ended up using Elixir and kept using it for almost every project since. The Elixir redirector formed a self healing multi-node cluster. It replaced a redirector written in Go and since has not caused any outage, memory usage is a flat low line. Too bad the ‘self healing’ was never needed in production….

Dynamic Favicons
So this one got rejected once…………

………millions playing multiplayer Doom looking a 16x16 pixels favicon😉

Conclusion
When you think you have a good idea, don’t give up easily. Just make it better.

4 Likes

It’s gonna be 3 I guess.

Took some time to explore the problem domain a little further. As a result, I will create multiple libs all targeted at manipulating the static head.

Phoenix Live Head
Low level lib which includes functions to manipulate the head of the page incl. generic Javascript. It also takes care of minimizing data over the wire. The other libs depend on it, direct usage will be discouraged.

Phoenix Live Favicon
Lib which supports dynamic favicons! It will include a helper function to cover some exotic use cases, but will mainly be used with the HTML link element(s).

Phoenix Live Metadata
This one is a convenience lib which can be used pre-production (on production it is harmful!). It updates the metadata in the head for easy inspection.

Phoenix Live -other head element-

—-

This way devs can pick the parts they need for each environment.

6 Likes

@all: Please post your planned/envisioned use case(s) for Live Favicons!

It might influence the design of public functions and requirements for the core lib.

1 Like

Update on this project
Still work in progress. Due to some life-events work was delayed. Had some time last evening to draft the first raw outlines of above mentioned libs. Works well. The main focus points at this moment are:

  • naming things (public functions)
  • we all love magic, we all love explicitness; have to choose between them and decide what possible pitfalls should be taken away by handling cases in the libs vs. documenting how to avoid them.
2 Likes

Update

The Live Head lib seems to work well and supports all (and more) functions currently in Live Favicon. Most work went into reset-functionality to set an element to it’s initial state and minimizing data over the wire (truth be told: for the use case it’s a bit over-engineered but was nice to practice…you never know if someone ever will use LiveFavicon for their 2 million websockets blog post ;))

Next: The Live Favicon lib. It supports the initial use cases already, but I will add a few extra convenience functions to mimick LiveView.JS. And of course docs!

As it’s quite straight forward this shouldn’t take long.

4 Likes

Just to build up some tension

Monday I’ll leave for a non-digital holiday trip. I will be gone for 2 weeks. So tomorrow the first version of Phoenix Live Favicon should be released!

Won’t have time to add a test suite and docs will be a bit rough so it’s gonna be a “Works for me” / “You are holding it wrong” release. But should be enough to collect some feedback and/or pull requests.

4 Likes

Phoenix Live Favicon 0.1.0
The first version is up!

Warning
Didn’t have time to add a test suite and docs will be a bit rough so it’s a “Works for me” / “You are holding it wrong” release. Really would like to see feedback and/or pull requests when I get home!

Example App

5 Likes