brettlreed.com

Creating a tooltip dictionary

Demo

Snigglewinks.

If you can hover over (or click/tap) that word, and you get a definition for that word, then you know the code works!

What’s this for anyway?

Sometimes, in the world of tech, it can be difficult to remember what a word or acronym means. Especially now, when I’m currently studying for the A+ certification, and plan to continue to study new things. And also, when I’m writing these articles, I don’t have to fully explain what something is when I’m talking about it. Half ingenuity, half laziness.

So, I’ve implemented a system on this website where I can store a word, and associated definition, in a JS file. A separate JS script will search the page for any words that might have an available definition, and allow the user to hover or click/tap on that word to learn more about it.

I’m going to show you how I did it.

Implementation

First, we need somewhere to store our definitions. I created a file called definitions.js. I can continually come back to this file to create more definitions as needed:

export const wordDefinitions = {
    "Snigglewinks": "I don't know what this means.",
    "JS": "Javascript: A scripting language.",
};

Done! That’s all I need for that.

Now, I’m going to use a library called Tippy.js and install it to my website’s directory:

npm install tippy.js@latest

Then, I created a <script> tag at the end of my BlogPost.astro file so that this feature only runs on my blog posts. I will write everything else inside of this tag.

Import Tippy, its default styles, and our definitions.js:

import tippy from 'tippy.js';
import 'tippy.js/dist/tippy.css';
import { wordDefinitions } from '../../definitions.js';

Okay, now I need to write a script that will search through all of the text on the page and dynamically wrap the defined words with a <span>.

As an added complexity, I need to ensure that it’s not wrapping any words that are in headers, or code blocks. So I’ll only wrap words that are within <p> tags.

Start with an Event Listener so that the code waits for the HTML to finish loading before running the script:

document.addEventListener('DOMContentLoaded', function () {

We select all <p> elements on the page, iterate through them using a forEach method, and get the HTML content:

let paragraphs = document.querySelectorAll('p');

paragraphs.forEach((paragraph) => {
	let modifiedHTML = paragraph.innerHTML;

Now iterate through each word in wordDefinitions:

for (const word in wordDefinitions) {
    if (wordDefinitions.hasOwnProperty(word)) {

Next, I’ll create a regular expression to match with a word on the page. I’ll break down what the code does after I show you the code:

const regex = new RegExp('\\b' + word + '\\b', 'g');

This is just creating a search on the page for the word we need to find. The \\b is a boundary anchor, and is telling the search that there can be no characters before or after the word we’re searching for. So that way JS has a definition, while BOJSK does not. Hint: bojsk is not a real word.

g is a global flag and indicates that we should be searching for every instance of the word, not just the first one it finds.

Great! We found the words from our dictionary and now let’s do something with them:

modifiedHTML = modifiedHTML.replace(regex, (match) => {
    return `<span class="defined-word" data-tippy-content="${wordDefinitions[word]}">${match}</span>`;
});

We’re replacing the instance of any found word, with a line of code that includes a class for styling it, a variable for the word itself, and a variable that gets plugged into something that Tippy.js can use. Let’s apply our changes:

}

paragraph.innerHTML = modifiedHTML;

Almost done, now we just need to tell Tippy to interact with our words:

tippy('.defined-word', {
	arrow: true,
	interactive: true,
	placement: 'top',
	trigger: 'mouseenter click',
});
});
});

You can refer to Tippy’s docs for making any changes.

That’s it! All that’s left to do is styling!

CSS

.defined-word {
  cursor: pointer;
  color: var(--preformatted);
  border-bottom: 3px dotted var(--accent);
  font-weight: 500;
}

.defined-word:hover {
  color: var(--accent);
}

.tippy-content{
  font-weight: 400;
  font-style: normal;
  color:#fff;
}

I was really happy with the way Tippy looks out of the box, just had to do some things to keep the styling consistent. Because, the tooltips would inherit the font styles of the initial word.

Issues

Had an issue where Tippy was creating many many copies of itself while hovering over a word. Modified the Tippy part of the script we created to ensure that we only initialize Tippy when the elements are first added. Here’s the full script:

import tippy from 'tippy.js';
import 'tippy.js/dist/tippy.css';
import { wordDefinitions } from '../../definitions.js';

document.addEventListener('DOMContentLoaded', function () {
    let paragraphs = document.querySelectorAll('p');

    paragraphs.forEach((paragraph) => {
        let modifiedHTML = paragraph.innerHTML;

        for (const word in wordDefinitions) {
            if (wordDefinitions.hasOwnProperty(word)) {
                const regex = new RegExp('\\b' + word + '\\b', 'g');

                modifiedHTML = modifiedHTML.replace(regex, (match) => {
                    return `<span class="defined-word" data-tippy-content="${wordDefinitions[word]}">${match}</span>`;
                });
            }
        }

        paragraph.innerHTML = modifiedHTML;

        const newDefinedWords = paragraph.querySelectorAll('.defined-word:not([data-tippy-initialized])');

        tippy(newDefinedWords, {
            arrow: true,
            interactive: true,
            placement: 'top',
            trigger: 'mouseenter click',
            allowHTML: true,
        });

        newDefinedWords.forEach((element) => {
            element.setAttribute('data-tippy-initialized', 'true');
        });
    });
});

All done

Snigglewinks and Sifferdips

This probably isn’t the best way to go about this, but it works for me. I needed something simple and straightforward.

Let me know if you have any questions, and please utilize the feature on this site so it doesn’t go to waste!