Home > Net >  use dispatchEvent to dispatch it and wait until promise resolves
use dispatchEvent to dispatch it and wait until promise resolves

Time:10-07

I have a custom build modal that has to stop executing any code after it when displayed. For me, this is not a problem when I call it directly (I can handle that) but the problem is when I have to use dispatchEvent on the related element. I've constructed the basic example here:

const modal = document.getElementById("myModal");
const btn = document.getElementById("myBtn");
const span = document.getElementsByClassName("close")[0];

btn.onclick = async function() {
  modal.style.display = "block";
  return new Promise(resolve => {
    span.onclick = async function() {
      modal.style.display = "none";
      return await true;
    };
  });
};

(async () => {

  btn.style.color = "black";

  const event = new Event('click');
  await btn.dispatchEvent(event);
  
  btn.style.color = "red";
  
})();
.modal {
  display: none; 
  position: fixed; 
  z-index: 1; 
  left: 0;
  top: 0;
  width: 100%; 
  height: 100%; 
  overflow: auto; 
  background-color: rgb(0,0,0);
  background-color: rgba(0,0,0,0.4);
}

.modal-content {
  background-color: #fefefe;
  margin: 15% auto;
  padding: 20px;
  border: 1px solid #888;
  width: 80%; 
}


.close {
  color: #aaa;
  float: right;
  font-size: 28px;
  font-weight: bold;
}

.close:hover,
.close:focus {
  color: black;
  text-decoration: none;
  cursor: pointer;
}
<button id="myBtn">Open Modal and wait (Note: this should't be red before closing modal)</button>

<div id="myModal" >

  <div >
    <span >&times;</span>
    <p>Some text in the Modal..</p>
  </div>

</div>

How can btn.dispatchEvent(event) be awaited?

CodePudding user response:

There are a couple of issues there:

  1. It doesn't make any sense to use async functions as DOM event listeners. The DOM does nothing with the promise that is returned.
  2. Avoid the explicit promise construction anti-pattern — async functions always return promises, you don't need to construct one unless you need an explicit resolve or reject function.
  3. You'll need to coordinate outside of the DOM event system, since dispatchEvent won't return the promise you're creating.

For instance, you can have a modalPromise and modalResolve that you set up initially like this:

let modalPromise = Promise.resolve();
let modalResolve = null;

...and then update like this when showing the modal:

modalPromise = modalPromise.then(() => {
    return new Promise((resolve) => {
        modalResolve = resolve;
    });
});

Then you await that after showing the modal.

Here's an idea (I've also updated it to use modern event handling, and to not use var — var has no place in modern JavaScript code):

const modal = document.getElementById("myModal");
const btn = document.getElementById("myBtn");
const span = document.getElementsByClassName("close")[0];

let modalPromise = Promise.resolve();
let modalResolve = () => {};

span.addEventListener("click", function () {
    modal.style.display = "none";
    if (modalResolve) {
        modalResolve();
        modalResolve = null;
    }
});

btn.addEventListener("click", function () {
    modal.style.display = "block";
    modalPromise = modalPromise.then(() => {
        return new Promise((resolve) => {
            modalResolve = resolve;
        });
    });
});

(async () => {
    btn.style.color = "black";

    const event = new Event("click");
    btn.dispatchEvent(event);
    await modalPromise;

    btn.style.color = "red";
})();
.modal {
  display: none; 
  position: fixed; 
  z-index: 1; 
  left: 0;
  top: 0;
  width: 100%; 
  height: 100%; 
  overflow: auto; 
  background-color: rgb(0,0,0);
  background-color: rgba(0,0,0,0.4);
}

.modal-content {
  background-color: #fefefe;
  margin: 15% auto;
  padding: 20px;
  border: 1px solid #888;
  width: 80%; 
}


.close {
  color: #aaa;
  float: right;
  font-size: 28px;
  font-weight: bold;
}

