How close a pop-up bottom footnote by clicking anywhere

I managed to realize a footnotes “system”, with a pop-up at the bottom of a window (as I said in another, recent, post). My present code works, but to close the bottom pop-up I have either to scroll the page or to click on a “x” inside each pop-up.
I wonder if, and how would be possible to close the pop-up by clicking anywhere (and not only, or not at all, over the “x” inside the pop-up)?
I tried adding window.addEventListener("click", handler("close")); after (or even replacing) window.addEventListener("scroll", handler("close"));
But unsuccessfully.
My whole js code is:

function getFootnoteContent(index) {
    const id = "fn:" + index;
    const fn = document.getElementById(id);
    return fn.innerHTML.trim();
};

function footnotePopup(showIndex, showCloseBtn) {
    const popupWrapper = document.querySelector("#popup-wrapper");
    
    // Set whether to display index and/or close button. Default is true for both
    if (showIndex === undefined) {
        showIndex = true;
    } 
    
    if (showCloseBtn === undefined) {
        showCloseBtn = false;
    }

    // Create main container that will hold footnote content
    const popupContent = popupWrapper.appendChild(document.createElement("div"));
    popupContent.id = "popup-content";

    let popupIndex = null;
    if (showIndex) {
        popupIndex = popupWrapper.insertBefore(document.createElement("div"), popupContent);
        popupIndex.id = "popup-index";
    }

    let popupCloseButton = null;
    if (showCloseBtn) {
        popupCloseButton = popupWrapper.appendChild(document.createElement("div"));
        popupCloseButton.innerHTML = "[x]";
        popupCloseButton.id = "popup-close";
    }

    // Remove redundant [return] links from footnote list (optional)
    const fnReturns = document.querySelectorAll("a.footnote-return");
    fnReturns.forEach(function(fnReturn) {
        const parent = fnReturn.parentNode;
        parent.removeChild(fnReturn);
    });

    const fnRefs = document.querySelectorAll("a[id^='fnref:']");
    fnRefs.forEach(function(fnRef) {
        fnRef.addEventListener("click", handler("refs", fnRef));
    });

    window.addEventListener("scroll", handler("close"));
    //window.addEventListener("click", handler("close"));

    if (showCloseBtn) {
        popupCloseButton.addEventListener("click", handler("close"));
    }

    function handler(type, node) {
        return function(event) {
            if (type === "close") {
                popupWrapper.style.display = "none";
            }

            if (type === "refs") {
                event.preventDefault();

                const index = node.id.substring(6);

                if (showIndex) {
                    popupIndex.innerHTML = index + ".";
                }

                popupContent.innerHTML = getFootnoteContent(index);
                popupWrapper.style.display = "flex";
            }
        };
    };
};

footnotePopup(false, true);

Thank you!

I believe detecting a click event on the body or HTML of the page would give you the hook you need to then run any action. Keep in mind that events bubble up to higher level elements.

1 Like

Thank you, but my knowledge of javascript is too poor to understand fully what I should change in my code (even with the help of the linked page) …

EDIT

Maybe I should use event.stopPropagation();, but how?

You’re on the right track.

If the user clicked on a refs link, not only should you prevent the default, you should prevent propagation.
Then you can bind a close handler to a higher level object, like the body.

1 Like

It is too difficult for my knowledge.
And I don’t understand why window.addEventListener("click", handler("close")); doesn’t work…

EDIT

I tried this, unsuccessfully:

const fnRefs = document.querySelectorAll("a[id^='fnref:']");
    fnRefs.forEach(function(fnRef) {
        event.stopPropagation(fnRef);
        fnRef.addEventListener("click", handler("refs", fnRef));
    });

I noticed that this code works somehow

    const fnRefs = document.querySelectorAll("a[id^='fnref:']");
    fnRefs.forEach(function(fnRef) {
        fnRef.addEventListener("click", handler("refs", fnRef));
    });
    
    event.stopPropagation();

But this way I cannot close the pop-up, even it changes as expected by clicking a note ref.

This may be a bit simplistic but seems to work.

 document.addEventListener(
    "click",
    function (event) {
      if (
        event.target.matches("a[id^='fnref']") ||
        event.target.matches("#popup-content")
      ) {
        return;
      }
      popupWrapper.style.display = "none";
    },
    false
  );

It may not be the best way though :slight_smile:

I put it here:

function getFootnoteContent(index) {
  const id = "fn:" + index;
  const fn = document.getElementById(id);
  return fn.innerHTML.trim();
}

function footnotePopup(showIndex, showCloseBtn) {
  const popupWrapper = document.querySelector("#popup-wrapper");

  // Set whether to display index and/or close button. Default is true for both
  if (showIndex === undefined) {
    showIndex = true;
  }

  if (showCloseBtn === undefined) {
    showCloseBtn = false;
  }

  // Create main container that will hold footnote content
  const popupContent = popupWrapper.appendChild(document.createElement("div"));
  popupContent.id = "popup-content";

  let popupIndex = null;
  if (showIndex) {
    popupIndex = popupWrapper.insertBefore(
      document.createElement("div"),
      popupContent
    );
    popupIndex.id = "popup-index";
  }

  let popupCloseButton = null;
  if (showCloseBtn) {
    popupCloseButton = popupWrapper.appendChild(document.createElement("div"));
    popupCloseButton.innerHTML = "[x]";
    popupCloseButton.id = "popup-close";
  }

  // Remove redundant [return] links from footnote list (optional)
  const fnReturns = document.querySelectorAll("a.footnote-return");
  fnReturns.forEach(function (fnReturn) {
    const parent = fnReturn.parentNode;
    parent.removeChild(fnReturn);
  });

  const fnRefs = document.querySelectorAll("a[id^='fnref:']");
  fnRefs.forEach(function (fnRef) {
    fnRef.addEventListener("click", handler("refs", fnRef));
  });

  window.addEventListener("scroll", handler("close"));
  
  document.addEventListener(
    "click",
    function (event) {
      if (
        event.target.matches("a[id^='fnref']") ||
        event.target.matches("#popup-content")
      ) {
        return;
      }
      popupWrapper.style.display = "none";
    },
    false
  );

  if (showCloseBtn) {
    popupCloseButton.addEventListener("click", handler("close"));
  }

  function handler(type, node) {
    console.log("test");
    return function (event) {
      if (type === "close") {
        popupWrapper.style.display = "none";
      }

      if (type === "refs") {
        event.preventDefault();

        const index = node.id.substring(6);

        if (showIndex) {
          popupIndex.innerHTML = index + ".";
        }

        popupContent.innerHTML = getFootnoteContent(index);
        popupWrapper.style.display = "flex";
      }
    };
  }
}

footnotePopup(false, true);
2 Likes

very good! :grinning: :+1: :+1:
Thank you! This is what I was searching for.

I had to check this, but you can match multiple comma separated selectors.

So in theory you could instead write that as

if (
  event.target.matches("a[id^='fnref'], #popup-content")
)

Just a simple test here

https://codepen.io/rpg2019/pen/VwGpMQY

1 Like

I did wonder :slight_smile:

That’s much neater.