Implementing Hover Cards With Minimum Javascript
Monday 20 March 2023

#ruby #html #javascript #css

Boring Rails

Couple days ago Amr shared a nice website called Boring Rails that resonates with what I believe a simple way to develop web applications that has less JavaScript fatigue.

I tend to minimize JavaScript on my websites to the point that sometimes I remove JavaScript all together like on this blog.

The first article Building GitHub-style Hover cards with StimulusJS and HTML-over-the-wire talks in details what’s wrong with the modern culture of developing web applications and the SPA frenzy going on.

Then the article went to implement hover cards with some tools, I believe that the same feature can be delivered with less code, The following paragraphs will walk you through implementing the same feature without any JavaScript dependency.

Write it in HTML

You’ll need a simple HTML that shows/hides hovercards like so

screenshot-hover-card.png

 1<html>
 2  <style>
 3   .has-hover-card {
 4       display: inline-block;
 5       position: relative;
 6   }
 7
 8   .hover-card {
 9       display: none;
10       background: #ddd;
11       border: 1px solid black;
12       padding: 10px;
13       position: absolute;
14       top: 1em;
15       left: 0;
16       white-space: nowrap;
17   }
18
19   .has-hover-card:hover .hover-card {
20       display: inline-block;
21   }
22  </style>
23  <body>
24    User:
25    <span class="has-hover-card">
26      Emad Elsaid
27      <span class="hover-card">
28        User: Emad Elsaid <br />
29        Title: Software Engineer <br />
30        Web: <a href="https://www.emadelsaid.com">emadelsaid.com</a>
31      </span>
32    </span>
33    Has created a new repository, and 30 other actions.
34  </body>
35</html>

You can save this on your disk and open it in the browser, putting your mouse over my name will show the card, and moving away will hide it, what make this behavior possible is the display CSS property change when we hover on the name, checkout the Style tag.

Create a server

I’ll use Ruby and Sinatra here so install Sinatra

1$ gem install sinatra

Move your HTML page to views/index.erb and write a server file as follows

1#!/usr/bin/env ruby
2
3require 'sinatra'
4set :port, 3000
5
6get '/' do
7  erb :index
8end

Make it executable

1$ chmod +x server

And run it

1$ ./server

It should listen on port 3000 so opening localhost:3000 will show your page.

But as your page grows inserting the card for every name on the page will hurt your performance, so to make this page faster we’ll load only the card when the user move on the name.

Isolate the card to another page

Remove your hover card from the body and add a reference in the parent to the URL that will return it from the server.

1User:
2<span data-hover-card="/card/emad elsaid" class="has-hover-card">Emad Elsaid</span>
3Has created a new repository, and 30 other actions.
4
5<script type="text/javascript" src="/hovercard.js"> </script>

The endpoint can take the user name as a parameter as follows

1get '/card/:name' do
2  erb :card, locals: { name: params[:name] }
3end

And the card views/card.erb can print anything the server pass to it like that:

1<span class="hover-card">
2  User: <%= name %> <br />
3  Title: Software Engineer <br />
4  Web: <a href="https://www.emadelsaid.com">emadelsaid.com</a>
5</span>

Now if you visit localhost:3000/card/emad elsaid it’ll return the hover card content.

Load the card with JavaScript

We now need to load the card when hovering on any element with data-hover-card attribute, we’ll load it once in the page lifetime then append it to this element.

your hovercard.js will look like that

 1function loadCard(event) {
 2  let target = event.target;
 3  target.removeEventListener('mouseover', loadCard);
 4  let url = target.getAttribute('data-hover-card');
 5
 6  fetch(url).then(function(response) {
 7    return response.text();
 8  }).then(function(html){
 9    target.insertAdjacentHTML('beforeend', html);
10  });
11}
12
13let hasHoverCards = document.querySelectorAll('[data-hover-card]');
14hasHoverCards.forEach(function(element) {
15  element.addEventListener('mouseover', loadCard);
16});

It’ll attach a function to the mouseover of every element that has data-hover-card attribute, the function will remove itself to avoid loading the card twice.

Then we’ll get the card from the server and append it to this element.

Conclusion

This approach doesn’t need Sinatra/ruby at all, any web server will work, it doesn’t depend on any JavaScript package, or other dependencies other than the browser itself and CSS to show/hide the card.

You can generalize this technique to other things you want to fetch from the server on click or any other event.

Backlinks

See Also