Creating a unique custom bottom position for the popover and ensuring it is fixed while allowing it to expand

Creating a customized popover similar to Bootstrap's popover, I have successfully implemented popovers in all directions except for the top direction. My challenge lies in fixing the popover at the bottom just above the button that triggers it, and ensuring that as the content inside the popover grows in height, it expands towards the top while keeping the bottom portion fixed.

let popoverBtn = document.querySelectorAll("[data-toggle='popover']");
let popover = document.querySelector(".popover-overlay");
let popoverHeading = document.querySelector(".popover-overlay-heading h4");
let popoverContent = document.querySelector(".popover-overlay-content p");
for (let i = 0; i < popoverBtn.length; i++) {
  if (popoverBtn[i]) {
    popoverBtn[i].addEventListener("click", function(e) {
      let top = e.target.offsetTop;
      let left = e.target.offsetLeft;
      let width = e.target.offsetWidth;
      let height = e.target.offsetHeight;
      let heading = e.target.getAttribute("title");
      let description = e.target.getAttribute("data-content");
      let popoverHeight = parseFloat(getComputedStyle(popover).height);
      let popoverWidth = parseFloat(getComputedStyle(popover).width);
      let dir = e.target.getAttribute("data-placement");
      popoverHeading.textContent = heading;
      popoverContent.textContent = description;
      popover.classList.add("active");
      popover.style.top = '0px';
      popover.style.left = '0px'

      switch (dir) {
        case "top":
          popover.style.top = top - popoverHeight - 20 + "px";
          popover.style.left = left + "px";
          popover.style.bottom = top;
          break;
        case "bottom":
          popover.style.top = top + height + 5 + "px";
          popover.style.left = left + "px";
          break;
        case "left":
          popover.style.top = top + "px";
          popover.style.left = left - popoverWidth - 20 + "px";
          break;
        case "right":
          popover.style.top = top + "px";
          popover.style.left = left + width + 30 + "px";
          break;
        default:
          popover.style.top = top + "px";
          popover.style.left = left + width + 30 + "px";
          break;
      }



    })
  }
}
body {
  margin: 0px;
  padding: 0px;
  font-family: sans-serif;
}

h3,
h4,
p {
  margin: 0px;
}

.container {
  width: 100%;
}

.bg-gray {
  background-color: rgba(0, 0, 0, 0.3);
}

.bg-light-gray {
  background-color: rgba(0, 0, 0, 0.1);
}

section {
  min-height: 400px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 20px;
}

section button {
  display: inline-block;
  border: none;
  background-color: #dc3545;
  color: #fff;
  padding: 0.5rem 1rem;
  font-size: 1.25rem;
  line-height: 1.5;
  border-radius: 0.3rem;
}

.popover-overlay {
  position: fixed;
  z-index: 100;
  border-radius: 5px;
  border: 1px solid rgba(0, 0, 0, 0.1);
  box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1);
  display: none;
  width: 280px;
  height: 121px;
}

.popover-overlay.active {
  display: block;
}

.popover-overlay-heading {
  background-color: #f7f7f7;
  border-bottom: 1px solid #d3d3d3;
  padding: 1rem;
}

.popover-overlay-content {
  background-color: #fff;
  padding: 1rem;
}
<div class="popover-overlay">
  <div class="popover-overlay-heading">
    <h4></h4>
  </div>
  <div class="popover-overlay-content">
    <p></p>
  </div>
</div>
<div class="container">
  <section class="bg-gray">
    <button data-placement="top" type="button" class="btn btn-lg btn-danger" data-toggle="popover" title="Popover title" data-content="It's very engaging. Right? And here's some amazing content. It's very engaging. It's very engaging. Right? And here's some amazing content. It's very engaging.">Popover Top</button>
    <button data-placement="left" type="button" class="btn btn-lg btn-danger" data-toggle="popover" title="Popover title" data-content="And here's some amazing content. It's very engaging. Right?">Popover Left</button>
    <button data-placement="right" type="button" class="btn btn-lg btn-danger" data-toggle="popover" title="Popover title" data-content="And here's some amazing content. It's very engaging. Right?">Popover Right</button>
    <button data-placement="bottom" type="button" class="btn btn-lg btn-danger" data-toggle="popover" title="Popover title" data-content="And here's some amazing content. It's very engaging. Right?">Popover Bottom</button>
  </section>
</div>

Answer №1

The height of the top popover element needs to be redefined

