Search code examples
javascriptmodal-dialogsimplemodal

How to Fix 'One Closing Button Closes All Modals'


I'm trying to create a vanilla JavaScript Modal that has the capability of being customized by the User, when instantiating it from the HTML file (or JS file). However, when it comes to dealing with the close() function to close the modal, instead of closing ONE modal at a time, using its close button, the close button of the FIRST modal closes ALL modals of the page. I'm not sure what I'm doing wrong...

I've researched other similar vanilla JavaScript, customizable modal libraries, but most of them use either jQuery, some framework, or include a lot of complications that I am not familiar with (I'm still a beginner). I've researched on GitHub, CodePen, Google, and on here; but I have yet to find a solution that satisfies what I need.

Since the code is quite long, I'd suggest you go directly to my CodePen account, where you can have the full code.

https://codepen.io/jdriviere/pen/zYOyJvv?editors=0011

But here is my close() function:

Modal.prototype.close = function() {
  let modal = document.getElementById(this.options.id);
  let modalBody = modal.children[0];

  // Delete elements from Modal Body
  for (let i = 0; i < modalBody.children.length; i++) {
    modalBody.removeChild(modalBody.children[i]);
  } // End of LOOP

  // Delete Modal Body from Modal
  modal.removeChild(modalBody);

  // Delete Modal from DOM
  modal.style.display = 'none';
  document.body.removeChild(modal);

  return this;
};

I would expect the code to close ONE modal at a time, and preferably the modal that has the proper ID (which should be either assigned by the User or by default have a "NoID" ID). Instead, if I close subsequent modals, it closes them; but if I close the FIRST one, it closes ALL of them. Also, is there a way to init() the Modal functionalities as soon as you create the modal instance (I hate manually initiating them)? If so, please include your solution here too, if not much asked.

Been at it for quite some time now. Your help would be greatly appreciated.

Thank you. :)


Solution

  • You have couple of mistakes in your code:

    1. Always use a proper id pattern for the HTML element. You have used n/a for the modal that does not have id property in their options object. Using such id will break the query selector when you use jQuery.
    2. Since, you are calling the init() function twice and in each call for init() the closeBtn is selecting both the close buttons of two modals and assigning the click event handler to each of them twice. That was the reason when you clicked on one button the click event for another button was executing itself. So, what you can do is, only associate a click function once only to that close button of the modal for which the init() function was called. I used let closeBtn = document.querySelector('#'+this.options.id + ' .modal-close'); to select that particular close button inside that init() function.

    Overall your JS code will look like:

    /**
    * Blueprint function (class) that describes a Modal object.
    * @param {Object} options Object parameter containing elements that describe the Modal.
    * @returns {Object} options Returns options from current modal object.
    */
    function Modal(options) {
        // If constructor params is available
        if (options) {
        this.options = options;
      } else {
        this.options = {};
    
      } // End of IF-ELSE
    
      // Add to options object
      if (options.id) {
        // Check type of ID entry
        if (typeof options.id === 'number') {
          this.options.id = options.id.toString();
        } else {
          this.options.id = options.id;
        } // End of IF-ELSE
      } else if (options.id === undefined) {
        this.options.id = 'NA';
      } // End of IF-ELSE
      if (options.name) {
        this.options.name = options.name;
      } // End of IF
      if (options.closable) {
        this.options.closable = options.closable;
      } // End of IF
    
      return this;
    };
    
    // Prototypes
    /**
    * Displays some information concerning the current Modal object.
    * @returns {Object} this Returns current modal object.
    */
    Modal.prototype.open = function() {
        let demo = document.getElementById('demo');
    
      return this;
    };
    
    /**
    * Creates an instance of a Modal object with the specified object elements.
    * @returns {Object} this Returns current Modal object.
    */
    Modal.prototype.create = function() {
      // Create Modal Element
      let modal = document.createElement('div');
      let modalBody = document.createElement('div');
    
      // Create Modal
      !modal.classList.contains('modal') ?
        modal.classList.add('modal') :
        modal.classList.add('');
      modal.id = this.options.id || 'noID';
    
      // Create modal body element
      !modalBody.classList.contains('modal-body') ?
        modalBody.classList.add('modal-body') :
        modalBody.classList.add('');document.querySelector('#' + this.options.id + ' .modal-close');
      modal.appendChild(modalBody);
    
      // Adding modal sub-elements  
      if (this.options.title) {
        let modalTitle = document.createElement('h2');
        !modalTitle.classList.contains('modal-title') ?
          modalTitle.classList.add('modal-title') :
          modalTitle.classList.add('');
        modalTitle.textContent = this.options.title;
        modalBody.appendChild(modalTitle);
        console.log('Added title!');
      } // End of IF
    
      if (this.options.subtitle) {
        let modalSubtitle = document.createElement('h4');
        !modalSubtitle.classList.contains('modal-subtitle') ?
          modalSubtitle.classList.add('modal-subtitle') :
          modalSubtitle.classList.add('');
        modalSubtitle.textContent = this.options.subtitle;
        modalBody.appendChild(modalSubtitle);
        console.log('Added subtitle!');
      } // End of IF
    
      if (this.options.content) {
        let modalContent = document.createElement('p');
        !modalContent.classList.contains('modal-content') ?
          modalContent.classList.add('modal-content') :
          modalContent.classList.add('');
        modalContent.textContent = this.options.content;
        modalBody.appendChild(modalContent);
        console.log('Added contents!');
      } // End of IF
    
      if (this.options.closable) {
        let modalClose = document.createElement('span');
        !modalClose.classList.contains('modal-close') ?
          modalClose.classList.add('modal-close') :
          modalClose.classList.add('');
        modalClose.innerHTML = '&times;';
        modalBody.appendChild(modalClose);
        console.log('Close button added!');
      } // End of IF
    
      document.body.appendChild(modal);
      console.log('Modal created with ID', modal.id);
    
      return this;
    };
    
    /**
    * Closes the current Modal object.
    * @returns {Object} this Returns current Modal object.
    */
    Modal.prototype.close = function() {    
      let modal = document.getElementById(this.options.id);
      let modalBody = modal.children[0];
    
      // Delete elements from Modal Body
      for (let i = 0; i < modalBody.children.length; i++) {
        modalBody.removeChild(modalBody.children[i]);
      } // End of LOOP
    
      // Delete Modal Body from Modal
      modal.removeChild(modalBody);
    
      // Delete Modal from DOM
      modal.style.display = 'none';
      document.body.removeChild(modal);
    
      return this;
    };
    
    /**
    * Initializes the inner functions of the modal, such as the closing capacity.
    * @returns {Object} this Returns current Modal object.
    */
    Modal.prototype.init = function(e) {
      // let closeBtnAll = document.querySelectorAll('.modal-close');
      let closeBtn = document.querySelector('#'+this.options.id + ' .modal-close');
    
      // Assign close() function to all close buttons
      closeBtn.addEventListener('click', () => {
        if (this.options.closable) {
          this.close();
        }
      })
    
      // Press ESC to close ALL modals
    
      return this;
    };
    
    // Create a Modal object
    let modal1 = new Modal({
      id: 'post1',
      name: 'modal',
      title: 'First Post',
      subtitle: 'I contain all the elements',
      content: 'This is awesome!',
      closable: true
    });
    
    let modal2 = new Modal({
      title: 'Second Post',
      subtitle: 'Trying new things',
      content: 'Hehehehehe',
      closable: true
    });
    
    modal1.open();
    modal1.create();
    modal1.init();
    
    modal2.open();
    modal2.create();
    modal2.init();
    

    Just replace the above JS code in your codepen and try. It will work.