Integrating server-side validation into HTML's client-side validation APIs

by mcsf

HTML

<p><strong>
  Integrating server-side validation into HTML's client-side validation APIs
</strong></p>
<p>
  Try with: 
</p>
<ul>
  <li><a href="#" onclick="tryAddress(event)">localhost</a></li>
  <li><a href="#" onclick="tryAddress(event)">a@localhost</a></li>
  <li><a href="#" onclick="tryAddress(event)">b@localhost</a></li>
</ul>

<form onsubmit="event.preventDefault(); validateForm(this)">
  <fieldset>
    <legend>Email</legend>
    <input type="email" required oninput="this.setCustomValidity('')">
  </fieldset>
  <p><input type="submit"></p>
</form>

<dialog>
  <p>OK!</p>
  <form method="dialog">
    <button>Close</button>
  </form>
</dialog>

CSS

:user-invalid {
  background-color: yellow;
}

.pending, .pending input {
  color: lightgrey;
}

JavaScript

async function validateForm(form) {
	const input = form.querySelector('input[type="email"]');
  const button = form.querySelector('input[type="submit"]');
  const dialog = document.querySelector('dialog');

  form.classList.add('pending');
  button.disabled = true;
  try {
    const result = await doAsyncRequest(input.value);
		dialog.showModal();
  } catch (e) {
  	input.setCustomValidity(e);
    form.reportValidity();
  } finally {
  	form.classList.remove('pending');
    button.disabled = undefined;
  }
}

function doAsyncRequest(email) {
	return new Promise((resolve, reject) => {
  	setTimeout(() => {
    	if (email === 'a@localhost') {
      	reject('Address taken');
      } else {
        resolve();
      }
    }, 600);
  })
}

function tryAddress({target}) {
	const input = document.querySelector('input[type="email"]');
  const button = document.querySelector('input[type="submit"]');
  input.setCustomValidity('');
  input.value = target.innerText;
  button.click();
}