.close:hover,
.close:focus {
  color: black;
  text-decoration: none;
  cursor: pointer;
}
<button id="myBtn">Open Modal and wait (Note: this should't be red before closing modal)</button>

<div id="myModal" >

  <div >
    <span >&times;</span>
    <p>Some text in the Modal..</p>
  </div>

</div>

That's just a sketch, you'll want to harden it against issues like trying to show the modal twice overlapping, etc.

CodePudding user response:

Two possibilities, the first one is - if you do not depend exactly on "dispatchEvent" - to use a possible replacer method, like this one:

var modal = document.getElementById("myModal");
var btn = document.getElementById("myBtn");
var span = document.getElementsByClassName("close")[0];

const openModal = () => {
  // This method will return a promise which we want to await,
  // until the modal is closed again. The promise awaiting for
  // close is called p.
  const p = new Promise((resolve) => {
    const onSpanClick = () => {
      modal.style.display = "none";
      span.removeEventListener('click', onSpanClick);
      resolve();
    };
  
    const onClick = () => {
      modal.style.display = "block";
      span.addEventListener('click', onSpanClick);
      btn.removeEventListener('click', onClick);
    };

    btn.addEventListener('click', onClick);
  });

  // We need to return a double promise here, as we need to leave the function and return a promise to wait on, BEFORE the click is executed.
  return new Promise((resolve) => {
    p.then(resolve);
    
    // otherwise we would return the promise after the click
    // was executed and our promise didn't had any chance to wait
    const event = new Event('click');
    btn.dispatchEvent(event);
  });
};

(async () => {
  btn.style.color = "black";
  await openModal();
  btn.style.color = "red";
})();
.modal {
  display: none; 
  position: fixed; 
  z-index: 1; 
  left: 0;
  top: 0;
  width: 100%; 
  height: 100%; 
  overflow: auto; 
  background-color: rgb(0,0,0);
  background-color: rgba(0,0,0,0.4);
}

.modal-content {
  background-color: #fefefe;
  margin: 15% auto;
  padding: 20px;
  border: 1px solid #888;
  width: 80%; 
}


.close {
  color: #aaa;
  float: right;
  font-size: 28px;
  font-weight: bold;
}

.close:hover,
.close:focus {
  color: black;
  text-decoration: none;
  cursor: pointer;
}
<button id="myBtn">Open Modal and wait (Note: this should't be red before closing modal)</button>

<div id="myModal" >

  <div >
    <span >&times;</span>
    <p>Some text in the Modal..</p>
  </div>

</div>

The other possiblity would be to do a hacky override of "btn.dispatchEvent" in order to assure that this behaviour is always guaranteed when "dispatchEvent" is called on the "btn"-instance, like this way:

const oldDispatchEvent = btn.dispatchEvent.bind(btn);
btn.dispatchEvent = (event) => {
  // analyze event if it is a click event and create promise p (as in the
  //  "openModal" function in the snippet above (solution 1)
  
  return new Promise((resolve) => {
   p.then(resolve);
   oldDispatchEvent(event);
  });

  // else
  return oldDispatchEvent(event);
};

I personally wouldn't recommend the second solution as it is quiet dirty and non-standard, which means headaches, when you will have a view on your code in some months again. :D

CodePudding user response:

Here's a simpler setup that will turn the text read, right after the modal is closed:

var modal = document.getElementById("myModal");
var btn = document.getElementById("myBtn");
var span = document.getElementsByClassName("close")[0];
console.clear();

btn.onclick = async function() {
  modal.style.display = "block";
  span.onclick = function() {
    modal.style.display = "none";
    btn.style.color = "red";
  };

};

(async () => {

  btn.style.color = "black";

  const event = new Event('click');
  btn.dispatchEvent(event);
  
})();

var modal = document.getElementById("myModal");
var btn = document.getElementById("myBtn");
var span = document.getElementsByClassName("close")[0];
console.clear();

btn.onclick = async function() {
  modal.style.display = "block";
  span.onclick = function() {
    modal.style.display = "none";
    btn.style.color = "red";
    return true;
  };

};

(async () => {

  btn.style.color = "black";

  const event = new Event('click');
  btn.dispatchEvent(event);
  
})();
.modal {
  display: none; 
  position: fixed; 
  z-index: 1; 
  left: 0;
  top: 0;
  width: 100%; 
  height: 100%; 
  overflow: auto; 
  background-color: rgb(0,0,0);
  background-color: rgba(0,0,0,0.4);
}

.modal-content {
  background-color: #fefefe;
  margin: 15% auto;
  padding: 20px;
  border: 1px solid #888;
  width: 80%; 
}


.close {
  color: #aaa;
  float: right;
  font-size: 28px;
  font-weight: bold;
}

.close:hover,
.close:focus {
  color: black;
  text-decoration: none;
  cursor: pointer;
}
<button id="myBtn">Open Modal and wait (Note: this should't be red before closing modal)</button>

<div id="myModal" >

  <div >
    <span >&times;</span>
    <p>Some text in the Modal..</p>
  </div>

</div>

  • Related