<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>
  • Content:
    .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;
    	}
    }
    
  • URL: /components/raw/m-form/_m-form.scss
  • Filesystem Path: styleguide/src/components/molecules/m-form/_m-form.scss
  • Size: 936 Bytes
  • Content:
    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 );
    	}
    }
    
  • URL: /components/raw/m-form/m-form.js
  • Filesystem Path: styleguide/src/components/molecules/m-form/m-form.js
  • Size: 7.2 KB
{
  "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.