mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-21 10:16:13 +02:00
Write JavaScript to check domain availability
This commit is contained in:
parent
d8ae0d9753
commit
526178a767
2 changed files with 122 additions and 12 deletions
|
@ -1,13 +1,26 @@
|
||||||
|
/**
|
||||||
|
* @file get-gov.js includes custom code for the .gov registrar.
|
||||||
|
*
|
||||||
|
* Constants and helper functions are at the top.
|
||||||
|
* Event handlers are in the middle.
|
||||||
|
* Initialization (run-on-load) stuff goes at the bottom.
|
||||||
|
*/
|
||||||
|
|
||||||
/** Strings announced to assistive technology users. */
|
/** Strings announced to assistive technology users. */
|
||||||
var ARIA = {
|
var ARIA = {
|
||||||
QUESTION_REMOVED: "Previous follow-up question removed",
|
QUESTION_REMOVED: "Previous follow-up question removed",
|
||||||
QUESTION_ADDED: "New follow-up question required"
|
QUESTION_ADDED: "New follow-up question required"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var DEFAULT_ERROR = "Please check this field for errors.";
|
||||||
|
|
||||||
var REQUIRED = "required";
|
var REQUIRED = "required";
|
||||||
var INPUT = "input";
|
var INPUT = "input";
|
||||||
|
|
||||||
/** Helper function. Makes an element invisible. */
|
// <<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>
|
||||||
|
// Helper functions.
|
||||||
|
|
||||||
|
/** Makes an element invisible. */
|
||||||
function makeHidden(el) {
|
function makeHidden(el) {
|
||||||
el.style.position = "absolute";
|
el.style.position = "absolute";
|
||||||
el.style.left = "-100vw";
|
el.style.left = "-100vw";
|
||||||
|
@ -18,7 +31,7 @@ function makeHidden(el) {
|
||||||
el.style.visibility = "hidden";
|
el.style.visibility = "hidden";
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Helper function. Makes visible a perviously hidden element. */
|
/** Makes visible a perviously hidden element. */
|
||||||
function makeVisible(el) {
|
function makeVisible(el) {
|
||||||
el.style.position = "relative";
|
el.style.position = "relative";
|
||||||
el.style.left = "unset";
|
el.style.left = "unset";
|
||||||
|
@ -33,19 +46,19 @@ function forEachChild(el, selector, func) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Helper function. Removes `required` attribute from input. */
|
/** Removes `required` attribute from input. */
|
||||||
const removeRequired = input => input.removeAttribute(REQUIRED);
|
const removeRequired = input => input.removeAttribute(REQUIRED);
|
||||||
|
|
||||||
/** Helper function. Adds `required` attribute to input. */
|
/** Adds `required` attribute to input. */
|
||||||
const setRequired = input => input.setAttribute(REQUIRED, "");
|
const setRequired = input => input.setAttribute(REQUIRED, "");
|
||||||
|
|
||||||
/** Helper function. Removes `checked` attribute from input. */
|
/** Removes `checked` attribute from input. */
|
||||||
const removeChecked = input => input.checked = false;
|
const removeChecked = input => input.checked = false;
|
||||||
|
|
||||||
/** Helper function. Adds `checked` attribute to input. */
|
/** Adds `checked` attribute to input. */
|
||||||
const setChecked = input => input.checked = true;
|
const setChecked = input => input.checked = true;
|
||||||
|
|
||||||
/** Helper function. Creates and returns a live region element. */
|
/** Creates and returns a live region element. */
|
||||||
function createLiveRegion(id) {
|
function createLiveRegion(id) {
|
||||||
const liveRegion = document.createElement("div");
|
const liveRegion = document.createElement("div");
|
||||||
liveRegion.setAttribute("role", "region");
|
liveRegion.setAttribute("role", "region");
|
||||||
|
@ -64,8 +77,8 @@ var radioToggles = {};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function. Tracks state of selected radio button.
|
* Tracks state of selected radio button.
|
||||||
*
|
*
|
||||||
* This is required due to JavaScript not having a native
|
* This is required due to JavaScript not having a native
|
||||||
* event trigger for "deselect" on radio buttons. Tracking
|
* event trigger for "deselect" on radio buttons. Tracking
|
||||||
* which button has been deselected (and hiding the associated
|
* which button has been deselected (and hiding the associated
|
||||||
|
@ -75,7 +88,7 @@ function rememberSelected(radioButton) {
|
||||||
selected[radioButton.name] = radioButton;
|
selected[radioButton.name] = radioButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Helper function. Announces changes to assistive technology users. */
|
/** Announces changes to assistive technology users. */
|
||||||
function announce(id, text) {
|
function announce(id, text) {
|
||||||
const liveRegion = document.getElementById(id + "-live-region");
|
const liveRegion = document.getElementById(id + "-live-region");
|
||||||
liveRegion.innerHTML = text;
|
liveRegion.innerHTML = text;
|
||||||
|
@ -123,6 +136,70 @@ function revealToggleable(e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slow down event handlers by limiting how frequently they fire.
|
||||||
|
*
|
||||||
|
* A wait period must occur with no activity (activity means "this
|
||||||
|
* debounce function being called") before the handler is invoked.
|
||||||
|
*
|
||||||
|
* @param {Function} handler - any JS function
|
||||||
|
* @param {number} cooldown - the wait period, in milliseconds
|
||||||
|
*/
|
||||||
|
function debounce(handler, cooldown=600) {
|
||||||
|
let timeout;
|
||||||
|
return function(...args) {
|
||||||
|
const context = this;
|
||||||
|
clearTimeout(timeout);
|
||||||
|
timeout = setTimeout(() => handler.apply(context, args), cooldown);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Asyncronously fetches JSON. No error handling. */
|
||||||
|
function fetchJSON(endpoint, callback, url="/api/v1/") {
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('GET', url + endpoint);
|
||||||
|
xhr.send();
|
||||||
|
xhr.onload = function() {
|
||||||
|
if (xhr.status != 200) return;
|
||||||
|
callback(JSON.parse(xhr.response));
|
||||||
|
};
|
||||||
|
// nothing, don't care
|
||||||
|
// xhr.onerror = function() { };
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Modifies CSS and HTML when an input is valid/invalid. */
|
||||||
|
function toggleInputValidity(el, valid, msg=DEFAULT_ERROR) {
|
||||||
|
if (valid) {
|
||||||
|
el.setCustomValidity("");
|
||||||
|
el.removeAttribute("aria-invalid");
|
||||||
|
el.classList.remove('usa-input--error');
|
||||||
|
} else {
|
||||||
|
el.classList.remove('usa-input--success');
|
||||||
|
el.setAttribute("aria-invalid", "true");
|
||||||
|
el.setCustomValidity(msg);
|
||||||
|
// this is here for testing: in actual use, we might not want to
|
||||||
|
// visually display these errors until the user tries to submit
|
||||||
|
el.classList.add('usa-input--error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _checkDomainAvailability(e) {
|
||||||
|
const callback = (response) => {
|
||||||
|
toggleInputValidity(e.target, (response && response.available));
|
||||||
|
if (e.target.validity.valid) {
|
||||||
|
e.target.classList.add('usa-input--success');
|
||||||
|
// do other stuff, like display a toast?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fetchJSON(`available/${e.target.value}`, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Call the API to see if the domain is good. */
|
||||||
|
const checkDomainAvailability = debounce(_checkDomainAvailability);
|
||||||
|
|
||||||
|
// <<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>
|
||||||
|
// Event handlers.
|
||||||
|
|
||||||
/** On radio button selection change, handles associated toggleables. */
|
/** On radio button selection change, handles associated toggleables. */
|
||||||
function handleToggle(e) {
|
function handleToggle(e) {
|
||||||
// hide any previously visible HTML associated with previously selected radio buttons
|
// hide any previously visible HTML associated with previously selected radio buttons
|
||||||
|
@ -131,9 +208,42 @@ function handleToggle(e) {
|
||||||
revealToggleable(e);
|
revealToggleable(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** On input change, handles running any associated validators. */
|
||||||
|
function handleInputValidation(e) {
|
||||||
|
const attribute = e.target.getAttribute("validate") || "";
|
||||||
|
if (!attribute.length) return;
|
||||||
|
const validators = attribute.split(" ");
|
||||||
|
let isInvalid = false;
|
||||||
|
for (const validator of validators) {
|
||||||
|
switch (validator) {
|
||||||
|
case "domain":
|
||||||
|
checkDomainAvailability(e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toggleInputValidity(e.target, !isInvalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// <<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>
|
||||||
|
// Initialization code.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An IIFE that will attach validators to inputs.
|
||||||
|
*
|
||||||
|
* It looks for elements with `validate="<type> <type>"` and adds
|
||||||
|
* change handlers for each known type.
|
||||||
|
*/
|
||||||
|
(function validatorsInit() {
|
||||||
|
"use strict";
|
||||||
|
const needsValidation = document.querySelectorAll('[validate]');
|
||||||
|
for(const input of needsValidation) {
|
||||||
|
input.addEventListener('input', handleInputValidation);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An IIFE that will hide any elements with `hide-on-load` attribute.
|
* An IIFE that will hide any elements with `hide-on-load` attribute.
|
||||||
*
|
*
|
||||||
* Why not start with `hidden`? Because this is for use with form questions:
|
* Why not start with `hidden`? Because this is for use with form questions:
|
||||||
* if Javascript fails, users will still need access to those questions.
|
* if Javascript fails, users will still need access to those questions.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -23,7 +23,7 @@ of a larger entity. If so, enter information about your part of the larger entit
|
||||||
|
|
||||||
<fieldset class="usa-fieldset">
|
<fieldset class="usa-fieldset">
|
||||||
{{ wizard.form.street_address|add_label_class:"usa-label" }}
|
{{ wizard.form.street_address|add_label_class:"usa-label" }}
|
||||||
{{ wizard.form.street_address|add_class:"usa-input" }}
|
{{ wizard.form.street_address|add_class:"usa-input"|attr:"validate:domain" }}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
{% if wizard.steps.prev %}
|
{% if wizard.steps.prev %}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue