Skip to content

Commit

Permalink
Better error messages for standalone contact forms
Browse files Browse the repository at this point in the history
Refs #20
  • Loading branch information
Ugoku committed Dec 4, 2019
1 parent 9f7432b commit 0f21d9e
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 10 deletions.
5 changes: 5 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## unreleased
* Small styling improvement
* Option `showSubmit` for contact forms is now named `standalone`. The old option will remain as alias until the next major version
* Better error messages for standalone contact forms

## 1.2.0 (2019-12-03)
* Show error when input is higher than allowed
* Allow selecting only a few package IDs
Expand Down
5 changes: 5 additions & 0 deletions docs/demo.css
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ input[type="number"] {
color: #a00;
padding-left: 0.25em;
}
.booking-error {
background: hsl(0, 25%, 96%);
border: 1px solid #a00;
padding: 0.5em;
}

.recras-onlinebooking > *:not(:first-child) + * {
border-top: 2px solid hsla(147, 25%, 25%, 0.25);
Expand Down
110 changes: 104 additions & 6 deletions src/contactForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ class RecrasContactForm {
return this.element.querySelectorAll(querystring);
}

isStandalone(options) {
if (options.standalone) {
return true;
}
if (options.showSubmit) {
console.warn('Option "showSubmit" was renamed to "standalone". Please update your code.')
}
return false;
}

generateForm(extraOptions = {}) {
let waitFor = [];

Expand All @@ -88,14 +98,16 @@ class RecrasContactForm {
RecrasCSSHelper.loadCSS('pikaday');
}
return Promise.all(waitFor).then(() => {
let html = '<form class="recras-contactform">';
const standalone = this.isStandalone(extraOptions);
const validateText = standalone ? 'novalidate' : '';
let html = `<form class="recras-contactform" ${ validateText }>`;
if (extraOptions.voucherQuantitySelector) {
html += this.quantitySelector();
}
this.contactFormFields.forEach((field, idx) => {
html += '<div>' + this.showField(field, idx) + '</div>';
});
if (extraOptions.showSubmit) {
if (standalone) {
html += this.submitButtonHtml();
}
html += '</form>';
Expand Down Expand Up @@ -163,6 +175,30 @@ class RecrasContactForm {
});
}

getInvalidFields() {
let invalid = [];
let required = this.getRequiredFields();

let els = this.findElements('.recras-contactform :invalid');
for (let el of els) {
if (!required.includes(el)) {
invalid.push(el);
}
}
return invalid;
}

getRequiredFields() {
let isEmpty = [];
let els = this.findElements('.recras-contactform :required');
for (let el of els) {
if (el.value === undefined || el.value === '') {
isEmpty.push(el);
}
}
return isEmpty;
}

hasFieldOfType(identifier) {
return this.contactFormFields.filter(field => {
return field.field_identifier === identifier;
Expand All @@ -178,6 +214,22 @@ class RecrasContactForm {
return this.hasFieldOfType('boeking.arrangement');
}

isEmpty() {
let isEmpty = true;
let els = this.findElements('.recras-contactform input, .recras-contactform select, .recras-contactform textarea');
let formValues = [...els].map(el => el.value);
for (let val of formValues) {
if (val !== '') {
isEmpty = false;
}
}
return isEmpty;
}

isValid() {
return this.findElement('.recras-contactform').checkValidity();
}

loadingIndicatorHide() {
[...document.querySelectorAll('.recrasLoadingIndicator')].forEach(el => {
el.parentNode.removeChild(el);
Expand All @@ -195,12 +247,22 @@ class RecrasContactForm {
return `<div><label for="number-of-vouchers">${ this.languageHelper.translate('VOUCHER_QUANTITY') }</label><input type="number" id="number-of-vouchers" class="number-of-vouchers" min="1" value="1" required></div>`;
}

removeErrors(parentQuery = '') {
[...this.findElements(parentQuery + ' .booking-error')].forEach(el => {
el.parentNode.removeChild(el);
});
}

removeWarnings() {
[...this.findElements('.recrasError')].forEach(el => {
el.parentNode.removeChild(el);
});
}

requiredIsEmpty() {
return this.getRequiredFields().length > 0;
}

showField(field, idx) {
if (field.soort_invoer === 'header') {
return `<h3>${ field.naam }</h3>`;
Expand Down Expand Up @@ -309,7 +371,7 @@ class RecrasContactForm {
this.loadingIndicatorShow(this.element);
return this.getContactFormFields()
.then(() => this.generateForm({
showSubmit: true,
standalone: true,
}))
.then(html => {
this.appendHtml(html);
Expand All @@ -330,6 +392,29 @@ class RecrasContactForm {
});
}

showInlineErrors() {
for (let el of this.getRequiredFields()) {
const labelEl = el.parentNode.querySelector('label');
const requiredText = this.languageHelper.translate('CONTACT_FORM_FIELD_REQUIRED', {
FIELD_NAME: labelEl.innerText,
});
el.parentNode.insertAdjacentHTML(
'afterend',
`<div class="booking-error">${ requiredText }</div>`
);
}
for (let el of this.getInvalidFields()) {
const labelEl = el.parentNode.querySelector('label');
const invalidText = this.languageHelper.translate('CONTACT_FORM_FIELD_INVALID', {
FIELD_NAME: labelEl.innerText,
});
el.parentNode.insertAdjacentHTML(
'afterend',
`<div class="booking-error">${ invalidText }</div>`
);
}
}

showLabel(field, idx) {
let labelText = field.naam;
if (field.verplicht) {
Expand All @@ -344,14 +429,27 @@ class RecrasContactForm {

submitForm(e) {
e.preventDefault();
this.eventHelper.sendEvent(RecrasEventHelper.PREFIX_CONTACT_FORM, RecrasEventHelper.EVENT_CONTACT_FORM_SUBMIT, this.options.getFormId());

let submitButton = this.findElement('.submitForm');

let status = this.checkRequiredCheckboxes();
if (!status) {
this.removeErrors('.recras-contactform');
if (this.isEmpty()) {
submitButton.parentNode.insertAdjacentHTML(
'afterend',
`<div class="booking-error">${ this.languageHelper.translate('ERR_CONTACT_FORM_EMPTY') }</div>`
);
return false;
} else if (this.requiredIsEmpty() || !this.isValid()) {
this.showInlineErrors();
return false;
}

if (!this.checkRequiredCheckboxes()) {
return false;
}

this.eventHelper.sendEvent(RecrasEventHelper.PREFIX_CONTACT_FORM, RecrasEventHelper.EVENT_CONTACT_FORM_SUBMIT, this.options.getFormId());

this.loadingIndicatorHide();
this.loadingIndicatorShow(submitButton);

Expand Down
15 changes: 12 additions & 3 deletions src/languageHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ class RecrasLanguageHelper {
ATTR_REQUIRED: 'Erforderlich',
BOOKING_DISABLED_AGREEMENT: 'You have not agreed to the terms yet',
BOOKING_DISABLED_AMOUNTS_INVALID: 'Programme amounts are invalid',
BOOKING_DISABLED_CONTACT_FORM_INVALID: 'Contact form is not filled in completely, or contains invalid values',
BOOKING_DISABLED_CONTACT_FORM_INVALID: 'Contact form is not filled in correctly',
BOOKING_DISABLED_INVALID_DATE: 'No date selected',
BOOKING_DISABLED_INVALID_TIME: 'No time selected',
BOOKING_DISABLED_REQUIRED_PRODUCT: 'Required product not yet selected',
BUTTON_BOOK_NOW: 'Jetzt buchen',
BUTTON_BUY_NOW: 'Jetzt kaufen',
BUTTON_SUBMIT_CONTACT_FORM: 'Submit',
CONTACT_FORM_CHECKBOX_REQUIRED: 'At least one option must be checked',
CONTACT_FORM_FIELD_INVALID: '"{FIELD_NAME}" is invalid',
CONTACT_FORM_FIELD_REQUIRED: '"{FIELD_NAME}" is a required field',
CONTACT_FORM_SUBMIT_FAILED: 'The contact form could not be sent. Please try again later.',
CONTACT_FORM_SUBMIT_SUCCESS: 'The contact form was sent successfully.',
DATE: 'Datum',
Expand Down Expand Up @@ -59,6 +61,7 @@ class RecrasLanguageHelper {
DISCOUNT_TITLE: 'Rabattcode oder Gutschein',
DISCOUNT_INVALID: 'Ungültiger Rabattcode oder Gutschein',
ERR_AMOUNTS_NO_PACKAGE: 'Option "productAmounts" is set, but "package_id" is not set',
ERR_CONTACT_FORM_EMPTY: 'Contact form is not filled in',
ERR_GENERAL: 'Etwas ist schief gelaufen:',
ERR_INVALID_ELEMENT: 'Option "Element" ist kein gültiges Element',
ERR_INVALID_HOSTNAME: 'Option "recras_hostname" ist ungültig.',
Expand Down Expand Up @@ -93,14 +96,16 @@ class RecrasLanguageHelper {
ATTR_REQUIRED: 'Required',
BOOKING_DISABLED_AGREEMENT: 'You have not agreed to the terms yet',
BOOKING_DISABLED_AMOUNTS_INVALID: 'Programme amounts are invalid',
BOOKING_DISABLED_CONTACT_FORM_INVALID: 'Contact form is not filled in completely, or contains invalid values',
BOOKING_DISABLED_CONTACT_FORM_INVALID: 'Contact form is not filled in correctly',
BOOKING_DISABLED_INVALID_DATE: 'No date selected',
BOOKING_DISABLED_INVALID_TIME: 'No time selected',
BOOKING_DISABLED_REQUIRED_PRODUCT: 'Required product not yet selected',
BUTTON_BOOK_NOW: 'Book now',
BUTTON_BUY_NOW: 'Buy now',
BUTTON_SUBMIT_CONTACT_FORM: 'Submit',
CONTACT_FORM_CHECKBOX_REQUIRED: 'At least one option must be checked',
CONTACT_FORM_FIELD_INVALID: '"{FIELD_NAME}" is invalid',
CONTACT_FORM_FIELD_REQUIRED: '"{FIELD_NAME}" is a required field',
CONTACT_FORM_SUBMIT_FAILED: 'The contact form could not be sent. Please try again later.',
CONTACT_FORM_SUBMIT_SUCCESS: 'The contact form was sent successfully.',
DATE: 'Date',
Expand Down Expand Up @@ -139,6 +144,7 @@ class RecrasLanguageHelper {
DISCOUNT_TITLE: 'Discount code or voucher',
DISCOUNT_INVALID: 'Invalid discount code or voucher',
ERR_AMOUNTS_NO_PACKAGE: 'Option "productAmounts" is set, but "package_id" is not set',
ERR_CONTACT_FORM_EMPTY: 'Contact form is not filled in',
ERR_GENERAL: 'Something went wrong:',
ERR_INVALID_ELEMENT: 'Option "element" is not a valid Element',
ERR_INVALID_HOSTNAME: 'Option "recras_hostname" is invalid.',
Expand Down Expand Up @@ -173,14 +179,16 @@ class RecrasLanguageHelper {
ATTR_REQUIRED: 'Vereist',
BOOKING_DISABLED_AGREEMENT: 'Je bent nog niet akkoord met de voorwaarden',
BOOKING_DISABLED_AMOUNTS_INVALID: 'Aantallen in programma zijn ongeldig',
BOOKING_DISABLED_CONTACT_FORM_INVALID: 'Contactformulier is niet volledig ingevuld, of bevat ongeldige waardes',
BOOKING_DISABLED_CONTACT_FORM_INVALID: 'Contactformulier is niet correct ingevuld',
BOOKING_DISABLED_INVALID_DATE: 'Geen datum geselecteerd',
BOOKING_DISABLED_INVALID_TIME: 'Geen tijd geselecteerd',
BOOKING_DISABLED_REQUIRED_PRODUCT: 'Vereist product nog niet geselecteerd',
BUTTON_BOOK_NOW: 'Nu boeken',
BUTTON_BUY_NOW: 'Nu kopen',
BUTTON_SUBMIT_CONTACT_FORM: 'Versturen',
CONTACT_FORM_CHECKBOX_REQUIRED: 'Ten minste één optie moet aangevinkt worden',
CONTACT_FORM_FIELD_INVALID: '"{FIELD_NAME}" is ongeldig',
CONTACT_FORM_FIELD_REQUIRED: '"{FIELD_NAME}" is een verplicht veld',
CONTACT_FORM_SUBMIT_FAILED: 'Het contactformulier kon niet worden verstuurd. Probeer het later nog eens.',
CONTACT_FORM_SUBMIT_SUCCESS: 'Het contactformulier is succesvol verstuurd.',
DATE: 'Datum',
Expand Down Expand Up @@ -219,6 +227,7 @@ class RecrasLanguageHelper {
DISCOUNT_TITLE: 'Kortingscode of tegoedbon',
DISCOUNT_INVALID: 'Ongeldige kortingscode of tegoedbon',
ERR_AMOUNTS_NO_PACKAGE: 'Optie "productAmounts" is ingesteld, maar "package_id" is niet ingesteld',
ERR_CONTACT_FORM_EMPTY: 'Contactformulier is niet ingevuld',
ERR_GENERAL: 'Er ging iets mis:',
ERR_INVALID_ELEMENT: 'Optie "element" is geen geldig Element',
ERR_INVALID_HOSTNAME: 'Optie "recras_hostname" is ongeldig.',
Expand Down
71 changes: 70 additions & 1 deletion test/js-unit/recrasContactFormSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,75 @@ describe('RecrasContactForm', () => {
expect(sorted[1]).toEqual(packs[0]);
expect(sorted[2]).toEqual(packs[1]);
});
})
});

describe('getRequiredFields', () => {
let rc;

beforeEach(async () => {
rc = new RecrasContactForm(new RecrasOptions({
element: this.mainEl,
form_id: 1,
recras_hostname: 'demo.recras.nl',
}));
await rc.showForm();
});

it('only returns required fields', () => {
const els = rc.getRequiredFields();
expect(els.length).toBeGreaterThan(0);
for (const el of els) {
expect(el.getAttribute('required')).not.toBeNull();
}
});
});

describe('getInvalidFields', () => {
let rc;

beforeEach(async () => {
rc = new RecrasContactForm(new RecrasOptions({
element: this.mainEl,
form_id: 1,
recras_hostname: 'demo.recras.nl',
}));
await rc.showForm();
});

it('returns invalid fields', () => {
let elEmail = rc.findElement('[type="email"]');
elEmail.value = 'invalid';

let invalid = rc.getInvalidFields();
expect(invalid.length).toBe(1);
expect(invalid[0]).toBe(elEmail);

elEmail.value = '[email protected]';

invalid = rc.getInvalidFields();
expect(invalid.length).toBe(0);
});
});

describe('isEmpty', () => {
let rc;

beforeEach(async () => {
rc = new RecrasContactForm(new RecrasOptions({
element: this.mainEl,
form_id: 5,
recras_hostname: 'demo.recras.nl',
}));
await rc.showForm();
});

it('returns if form is empty', () => {
expect(rc.isEmpty()).toBe(true);
let elEmail = rc.findElement('[type="email"]');
elEmail.value = '[email protected]';

expect(rc.isEmpty()).toBe(false);
});
});
});
});

0 comments on commit 0f21d9e

Please sign in to comment.