let popoverBtn = document.querySelectorAll("[data-toggle='popover']");
      let popover = document.querySelector(".popover-overlay");
      let popoverHeading = document.querySelector(".popover-overlay-heading h4");
      let popoverContent = document.querySelector(".popover-overlay-content p");
      for (let i = 0; i < popoverBtn.length; i++) {
        if (popoverBtn[i]) {
          popoverBtn[i].addEventListener("click", function (e) {
            let top = e.target.offsetTop;
            let left = e.target.offsetLeft;
            let width = e.target.offsetWidth;
            let height = e.target.offsetHeight;
            let heading = e.target.getAttribute("title");
            let description = e.target.getAttribute("data-content");
            let popoverHeight = parseFloat(getComputedStyle(popover).height);
            let popoverWidth = parseFloat(getComputedStyle(popover).width);
            let dir = e.target.getAttribute("data-placement");
            popoverHeading.textContent = heading;
            popoverContent.textContent = description;
            popover.classList.add("active");
            popover.style.top = "0px";
            popover.style.left = "0px";

            switch (dir) {
              case "top":
                popover.style.height = popoverHeight + "px";
                popoverHeight = parseFloat(getComputedStyle(popover).height);
                popover.style.top = top - popoverHeight - 1 + "px";
                popover.style.left = left + "px";
                popover.style.bottom = top;
                break;
              case "bottom":
                popover.style.top = top + height + 5 + "px";
                popover.style.left = left + "px";
                break;
              case "left":
                popover.style.top = top + "px";
                popover.style.left = left - popoverWidth - 20 + "px";
                break;
              case "right":
                popover.style.top = top + "px";
                popover.style.left = left + width + 30 + "px";
                break;
              default:
                popover.style.top = top + "px";
                popover.style.left = left + width + 30 + "px";
                break;
            }
          });
        }
      }
 body {
        margin: 0px;
        padding: 0px;
        font-family: sans-serif;
      }

      h3,
      h4,
      p {
        margin: 0px;
      }

      .container {
        width: 100%;
      }

      .bg-gray {
        background-color: rgba(0, 0, 0, 0.3);
      }

      .bg-light-gray {
        background-color: rgba(0, 0, 0, 0.1);
      }

      section {
        min-height: 400px;
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 20px;
      }

      section button {
        display: inline-block;
        border: none;
        background-color: #dc3545;
        color: #fff;
        padding: 0.5rem 1rem;
        font-size: 1.25rem;
        line-height: 1.5;
        border-radius: 0.3rem;
      }

      .popover-overlay {
        position: fixed;
        z-index: 100;
        border-radius: 5px;
        border: 1px solid rgba(0, 0, 0, 0.1);
        box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.1);
        display: none;
        width: 280px;
        /* height: 121px; */
        height: fit-content;
      }

      .popover-overlay.active {
        display: block;
      }

      .popover-overlay-heading {
        position: relative;
        background-color: #f7f7f7;
        border-bottom: 1px solid #d3d3d3;
        padding: 1rem;
      }

      .popover-overlay-content {
        position: relative;
        background-color: #fff;
        padding: 1rem;
      }
  <div class="popover-overlay">
      <div class="popover-overlay-heading">
        <h4></h4>
      </div>
      <div class="popover-overlay-content">
        <p></p>
      </div>
    </div>
    <div class="container">
      <section class="bg-gray">
        <button data-placement="top" type="button" class="btn btn-lg btn-danger" data-toggle="popover" title="Popover title"
         data-content="It's very engaging. Right? And here's some amazing content. It's very engaging. It's very engaging. Right? And here's some amazing content. It's very engaging.">
          Popover Top
        </button>
        <button
          data-placement="left"
          type="button"
          class="btn btn-lg btn-danger"
          data-toggle="popover"
          title="Popover title"
          data-content="And here's some amazing content. It's very engaging. Right?"
        >
          Popover Left
        </button>
        <button
          data-placement="right"
          type="button"
          class="btn btn-lg btn-danger"
          data-toggle="popover"
          title="Popover title"
          data-content="And here's some amazing content. It's very engaging. Right?"
        >
          Popover Right
        </button>
        <button
          data-placement="bottom"
          type="button"
          class="btn btn-lg btn-danger"
          data-toggle="popover"
          title="Popover title"
          data-content="And here's some amazing content. It's very engaging. Right?"
        >
          Popover Bottom
        </button>
      </section>
    </div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Shuffling 4 div elements horizontally using Javascript and JQuery

I have a row of 4 div elements (cards) that I want to mix up every time the page is refreshed. How can I achieve this? Currently, I am using the following code: var random = Math.floor(Math.random() * $(".card").length); $(".card") .hide() ...

Measuring Time Passed with JavaScript

I've been working on a small piece of code that needs to achieve three tasks: User should input their birthday User must be able to view the current date When the user clicks a button, they should see the time elapsed between their birthday and the ...

