Search code examples
javascriptiife

Call code outside of IIFE from within IIFE


I have code that successfully calls a function in an IIFE that, in turn, references code outside of the IIFE that I can't figure out how to call.

const msgbox = (function () {
  function showMsgBox(msg) {
    const modal = document.createElement("div");
    modal.classList.add("modal");
    modal.innerHTML = `<div class="modal-content"><p>${msg}</p></div>`;
    document.body.appendChild(modal);
    modal.style.display = "block";
    window.onclick = function (event) {
      if (event.target === modal) {
        modal.style.display = "none";
      }
    };
  }
  return {
    showMsgBox: showMsgBox
  };
})();

  const example = document.getElementById("example");

  function changeClass() {
    if (example.getAttribute("class") === "classTwo") {
      example.classList.remove("classTwo");
      example.classList.add("classOne");
    } else {
      example.classList.add("classTwo");
      example.classList.remove("classOne");
    }
  }

  document.querySelectorAll(".change_class").forEach((item) => {
    item.addEventListener("click", function (e) {
      changeClass();
    });
  });
  
  document.querySelector(".showmsg").addEventListener("click", function (e) {
    msgbox.showMsgBox(
      'Call Function outside of IIFE=> <button class="change_class">Change Class</button> (<= this button should have the same functionality as the other button)'
    );
  });

I tried putting the non-IIFE section into its own IIFE and giving it a separate namespace but that didn't work. The example above is a simplified version of my actual problem, but this encapsulates my problem more succinctly and makes it less confusing. Yes, I could make the IIFE simply a function and call it that way, but I'm trying to expand my knowledge.

Here's a link to the CodePen example I created for this: https://codepen.io/NoahBoddy/pen/PoOVMzK Hopefully, my explanation is clear. If not, I can provide more detail. Thanks!


Solution

  • The reason the new button doesn't run the click event code is because it isn't added to it

    Once

    document.querySelectorAll(".change_class").forEach((item) => {
      item.addEventListener("click", function (e) {
        changeClass();
      });
    });
    

    runs, that's it. Any NEW elements with that class don't make the above code run again automagically

    You could add the same event handler to the newly created button it once you've created the button - that's tedious, and duplicates code - if you need to make a change, you may need to change multiple places. A recipe for bad code.

    or ... here's a solution using event delegation

    const msgbox = (function () {
        function showMsgBox(msg) {
            const modal = document.createElement("div");
            modal.classList.add("modal");
            modal.innerHTML = `<div class="modal-content"><p>${msg}</p></div>`;
            document.body.appendChild(modal);
            modal.style.display = "block";
            window.onclick = function (event) {
                if (event.target === modal) {
                    modal.style.display = "none";
                }
            };
        }
        return {
            showMsgBox: showMsgBox
        };
    })();
    
    const example = document.getElementById("example");
    function changeClass() {
        if (example.getAttribute("class") === "classTwo") {
            example.classList.remove("classTwo");
            example.classList.add("classOne");
        } else {
            example.classList.add("classTwo");
            example.classList.remove("classOne");
        }
    }
    const change_class = document.getElementsByClassName("change_class");
    document.body.addEventListener('click', function(e) {
        if ([...change_class].includes(e.target)) {
            changeClass();
        }
    });
    
    document.querySelector(".showmsg").addEventListener("click", function (e) {
        msgbox.showMsgBox('<button class="change_class">Change Class</button>');
    });
    * { font-family: sans-serif; }
    /* The Modal (background) */
    .modal {
      display: none; /* Hidden by default */
      position: fixed; /* Stay in place */
      z-index: 1; /* Sit on top */
      left: 0;
      top: 0;
      width: 100%; /* Full width */
      height: 100%; /* Full height */
      overflow: auto; /* Enable scroll if needed */
      background-color: rgb(0, 0, 0); /* Fallback color */
      background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */
    }
    
    /* Modal Content/Box */
    .modal-content {
      background-color: #fefefe;
      margin: 15% auto; /* 15% from the top and centered */
      padding: 20px;
      border: 1px solid #888;
      width: 80%; /* Could be more or less, depending on screen size */
    }
    
    /* The Close Button */
    .close {
      color: #aaa;
      float: right;
      font-size: 28px;
      font-weight: bold;
    }
    
    .close:hover,
    .close:focus {
      color: black;
      text-decoration: none;
      cursor: pointer;
    }
    .classOne {
      background: #088;
      color: #fff;
      padding: 0.5rem;
    }
    .classTwo {
      background: #808;
      color: #fff;
      padding: 0.5rem;
    }
    <button class="change_class">Change Class</button>
    <br><br>
    <div id="example" class="classOne">This will alternately use 'classOne' and 'classTwo'</div>
    <br>
    <button class="showmsg">Show MsgBox</button>

    As noted in the code document.getElementsByClassName returns a live list (querySelectorAll does NOT)

    This means that any new elements added with the specified class will appear in that list without you needing to do anything. Same for removed elements, they just disappear from the list

    note: getElementsByClassName is also a method on any element, so you don't have to get all such elements in the document, you could use it to just get the target elements under a selected element in the DOM - not applicable to this code as is, but something that may be useful