🛠️ Popover: Making popovers with JavaScript

Hey ,

I'm thrilled to help you learn JavaScript. Unfortunately, you've landed on a page where you cannot access with your current purchase.

Please upgrade (use this link) access this content.

I'm super eager to help you learn more!

🛠️ Popover: Making popovers with JavaScript

When we created popovers in the previous few lessons, we had to make them manually and add them into the HTML. This method is great for creating custom popovers.

But if you need a generic popover that contains one paragraph of text, you can create the popover programmatically.

Preparations

Let’s start by deleting the bottom popover. We’ll create it with JavaScript.

<!-- Delete this -->
<div id="pop-4" class="popover" data-position="bottom">
  <p>The quick brown fox jumps over the lazy dog.</p>
</div>

Making a popover with JavaScript

Let’s create a function called createPopover to make a popover.

const createPopover = _ => {
  // ...
}

Each popover is a <div> element. We can create the <div> with createElement.

const createPopover = _ => {
  const popover = document.createElement('div')
}

Each popover has the popover class. We can add the class with classList.add.

const createPopover = _ => {
  const popover = document.createElement('div')
  popover.classList.add('popover')
}

We need to know which position to display the popover (top, right, bottom, or left). We can get the direction from the trigger.

const createPopover = popoverTrigger => {
  // ...
  popover.dataset.position = popoverTrigger.dataset.popoverPosition
}

We also need to set the popover’s id to the same value as the trigger’s data-target attribute.

const createPopover = popoverTrigger => {
  // ...
  popover.id = popoverTrigger.dataset.target
}

Finally, the popover needs content. We can send content from the trigger to the popover through another custom attribute. Let’s call it data-content.

<button
  id="pop-4"
  class="popover-trigger"
  data-popover-position="bottom"
  data-content="Hello world!"
>
  <svg viewBox="0 0 40 20">
    <use xlink:href="#arrow"></use>
  </svg>
</button>
const createPopover = popoverTrigger => {
  // ...

  const p = document.createElement('p')
  p.textContent = popoverTrigger.dataset.content

  popover.appendChild(p)
}

You should see the correct Popover’s HTML if you log it into the console. (You’ll have to comment out the forEach code to log this).

const createPopover = popoverTrigger => {
  // ...
  console.log(popover)
}

const bottomPopoverTrigger = document.querySelector('.popover-trigger[data-popover-position="bottom"]')
const popover = createPopover(bottomPopoverTrigger)
Logs the popover into the console.

The popover will always be placed as a direct descendant of the <bod> element. Since that’s the case, we can append the popover to the DOM inside createPopover.

const createPopover = popoverTrigger = {
  // ...
  document.body.appendChild(popover)
}
Created the bottom popover.

Positioning the created popover

We assumed all popovers are in the DOM in the forEach code.

popoverTriggers.forEach(popoverTrigger => {
  // This assumes the popover is in the DOM
  const popover = document.querySelector(`#${popoverTrigger.dataset.target}`)

  // ...
})

But we know that assumption is invalid now. We need to create popovers when we can’t find them in the DOM.

popoverTriggers.forEach(popoverTrigger => {
  let popover = document.querySelector(`#${popoverTrigger.dataset.target}`)

  if (!popover) {
    createPopover(popoverTrigger)
  }
  // ...
})

Next, we need to get the created popover from the DOM to use it in calculatePopoverPosition. We can get the popover if we return the popover from createPopover.

const createPopover = popoverTrigger = {
  // ...
  return popover
}
popoverTriggers.forEach(popoverTrigger => {
  // ...
  if (!popover) {
    popover = createPopover(popoverTrigger)
  }
  // ...
})

Now the popover should work as before.

Created the bottom popover.

A small cleanup

Here are the first few lines of code we wrote in the forEach block.

popoverTriggers.forEach(popoverTrigger => {
  let popover = document.querySelector(`#${popoverTrigger.dataset.target}`)

  if (!popover) {
    popover = createPopover(popoverTrigger)
  }
  // ...
})

We’re trying to do two things here:

  1. Find the popover from the DOM
  2. If it fails, create a popover

We can simplify these two statements with an OR operator.

popoverTriggers.forEach(popoverTrigger => {
  const popover = document.querySelector(`#${popoverTrigger.dataset.target}`) || createPopover(popoverTrigger)
  // ...
})

The document.querySelector part is really hard to read. We can put it into a function called getPopover to simplify things.

const getPopover = popoverTrigger => {
  return document.querySelector(`#${popoverTrigger.dataset.target}`)
}

Using it:

popoverTriggers.forEach(popoverTrigger => {
  const popover = getPopover(popoverTrigger) || createPopover(popoverTrigger)
  // ...
})

That’s it!