Validation is based on Parsley.js
formValidator.data-parsley-trigger attribute.input, as they would otherwise display an error immediately upon entry.data-parsley-file-types attribute. The value must be a comma-separated list of file types (e.g. image/jpeg, image/png).data-parsley-max-file-size attribute. The value must be specified in kilobytes without a unit (e.g. 1024).Internet Explorer
There are problems with input fields of type “Date” in Internet Explorer (all versions) and Parsley.js, as Internet Explorer ignores the local format. The value of the field in IE always has the pattern “YYYY-MM-DD”, but Parsley expects, for example, “DD.MM.YYYY” in German and marks the field as erroneous.
This incompatibility is solved via a script that converts all fields of type “Date” to fields of type “Text” in Internet Explorer and tells Parsley via the data-parsley-pattern attribute to test it like a date (in the German notation).
<div class="formInset">
<form class="formInset-form formValidator" method="POST" action="./" accept-charset="UTF-8" data-parsley-validate data-parsley-trigger="input focusout">
<fieldset class="formFieldset">
<div class="formFieldset-fields">
<div class="formFieldset-field">
<div class="formField is-email is-required" data-validate="true">
<label class="label is-required" for="field-business-email">Business Email Address</label>
<span class="formField-field">
<input class="field" type="email" id="field-business-email" name="business-email" placeholder="" spellcheck="false" required="" aria-required="true" data-parsley-trigger="focusout" data-parsley-business-email="" />
</span>
<div class="formMessage js-formValidator-message">
Because our offer is only aimed at business customers, we do not accept e-mail addresses from service providers such as Gmail.
</div>
</div>
</div>
</div>
<div class="buttonGroup">
<button class="button" type="submit"><span class="button-label">Submit</span></button>
</div>
</fieldset>
</form>
</div>
<div class="formInset{{#modifier}} {{.}}{{/modifier}}">
{{#title}}
<h2 class="formInset-title">{{{.}}}</h2>
{{/title}}
{{#if description}}
<div class="formInset-description"{{#theme}} data-theme="{{.}}"{{/theme}}>
{{{description}}}
{{#figure}}
{{render '@figure' (contextData '@forminset' this) merge=true}}
{{/figure}}
{{#documentmockup}}
{{render '@documentmockup' (contextData '@forminset' this) merge=true}}
{{/documentmockup}}
</div>
{{/if}}
<form class="formInset-form{{#if validate}} formValidator{{/if}}" method="{{#method}}{{.}}{{/method}}{{^method}}POST{{/method}}" action="{{#action}}{{.}}{{/action}}{{^action}}./{{/action}}" accept-charset="UTF-8" {{#if validate}} data-parsley-validate data-parsley-trigger="input focusout"{{/if}}>
{{#formfield}}
<!-- Only for demonstration non-compliant markup -->
{{render '@formfield' (contextData '@forminset' this) merge=false}}
{{/formfield}}
{{#formhidden}}
<div>
<!-- Only for demonstration non-compliant markup -->
{{render '@formhidden' (contextData '@forminset' this) merge=false}}
</div>
{{/formhidden}}
{{#formfieldsets}}
{{render '@formfieldset' (contextData '@forminset' this) merge=false}}
{{/formfieldsets}}
{{#buttongroup}}
{{render '@buttongroup' (contextData '@forminset' this) merge=false}}
{{/buttongroup}}
</form>
</div>
{
"title": null,
"description": null,
"formfieldsets": [
{
"buttongroup": {
"buttons": [
{
"type": "submit",
"label": "Submit"
}
]
},
"fields": [
{
"formfield": {
"is-text": true,
"is-required": true,
"validate": true,
"type": "email",
"id": "business-email",
"name": "business-email",
"label": "Business Email Address",
"message": {
"content": "Because our offer is only aimed at business customers, we do not accept e-mail addresses from service providers such as Gmail."
},
"parsley": {
"trigger": "focusout",
"custom-validator": {
"name": "business-email",
"value": null
}
}
}
}
]
}
],
"buttongroup": null,
"validate": true
}
import formValidator from "./_formInset.script";
function addBusinessEmailValidator() {
function init(data) {
/*
* data {object}: Contains an array "list" with the blacklisted domains.
* If the top level domain is an asterisk, all possible top levels are filtered.
* {
* list: [
* "gmx.de",
* "gmail.*"
* ]
* }
*/
if (typeof data !== "object" || typeof data.list !== "object" || ! data.list.length) {
console.warn("formInset: No definition for business email is found.")
}
const domainsBlacklist = data.list;
window.Parsley.addValidator('businessEmail', {
validateString: function(value) {
const emailChunks = value.split('@');
if (emailChunks.length < 2) {
return true;
}
const domain = emailChunks[1].toLowerCase();
if (domain === "") {
return true;
}
const domainWithoutTopLevel = domain.replace(/\.[^.]+$/, ".*");
return ! domainsBlacklist.includes(domain) && ! domainsBlacklist.includes(domainWithoutTopLevel);
},
messages: {
en: 'Only business email addresses are accepted.',
de: 'Es werden nur geschäftliche E-Mail-Adressen akzeptiert.',
pl: 'Akceptujemy tylko firmowe adresy e-mail.',
},
});
}
fetch('/domainsBlacklist.json')
.then(response => response.json())
.then(data => {
init(data);
})
.catch(error => console.error('formInset: Can’t load JSON with definitions for business email.', error));
}
/**
* Telephone number validator for Parsley.js
*
* Regex: /^(\+?\d{2,4}\s?)?[\d\s]{8,20}$/
* Accepts: +49 894238438290, 0049 980502389, 04584 539894385, 894238438290
*/
window.Parsley.addValidator('telephone', {
validateString: function (value, requirement, instance) {
// Empty field is allowed (optional)
if (!value || value.trim() === '') {
return true;
}
// Regex: /^(\+?\d{2,4}\s?)?[\d\s]{8,20}$/
// Accepts: +49 894238438290, 0049 980502389, 04584 539894385, 894238438290
const phoneRegex = /^(\+?\d{2,4}\s?)?[\d\s]{8,20}$/,
isValid = phoneRegex.test(value.trim());
return isValid;
},
});
/**
* Automatically attach telephone validation to all input[type="tel"]
*/
function addTelephoneValidation() {
const telInputs = document.querySelectorAll('input[type="tel"]');
telInputs.forEach( input => {
input.setAttribute('data-parsley-telephone', 'true');
input.setAttribute('data-parsley-trigger', 'input focusout');
input.setAttribute('data-parsley-validation-threshold', '8');
});
}
/**
* Init
*/
addTelephoneValidation();
addBusinessEmailValidator();
formValidator.init({
lang: "en",
});
export default (function (){
var defaults = {
lang: "de",
selectors: {
forms: "form[data-parsley-validate]",
fieldContainer: '*[data-validate="true"]',
},
submit: {
selector: "",
disable: true,
},
};
var init = function(options){
var forms = $(defaults.selectors.forms);
if (forms.length === 0){
return;
}
const settings = Object.assign({}, defaults, options);
addValidators();
fixIEDateInput();
forms.each(function(){
new Validator(this, settings);
});
};
var Validator = function(form, settings){
var self = this;
this.settings = $.extend(true, {}, settings);
this.form = $(form);
this.submit = $('button[type="submit"]', this.form);
if (! $.isFunction($.fn.parsley)){
return;
}
this.form.parsley({
excluded: "input[type=button], input[type=submit], input[type=reset], input[type=hidden], [disabled]",
errorClass: "",
successClass: "",
errorsWrapper: '<ul class="parsley-errors-list"></ul>',
errorsContainer: function (field) { return getContainer(field) },
lang: this.settings.lang,
}).on("field:validated", function(formField){
self.updateStatus(self.form.parsley().isValid());
}).on("field:success", function(formField){
self.setSuccessStatus(formField);
}).on("field:error", function(formField){
self.setSuccessStatus(formField, false);
});
// Set language
if ( $("html").attr("lang") ){
var locale = $("html").attr("lang") || defaults.lang;
var matches = locale.match(/^[a-zA-Z]{2}/),
lang = matches[0] || this.settings.lang;
try {
window.Parsley.setLocale(lang);
} catch (e) {
console.log("formValidator: The set language is not available.");
}
}
this.updateStatus(false);
};
function getContainer(field){
var element = field.$element;
if (! element.hasClass(defaults.selectors.fieldContainer)){
return element.closest(defaults.selectors.fieldContainer);
}
return;
}
Validator.prototype.updateStatus = function(status){
this.form[0].dataset.valid = status;
}
Validator.prototype.getParent = function(element){
return element.parents(this.settings.selectors.fieldContainer);
}
Validator.prototype.setSuccessStatus = function(formField, valid){
if (typeof valid !== "boolean") {
valid = true;
}
formField.$element.attr("aria-invalid", ! valid);
const parent = formField.$element.parents(this.settings.selectors.fieldContainer);
parent.attr("data-invalid", ! valid);
const siblings = formField.$element.siblings();
siblings.attr("data-invalid", ! valid);
}
Validator.prototype.updateUI = function(refresh) {
};
Validator.prototype.validate = function(refresh) {
this.form.parsley().validate();
return this.form.parsley().isValid();
};
Validator.prototype.submit = function(callback){
var callback = (typeof callback === "function") ? callback : function(){};
if (! $.isFunction($.fn.parsley)){
callback();
return true;
}
var isValid = this.validate();
if (isValid){
callback();
}
return isValid;
};
var addValidators = function(){
window.Parsley.addValidator('maxFileSize', {
validateString: function(_value, maxSize, parsleyInstance) {
if (! window.FormData) {
return true;
}
var files = parsleyInstance.$element[0].files;
return files.length != 1 || files[0].size <= maxSize * 1024;
},
requirementType: 'integer',
messages: {
en: 'This file should not be larger than %s Kb.',
de: 'Die Datei darf nicht größer als %s KB sein.',
}
});
window.Parsley.addValidator('fileTypes', {
validateString: function(_value, types, parsleyInstance) {
if (! window.FormData || ! types) {
return true;
}
var types = types.split(/, ?/);
var files = parsleyInstance.$element[0].files;
return files.length != 1 || types.indexOf(files[0].type) !== -1;
},
requirementType: 'string',
messages: {
en: 'This file is not the right type.',
de: 'Die Datei ist vom falschen Typ.',
}
});
};
var fixIEDateInput = function(input){
function getInternetExplorerVersion(){
var rV = -1; // Return value assumes failure.
if (navigator.appName == 'Microsoft Internet Explorer' || navigator.appName == 'Netscape') {
var uA = navigator.userAgent;
var rE = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})");
if (rE.exec(uA) != null) {
rV = parseFloat(RegExp.$1);
}
else if (!!navigator.userAgent.match(/Trident.*rv\:11\./)) {
rV = 11;
}
}
return rV;
}
if (getInternetExplorerVersion() === -1){
return;
}
$('input[type="date"]').each(function(){
var input = $(this);
// Please regard: Pattern does not check if date exists,
// so eg. `31.02.2000` will be valid
var pattern = "([0-2][1-9]|3[0-1])\\.(0[1-9]|1[0-2])\\.(19|20)\\d{2}";
input.attr("type", "text")
.attr("data-parsley-pattern", pattern)
.attr("data-parsley-validation-threshold", "10");
});
}
return {
init: init
};
})();
@import "_formInset.settings";
@import "_formInset.styles";
%formInset {
.fileCard {
background-color: $_page-color;
}
.buttonGroup {
@include stack-spacing(large);
}
&-title + &-description {
@include stack-spacing(component);
text-align: center;
}
&-description {
> .figure {
@include stack-spacing(large);
}
}
.documentMockup {
@include stack-spacing(large);
background-color: $_BACKDROP-COLOR;
}
&-form {
box-shadow: none;
.buttonGroup {
@include stack-spacing(component);
}
}
&-title ~ &-form > .buttonGroup {
justify-content: center;
}
@include only-on-desktop(){
width: get-columns-width(8);
margin-left: auto;
margin-right: auto;
}
}
%formInset--side-by-side {
width: 100%;
.buttonGroup {
justify-content: flex-start;
}
%formInset-description {
text-align: left !important;
}
%formInset-description[data-theme] {
padding: var(--bp);
}
@include only-on-desktop(){
display: grid;
column-gap: var(--gg);
row-gap: var(--sp-component);
grid-template-columns: 1fr 1fr;
.formFieldset {
@include stack-spacing(0);
}
.formFieldset + .formFieldset {
padding-top: 0;
}
%formInset-title {
grid-column: 1 / span 2;
}
%formInset-description {
@include stack-spacing(0);
grid-column: 1 / span 1;
}
%formInset-form {
@include stack-spacing(0);
grid-column: 2 / span 1;
height: 100%;
border-radius: 0 var(--br) var(--br) 0;
padding: var(--bp);
background-color: $card_background-color;
display: flex;
flex-direction: column;
.formFieldset {
padding: 0;
}
.formFieldset:only-child {
height: 100%;
}
> *:last-child:not(:only-child),
.formFieldset > .buttonGroup:last-child {
flex-grow: 1;
align-items: flex-end;
}
> .buttonGroup {
@include stack-spacing(0);
padding: $formFieldset_stack-spacing 0 0;
}
}
%formInset-description[data-theme] {
padding: var(--bp-large);
margin-right: calc(-.5 * var(--gg));
border-top-left-radius: var(--br);
border-bottom-left-radius: var(--br);
}
%formInset-description[data-theme] + %formInset-form {
margin-left: calc(-.5 * var(--gg));
padding: var(--bp-large);
.formFieldset + .buttonGroup {
padding-top: $formFieldset_stack-spacing;
}
}
}
}
.formInset.is-side-by-side {
@extend %formInset--side-by-side;
}
// Highlighting non-compliant markup
.formInset-form:has(> *[class^="form"]:not(.formFieldset)) {
@extend %_not-compliant;
}
$formInset_button_style--invalid: (
) !default;
%formInset {
@include stack-spacing(section);
&-title {
@extend %sectionTitle;
@include stack-spacing(0);
}
&-description {
@extend %richtextBlock;
&:not(:first-child) {
@include stack-spacing();
}
> *:first-child {
@include stack-spacing(0);
}
}
&-form {
@include stack-spacing(0);
&:not(:first-child) {
@include stack-spacing(component);
}
.buttonGroup {
@include stack-spacing();
justify-content: flex-end;
}
.formFieldset:first-child {
@include stack-spacing(0);
}
}
}
.formInset {
@extend %formInset;
&-title {
@extend %formInset-title;
}
&-description {
@extend %formInset-description;
}
&-form {
@extend %formInset-form;
}
}
//***** Validation *****//
.formInset-form[data-parsley-validate] {
transition-duration: $_transition-duration--out;
*[data-invalid="true"] {
transition: color $_transition-duration--in;
.formMessage {
display: none;
}
}
label {
transition-property: color;
transition-duration: inherit
}
input {
transition-property: border-color;
transition-duration: inherit
}
&[data-valid="false"] .button[type="submit"] {
@include styles($formInset_button_style--invalid);
}
.formToggle-label {
flex-wrap: wrap;
span:not(.toggle) {
flex: 1 1;
}
}
}
.parsley-errors {
&-list {
list-style: none;
padding: 0;
margin-top: 0;
&:empty {
display: none;
}
li {
@extend %formMessage;
}
li + li {
@include stack-spacing(small);
}
.formToggle-label & {
order: 99;
width: 100%;
margin-top: $field_stack-spacing;
}
}
}