sinaxace / Dynamic-Tooltip

In this vanilla JS script, I have a JSON dictionary list and within each index contains a word & definition property. The JavaScript program is meant to scan any HTML5 page, highlight any keywords that matches the word property from the JSON data and include a tooltip hover event with it's correct definition depending on the JSON definition property.
0 stars 0 forks source link

Would love to make this work! #1

Closed axelferdinand closed 1 year ago

axelferdinand commented 1 year ago

Hey there!

This is EXACTLY what I'm looking for... However, I can't make it work on it's own. Are there any dependencies?

axelferdinand commented 1 year ago

I now managed to make it work on a clean site, but I can't make it work with my site using AngularJS – do you know of any conflict? :)

axelferdinand commented 1 year ago

Never mind, friend – made it work by putting your wonderful code of JS inside an Angular controller! Thanks for this script, it's really wonderful!

sinaxace commented 1 year ago

That's so cool that you found a use for it! Hope the comments in the match.js script were helpful, good luck with your project!

axelferdinand commented 1 year ago

Yeah, it's really, really great! Just what I was looking for, and luckily I managed to google the problem in a way leading to this repo... Thanks!!

PS: Using ChatGPT, I did a few improvements to the code (or at least, ChatGPT claims it's improvements). Here's the final result:

/*######################--Start of Main--######################*/

// Define constants for class names and keys
const TEXT_CLASS_NAME = 'text';
const HIGHLIGHT_CLASS_NAME = 'highlight';
const TITLE_KEY = 'title'; // Key for title in dictionary entries
const DESCRIPTION_KEY = 'description'; // Key for description in dictionary entries

function main(dictionaryEntries) {
    console.log(dictionaryEntries);

    // Get all elements with class 'text'
    let textElements = document.querySelectorAll(`.${TEXT_CLASS_NAME}`);

    // Loop through all child nodes of the text elements
    for (let element of textElements) {
        for (let searchNode of element.childNodes) {
            listIteration(dictionaryEntries, searchNode, false);
        }
    }

    // Add event listeners for mouseover and mouseout events
    document.body.addEventListener("mouseover", (e) => listIteration(dictionaryEntries, e.target, true), true);
    document.body.addEventListener("mouseout", (e) => tooltip(null, e.target, false));
}

/*######################--End of Main--######################*/

// Function to iterate through dictionary entries and apply highlighting or tooltips
function listIteration(dictionaryEntries, currentNode, isEventTriggered) {
    for (let item of dictionaryEntries) {
        if (currentNode.hasChildNodes() && !isEventTriggered) {
            makeTargetable(currentNode, item[TITLE_KEY]);
        }
        if (isEventTriggered) {
            if (currentNode.classList.contains(item[TITLE_KEY].toLowerCase())) {
                tooltip(item[DESCRIPTION_KEY], currentNode, true);
            }
        }
    }
}

// Function to apply highlighting to a node
function makeTargetable(currentNode, title) {
    const REGEX = new RegExp(`\\b${title}\\b`, 'i');
    if (currentNode.nodeName === 'P' && currentNode.parentNode.nodeName !== 'A' && currentNode.innerText.match(REGEX) !== null) {
        let textNodes = Array.from(currentNode.childNodes).filter(node => node.nodeType === 3);
        textNodes.forEach(textNode => {
            let text = textNode.nodeValue;
            let match = REGEX.exec(text);
            if (match) {
                let before = document.createTextNode(text.substring(0, match.index));
                let highlighted = document.createElement('span');
                highlighted.className = `${HIGHLIGHT_CLASS_NAME} ${title.toLowerCase()}`;
                highlighted.textContent = match[0];
                let after = document.createTextNode(text.substring(match.index + match[0].length));
                currentNode.insertBefore(before, textNode);
                currentNode.insertBefore(highlighted, textNode);
                currentNode.insertBefore(after, textNode);
                currentNode.removeChild(textNode);
            }
        });
    }
}

// Function to show or hide tooltips
function tooltip(tip, target, isHovering) {
    const tooltipElement = document.getElementById('tooltip');
    if (isHovering) {
        if (!tooltipElement) {
            const tooltipSpan = document.createElement('span');
            tooltipSpan.id = 'tooltip';
            tooltipSpan.textContent = tip;
            target.parentNode.insertBefore(tooltipSpan, target.nextSibling);
        }
    } else {
        if (tooltipElement) {
            tooltipElement.parentNode.removeChild(tooltipElement);
        }
    }
}

// AJAX request to get dictionary entries
(function () {
    fetch('/api/dictionary.json')
        .then(response => {
            if (!response.ok) {
                throw new Error('Network response was not ok');
            }
            return response.json();
        })
        .then(data => {
            main(data);
        })
        .catch(error => {
            console.error('There has been a problem with your fetch operation: ', error);
        });
})();

It's supposed to be faster, and user more "modern" approaches. Also, CSS is moved out of the JS.

Have a great day!