This post shows you how to customize browser built-in HTML5 form validation with JavaScript and CSS. You will be able to validate from JavaScript, add customized error messages, perform remote validation, perform confirmation validation, perform file validation and apply customized styling to error messages.
We are going to use built-in form validation (HTML5) as much as possible, it is faster than JavaScript validation and it means less work for us. We will need JavaScript, the Constraint Validation API and CSS to add more rules, customize messages and to style error messages.
This plugin for HTML5 validation is designed so that you have a choice to add some custom error messages and to style some error messages. Browser built-in error messages will be used if you don’t add custom error messages and built-in styling will be used if you don’t add custom error containers.
This plugin has been tested and is working with Google Chrome (75.0.3770.100), Mozilla Firefox (67.0.4) and Microsoft Edge (42.17134.1.0), without any polyfill. If you want to support older browsers, check out our post on how to transpile and polyfill JavaScript. We need to transpile the code and add polyfills to get this plugin to work in Internet Explorer (11.829.17134.0), we write about this at the end in this post.
JavaScript
This is the JavaScript that handles all forms of validation, validation can be triggered from a submit event or by calling the public function named valid.
var annytab = annytab || {};
annytab.validation = (function ()
{
'use_strict';
// Variables
var forms = document.getElementsByTagName('form');
// Loop forms
for (var i = 0; i < forms.length; i++) {
// Set forms to not validate, this code will handle validation
forms[i].noValidate = true;
// Add submit listener
window.onload = forms[i].addEventListener('submit', async function (event) {
// Prevent the form from submitting
event.preventDefault();
// Validate a form
if (await validate(this) === true)
{
// Submit the form
this.submit();
}
}, false);
// Get elements in the form
var elements = forms[i].querySelectorAll('input, select, textarea');
// Add listener for elements
for (var j = 0; j < elements.length; j++)
{
// Add input listeners
elements[j].addEventListener('keydown', removeValidationErrors, false);
elements[j].addEventListener('mousedown', removeValidationErrors, false);
}
} // End of for (var i = 0; i < forms.length; i++)
// Remove validation errors
function removeValidationErrors(event)
{
// Variables
var error_output = null;
// Find equalto element
var confirmation = event.target.form.querySelector('[data-validation-equalto="' + event.target.getAttribute('name') + '"]');
// Remove confirmation error
if (confirmation !== null)
{
error_output = event.target.form.querySelector('[data-error-for="' + confirmation.name + '"]');
if (error_output !== null) { error_output.innerHTML = ''; }
}
// Remove all errors for this element
error_output = event.target.form.querySelector('[data-error-for="' + event.target.getAttribute('name') + '"]');
if (error_output !== null) { error_output.innerHTML = ''; }
// Remove IE 11 errors
removeErrorsIE11(event.target.form);
} // End of the removeValidationErrors method
// Validate a form
async function validate(form)
{
// Get elements in the form
var elements = form.querySelectorAll('input, select, textarea');
// Remove IE 11 errors
removeErrorsIE11(form);
// Create a focus flag
var focus = false;
// Loop elements
for (i = 0; i < elements.length; i++)
{
// Get the control
var ctrl = elements[i];
// Check if the control should be validated
if (ctrl.willValidate === false) { continue; }
// Get error message container
var error_output = form.querySelector('[data-error-for="' + ctrl.getAttribute('name') + '"]');
// Get custom validation attributes
var data_validation_remote = ctrl.getAttribute('data-validation-remote');
var data_validation_equalto = ctrl.getAttribute('data-validation-equalto');
var data_validation_file = ctrl.getAttribute('data-validation-file');
// Get custom error attributes
var data_error_input = ctrl.getAttribute('data-error-input');
var data_error_pattern = ctrl.getAttribute('data-error-pattern');
var data_error_range = ctrl.getAttribute('data-error-range');
var data_error_step = ctrl.getAttribute('data-error-step');
var data_error_length = ctrl.getAttribute('data-error-length');
var data_error_type = ctrl.getAttribute('data-error-type');
var data_error_required = ctrl.getAttribute('data-error-required');
var data_error_remote = ctrl.getAttribute('data-error-remote');
var data_error_equalto = ctrl.getAttribute('data-error-equalto');
var data_error_file = ctrl.getAttribute('data-error-file');
var data_error_datalist = ctrl.getAttribute('data-error-datalist');
// Reset custom validation
ctrl.setCustomValidity('');
// Check for errors
if (ctrl.validity.badInput === true) // parsing error
{
if (data_error_input !== null) { ctrl.setCustomValidity(data_error_input); }
}
else if (ctrl.validity.patternMismatch === true) // pattern
{
if (data_error_pattern !== null) { ctrl.setCustomValidity(data_error_pattern); }
}
else if (ctrl.validity.rangeOverflow === true || ctrl.validity.rangeUnderflow === true) // max value or min value
{
if (data_error_range !== null) { ctrl.setCustomValidity(data_error_range); }
}
else if (ctrl.validity.stepMismatch === true) // step value
{
if (data_error_step !== null) { ctrl.setCustomValidity(data_error_step); }
}
else if (ctrl.validity.tooLong === true || ctrl.validity.tooShort) // max length or min length
{
if (data_error_length !== null) { ctrl.setCustomValidity(data_error_length); }
}
else if (ctrl.validity.typeMismatch === true) // input type error
{
if (data_error_type !== null) { ctrl.setCustomValidity(data_error_type); }
}
else if (ctrl.validity.valueMissing === true) // required
{
if (data_error_required !== null) { ctrl.setCustomValidity(data_error_required); }
}
else if (data_validation_equalto !== null) // confirmation
{
if (equaltoValidation(ctrl, data_validation_equalto) === false) { ctrl.setCustomValidity(data_error_equalto); }
}
else if (data_validation_file !== null) // File
{
if (fileValidation(ctrl, data_validation_file) === false) { ctrl.setCustomValidity(data_error_file); }
}
else if (data_error_datalist !== null) // Datalist
{
if (datalistValidation(ctrl) === false) { ctrl.setCustomValidity(data_error_datalist); }
}
else if (data_validation_remote !== null) {
// Perform remote validation
if (await remoteValidation(ctrl, data_validation_remote) === false) { ctrl.setCustomValidity(data_error_remote); }
}
// Set error message in custom control or report validity
if (ctrl.validationMessage !== '' && error_output !== null) {
error_output.innerHTML = ctrl.validationMessage;
// Set focus to the first element
if (focus === false) { focus = true; ctrl.focus(); }
}
else if (ctrl.validationMessage !== '' && ctrl.reportValidity) {
ctrl.reportValidity();
}
else if (ctrl.validationMessage !== '') {
// IE 11
ctrl.insertAdjacentHTML('afterend', '<div class="validation-error ie-11-error-output">' + ctrl.validationMessage + '</div>');
}
} // for (i = 0; i < elements.length; i++)
// Return true or false
return form.checkValidity();
} // End of the validate method
// Perform equalto validation
function equaltoValidation(ctrl, other_field)
{
// Get the value of the other field
var other_value = document.getElementsByName(other_field)[0].value;
// Check if values are different
if (ctrl.value !== other_value) {
return false;
}
// Return true
return true;
} // End of the equaltoValidation method
// Perform file validation
function fileValidation(ctrl, max_size)
{
// Make sure that there is files
if (ctrl.files.length <= 0) {
return true;
}
// Check accept attribute
var accepts = ctrl.getAttribute('accept') !== null ? ctrl.getAttribute('accept').toLowerCase().replace(' ', '').split(',') : null;
// Loop files
for (var i = 0; i < ctrl.files.length; i++) {
// Get the file extension
var extension = ctrl.files[i].name.substring(ctrl.files[i].name.lastIndexOf('.')).toLowerCase();
// Check for errors
if (accepts !== null && accepts.includes(extension) === false) {
return false;
}
else if (max_size !== null && max_size > 0 && ctrl.files[i].size >= max_size) {
return false;
}
}
// Return true
return true;
} // End of the fileValidation method
// Perform a datalist validation
function datalistValidation(ctrl)
{
// Loop options
for (var i = 0; i < ctrl.list.options.length; i++) {
if (ctrl.value === ctrl.list.options[i].value) {
return true;
}
}
// Return false
return false;
} // End of the datalistValidation method
// Perform remote validation
async function remoteValidation(ctrl, input)
{
// Return a promise
return new Promise((resolve, reject) => {
// Get input values
var values = input.split(',');
var uri = values[0].trim();
var fields = [];
fields.push(ctrl.getAttribute('name'));
for (var i = 1; i < values.length; i++) { fields.push(values[i].trim()); }
// Create form data
var fd = new FormData();
for (i = 0; i < fields.length; i++) { fd.append(fields[i], document.getElementsByName(fields[i])[0].value); }
var xhr = new XMLHttpRequest();
xhr.open('POST', uri, true);
xhr.onload = function () {
// Check if the response is successful
if (xhr.status === 200) {
// Return a success response
if (xhr.response === 'false') { resolve(false); } else { resolve(true); }
}
else {
// Return a reject response
reject(xhr.status + ' ' + xhr.statusText);
}
};
xhr.onerror = function () {
// Return a reject response
reject('There was a network error.');
};
xhr.send(fd);
});
} // End of the remoteValidation method
// Remove IE 11 errors
function removeErrorsIE11(form)
{
var ie_errors = form.querySelectorAll('.ie-11-error-output');
for (var i = 0; i < ie_errors.length; i++) { ie_errors[i].remove(); }
} // End of the removeErrorsIE11 method
// Public methods
return {
valid: async function (form) {
return await validate(form);
}
};
})();
Form
This is a form with html syntax and razor syntax, it shows som input elements and includes a method to post the form. You do not need to post the form with JavaScript, you can use a submit button instead.
@{
// Set the layout for the page
Layout = "/Views/shared_admin/_standard_layout.cshtml";
// Get form data
WebDomain current_domain = ViewBag.CurrentDomain;
KeyStringList tt = ViewBag.TranslatedTexts;
AdministratorDocument post = ViewBag.Post;
// Get translated texts
string administrator_tt = tt.Get("administrator");
string new_tt = tt.Get("new");
string edit_tt = tt.Get("edit");
string id_tt = tt.Get("id");
string username_tt = tt.Get("username");
string password_tt = tt.Get("password");
string confirm_password_tt = tt.Get("confirm") + " " + tt.Get("password").ToLower();
string admin_role_tt = tt.Get("role");
string email_tt = tt.Get("email");
string save_tt = tt.Get("save");
string cancel_tt = tt.Get("cancel");
// Set the title for the page
if (post.admin_username == "")
{
ViewBag.Title = administrator_tt + " - " + new_tt.ToLower();
}
else
{
ViewBag.Title = administrator_tt + " - " + edit_tt.ToLower();
}
}
@*Title*@
<h1>@ViewBag.Title</h1>
@*Menu*@
@await Html.PartialAsync("/Views/admin_administrators/_form_menu.cshtml")
@*Main form*@
<form id="inputForm" action="/admin_administrators/edit" method="post" enctype="multipart/form-data">
@*Hidden data*@
@Html.AntiForgeryToken()
@*Id*@
<div class="annytab-form-label">@id_tt</div>
<input name="txtId" type="text" class="annytab-form-control" tabindex="-1" readonly value="@post.id" />
@*Text*@
<div class="annytab-form-label">@username_tt</div>
<input name="txtUsername" type="text" class="annytab-form-control" value="@post.admin_username" placeholder="@username_tt" autofocus required
data-error-required="You must enter a username!" data-validation-remote="/admin_administrators/verify_username,txtId"
data-error-remote="The username is already in use!" maxlength="10" minlength="2" />
<div class="validation-error" data-error-for="txtUsername"></div>
@*Confirmation*@
<div class="annytab-form-label">@password_tt</div>
<input name="txtPassword" type="password" class="annytab-form-control" value="" placeholder="@password_tt" />
<input name="txtConfirmPassword" type="password" class="annytab-form-control" value="" placeholder="@confirm_password_tt"
data-validation-equalto="txtPassword" data-error-equalto="Passwords does not match!" />
<div class="validation-error" data-error-for="txtConfirmPassword"></div>
@*Select*@
<div class="annytab-form-label">@admin_role_tt</div>
<select name="selectAdminRole" class="annytab-form-control" required>
<!option value="">--- Select a role ---</!option>
<!option value="Administrator" @(post.admin_role == "Administrator" ? "selected" : "")>Administrator</!option>
<!option value="Editor" @(post.admin_role == "Editor" ? "selected" : "")>Editor</!option>
<!option value="Translator" @(post.admin_role == "Translator" ? "selected" : "")>Translator</!option>
</select>
@*Email*@
<div class="annytab-form-label">Email</div>
<input name="txtEmail" type="email" class="annytab-form-control" value="@post.admin_email" placeholder="my@email.com" required data-error-input="Bad input!" />
@*Multiple emails*@
<div class="annytab-form-label">Multiple emails</div>
<input name="txtEmail" type="email" class="annytab-form-control" value="" placeholder="my@email.com, your@email.se" multiple required />
@*Hidden field*@
<div class="annytab-form-label">Hidden field</div>
<input name="txtHidden" type="text" class="annytab-form-control" style="display:none;" value="ee" required data-error-required="Hidden field is required!" />
<div class="validation-error" data-error-for="txtHidden"></div>
@*Date*@
<div class="annytab-form-label">Date</div>
<input name="txtDate" type="date" class="annytab-form-control" value="" />
@*Number*@
<div class="annytab-form-label">Number</div>
<input name="txtNumber" type="number" class="annytab-form-control" value="" min="1" max="10" />
@*Range*@
<div class="annytab-form-label">Range</div>
<input name="txtRange" type="range" class="annytab-form-control" value="" min="1" max="10" step="1" />
@*Pattern*@
<div class="annytab-form-label">True/false</div>
<input name="txtPattern" type="text" class="annytab-form-control" value="" pattern="^(?:tru|fals)e$" data-error-pattern="true or false only!" />
@*Textarea*@
<div class="annytab-form-label">Textarea</div>
<textarea name="txtContents" required></textarea>
<div class="annytab-basic-space"></div>
@*Button panel*@
<div class="annytab-form-button-container">
<input type="button" class="annytab-form-button btn-disablable" value="@save_tt" onclick="sendForm(document.getElementById('inputForm'))" />
@*<input type="submit" class="annytab-form-button" value="@save_tt" />*@
<input type="button" class="annytab-form-button btn-disablable" value="@cancel_tt" onclick="location.href='/admin_administrators'" />
</div>
</form>
@section scripts {
<script>
// Submit a form
async function sendForm(form)
{
// Disable buttons
disableButtons();
// Make sure that the form is valid
if (await annytab.validation.valid(form) === false) { enableButtons(); return false; }
// Get form data
var fd = new FormData(form);
// Post form data
var xhr = new XMLHttpRequest();
xhr.open('POST', form.getAttribute('action'), true);
xhr.onload = function () {
if (xhr.status === 200)
{
// Get the response
var data = JSON.parse(xhr.response);
// Check the success status
if (data.success === true)
{
// Output a success message
toastr['success'](data.message);
}
else
{
// Output error information
toastr['error'](data.message);
}
}
else
{
// Output error information
toastr['error'](xhr.status + " - " + xhr.statusText);
}
// Enable buttons
enableButtons();
};
xhr.onerror = function ()
{
// Output error information
toastr['error'](xhr.status + " - " + xhr.statusText);
// Enable buttons
enableButtons();
};
xhr.send(fd);
} // End of the sendForm method
// Disable buttons
function disableButtons()
{
var buttons = document.getElementsByClassName('btn-disablable');
for (var i = 0; i < buttons.length; i++) {
buttons[i].setAttribute('disabled', true);
}
} // End of the disableButtons method
// Enable buttons
function enableButtons()
{
var buttons = document.getElementsByClassName('btn-disablable');
for (var i = 0; i < buttons.length; i++) {
setTimeout(function (button) { button.removeAttribute('disabled'); }, 1000, buttons[i]);
}
} // End of the enableButtons method
</script>
}
Styling
You can style error messages, apply styles to invalid and valid elements. This is an element that is used to output error messages for the txtUsername element.
<div class="validation-error" data-error-for="txtUsername"></div>
This is example CSS to style the error output element and to apply a style to invalid elements.
/* Validation */
.validation-error {
display: block;
font-weight: normal;
font-size: 16px;
line-height: 16px;
color: #f00000;
padding: 5px 5px 10px 0;
margin: 0;
}
input:invalid, textarea:invalid{
border: 1px solid #f00000;
}
input:disabled, button:disabled, input:disabled:hover, button:disabled:hover {
resize: none;
outline: none;
color: #000000;
background-color: #ddd;
border-color: #ddd;
cursor: default;
}
A visual image of a form is displayed below, browers built-in styling is applied if a error message element not is present.
Remote validation
Remote validation means that we call a server method to get a true/false response. If you want to add remote validation you need to add a data-validation-remote attribute and a data-error-remote attribute to an element. The data-validation-remote attribute should include an url and names of additional fields, data is delimited by comma (,).
<input name="txtUsername" type="text" class="annytab-form-control" value="" data-validation-remote="/admin_administrators/verify_username,txtId" data-error-remote="The username is already in use!" />
You will also need a server method that is called by the validation code.
HttpPost]
[Authorize(Roles = "Administrator")]
public async Task<IActionResult> verify_username(IFormCollection collection)
{
// Get the current domain
WebDomain current_domain = await this.web_domain_repository.GetCurrentDomain(ControllerContext.HttpContext);
// Get translated texts
KeyStringList tt = await this.static_text_repository.GetFromCache(current_domain.back_end_language_code);
// Get form data
string id = collection["txtId"].ToString();
string username = collection["txtUsername"].ToString();
// Get a administrator on user name
ModelItem<AdministratorDocument> admin_username_model = await this.administrator_repository.GetByUsername(username);
// Check if the username exists already
if (admin_username_model.item != null && id != admin_username_model.item.id)
{
// Return false
return Ok(false);
//return Json(data: false);
}
// Return success
return Ok(true);
//return Json(data: true);
} // End of the verify_username method
Confirmation validation
Confirmation validation (equalto) requires you to add a data-validation-equalto attribute and a data-error-equalto attribute to the element that is used to confirm another input element.
<input name="txtConfirmPassword" type="password" class="annytab-form-control" value="" data-validation-equalto="txtPassword" data-error-equalto="Passwords does not match!" />
File validation
File validation requires you to add a data-validation-file attribute with a maximum file size in bytes and a data-error-file attribute with an error message. Set the maximum file size to -1 if you only want to validate on file extensions. You can add an accept attribute to restrict upload to allowed file extensions only.
<div class="annytab-form-label">File</div>
<input type="file" accept=".jpg, .PNG" data-validation-file="262144" data-error-file="Max 256 KiB per file, must be .jpg or .png!" />
Validation of a datalist
A datalist can be used to combine search and select. The datalist element is not supported in older browsers, download a datalist polyfill to add this functionality to incompatible browsers. Add a data-error-datalist attribute if you want to validate that a user only selects one of the specified options.
<input name="selectLanguage" type="text" value="sv"
placeholder="Select language" list="dtLanguagues"
data-error-datalist="You must select a valid language code!" />
<datalist id="dtLanguagues">
<option value="sv">Swedish</option>
<option value="no">Norwegian</option>
<option value="da">Danish</option>
<option value="en">English</option>
<option value="fi">Finnish</option>
</datalist>
Internet Explorer 11 (older browsers)
Internet Explorer 11 and older versions of browsers do not support Async/Await, XMLHttpRequest, Array.prototype.includes, Promise and Element.prototype.remove. We are going to compile our JavaScript as TypeScript by using JavaScript Transpiler by Mads Kristensen. We add a tsconfig.json file to our root folder with the contents shown below. Included files will be transpiled to new files in the specified directory. Files in the transpiled folder can be minified and you can decide on the order in which files should be loaded in a browser.
{
"compileOnSave": true,
"files": [
"wwwroot/js/annytab.html5.validation.js",
"wwwroot/js/annytab.effects.js"
],
"compilerOptions": {
"allowJs": true,
"sourceMap": false,
"target": "es5",
"outDir": "wwwroot/tjs-typescript"
}
}
Transpiled files will be created to the outDir folder each time we build/debug our project or when one of the files is changed.
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var annytab = annytab || {};
annytab.validation = (function () {
'use_strict';
// Variables
var forms = document.getElementsByTagName('form');
// Loop forms
for (var i = 0; i < forms.length; i++) {
// Set forms to not validate, this code will handle validation
forms[i].noValidate = true;
// Add submit listener
window.onload = forms[i].addEventListener('submit', function (event) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// Prevent the form from submitting
event.preventDefault();
return [4 /*yield*/, validate(this)];
case 1:
// Validate a form
if ((_a.sent()) === true) {
// Submit the form
this.submit();
}
return [2 /*return*/];
}
});
});
}, false);
// Get elements in the form
var elements = forms[i].querySelectorAll('input, select, textarea');
// Add listener for elements
for (var j = 0; j < elements.length; j++) {
// Add input listeners
elements[j].addEventListener('keydown', removeValidationErrors, false);
elements[j].addEventListener('mousedown', removeValidationErrors, false);
}
} // End of for (var i = 0; i < forms.length; i++)
// Remove validation errors
function removeValidationErrors(event) {
// Variables
var error_output = null;
// Find equalto element
var confirmation = event.target.form.querySelector('[data-validation-equalto="' + event.target.getAttribute('name') + '"]');
// Remove confirmation error
if (confirmation !== null) {
error_output = event.target.form.querySelector('[data-error-for="' + confirmation.name + '"]');
if (error_output !== null) {
error_output.innerHTML = '';
}
}
// Remove all errors for this element
error_output = event.target.form.querySelector('[data-error-for="' + event.target.getAttribute('name') + '"]');
if (error_output !== null) {
error_output.innerHTML = '';
}
// Remove IE 11 errors
removeErrorsIE11(event.target.form);
} // End of the removeValidationErrors method
// Validate a form
function validate(form) {
return __awaiter(this, void 0, void 0, function () {
var elements, focus, ctrl, error_output, data_validation_remote, data_validation_equalto, data_validation_file, data_error_input, data_error_pattern, data_error_range, data_error_step, data_error_length, data_error_type, data_error_required, data_error_remote, data_error_equalto, data_error_file, data_error_datalist;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
elements = form.querySelectorAll('input, select, textarea');
// Remove IE 11 errors
removeErrorsIE11(form);
focus = false;
i = 0;
_a.label = 1;
case 1:
if (!(i < elements.length)) return [3 /*break*/, 15];
ctrl = elements[i];
// Check if the control should be validated
if (ctrl.willValidate === false) {
return [3 /*break*/, 14];
}
error_output = form.querySelector('[data-error-for="' + ctrl.getAttribute('name') + '"]');
data_validation_remote = ctrl.getAttribute('data-validation-remote');
data_validation_equalto = ctrl.getAttribute('data-validation-equalto');
data_validation_file = ctrl.getAttribute('data-validation-file');
data_error_input = ctrl.getAttribute('data-error-input');
data_error_pattern = ctrl.getAttribute('data-error-pattern');
data_error_range = ctrl.getAttribute('data-error-range');
data_error_step = ctrl.getAttribute('data-error-step');
data_error_length = ctrl.getAttribute('data-error-length');
data_error_type = ctrl.getAttribute('data-error-type');
data_error_required = ctrl.getAttribute('data-error-required');
data_error_remote = ctrl.getAttribute('data-error-remote');
data_error_equalto = ctrl.getAttribute('data-error-equalto');
data_error_file = ctrl.getAttribute('data-error-file');
data_error_datalist = ctrl.getAttribute('data-error-datalist');
// Reset custom validation
ctrl.setCustomValidity('');
if (!(ctrl.validity.badInput === true)) return [3 /*break*/, 2];
if (data_error_input !== null) {
ctrl.setCustomValidity(data_error_input);
}
return [3 /*break*/, 13];
case 2:
if (!(ctrl.validity.patternMismatch === true)) return [3 /*break*/, 3];
if (data_error_pattern !== null) {
ctrl.setCustomValidity(data_error_pattern);
}
return [3 /*break*/, 13];
case 3:
if (!(ctrl.validity.rangeOverflow === true || ctrl.validity.rangeUnderflow === true)) return [3 /*break*/, 4];
if (data_error_range !== null) {
ctrl.setCustomValidity(data_error_range);
}
return [3 /*break*/, 13];
case 4:
if (!(ctrl.validity.stepMismatch === true)) return [3 /*break*/, 5];
if (data_error_step !== null) {
ctrl.setCustomValidity(data_error_step);
}
return [3 /*break*/, 13];
case 5:
if (!(ctrl.validity.tooLong === true || ctrl.validity.tooShort)) return [3 /*break*/, 6];
if (data_error_length !== null) {
ctrl.setCustomValidity(data_error_length);
}
return [3 /*break*/, 13];
case 6:
if (!(ctrl.validity.typeMismatch === true)) return [3 /*break*/, 7];
if (data_error_type !== null) {
ctrl.setCustomValidity(data_error_type);
}
return [3 /*break*/, 13];
case 7:
if (!(ctrl.validity.valueMissing === true)) return [3 /*break*/, 8];
if (data_error_required !== null) {
ctrl.setCustomValidity(data_error_required);
}
return [3 /*break*/, 13];
case 8:
if (!(data_validation_equalto !== null)) return [3 /*break*/, 9];
if (equaltoValidation(ctrl, data_validation_equalto) === false) {
ctrl.setCustomValidity(data_error_equalto);
}
return [3 /*break*/, 13];
case 9:
if (!(data_validation_file !== null)) return [3 /*break*/, 10];
if (fileValidation(ctrl, data_validation_file) === false) {
ctrl.setCustomValidity(data_error_file);
}
return [3 /*break*/, 13];
case 10:
if (!(data_error_datalist !== null)) return [3 /*break*/, 11];
if (datalistValidation(ctrl) === false) {
ctrl.setCustomValidity(data_error_datalist);
}
return [3 /*break*/, 13];
case 11:
if (!(data_validation_remote !== null)) return [3 /*break*/, 13];
return [4 /*yield*/, remoteValidation(ctrl, data_validation_remote)];
case 12:
// Perform remote validation
if ((_a.sent()) === false) {
ctrl.setCustomValidity(data_error_remote);
}
_a.label = 13;
case 13:
// Set error message in custom control or report validity
if (ctrl.validationMessage !== '' && error_output !== null) {
error_output.innerHTML = ctrl.validationMessage;
// Set focus to the first element
if (focus === false) {
focus = true;
ctrl.focus();
}
}
else if (ctrl.validationMessage !== '' && ctrl.reportValidity) {
ctrl.reportValidity();
}
else if (ctrl.validationMessage !== '') {
// IE 11
ctrl.insertAdjacentHTML('afterend', '<div class="validation-error ie-11-error-output">' + ctrl.validationMessage + '</div>');
}
_a.label = 14;
case 14:
i++;
return [3 /*break*/, 1];
case 15: // for (i = 0; i < elements.length; i++)
// Return true or false
return [2 /*return*/, form.checkValidity()];
}
});
});
} // End of the validate method
// Perform equalto validation
function equaltoValidation(ctrl, other_field) {
// Get the value of the other field
var other_value = document.getElementsByName(other_field)[0].value;
// Check if values are different
if (ctrl.value !== other_value) {
return false;
}
// Return true
return true;
} // End of the equaltoValidation method
// Perform file validation
function fileValidation(ctrl, max_size) {
// Make sure that there is files
if (ctrl.files.length <= 0) {
return true;
}
// Check accept attribute
var accepts = ctrl.getAttribute('accept') !== null ? ctrl.getAttribute('accept').toLowerCase().replace(' ', '').split(',') : null;
// Loop files
for (var i = 0; i < ctrl.files.length; i++) {
// Get the file extension
var extension = ctrl.files[i].name.substring(ctrl.files[i].name.lastIndexOf('.')).toLowerCase();
// Check for errors
if (accepts !== null && accepts.includes(extension) === false) {
return false;
}
else if (max_size !== null && max_size > 0 && ctrl.files[i].size >= max_size) {
return false;
}
}
// Return true
return true;
} // End of the fileValidation method
// Perform a datalist validation
function datalistValidation(ctrl) {
// Loop options
for (var i = 0; i < ctrl.list.options.length; i++) {
if (ctrl.value === ctrl.list.options[i].value) {
return true;
}
}
// Return false
return false;
} // End of the datalistValidation method
// Perform remote validation
function remoteValidation(ctrl, input) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
// Return a promise
return [2 /*return*/, new Promise(function (resolve, reject) {
// Get input values
var values = input.split(',');
var uri = values[0].trim();
var fields = [];
fields.push(ctrl.getAttribute('name'));
for (var i = 1; i < values.length; i++) {
fields.push(values[i].trim());
}
// Create form data
var fd = new FormData();
for (i = 0; i < fields.length; i++) {
fd.append(fields[i], document.getElementsByName(fields[i])[0].value);
}
var xhr = new XMLHttpRequest();
xhr.open('POST', uri, true);
xhr.onload = function () {
// Check if the response is successful
if (xhr.status === 200) {
// Return a success response
if (xhr.response === 'false') {
resolve(false);
}
else {
resolve(true);
}
}
else {
// Return a reject response
reject(xhr.status + ' ' + xhr.statusText);
}
};
xhr.onerror = function () {
// Return a reject response
reject('There was a network error.');
};
xhr.send(fd);
})];
});
});
} // End of the remoteValidation method
// Remove IE 11 errors
function removeErrorsIE11(form) {
var ie_errors = form.querySelectorAll('.ie-11-error-output');
for (var i = 0; i < ie_errors.length; i++) {
ie_errors[i].remove();
}
} // End of the removeErrorsIE11 method
// Public methods
return {
valid: function (form) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, validate(form)];
case 1: return [2 /*return*/, _a.sent()];
}
});
});
}
};
})();
We need to add a polyfill and transpile JavaScript code that calls our valid method to be able to use our es5 code. Our sendForm method in the example above has been compiled to es5 as TypeScript as well, it uses helper methods from the transpiled file.
<script crossorigin="anonymous" src="https://polyfill.io/v3/polyfill.min.js?features=Array.prototype.includes%2CPromise%2CXMLHttpRequest%2CElement.prototype.remove"></script>
<script src="/tjs-typescript/annytab.html5.validation.js"></script>
<script>
function sendForm(form)
{
return __awaiter(this, void 0, void 0, function () {
var fd, xhr;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// Disable buttons
disableButtons();
return [4 /*yield*/, annytab.validation.valid(form)];
case 1:
// Make sure that the form is valid
if ((_a.sent()) === false) {
enableButtons();
return [2 /*return*/, false];
}
fd = new FormData(form);
xhr = new XMLHttpRequest();
xhr.open('POST', form.getAttribute('action'), true);
xhr.onload = function () {
if (xhr.status === 200) {
// Get the response
var data = JSON.parse(xhr.response);
// Check the success status
if (data.success === true) {
// Output a success message
alert(data.message);
}
else {
// Output error information
alert(data.message);
}
}
else {
// Output error information
alert(xhr.status + " - " + xhr.statusText);
}
// Enable buttons
enableButtons();
};
xhr.onerror = function () {
// Output error information
alert(xhr.status + " - " + xhr.statusText);
// Enable buttons
enableButtons();
};
xhr.send(fd);
return [2 /*return*/];
}
});
});
} // End of the sendForm method
</script>