bakrall
bakrall

Reputation: 633

Why several clicks fire for each instance of a Class?

I have three tooltip buttons on a page. I can close any open tooltip by clicking anywhere outside buttons. And this is what I came across:

In the code below, when I click on any place on the page, the handler of this part of code is activated $(document).on('click', (event) => this.closeOnOutsideClick(event)); I can see in inspector, that function closeOnOutsideClick is fired three times - it makes three checks for each tooltip button present on the page. I cannot figure out what mechanism is responsible for that and why the check if (!$(event.target).closest(this.$elem)) is not performed only once? My code can be found here and also below: https://jsfiddle.net/bakrall/786cz40L/

This is a simplified version of a more complex code just to give example of my issue:

const selectors = {
  tooltip: '.tooltip-container',
  tooltipButton: '.tooltip-button',
  tooltipMessage: '.tooltip-message'
}

class Tooltip {
  constructor(tooltip) {
    this.$elem = $(tooltip);
    this.$tooltipButton = this.$elem.find(selectors.tooltipButton);
    this.$tooltipMessage = this.$elem.find(selectors.tooltipMessage);
    this.$tooltipMessageText = this.$tooltipButton.attr('data-tooltip-content');

    this.bindUiEvents();
  }

  bindUiEvents() {
    $(document).on('click', (event) => this.closeOnOutsideClick(event));
    this.$tooltipButton.on('click', () => this.showTooltipMessage());
    this.$tooltipButton.on('blur', () => this.hideTooltip());
  }

  showTooltipMessage() {
    this.$tooltipMessage
      .text(this.$tooltipMessageText)
      .addClass('shown-message');
  }

  hideTooltip() {
    this.$tooltipMessage
      .text('')
      .removeClass('shown-message');
  }

  closeOnOutsideClick(event) {
    if (!$(event.target).closest(this.$elem)) {
      this.hideTooltip();
    }
  }
}


//class in another file
const tooltip = $('.tooltip-container');

tooltip.each(function(index, item) {
  new Tooltip(item);
})
.input-wrapper {
  margin-bottom: 2em;
}

.tooltip-container {
  position: relative;
  display: inline-block;
}

.tooltip-message {
  display: none;
  position: absolute;
  left: 100%;
  top: 0;
  width: 10em;
  padding: 0.5rem;
  background: #000;
  color: #fff;
}

.tooltip-message.shown-message {
  display: inline-block;
}

button {
  width: 1.2em;
  height: 1.2em;
  border-radius: 50%;
  border: 0;
  background: #000;
  font-family: serif;
  font-weight: bold;
  color: #fff;
} 

button:focus {
  outline: none;
  box-shadow: 0 0 0 0.25rem skyBlue;
}

input {
  display: block;
}
<!doctype html>
<html class="no-js" lang="">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="x-ua-compatible" content="ie=edge">
        <title>	</title>
        <meta name="description" content="">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="tooltip.css">
    </head>
    <body>
        <div class="input-wrapper">
            <label for="name">
                What's your name?
                <span class="tooltip-container">
                  <button class="tooltip-button" type="button" aria-label="more info"
                    data-tooltip-content="This clarifies whatever needs clarifying">i</button>
                  <span class="tooltip-message" role="status"></span>
                </span>
            </label>
            <input id="name" type="text"/>
        </div>
        <div class="input-wrapper">
            <label for="age">
                What's your age?
                <span class="tooltip-container">
                  <button class="tooltip-button" type="button" aria-label="more info"
                    data-tooltip-content="This is to know how old you are">i</button>
                  <span class="tooltip-message" role="status"></span>
                </span>
            </label>
            <input id="age" type="text"/>
        </div>
        <div class="input-wrapper">
            <label for="nationality">
                What's your nationality
                <span class="tooltip-container">
                  <button class="tooltip-button" type="button" aria-label="more info"
                    data-tooltip-content="What country are you from?">i</button>
                  <span class="tooltip-message" role="status"></span>
                </span>
            </label>
            <input id="nationality" type="text"/>
        </div>
        <script
          src="https://code.jquery.com/jquery-3.4.1.min.js"
          integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
          crossorigin="anonymous">
        </script>
        <script src="tooltip.js" async defer></script>
    </body>
</html>

Upvotes: 0

Views: 28

Answers (2)

Omn
Omn

Reputation: 3070

tooltip.each(function(index, item) {
  new Tooltip(item);
})

Since you instantiate 3 Tooltips, you bind a separate event listener to the document each time. Each listener is getting triggered with each click. However, each of those listeners has a different this which is what allows each listener to tell if its Tooltip was clicked and if not, hide it.

If you want a single listener you could store a list of all your Tooltips and have the event listener iterate through the list of Tooltips, closing all Tooltips that were not clicked.

Upvotes: 1

tom
tom

Reputation: 10549

Your click event is firing on mutliple elements, because you specified just (document). Maybe you can be more specific:

$(document).on('click', '.input-wrapper', (event) => this.closeOnOutsideClick(event));

Upvotes: 0

Related Questions