2

I have a button that displays a custom dialogue box. If the user selects 'cancel', the dialogue box should be removed from the DOM and no further action is taken. If they click 'Start Delete', it should proceed with code.

The dialogue box shows and hides correctly, however, after I cancel and hide the dialogue box, the button no longer works to display the dialogue box again.

I have created a popup class that uses a template string to display the popup and I then inject it into the DOM. The popup takes a callback in the constructor, which I use to hide the popup.

Please see the following JS Fiddle: https://jsfiddle.net/khgriffi259/vs6r5ake/13/

  class Popup {

    constructor(title, message, callback) {
      this.title = title;
      this.message = message;
      this.callback = callback;
      this.container = document.querySelector('body');
      this.result = '';
    }
  
    init() {
      const popup = `
          <div class="popup-wrapper">
              <div class="popup bg-white p-1">
                  <div class="popup-close">X</div>
                  <div class="popup-content ">
                      <h2 class="">${this.title}</h2>
                      <p class="p-1 my-2">${this.message}</p>
                      <div class="dialogue_buttons my-1">
                          <button class="btn popup-no" >Cancel</button>
                          <button class="btn btn-danger my-1 popup-yes" >Start Delete</button>
                      </div>
                  </div>
              </div>
          </div>  
      `;
      this.container.innerHTML += popup;
  
      this.container.querySelector('.popup-no').addEventListener('click', () => this.cancelListener());
      this.container.querySelector('.popup-yes').addEventListener('click', () => this.startListener());
  
    }
  
    cancelListener() {
      this.result = false;
    
        this.callback();
    //   if (this.callback !== undefined) {
    //     this.callback(this.result);
    //   }
    }
  
    startListener() {
      this.result  = true;
      if (this.callback !== undefined) {
        this.callback();
      }
    }
 

     
       getResult() {
         console.log(this.result) ;
        return this.result;
        }

  
  }

    //end of Popup Class

const button = document.querySelector('button');
button.addEventListener('click',  e => {
  if (e.target.tagName === 'BUTTON') {
    
    const confirmDelete = new Popup(
      'Delete', 
      'This will permanently delete this experience record.',
      ()=>{
        console.log('hello');
        let elem = document.querySelector('.popup-wrapper');
        elem.parentNode.removeChild(elem);
        
      }
    );
    confirmDelete.init();
     }
})
<button>
click me
</button>

I expect the popup to hide and for the button to be functional to generate a new Popup if user clicks the button again.

melpomene
  • 84,125
  • 8
  • 85
  • 148
user2954945
  • 121
  • 9
  • 3
    Apparently, using `innerHTML` to change the HTML contents of a node will remove the events in the node, as seen [in this answer](https://stackoverflow.com/a/5113120). – danirod May 11 '19 at 20:44

2 Answers2

3

See below for a fixed version of your code.

The only change is from

this.container.innerHTML += popup;

to

this.container.insertAdjacentHTML('beforeend', popup);

The reason your original code fails is that .innerHTML serializes the DOM tree back to an HTML string, appends popup, then the assignment to .innerHTML reparses the whole thing, recreating all elements from scratch. As a side effect, your original click listener is lost because it's still attached to the old button element. Assigning to .innerHTML means you get a completely new DOM tree.

As the documentation for insertAdjacentHTML states:

The insertAdjacentHTML() method of the Element interface parses the specified text as HTML or XML and inserts the resulting nodes into the DOM tree at a specified position. It does not reparse the element it is being used on, and thus it does not corrupt the existing elements inside that element. This avoids the extra step of serialization, making it much faster than direct innerHTML manipulation.

(Emphasis mine.)

  class Popup {

    constructor(title, message, callback) {
      this.title = title;
      this.message = message;
      this.callback = callback;
      this.container = document.querySelector('body');
      this.result = '';
    }
  
    init() {
      const popup = `
          <div class="popup-wrapper">
              <div class="popup bg-white p-1">
                  <div class="popup-close">X</div>
                  <div class="popup-content ">
                      <h2 class="">${this.title}</h2>
                      <p class="p-1 my-2">${this.message}</p>
                      <div class="dialogue_buttons my-1">
                          <button class="btn popup-no" >Cancel</button>
                          <button class="btn btn-danger my-1 popup-yes" >Start Delete</button>
                      </div>
                  </div>
              </div>
          </div>  
      `;
      this.container.insertAdjacentHTML('beforeend', popup);
  
      this.container.querySelector('.popup-no').addEventListener('click', () => this.cancelListener());
      this.container.querySelector('.popup-yes').addEventListener('click', () => this.startListener());
  
    }
  
    cancelListener() {
      this.result = false;
    
        this.callback();
    //   if (this.callback !== undefined) {
    //     this.callback(this.result);
    //   }
    }
  
    startListener() {
      this.result  = true;
      if (this.callback !== undefined) {
        this.callback();
      }
    }
 

     
       getResult() {
         console.log(this.result) ;
        return this.result;
        }

  
  }

    //end of Popup Class

const button = document.querySelector('button');
button.addEventListener('click',  e => {
  if (e.target.tagName === 'BUTTON') {
    
    const confirmDelete = new Popup(
      'Delete', 
      'This will permanently delete this experience record.',
      ()=>{
        console.log('hello');
        let elem = document.querySelector('.popup-wrapper');
        elem.parentNode.removeChild(elem);
        
      }
    );
    confirmDelete.init();
     }
})
<button>
click me
</button>
melpomene
  • 84,125
  • 8
  • 85
  • 148
  • Awesome! Thank you for the quick and concise edit. Looks like there are multiple solutions to this issue (braza also had a working solution below). Anyway, I did not know that the innerHTML method serializes the HTML to a string. Thanks again! – user2954945 May 11 '19 at 21:25
1

When you edit the innerHTML of the body you clear the eventListener on the button.

I've updated the fiddle to a working version here https://jsfiddle.net/n5k9rsbe/6/

There are 2 ways to get this working:

1 - Create a container element and modify that

<div id="container"></div>

JavaScript:

document.querySelector('#container').innerHTML = '...';

2 - Use appendChild on the body

const elem = document.createElement('div');
elem.innerHTML = popup
this.container.appendChild(elem)
braza
  • 4,260
  • 1
  • 26
  • 36