<form action="#" class="m-form" data-sending-lbl="Senden...">
<div class="m-form__custom-width-block">
<div class="m-form__element" style="width: calc(30% - 0.5em);">
<div class="a-input a-input--custom-width">
<label for="plz" class="a-input__label ">
PLZ
</label>
<input id="plz" class="a-input__field " type="text" name="plz" value="" false>
<div class="a-input__helptext">This field is optional</div>
</div>
</div>
<div class="m-form__element" style="width: calc(70% - 0.5em);">
<div class="a-input a-input--custom-width">
<label for="e_mail" class="a-input__label a-input__label--required">
E-Mail
</label>
<input id="e_mail" class="a-input__field is-required" type="email" name="e_mail" value="" true>
<div class="a-input__helptext">This field is required</div>
</div>
</div>
</div>
<div class="m-form__custom-width-block">
<div class="m-form__element" style="width: calc(50% - 0.5em);">
<div class="a-input a-input--custom-width">
<label for="vorname" class="a-input__label ">
Vorname
</label>
<input id="vorname" class="a-input__field " type="text" name="vorname" value="" false>
</div>
</div>
<div class="m-form__element" style="width: calc(50% - 0.5em);">
<div class="a-input a-input--custom-width">
<label for="nachname" class="a-input__label ">
Nachname
</label>
<input id="nachname" class="a-input__field " type="text" name="nachname" value="" false>
</div>
</div>
</div>
<div class="m-form__element">
<div class="a-input">
<label for="adresse" class="a-input__label ">
Adresse
</label>
<input id="adresse" class="a-input__field " type="text" name="adresse" value="">
</div>
</div>
<div class="m-form__element">
<div class="m-form__option-label">Thirsty?</div>
<fieldset class="m-form__option-fieldset">
<div class="a-checkoption">
<input type="radio" name="thirsty" value="beer" id="beer" class="a-checkoption__field">
<label for="beer" class="a-checkoption__label a-checkoption--option">
Beer
</label>
</div>
<div class="a-checkoption">
<input type="radio" name="thirsty" value="wine" id="wine" class="a-checkoption__field">
<label for="wine" class="a-checkoption__label a-checkoption--option">
Wine
</label>
</div>
<div class="a-checkoption">
<input type="radio" name="thirsty" value="schnaps" id="schnaps" class="a-checkoption__field">
<label for="schnaps" class="a-checkoption__label a-checkoption--option">
Schnaps
</label>
</div>
</fieldset>
</div>
<div class="m-form__element">
<div class="m-form__checks-label">Hungry?</div>
<fieldset class="m-form__checks-fieldset">
<div class="a-checkoption">
<input type="hidden" name="apero" value="0">
<input id="apero" class="a-checkoption__field" type="checkbox" name="apero">
<label for="apero" class="a-checkoption__label ">
Apero
</label>
</div>
<div class="a-checkoption">
<input type="hidden" name="main" value="0">
<input id="main" class="a-checkoption__field" type="checkbox" name="main">
<label for="main" class="a-checkoption__label ">
Main course
</label>
</div>
<div class="a-checkoption">
<input type="hidden" name="dessert" value="0">
<input id="dessert" class="a-checkoption__field" type="checkbox" name="dessert">
<label for="dessert" class="a-checkoption__label ">
Dessert
</label>
</div>
</fieldset>
</div>
<div class="m-form__custom-width-block">
<div class="m-form__element" style="width: calc(50% - 0.5em);">
<div class="a-select ">
<label for="payment" class="a-select__label a-select__label--required">
Select Payment Method
</label>
<div class="a-select__background">
<select id="payment" class="a-select__field" name="payment" true>
<option value=""> </option>
<option value="cash">Cash</option>
<option value="card">Card</option>
</select>
</div>
</div>
</div>
<div class="m-form__element" style="width: calc(50% - 0.5em);">
<div class="a-input a-input--custom-width">
<label for="amount" class="a-input__label ">
Betrag
</label>
<input id="amount" class="a-input__field " type="text" name="amount" value="" false>
</div>
</div>
</div>
<div class="m-form__element m-form__privacy-policy">
<div class="a-checkoption">
<input type="hidden" name="privacy_policy" value="0">
<input id="privacy_policy" class="a-checkoption__field" type="checkbox" name="privacy_policy" required>
<label for="privacy_policy" class="a-checkoption__label a-checkoption--legal">
Ich stimme der Datenverarbeitung gemäss Datenschutzbestimmung zu.
</label>
</div>
</div>
<div class="m-form__submit-wrapper">
<div class="m-form__submit-button">
<button type="button" class="a-button a-button--secondary" data-form-submit>Informiert bleiben</button>
</div>
<div class="m-form__submit-byline">Ich abonniere den Newsletter.</div>
</div>
<div class="m-form__message m-form__message--success">
<h2 class="m-form__message-title">Vielen Dank!</h2>
<p class="m-form__message-body">Schon bald erhälst du unseren Newsletter :)</p>
</div>
<div class="m-form__message m-form__message--failure">
<h2 class="m-form__message-title">Uups, da ist etwas schief gelaufen</h2>
<p class="m-form__message-body">Bitte kontaktiere uns, falls das Problem nochmals vor kommt.</p>
<p class="m-form__message-error"></p>
</div>
<div class="m-form__message m-form__message--invalid">
<h2 class="m-form__message-title"></h2>
<p class="m-form__message-body"></p>
<p class="m-form__message-error"></p>
</div>
</form>
<form action="#" class="m-form" data-sending-lbl="{{ submitting }}">
{{#each fields}}
{{#if custom_width_start}}<div class="m-form__custom-width-block">{{/if}}
{{#if this.confirmation}}
<div class="m-form__element" {{#if this.style}} style="{{ style }}"{{/if}}>
{{> @a-checkoption this}}
</div>
{{/if}}
{{#if this.select}}
<div class="m-form__element" {{#if this.style}} style="{{ style }}"{{/if}}>
{{> @a-select this}}
</div>
{{/if}}
{{#if this.checkbox}}
<div class="m-form__element" {{#if this.style}} style="{{ style }}"{{/if}}>
<div class="m-form__checks-label">{{this.label}}</div>
<fieldset class="m-form__checks-fieldset">
{{#each this.options}}
{{> @a-checkoption this}}
{{/each}}
</fieldset>
</div>
{{/if}}
{{#if this.option}}
<div class="m-form__element" {{#if this.style}} style="{{ style }}"{{/if}}>
<div class="m-form__option-label">{{this.label}}</div>
<fieldset class="m-form__option-fieldset">
{{#each this.options}}
{{> @a-checkoption this}}
{{/each}}
</fieldset>
</div>
{{/if}}
{{#if this.input}}
<div class="m-form__element" {{#if this.style}} style="{{ style }}"{{/if}}>
{{> @a-input this}}
</div>
{{/if}}
{{#if custom_width_end}}</div>{{/if}}
{{/each}}
<div class="m-form__element m-form__privacy-policy" {{#if this.style}} style="{{ style }}"{{/if}}>
{{> @a-checkoption
name='privacy_policy'
label=privacy_policy
required=true
modifiers='legal'
id='privacy_policy'
}}
</div>
<div class="m-form__submit-wrapper">
<div class="m-form__submit-button">
{{> @a-button label=submit js_hook='form-submit' modifiers='secondary'}}
</div>
<div class="m-form__submit-byline">{{ submit_byline }}</div>
</div>
<div class="m-form__message m-form__message--success">
<h2 class="m-form__message-title">{{ success_title }}</h2>
<p class="m-form__message-body">{{ success_body }}</p>
</div>
<div class="m-form__message m-form__message--failure">
<h2 class="m-form__message-title">{{ error_title }}</h2>
<p class="m-form__message-body">{{ error_body }}</p>
<p class="m-form__message-error">{{ error_message }}</p>
</div>
<div class="m-form__message m-form__message--invalid">
<h2 class="m-form__message-title">{{ validation_title }}</h2>
<p class="m-form__message-body">{{ validation_body }}</p>
<p class="m-form__message-error">{{ validation_message }}</p>
</div>
</form>
.m-form {
&__element {
padding: 0.2em 0 0;
}
&__custom-width-block {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
&__message {
max-height: 0;
overflow: hidden;
transition: max-height $transition-ease $transition-slow;
}
&__message.is-shown {
max-height: 10000px;
}
&__sending {
display: inline-block;
width: 0.7em;
text-align: left;
margin-left: 0.2em;
}
&__submit-wrapper {
max-height: 10000px;
overflow: hidden;
transition: height $transition-ease $transition-slow;
display: flex;
flex-direction: column-reverse;
margin-top: 1em;
@include media($size-small) {
align-items: center;
flex-direction: row;
}
}
&__submit-byline {
margin: 0.25em 0 0.4em;
line-height: 1.4em;
@include media($size-small) {
margin: 0 0 0 1em;
}
}
&__submit-wrapper.is-hidden {
height: 0;
}
&__checks-label, &__option-label {
margin-top: 0.75em;
}
}
import BaseView from 'base-view';
import ajax from '../../../js/service/ajax';
const SUBMIT_BUTTON_SELECTOR = '[data-form-submit]';
const SUBMIT_WRAPPER_SELECTOR = '.m-form__submit-wrapper';
const SUCCESS_MESSAGE_SELECTOR = '.m-form__message--success';
const ERROR_MESSAGE_SELECTOR = '.m-form__message--failure';
const INVALID_MESSAGE_SELECTOR = '.m-form__message--invalid';
const SERVER_FEEDBACK_MESSAGE_SELECTOR = '.m-form__message-error';
const FORM_SELECTOR = '.m-form';
const HIDDEN_STATE = 'is-hidden';
const SHOWN_STATE = 'is-shown';
const INVALID_STATE = 'is-invalid';
const LAST_SUBMISSION = 'pred';
// sync with a-progress.js
const SUBMISSION_NOTIFICATION_EVENT = 'supt_form_submission';
export default class MForm extends BaseView {
static getUrlParam( param, defaultValue ) {
const url = new URL( window.location.href );
const value = url.searchParams.get( param );
if (value) {
return value;
}
return defaultValue;
}
bind() {
super.bind();
/* @noinspection JSCheckFunctionSignaturesInspection */
this.on( 'submit', e => this.submit( e ) );
}
static buildRedirectUrl( url, formId ) {
url = new URL( url );
url.searchParams.set( LAST_SUBMISSION, formId );
return url.toString();
}
handleError( resp ) {
if (resp instanceof Object && 'data' in resp) {
if ('nonce' in resp.data) {
this.element.dataset.nonce = resp.data.nonce;
}
if ('general' in resp.data) {
this.showErrorMessage( resp.data.general );
}
else if ('validation' in resp.data) {
this.showInvalidMessage( resp.data.validation );
}
else {
this.showErrorMessage( '' );
}
}
else {
this.showErrorMessage( '' );
}
}
clearSendingState() {
clearInterval( this.sendingTimer );
this.submitButton.disabled = false;
this.submitted = false;
this.submitButton.innerHTML = this.origSubmitLbl;
}
showInvalidMessage( messages ) {
let invalidMessage = this.getScopedElement( INVALID_MESSAGE_SELECTOR );
let serverMessage = invalidMessage.querySelector(
SERVER_FEEDBACK_MESSAGE_SELECTOR );
let message = '';
for (const key of Object.keys( messages )) {
let el = this.getScopedElement( '[name=' + key + ']' );
this.addClass( el, INVALID_STATE );
message += `<li>${ messages[ key ] }</li>`;
}
serverMessage.innerHTML = `<ul>${ message }</ul>`;
this.addClass( invalidMessage, SHOWN_STATE );
invalidMessage.setAttribute( 'aria-hidden', 'false' );
}
showErrorMessage( message ) {
let errorMessage = this.getScopedElement( ERROR_MESSAGE_SELECTOR );
let serverMessage = errorMessage.querySelector(
SERVER_FEEDBACK_MESSAGE_SELECTOR );
serverMessage.innerHTML = `<ul><li>${ message }</li></ul>`;
this.addClass( errorMessage, SHOWN_STATE );
errorMessage.setAttribute( 'aria-hidden', 'false' );
}
initialize() {
this.submitButton = this.getScopedElement( SUBMIT_BUTTON_SELECTOR );
this.submitted = false;
this.sendingTimer = null;
this.origSubmitLbl = this.submitButton.innerHTML;
this.predecessorId = MForm.getUrlParam( LAST_SUBMISSION, - 1 );
}
showSending() {
let lbl = this.element.dataset.sendingLbl;
let counter = 0;
this.sendingTimer = setInterval( () => {
counter ++;
counter = counter <= 3 ? counter : 0;
let lblAdd = '';
for (let i = 0; i < counter; i ++) {
lblAdd += '.';
}
this.submitButton.innerHTML = lbl + '<span class="m-form__sending">' +
lblAdd + '</span>';
}, 300 );
}
submit( event ) {
event.preventDefault();
// hide error messages
let errorMessage = this.getScopedElement( ERROR_MESSAGE_SELECTOR );
let invalidMessage = this.getScopedElement( INVALID_MESSAGE_SELECTOR );
errorMessage.setAttribute( 'aria-hidden', 'true' );
invalidMessage.setAttribute( 'aria-hidden', 'true' );
this.removeClass( errorMessage, SHOWN_STATE );
this.removeClass( invalidMessage, SHOWN_STATE );
// prevent double submit
if (this.submitted) {
return;
}
this.submitButton.disabled = true;
this.submitted = true;
// mark as sending
this.showSending();
// the form data
const data = this.getFormData();
const url = this.element.action;
// get a nonce, then submit the form
this.getNonce().
then( nonce => {
data.append( 'nonce', nonce );
return this.getSecret( nonce );
} ).
then( secret => data.append( 'secret', secret ) ).
then( () => this.sendForm( url, data ) ).
then( resp => this.showSuccess( resp ) ).
then( () => this.sendSubmissionNotification() ).
catch( resp => this.handleError( resp ) ).
finally( this.clearSendingState.bind( this ) );
}
sendForm( url, data ) {
return ajax( url, 'POST', data ).then( ( resp ) => {
if (resp instanceof Object
&& 'success' in resp
&& true === resp.success) {
return Promise.resolve( resp.data );
}
else {
return Promise.reject( resp );
}
} );
}
getFormData() {
let data = new FormData( this.element );
/**
* append the following data in JS, so we have a first spam barrier
*/
// add wordpress action
data.append( 'action', 'supt_form_submit' );
// add form id
data.append( 'form_id', this.element.dataset.formId );
// add action id (engagement funnel plugin)
if (this.element.dataset.actionId) {
data.append( 'action_id', this.element.dataset.actionId );
}
// add config id (engagement funnel plugin)
if (this.element.dataset.configId) {
data.append( 'config_id', this.element.dataset.configId );
}
// add the id of the last form
data.append( 'predecessor_id', this.predecessorId );
return data;
}
showSuccess( data ) {
if (- 1 === data.next_action_id || !data.html) {
if (data.redirect && - 1 === data.next_action_id) {
window.location.href = MForm.buildRedirectUrl(
data.redirect,
data.predecessor_id
);
}
else {
this.predecessorId = data.predecessor_id;
let submitWrapper = this.getScopedElement( SUBMIT_WRAPPER_SELECTOR );
let successMessage = this.getScopedElement( SUCCESS_MESSAGE_SELECTOR );
submitWrapper.setAttribute( 'aria-hidden', 'true' );
successMessage.setAttribute( 'aria-hidden', 'false' );
this.addClass( submitWrapper, HIDDEN_STATE );
this.addClass( successMessage, SHOWN_STATE );
}
}
else {
let parent = this.element.parentNode.parentNode;
parent.innerHTML = data.html;
let form = new MForm( parent.querySelector( FORM_SELECTOR ) );
form.bind();
this.destroy();
}
}
getNonce() {
return ajax( this.element.dataset.nonce, 'GET' );
}
getSecret( nonce ) {
const rawSecret = `${ this.element.dataset.formId }${ nonce }`;
return this.sha256hex( rawSecret );
}
sha256hex( string ) {
const encoder = new TextEncoder();
const data = encoder.encode( string );
return crypto.subtle.digest( 'SHA-256', data ).then( hashBuffer => {
// convert ArrayBuffer to hex string
const hashArray = Array.from( new Uint8Array( hashBuffer ) );
return hashArray.map( b => b.toString( 16 ).padStart( 2, '0' ) ).
join( '' );
} );
}
sendSubmissionNotification() {
const notification = new CustomEvent( SUBMISSION_NOTIFICATION_EVENT, {
detail: { formId: this.element.dataset.formId },
} );
document.dispatchEvent( notification );
}
}
{
"submit": "Informiert bleiben",
"submit_byline": "Ich abonniere den Newsletter.",
"submitting": "Senden...",
"success_title": "Vielen Dank!",
"success_body": "Schon bald erhälst du unseren Newsletter :)",
"error_title": "Uups, da ist etwas schief gelaufen",
"error_body": "Bitte kontaktiere uns, falls das Problem nochmals vor kommt.",
"privacy_policy": "Ich stimme der Datenverarbeitung gemäss Datenschutzbestimmung zu.",
"fields": [
{
"input": true,
"text": true,
"id": "plz",
"name": "plz",
"label": "PLZ",
"helptext": "This field is optional",
"style": "width: calc(30% - 0.5em);",
"wrapper_classes": "a-input--custom-width",
"required": false,
"type": "text",
"custom_width_start": true
},
{
"input": true,
"text": true,
"id": "e_mail",
"name": "e_mail",
"label": "E-Mail",
"style": "width: calc(70% - 0.5em);",
"wrapper_classes": "a-input--custom-width",
"helptext": "This field is required",
"required": true,
"type": "email",
"custom_width_end": true
},
{
"input": true,
"text": true,
"id": "vorname",
"name": "vorname",
"label": "Vorname",
"style": "width: calc(50% - 0.5em);",
"wrapper_classes": "a-input--custom-width",
"required": false,
"type": "text",
"custom_width_start": true
},
{
"input": true,
"text": true,
"id": "nachname",
"name": "nachname",
"label": "Nachname",
"style": "width: calc(50% - 0.5em);",
"wrapper_classes": "a-input--custom-width",
"required": false,
"type": "text",
"custom_width_end": true
},
{
"input": true,
"text": true,
"id": "adresse",
"name": "adresse",
"label": "Adresse",
"type": "text"
},
{
"label": "Thirsty?",
"option": true,
"options": [
{
"name": "thirsty",
"value": "beer",
"label": "Beer",
"id": "beer",
"option": true,
"modifiers": [
"option"
]
},
{
"name": "thirsty",
"value": "wine",
"label": "Wine",
"id": "wine",
"option": true,
"modifiers": [
"option"
]
},
{
"name": "thirsty",
"value": "schnaps",
"label": "Schnaps",
"id": "schnaps",
"option": true,
"modifiers": [
"option"
]
}
]
},
{
"label": "Hungry?",
"checkbox": true,
"options": [
{
"name": "apero",
"label": "Apero",
"id": "apero"
},
{
"name": "main",
"label": "Main course",
"id": "main"
},
{
"name": "dessert",
"label": "Dessert",
"id": "dessert"
}
]
},
{
"label": "Select Payment Method",
"select": true,
"id": "payment",
"name": "payment",
"required": true,
"options": [
{
"value": "cash",
"label": "Cash"
},
{
"value": "card",
"label": "Card"
}
],
"style": "width: calc(50% - 0.5em);",
"wrapper_classes": "a-input--custom-width",
"custom_width_start": true
},
{
"input": true,
"text": true,
"id": "amount",
"name": "amount",
"label": "Betrag",
"style": "width: calc(50% - 0.5em);",
"wrapper_classes": "a-input--custom-width",
"required": false,
"type": "text",
"custom_width_end": true
}
]
}
No notes defined.