0

I have this Web Component that has cards with tooltips.

I am trying to accomplish 3 things. I am trying use Vanilla Javascript vs jQuery or other libraries.

  1. Show when a button is clicked then hide if button is clicked again. This is currently working.

  2. Hide when clicked outside of button. I tried selecting the document then comparing clicked element with the element I want to target, having trouble with targeting.

  3. Only show 1 tooltip at a time so if 1 is open then you click another only the second one shows. Not sure how to approach this.

Here is what I have so far.

class BuildABox extends HTMLElement {
  constructor() {
    super();
    this.querySelectorAll('[data-tooltip]').forEach((button) =>
      button.addEventListener('click', this.toggleTooltip.bind(this))
    );
  }

  toggleTooltip(e) {
    e.preventDefault();
    const toolTip = e.currentTarget.lastChild.previousSibling;
    toolTip.classList.toggle('tw-invisible');
  }
}

customElements.define('build-a-box', BuildABox);
<button id="info-btn" aria-label="Information" type="button" data-info="{{ block.settings.product_description }}">
  <div data-tooltip="tooltip" class="tw-w-12">
    <svg  xmlns="http://www.w3.org/2000/svg"
          viewBox="0 0 24 24"
          fill="currentColor"
          class="w-12 h-12"
    >
      <path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm8.706-1.442c1.146-.573 2.437.463 2.126 1.706l-.709 2.836.042-.02a.75.75 0 01.67 1.34l-.04.022c-1.147.573-2.438-.463-2.127-1.706l.71-2.836-.042.02a.75.75 0 11-.671-1.34l.041-.022zM12 9a.75.75 0 100-1.5.75.75 0 000 1.5z" clip-rule="evenodd" />
      </svg>
    {% comment %} This is tooltip to hide/show {% endcomment %}
    <div
      id="tooltip"
      class="tw-invisible z-50 tw-absolute tw-top-24  tw-max-w-[300px] tw-bg-blue-400 tw-text-white tw-border-graphite tw-border-2 tw-overflow-auto tw-rounded-2xl tw-p-4 tw-mt-1"
                              >
      <p>{{ block.settings.product_description }}</p>
    </div>
  </div>
</button>
Shakya Peiris
  • 504
  • 5
  • 11
