diff --git a/src/api/views.py b/src/api/views.py
index dcda09c69..deaacf0c6 100644
--- a/src/api/views.py
+++ b/src/api/views.py
@@ -1,7 +1,5 @@
"""Internal API views"""
-
from django.apps import apps
-from django.core.exceptions import BadRequest
from django.views.decorators.http import require_http_methods
from django.http import JsonResponse
@@ -17,6 +15,19 @@ DOMAIN_FILE_URL = (
)
+DOMAIN_API_MESSAGES = {
+ "required": "Enter the .gov domain you want. Don’t include “www” or “.gov.”"
+ " For example, if you want www.city.gov, you would enter “city”"
+ " (without the quotes).",
+ "extra_dots": "Enter the .gov domain you want without any periods.",
+ "unavailable": "That domain isn’t available. Try entering another one."
+ " Contact us if you need help coming up with a domain.",
+ "invalid": "Enter a domain using only letters,"
+ " numbers, or hyphens (though we don't recommend using hyphens).",
+ "success": "That domain is available!",
+}
+
+
# this file doesn't change that often, nor is it that big, so cache the result
# in memory for ten minutes
@ttl_cache(ttl=600)
@@ -72,6 +83,18 @@ def available(request, domain=""):
Domain.string_could_be_domain(domain)
or Domain.string_could_be_domain(domain + ".gov")
):
- raise BadRequest("Invalid request.")
+ return JsonResponse({
+ "available": False,
+ "message": DOMAIN_API_MESSAGES["invalid"]
+ })
# a domain is available if it is NOT in the list of current domains
- return JsonResponse({"available": not in_domains(domain)})
+ if in_domains(domain):
+ return JsonResponse({
+ "available": False,
+ "message": DOMAIN_API_MESSAGES["unavailable"]
+ })
+ else:
+ return JsonResponse({
+ "available": True,
+ "message": DOMAIN_API_MESSAGES["success"]
+ })
diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js
index 44d0cab57..c9f6d77af 100644
--- a/src/registrar/assets/js/get-gov.js
+++ b/src/registrar/assets/js/get-gov.js
@@ -9,6 +9,11 @@
var DEFAULT_ERROR = "Please check this field for errors.";
+var INFORMATIVE = "info";
+var WARNING = "warning";
+var ERROR = "error";
+var SUCCESS = "success";
+
// <<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>
// Helper functions.
@@ -89,44 +94,114 @@ function toggleInputValidity(el, valid, msg=DEFAULT_ERROR) {
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) {
+/** Display (or hide) a message beneath an element. */
+function inlineToast(el, id, style, msg) {
+ if (!el.id && !id) {
+ console.error("Elements must have an `id` to show an inline toast.");
+ return;
+ }
+ let toast = document.getElementById((el.id || id) + "--toast");
+ if (style) {
+ if (!toast) {
+ // create and insert the message div
+ toast = document.createElement("div");
+ const toastBody = document.createElement("div");
+ const p = document.createElement("p");
+ toast.setAttribute("id", (el.id || id) + "--toast");
+ toast.className = `usa-alert usa-alert--${style} usa-alert--slim`;
+ toastBody.classList.add("usa-alert__body");
+ p.classList.add("usa-alert__text");
+ p.innerText = msg;
+ toastBody.appendChild(p);
+ toast.appendChild(toastBody);
+ el.parentNode.insertBefore(toast, el.nextSibling);
+ } else {
+ // update and show the existing message div
+ toast.className = `usa-alert usa-alert--${style} usa-alert--slim`;
+ toast.querySelector("div p").innerText = msg;
+ makeVisible(toast);
+ }
+ } else {
+ if (toast) makeHidden(toast);
+ }
+}
+
+function _checkDomainAvailability(el) {
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?
+ toggleInputValidity(el, (response && response.available), msg=response.message);
+ announce(el.id, response.message);
+ if (el.validity.valid) {
+ el.classList.add('usa-input--success');
+ // use of `parentElement` due to .gov inputs being wrapped in www/.gov decoration
+ inlineToast(el.parentElement, el.id, SUCCESS, response.message);
+ } else {
+ inlineToast(el.parentElement, el.id, ERROR, response.message);
}
}
- fetchJSON(`available/${e.target.value}`, callback);
+ fetchJSON(`available/${el.value}`, callback);
}
/** Call the API to see if the domain is good. */
const checkDomainAvailability = debounce(_checkDomainAvailability);
-// <<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>
-// Event handlers.
+/** Hides the toast message and clears the aira live region. */
+function clearDomainAvailability(el) {
+ el.classList.remove('usa-input--success');
+ announce(el.id, "");
+ // use of `parentElement` due to .gov inputs being wrapped in www/.gov decoration
+ inlineToast(el.parentElement, el.id);
+}
-
-/** On input change, handles running any associated validators. */
-function handleInputValidation(e) {
- const attribute = e.target.getAttribute("validate") || "";
+/** Runs all the validators associated with this element. */
+function runValidators(el) {
+ const attribute = el.getAttribute("validate") || "";
if (!attribute.length) return;
const validators = attribute.split(" ");
let isInvalid = false;
for (const validator of validators) {
switch (validator) {
case "domain":
- checkDomainAvailability(e);
+ checkDomainAvailability(el);
break;
}
}
- toggleInputValidity(e.target, !isInvalid);
+ toggleInputValidity(el, !isInvalid);
+}
+
+/** Clears all the validators associated with this element. */
+function clearValidators(el) {
+ const attribute = el.getAttribute("validate") || "";
+ if (!attribute.length) return;
+ const validators = attribute.split(" ");
+ for (const validator of validators) {
+ switch (validator) {
+ case "domain":
+ clearDomainAvailability(el);
+ break;
+ }
+ }
+ toggleInputValidity(el, true);
+}
+
+// <<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>
+// Event handlers.
+
+/** On input change, handles running any associated validators. */
+function handleInputValidation(e) {
+ clearValidators(e.target);
+ if (e.target.hasAttribute("auto-validate")) runValidators(e.target);
+}
+
+/** On button click, handles running any associated validators. */
+function handleValidationClick(e) {
+ const attribute = e.target.getAttribute("validate-for") || "";
+ if (!attribute.length) return;
+ const input = document.getElementById(attribute);
+ runValidators(input);
}
// <<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>
@@ -135,8 +210,12 @@ function handleInputValidation(e) {
/**
* An IIFE that will attach validators to inputs.
*
- * It looks for elements with `validate=" Before requesting a .gov domain, please make sure it
@@ -29,7 +28,6 @@
{% block form_fields %}
- {% csrf_token %}
{{ forms.0.management_form }}
@@ -44,12 +42,16 @@
complete and submit the rest of this form.