If you've ever worked with the Trello app, you may have noticed that when
you have a new notification, the favicon (the little icon in the browser
tab) has a red dot added to it. This really helps grab your attention when
you receive a notification, but you are working on another tab.
So when the question "can we do this in our app as well?" came up, at first
I said, sure, no problem! After all, its just changing the href link to
point to another favicon image, but with the red dot.
But then I remembered we allow users to upload their own favicons... Now
there's a interesting challenge.
Using a Shrine to Add the Alert Dot
In our project we use the Shrine gem to handle file uploads, and MiniMagick
for image processing. So I added a new derivative to my favicon uploader:
class FaviconUploader < Shrine
Attacher.derivatives do |original|
magick = ImageProcessing::MiniMagick.source(original)
red_dot = Rails.root.join('app', 'assets', 'images',
'favicons', 'overlays', 'alert.png')
{
favicon: magick.resize_to_fill!(64, 64),
alert: magick.resize_to_fill(64,
64).composite!(red_dot,
mode: "over",
gravity: "south-east",
offset: [0, 0])
}
end
end
Notice that in the original derivative we call resize_to_fill!, while in the
new derivative we call resize_to_fill (without the bang) because the bang,
which tells MiniMagick to perform the chained effects, should be on the last
method called.
So in the new derivative we first resize the image, and then composite the
image with a custom PNG image of a red dot, so that it sits in the
bottom-right corner.
Dealing with Favicon Change
Now that we have the images, let's move on to the easy part - changing the
favicon to the one with the dot when there are notifications, and changing
back when the notifications are cleared.
I handled this by adding a data attribute to the link tags for each of the
icons. For example, this is what the link tag for standard non-Apple devices
looks like when using the static favicon:
<link rel="icon" type="image/png" sizes="64x64"
href="/assets/favicons/favicon-default.png"
data-alert-favicon="/assets/favicons/favicon-alert.png"
data-default-favicon="/assets/favicons/favicon-default.png">
The link tag for the dynamic favicon is rendered like this:
<% data_attrs = %[data-alert-favicon=#{@main_scope.favicon_url(:alert)}
data-default-favicon=#{@main_scope.favicon_url(:favicon)}] %>
<% default_href = @main_scope.favicon_url(:favicon) %>
<link rel="icon" type="image/png" sizes="64x64" href="<%= default_href
%>" <%= data_attrs %>>
Then I can call the following function when a notification is added or when
the last unread notification is marked as read:
function update_favicon_alert(alert_should_be_on){
$('[rel="apple-touch-icon"], [rel="icon"]').each(function(){
new_href = $(this).data(`${alert_should_be_on ? 'alert' :
'default'}-favicon`);
if (new_href && new_href.length > 0) {
$(this).attr('href', new_href);
}
})
}
That's it!
Conclusion
Shrine is a very powerful tool, and we love using it. It does have a steeper
learning curve than the Paperclip gem or even Active Storage, but once you
get over the hump, it's a versatile tool with enormous capabilities.
Technically we could also embed the number of notifications (from 1 up to
say 9, and for any more show 9+) by having 9 such derivatives, and as before
update the link to use the appropriate favicon, but that felt to us a bit
cumbersome and very unnecessary.