Mike Haslam
  • 245
  • 3
  • 21
  • Please add an [Minimal minimal-reproducible-example StackOverflow Snippet](https://stackoverflow.com/help/minimal-reproducible-example) to your post. It will help readers execute your code with one click. And help create answers with one click. See [How to add a StackOverflow snippet](https://meta.stackoverflow.com/questions/269753/feedback-requested-runnable-code-snippets-in-questions-and-answers) – Danny '365CSI' Engelman Feb 10 '23 at 11:20
  • Thanks Danny I will get up to speed on how to set up a snippet – Mike Haslam Feb 10 '23 at 16:21
  • Here is an example with an external style sheet: https://github.com/mdn/web-components-examples/blob/main/popup-info-box-external-stylesheet/main.js and this one has internal styles but looks more like "CSS" (well because it IS) to someone used to that: https://github.com/mdn/web-components-examples/blob/main/popup-info-box-web-component/main.js – Mark Schultheiss Feb 10 '23 at 21:23
  • Thanks Mark for the resources – Mike Haslam Feb 10 '23 at 22:09

2 Answers2

1

You don't want to create dependencies, thus keep as much of the logic inside the Web Component

Using shadowDOM here because it keeps the Web Component small
and you can style with global CSS using ::part

Key concept: <my-button tooltip>
The tooltip attribute determines of it is visible, or not

I whacked in additional styling to show more styling options.
For deep-deep-details on <slot> and ::slotted read: ::slotted CSS selector for nested children in shadowDOM slot

same code in: https://jsfiddle.net/WebComponents/xg3ns6od/

customElements.define('my-button', class extends HTMLElement {
  constructor() {
    super().attachShadow({mode:"open"}).innerHTML =
    `<style>`+
      `button{ width:120px; cursor:pointer } ::slotted(div){ pointer-events:none }` + 
      `[name="tooltip"]::slotted(div) { color:blue; font-size: 1.2rem }`+
      `[name="label"]::slotted(div) { font-size:1em;font-weight:bold }`+
      `:host([tooltip]) svg { fill:green!important } `+
      `:host(:hover:not([tooltip])) path{ scale:1.2; transform-origin: center }` +
      `:host(:not([tooltip])) [name="tooltip"]::slotted(div) { visibility:hidden } `+
    `</style>`+
    `<button part="button"><svg part="icon" viewBox="0 0 2400 2400">`+
      `<path fill-rule="evenodd" d="m225 1200c0-539 437-975 975-975s975 437 975 975-437 975-975 975-975-436-975-975zm871-144c115-57 244 46 213 171l-71 284 4-2a75 75 90 0167 134l-4 2c-115 57-244-46-213-171l71-284-4 2a75 75 90 11-67-134l4-2zm104-156a75 75 90 100-150 75 75 90 000 150z"/>`+
      `</svg><slot name="tooltip"></slot>`+
            `<slot name="label"></slot></button>`;
  }
  connectedCallback(){
    this.onclick = () => this.tooltip = !this.tooltip;
    this.globalListener = document.addEventListener("click",
                                  (evt) => this.tooltip = evt.target == this )
  }
  get tooltip()   { return this.hasAttribute("tooltip") }
  set tooltip(val){ this.toggleAttribute("tooltip",val) }

  disconnectedCallback(){
    document.removeEventListener("click", this.globalListener);
  }
});
/* style parts IN shadowDOM */
my-button::part(icon){ fill:red }

/* style lightDOM! */
my-button[tooltip] [slot="tooltip"] { background:lightgreen }
<my-button>
  <div slot="tooltip">Tooltip 1</div>
  <div slot="label">Product Description</div>
</my-button>
<my-button tooltip>
  <div slot="tooltip">Tooltip 2</div>
  <div slot="label">Product Description</div>
</my-button>
<my-button>
  <div slot="tooltip">Tooltip 3</div>
  <div slot="label">Product Description</div>
</my-button>
Danny '365CSI' Engelman
  • 16,526
  • 2
  • 32
  • 49
  • 1
    Danny thank you so much it really means a lot to me. Very elegant solution.Thanks for the feedback on Web Components I'm just getting started with these so very helpful. – Mike Haslam Feb 10 '23 at 19:05
0

To expound upon my prior comments which I deleted; I updated your code slightly and added some CSS to show it clearly here. I have a data element to choose a target and then use that in the click handler.

class BuildABox extends HTMLElement {
  constructor() {
    super();
    this.querySelectorAll('[data-tooltip-target]').forEach((button) => {
      button.addEventListener('click', this.toggleTooltip.bind(this))
    });
  }

  toggleTooltip(e) {
    e.preventDefault();
    const mytarget = e.currentTarget.dataset.tooltipTarget;
    const toolTip = e.currentTarget.querySelector(`[data-tooltip="${mytarget}"]`);
    toolTip.classList.toggle('tw-invisible');
  }
}

customElements.define('build-a-box', BuildABox);
.tw-invisible {
  display: none;
}

.tooltip-text {
  color: blue;
  font-size: 1.5rem;
}
<build-a-box>
  <button id="info-btn" aria-label="Information" type="button" data-info="{{ block.settings.product_description }}" data-tooltip-target="mine">
  <div class="tw-w-12">
    <svg  xmlns="http://www.w3.org/2000/svg"
          viewBox="0 0 24 24"
          fill="currentColor"
          class="w-12 h-12"
    >
      <path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm8.706-1.442c1.146-.573 2.437.463 2.126 1.706l-.709 2.836.042-.02a.75.75 0 01.67 1.34l-.04.022c-1.147.573-2.438-.463-2.127-1.706l.71-2.836-.042.02a.75.75 0 11-.671-1.34l.041-.022zM12 9a.75.75 0 100-1.5.75.75 0 000 1.5z" clip-rule="evenodd" />
      </svg>
    <tool-tip data-tooltip="mine" class="tw-invisible">
    <span class="tooltip-text">This is tooltip to hide/show</span>
    </tool-tip>
    <div class="z-50 tw-absolute tw-top-24  tw-max-w-[300px] tw-bg-blue-400 tw-text-white tw-border-graphite tw-border-2 tw-overflow-auto tw-rounded-2xl tw-p-4 tw-mt-1">
      <p>{{ block.settings.product_description }}</p>
    </div> 
  </div>
</button>
</build-a-box>
Mark Schultheiss
  • 32,614
  • 12
  • 69
  • 100