A Foolproof Method to Dynamically Toggle HTML Checkbox in ASP.NET Using JavaScript

Although there have been numerous inquiries related to this subject, I haven't been able to find a solution specific to my situation. I currently have a gridview that contains checkboxes. What I'm trying to achieve is that when I select the chec ...

Executing jQuery callback functions before the completion of animations

My issue revolves around attempting to clear a div after sliding it up, only to have it empty before completing the slide. The content I want to remove is retrieved through an Ajax call. Below you will find my complete code snippet: $('.more& ...

The perplexing simplicity of closure

Currently, I am endeavoring to enhance my knowledge of JavaScript closures. Let's consider the following two scenarios in Node.js: function A(){ console.log(data); //this will result in a null pointer } module.exports = function(data){ re ...

JQuery Load fails to load in real-time

I have a PHP file where I am using JQuery to load the content of another file called 'live.php' which should display the current time, but for some reason it is not loading live. Can anyone help me troubleshoot this issue? It used to work perfect ...

Learn how to manipulate the DOM by dynamically creating elements and aligning them on the same line

Providing some context for my page: The section I have always contains a single input field. Below that, there is an "add" button which generates additional input fields. Since only one field is required on the screen, the following fields come with a "de ...

arrange fixed-top elements in a stacked manner using Bootstrap 4

I am looking to inform users about enabling JavaScript for optimal use of the website, and I want this message to appear above the navigation bar using Bootstrap. However, currently they are stacking on top of one another instead of displaying in the desir ...

Leveraging $this in conjunction with a jQuery plugin

I'm experimenting with a code snippet to reverse the even text in an unordered list: $(document).ready(function () { $.fn.reverseText = function () { var x = this.text(); var y = ""; for (var i = x.length - 1; i >= 0; ...

Solution for dropdown boxes malfunctioning with required fields

Using Bootstrap 3 for field validation on forms has been effective, but there seems to be an issue with certain browsers such as ios safari not validating [required] form items. To address this, I created a script that checks if an element is marked as req ...

Having trouble connecting v-model to vue-date-picker

My experience with the vue-date-picker and v-model for two-way data binding has been interesting. Initially, I set the value to a date (referred as startDate in this case) and printed the passed value (i.e. startDate) in the console. The initial value pa ...

Result of utilizing Bootstrap Radio buttons

Looking at the HTML snippet below, I am trying to display two lines of radio buttons. However, when I click on a radio button in the first line and then proceed to click any radio button in the second line, the result of the radio button in the first line ...

Is it possible to declare variables within a React 'render' function?

I have data arranged in multiple columns on several rows, with a react element corresponding to each data element. The number of elements on the first row can vary between 4 and 6. For instance, I may display a user's name, birthday, and email. If t ...

Differences between JSX.Element, ReactNode, and ReactElement: When should each be utilized?

Currently in the process of transitioning a React application to TypeScript. Everything seems to be going smoothly, however I've encountered an issue with the return types of my render functions, specifically within my functional components. In the p ...

Show the HTTP parameter only if the value is not empty

When developing a React application, I've been using the following code snippet to set a value as a URL path parameter: const [exchangeId, setExchangeId] = useState(''); ..... const endURL = `?page=${paginationState.activePage}&so ...

Looking for a jQuery plugin that helps with form alignment?

In a blog post comment, I came across an interesting jQuery form alignment plugin: jQuery.fn.autoWidth = function(options) { var settings = { limitWidth : false } if(options) { jQuery.extend(settings, options); }; ...

Connecting CSS and JS files in JSP was a bit of a challenge

Just starting out with jsp and using Glassfish server via netbeans. Struggling to link my jsp file with css and javascripts. Below is the structure of my files: Web Pages --Web-Inf --assets --css --style.css --js --jquery.js ...

How can I access the data variables from a JSON request within a function?

My task involves loading multiple JSON files from an array called bunchOfData, with each file having a corresponding URL. How can I access my variable "myI" within the processData function? for(var i = 0; i < bunchOfData.length; i++){ $.getJS ...

What is the best way to populate empty dates within an array of objects using TypeScript or JavaScript?

I am trying to populate this object with dates from today until the next 7 days. Below is my initial object: let obj = { "sessions": [{ "date": "15-05-2021" }, { "date": "16-05-2021" }, { "date": "18-05-2021" }] } The desired ...

Symfony 4 Forms: Enhancing User Experience with Prototypes

Recently, I've delved into Symfony 4 and encountered an issue with rendering a form featuring a ChoiceType Field with numeric choices. The goal is to allow the user to select a specific number of tags. Here's a snippet from my controller: class ...