0

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" class="modal">

  <div class="modal-content">
    <span class="close">&times;</span>
    <p>Some text in the Modal..</p>
  </div>

</div>

How can btn.dispatchEvent(event) be awaited?

Dejan Dozet
  • 948
  • 10
  • 26
  • 1
    no, only Promise's can be awaited - `.dispatchEvent` returns a boolean (true || false) - also, your `span.onclick` makes no sense being `async` since the only thing it `await`s is a non-promise ... besides, the return value of an event handler isn't returned anywhere – Jaromanda X Oct 07 '22 at 07:07
  • 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. Also, avoid the [explicit promise construction anti-pattern](https://stackoverflow.com/questions/23803743/what-is-the-explicit-promise-construction-antipattern-and-how-do-i-avoid-it) -- `async` functions always return promises, you don't need to construct one. – T.J. Crowder Oct 07 '22 at 07:09
  • Exactly when do you want the #myBtn to become red? – Kostas Minaidis Oct 07 '22 at 07:14
  • @KostasMinaidis after closing modal – Dejan Dozet Oct 07 '22 at 07:15
  • Then you should put the btn.style.color = "red" line inside the span.onclick = function() handler. You can get rid of all the Promises and async/await statements. – Kostas Minaidis Oct 07 '22 at 07:17
  • @KostasMinaidis :) – Dejan Dozet Oct 07 '22 at 07:18
  • Also, remember that you can use the addEventListener to run some code when an event has been dispatched: btn.addEventListener('click', (e) => {}, false); – Kostas Minaidis Oct 07 '22 at 07:20

3 Answers3

1

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" class="modal">

  <div class="modal-content">
    <span class="close">&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.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • this is something that I can use, of course, I will have to take care because in the chain of events many modals can be shown – Dejan Dozet Oct 07 '22 at 07:25
  • can you explain to me how this works: modalPromise = modalPromise.then(() => { return new Promise((resolve) => { modalResolve = resolve; }); }); and the meaning of this call: modalResolve();, thanks – Dejan Dozet Oct 07 '22 at 07:46
  • 1
    @DejanDozet - `modalResolve = resolve;` saves the `resolve` function from [the `Promise` executor call](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Promises#promise_terminology) to a variable you can use later to resolve (in this case, fulfill) the promise. – T.J. Crowder Oct 07 '22 at 07:55
  • and I guess one function (modalPromise) is for one event handler to keep, but I have 3 buttons/click events on the modal so I will have to have 3 sets of modalPromise and modalResolve, isn't it? – Dejan Dozet Oct 07 '22 at 08:20
  • @DejanDozet - `modalPromise` isn't a function, it's a promise. Yes, if those modals can be fired independently and can overlap, you'll need to repeat the pattern three times. I'd do it in a more structured way than a bunch of globals, but that's just a refinement. – T.J. Crowder Oct 07 '22 at 08:27
  • sorry, modalPromise is a promise and modalResolve is a function – Dejan Dozet Oct 07 '22 at 08:31
1

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" class="modal">

  <div class="modal-content">
    <span class="close">&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

fohletex
  • 96
  • 5
  • hehe, I like that dirty solution better, but agree later when I forget that I've used it it could bring headaches, I like T.J. Crowder idea to keep the resolve function for later use because it is the closest that I have already, so will go with that approach, thanks – Dejan Dozet Oct 07 '22 at 08:04
-1

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" class="modal">

  <div class="modal-content">
    <span class="close">&times;</span>
    <p>Some text in the Modal..</p>
  </div>

</div>
Kostas Minaidis
  • 4,681
  • 3
  • 17
  • 25
  • the problem is that I have chaining in my code, those are loops that are checking and unchecking checkboxes some checkboxes when checked (or unchecked) will yield a modal that the user has to accept (or reject) - code can't go forward because from that action it can go different ways. T.J. Crowder gave me a very good answer and from it, I learned something new, thanks – Dejan Dozet Oct 07 '22 at 09:01
  • I see. That wasn't clear from the question and the code snippet. I understand the requirements clearly now, so an Event-based or Promise-based system might be the way to go. – Kostas Minaidis Oct 07 '22 at 09:21
  • I am using function call now instead using dispatchEvent, but dispatchEvent was the first idea that I got and I wondered if is it possible to use it with promises and that is why I asked this question. Now I learned that it is not possible + I learned that I can use promises as variables, and later use them in anywhere the code. This knowledge is important to me and helps me to understand better this cloudy topic. – Dejan Dozet Oct 07 '22 at 09:47