1

A tooltip shall be displayed on hovering over an a tag. Yet, these criteria apply:

  • no "a:hover" should be used, no CSS classes in the head tag, it must be done with inline CSS
  • it must be pure js, no library
  • it must work without predefined IDs so that it can be used generically (the pattern is always like this: a tag is followed by a span tag, while the span tag content is the tooltip content)
  • html formatting should be possible (at least, basic formatting like b, u, ul/ol/li)

I might already have it (using onmouseover/onmouseout) but only almost. See the code:

  • How to make it work that the tooltip is always displayed below the actual a tag (currently, it's always displayed below the first a)?
  • An a tag is used within a p tag. How to to make it work (the whole span is currently not hidden)?

function show(item) {
     const str = item.nextElementSibling;
     str.style.visibility = "visible";
  }
  function hide(item) {
    const str = item.nextElementSibling;
    str.style.visibility = "hidden";
  }
<a style="border-bottom: 1px dotted #000000; color: #000000; outline: none; cursor: help; text-decoration: none; position: relative;" href="#" onMouseOver="show(this)" onMouseOut="hide(this)">Info 1</a>
<span style="visibility: hidden; color: #000000; cursor: help; padding: 0.8em 1em; background: #FFFFAA; border: 1px solid #FFAD33; -webkit-border-radius: 5px; -webkit-box-shadow: 5px 5px rgba(0, 0, 0, 0.1); position: absolute; left: 1em; top: 2em; z-index: 99; margin-left: 0; width: 250px;"><b>Some info 1:</b><ul><li>test 1</li><li>test 2</li></ul></span>

<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>

<a style="border-bottom: 1px dotted #000000; color: #000000; outline: none; cursor: help; text-decoration: none; position: relative;" href="#" onMouseOver="show(this)" onMouseOut="hide(this)">Info 2</a>
<span style="visibility: hidden; color: #000000; cursor: help; padding: 0.8em 1em; background: #FFFFAA; border: 1px solid #FFAD33; -webkit-border-radius: 5px; -webkit-box-shadow: 5px 5px rgba(0, 0, 0, 0.1); position: absolute; left: 1em; top: 2em; z-index: 99; margin-left: 0; width: 250px;"><b>Some info 2</b><ul><li>test 1</li><li>test 2</li></ul></span>

<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>

<a style="border-bottom: 1px dotted #000000; color: #000000; outline: none; cursor: help; text-decoration: none; position: relative;" href="#" onMouseOver="show(this)" onMouseOut="hide(this)">Info 3</a>
<span style="visibility: hidden; color: #000000; cursor: help; padding: 0.8em 1em; background: #FFFFAA; border: 1px solid #FFAD33; -webkit-border-radius: 5px; -webkit-box-shadow: 5px 5px rgba(0, 0, 0, 0.1); position: absolute; left: 1em; top: 2em; z-index: 99; margin-left: 0; width: 250px;"><b>Some info 3</b><ul><li>test 1</li><li>test 2</li></ul></span>

<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<a style="border-bottom: 1px dotted #000000; color: #000000; outline: none; cursor: help; text-decoration: none; position: relative;" href="#" onMouseOver="show(this)" onMouseOut="hide(this)">(Info 4)</a>
<span style="visibility: hidden; color: #000000; cursor: help; padding: 0.8em 1em; background: #FFFFAA; border: 1px solid #FFAD33; -webkit-border-radius: 5px; -webkit-box-shadow: 5px 5px rgba(0, 0, 0, 0.1); position: absolute; left: 1em; top: 2em; z-index: 99; margin-left: 0; width: 250px;"><b>Some info 4</b><ul><li>test 1</li><li>test 2</li></ul></span> Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p>
jennab
  • 101
  • 7
  • No further questions please, a post should contain a single question only. What comes to the requirements, I find it weird that stylesheets should not be used, that's pretty much against the best practices, and causes a lot of extra work when updating the page. – Teemu Apr 28 '21 at 07:59
  • Well, simply put: There are indeed reasons. I removed the extra questions. – jennab Apr 28 '21 at 08:06
  • I could imagine the page is converted to a docx or pdf or sth ... Take a look at [this fiddle](https://jsfiddle.net/gmafw1nL/). Though it's using only a single element for the tooltip, and definitely uses a stylesheet for styling, it provides you some tips and tricks for how to handle the markup in JS without ids and it also introduces [event delegation](https://stackoverflow.com/questions/1687296/what-is-dom-event-delegation), which is almost a must when implementing everything you need. – Teemu Apr 28 '21 at 08:15
  • Appreciate it. thanks. But since I feel and hope that I'm very close to a proper solution with my snippet, I'd really like to have a hint why mine is not working 100%. I think the sibling stuff is wrong. – jennab Apr 28 '21 at 08:57
  • The parents of the spans are statically positioned, hence they're not [offsetParents](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent) of the spans. You've to set `position: relative` to every element in the parent chain of the spans in order to make the closest parent element offsetParent instead of the body. That's one of the inconveniences inline styling comes along with. If the page is really converted to a format which provides inline styling, I'd suggest you to use a a convertible documentFragment and adding the styles to the fragment only. – Teemu Apr 28 '21 at 09:07
  • On the top of all that, the sibling idea is OK, but you could take the advantage of the event delegation also in this case, even with an inline listener. Just add the listeners on the body (and use `mouseenter/leave` instead of `mouseover/out`) instead of every `a` element. – Teemu Apr 28 '21 at 09:20
  • One of your problems is that as soon as you do the mouseover action the mouseout event is triggered as there's an element made visible over the a in the same spot. Looking at it on Firefox this causes massive flashing of the tooltip on and off, on my Chrome is remains steady but if you console log the event.type in show and hide you'll see them alternating in quick succession. – A Haworth Apr 28 '21 at 09:22
  • @AHaworth That happens because the tipbox is incorrectly positioned. Fixing the position will also fix this issue, as the box will never overlap with the cursor. – Teemu Apr 28 '21 at 09:24
  • 1
    @Teemu agreed - one reason tooltips are normally offset. – A Haworth Apr 28 '21 at 09:25
  • Guys, really like your input. Could you be so kind and help me with the code snippet. – jennab Apr 28 '21 at 09:35
  • We could fix the issues with this particular markup example, but it won't help you much with some other markup. The solution is that you'd learn the offsetParent behavior, then you can make the needed changes to any markup. There could also be a relatively simple solution to create a generic tooltip code using a stylesheet, and add the inline styles dynamically only when they're needed. But to implement this, we need to know, why the inline styles are needed in the first place. – Teemu Apr 28 '21 at 09:40
  • The "_the whole span is currently not hidden_" is due to the invalid markup. A block level element (`ul`) is not permitted content in an inline element. If you'll open Inspector, you can see, that `ul` is pulled out from `span` and `p`, hence it's not hidden. The spans should be divs if you want to include block level elements in the tooltip (use also `display: block/none` instead of `visible` when toggling the visibility). The inconsistency between the elements is due to the lack of the standard for invalid markup, rendering engines are allowed to parse and show such markup as they wish. – Teemu Apr 28 '21 at 10:03

1 Answers1

1

I removed all the inline event handlers you used in your HTML.

Check the JS comments for my explanation on the code.

const tooltipPadding = { top: 32, left: 16, }; // The extra em padding you set on the span converted to px 
const hide = element => element.style.visibility = 'hidden'; // Hide function

function show({ target }) { // Destrucutring the event obejct to get only the targetted element
  const element = target.nextElementSibling;
  const { left, top } = target.getBoundingClientRect(); // To get the position of the hovered a tag

  element.style.visibility = "visible";

  // To set the top and left position of the element
  element.style.left = `${left + tooltipPadding.left}px`;
  element.style.top = `${top + tooltipPadding.top}px`;
  target.addEventListener('mouseleave', _ => hide(element), { once: true }); // Adding an event to the a tag on mouseleave to hide the span only once.
}

// Select all the a tag and add the mouseenter event listener
[...document.querySelectorAll('a')].forEach(a => a.addEventListener('mouseenter', show));
<a style="border-bottom: 1px dotted #000000; color: #000000; outline: none; cursor: help; text-decoration: none; position: relative;" href="#">Info 1</a>
<span style="visibility: hidden; color: #000000; cursor: help; padding: 0.8em 1em; background: #FFFFAA; border: 1px solid #FFAD33; -webkit-border-radius: 5px; -webkit-box-shadow: 5px 5px rgba(0, 0, 0, 0.1); position: absolute; left: 1em; top: 2em; z-index: 99; margin-left: 0; width: 250px;"><b>Some info 1:</b><ul><li>test 1</li><li>test 2</li></ul></span>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>

<a style="border-bottom: 1px dotted #000000; color: #000000; outline: none; cursor: help; text-decoration: none; position: relative;" href="#">Info 2</a>
<span style="visibility: hidden; color: #000000; cursor: help; padding: 0.8em 1em; background: #FFFFAA; border: 1px solid #FFAD33; -webkit-border-radius: 5px; -webkit-box-shadow: 5px 5px rgba(0, 0, 0, 0.1); position: absolute; left: 1em; top: 2em; z-index: 99; margin-left: 0; width: 250px;"><b>Some info 2</b><ul><li>test 1</li><li>test 2</li></ul></span>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
    
<a style="border-bottom: 1px dotted #000000; color: #000000; outline: none; cursor: help; text-decoration: none; position: relative;" href="#">Info 3</a>
<span style="visibility: hidden; color: #000000; cursor: help; padding: 0.8em 1em; background: #FFFFAA; border: 1px solid #FFAD33; -webkit-border-radius: 5px; -webkit-box-shadow: 5px 5px rgba(0, 0, 0, 0.1); position: absolute; left: 1em; top: 2em; z-index: 99; margin-left: 0; width: 250px;"><b>Some info 3</b><ul><li>test 1</li><li>test 2</li></ul></span>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
    
<a style="border-bottom: 1px dotted #000000; color: #000000; outline: none; cursor: help; text-decoration: none; position: relative;" href="#">(Info 4)</a>
<span style="visibility: hidden; color: #000000; cursor: help; padding: 0.8em 1em; background: #FFFFAA; border: 1px solid #FFAD33; -webkit-border-radius: 5px; -webkit-box-shadow: 5px 5px rgba(0, 0, 0, 0.1); position: absolute; left: 1em; top: 2em; z-index: 99; margin-left: 0; width: 250px;"><b>Some info 4</b><ul><li>test 1</li><li>test 2</li></ul></span>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
a.mola
  • 2,222
  • 3
  • 16