SELECT-REPLACE (Sonstige Elemente )

src/app/shared/utilities/select-replace/templates

Demo Section

Each variation will be presented in the following section.

Simple Select


Multiple Select


Readme

select-replace (utility)

Description

Utility that hides the native select and adds a custom UI which can be styled easily. Actions and the whole state of the ui are mapped to the native select. It has also multiselect support.


Requirements


Installation

Installation with Veams from local machine

veams install bp absolute/filepath/to/select-replace


JavaScript Options

The module gives you the possibility to override default JS options:

Option Type Default Description
classes.active String 'is-active' Class to be set on button if at least one item is selected
classes.disabled String 'is-disabled' Class to be set on item that should be disabled
classes.open String 'is-open' Class for open state of button and list
classes.uiReplaced String 'is-ui-replaced' Class to be set on native select after ui has been been created
i18n.placeholder String 'Please select' Fallback text for placeholder (should be overwritten in other languages)
i18n.selected String 'Selected' Fallback screenreader-only label for selected element (should be overwritten in other languages)
i18n.selectMenu String 'Select Menu' Fallback screenreader-only headline text for select element (should be overwritten in other languages)
selectors.btn String '[data-js-item="btn"]' Element selector for button
selectors.btnWrapper String '[data-js-item="btn-wrapper"]' Element selector for button wrapper
selectors.listItem String '[data-js-item="list-item"]' Element selector for list item
selectors.listWrapper String '[data-js-item="list-wrapper"]' Element selector for list wrapper
selectors.listWrapperInner String '[data-js-item="list-wrapper-inner"]' Element selector for list wrapper inner
templates.btn String 'SELECTREPLACE__BTN' Client side template for button (which opens the list)
templates.list String 'SELECTREPLACE__LIST' Client side template for list
templates.preRender String 'SELECTREPLACE__PRERENDER' Client side template for prerendering custom select UI
useSelectWidth Boolean true Sync width of replacement element with width of native select
validateSelectEvent String 'formValidation:validateSelect' Event triggered as soon as select is validated (domAction and errorClass as event obj props)

Templates

select-replace.hbs

{{#wrapWith "form"}}
	{{> form__select }}
{{/wrapWith}}

Data File

select-replace.hjson

{
	"variations": {
		"simple": {
			"docs": {
				"variationName": "Simple Select"
			},
			"settings": {
				"id": "select-1",
				"labelClasses": "is-aural",
				"required": false,
				"jsModule": true,
				"jsOptions": {
					"placeholder": "Land"
				}
			},
			"content": {
				"label": "Bitte Land auswählen",
				"options": [
					{
						"settings": {
							"disabled": true,
							"selected": true
						},
						"content": {
							"val": "",
							"name": "Land"
						}
					},
					{
						"content": {
							"val": "Lorem-1",
							"name": "Lorem-2"
						}
					},
					{
						"content": {
							"val": "Lorem-2",
							"name": "Lorem-2"
						}
					},
					{
						"content": {
							"val": "Lorem-3",
							"name": "Lorem-3"
						}
					},
					{
						"content": {
							"val": "Lorem-4",
							"name": "Lorem-4"
						}
					}
				]
			}
		},
		"multiple": {
			"docs": {
				"variationName": "Multiple Select"
			},
			"settings": {
				"id": "select-2",
				"labelClasses": "is-aural",
				"required": false,
				"jsModule": true,
				"jsOptions": {
					"placeholder": "Land"
				},
				"attributes": [
					{
						"attrKey": "multiple",
						"attrValue": "multiple"
					}
				]
			},
			"content": {
				"label": "Bitte Länder auswählen",
				"options": [
					{
						"content": {
							"val": "Lorem-1",
							"name": "Lorem-1"
						}
					},
					{
						"content": {
							"val": "Lorem-2",
							"name": "Lorem-2"
						}
					},
					{
						"content": {
							"val": "Lorem-3",
							"name": "Lorem-3"
						}
					},
					{
						"content": {
							"val": "Lorem-4",
							"name": "Lorem-4"
						}
					}
				]
			}
		}
	}
}

Scripts

select-replace.js

/**
 * This utility replaces the UI of native select for styling purposes.
 *
 * @module SelectReplace
 * @version v1.0.0
 *
 * @author Andy Gutsche
 */

// Imports
import $ from 'jquery';
import Component from '@veams/component';

// Variables

class SelectReplace extends Component {
	/**
	 * Class Properties
	 */
	$el = $(this.el);
	isMultiSelect = this.el.multiple;
	isOpen = false;
	modifierClasses = [];

	/**
	 * Constructor for our class
	 *
	 * @see module.js
	 *
	 * @param {Object} obj - Object which is passed to our class
	 * @param {Object} obj.el - element which will be saved in this.el
	 * @param {Object} obj.options - options which will be passed in as JSON object
	 */
	constructor(obj) {
		let options = {
			classes: {
				active: 'is-active',
				disabled: 'is-disabled',
				open: 'is-open',
				uiReplaced: 'is-ui-replaced'
			},
			i18n: {
				placeholder: 'Please select',
				selected: 'Selected',
				selectMenu: 'Select Menu'
			},
			selectors: {
				btn: '[data-js-item="btn"]',
				btnWrapper: '[data-js-item="btn-wrapper"]',
				listItem: '[data-js-item="list-item"]',
				listWrapper: '[data-js-item="list-wrapper"]',
				listWrapperInner: '[data-js-item="list-wrapper-inner"]'
			},
			templates: {
				btn: 'SELECTREPLACE__BTN',
				list: 'SELECTREPLACE__LIST',
				preRender: 'SELECTREPLACE__PRERENDER'
			},
			useSelectWidth: true,
			validateSelectEvent: 'formValidation:validateSelect'
		};

		super(obj, options);
	}

	/**
	 * Get module information
	 *
	 */
	static get info() {
		return {
			version: '1.0.0'
		};
	}

	/**
	 * Event handling
	 *
	 */
	get events() {
		return {
			'{{this.context.EVENTS.change}}': 'renderAll',
			'{{this.options.validateSelectEvent}}': 'onValidateSelect'
		};
	}

	/**
	 * Subscribe handling
	 *
	 */
	get subscribe() {
		return {
			'{{this.context.EVENTS.selectReplace.closeAll}}': 'closeList'
		};
	}

	/**
	 * Get data from native select
	 *
	 */
	get data() {
		let $options = this.$el.find('option');
		let data = {};
		let optionsArr = [];
		let i = 0;

		for (i; i < $options.length; i++) {
			let $option = $($options[i]);

			optionsArr[i] = {};
			optionsArr[i].text = $option.text();
			optionsArr[i].value = $option[0].value;
			optionsArr[i].selected = $option[0].selected;
			optionsArr[i].disabled = $option[0].disabled;
		}

		data.items = optionsArr;
		data.i18n = this.options.i18n;
		data.disabled = this.el.disabled;

		return data;
	}

	/**
	 * Initialize the view
	 *
	 */
	didMount() {}

	/**
	 * Bind events
	 *
	 */
	bindEvents() {
		let fnDefaultClickHandler = this.defaultClickHandler.bind(this);
		let fnButtonHandler = this.buttonHandler.bind(this);
		let fnSelectItemHandler = this.selectItemHandler.bind(this);

		// Global events
		if (this.options.useSelectWidth) {
			this.registerEvent('{{this.context.EVENTS.resize}}', 'setWidth', true);
		}

		// Local events
		$(document.body).on(this.context.EVENTS.click, fnDefaultClickHandler);
		this.$btnWrapper.on(this.context.EVENTS.click, fnButtonHandler);
		this.$listWrapper.on(
			this.context.EVENTS.click,
			this.options.selectors.listItem,
			fnSelectItemHandler
		);
	}

	/**
	 * Handle select validation state
	 *
	 * @param {Object} e - Event object
	 * @param {Object} obj - Additional event data
	 * @param {String} obj.domAction - DOM action ('addClass' | 'removeClass')
	 * @param {String} obj.errorClass - Error class (eg. 'is-error')
	 */
	onValidateSelect(e, obj) {
		this.$btnWrapper[obj.domAction](obj.errorClass);
	}

	/**
	 * Handle click outside button or list
	 *
	 * @param {Object} [e] - Event object
	 */
	defaultClickHandler(e) {
		this.closeList();
	}

	/**
	 * Handle click on button
	 *
	 * @param {Object} e - Event object
	 */
	buttonHandler(e) {
		e.stopPropagation();

		this.isOpen ? this.closeList() : this.openList();
	}

	/**
	 * Handle click on list item
	 *
	 * @param {Object} e - Event object
	 */
	selectItemHandler(e) {
		let $clickedEl = $(e.target);
		let $option = $('option[value="' + $clickedEl.attr('data-value') + '"]', this.el);

		e.stopPropagation();

		if ($clickedEl.hasClass(this.options.classes.disabled)) {
			return;
		}

		if ($option[0].selected) {
			$option[0].selected = false;
		} else {
			$option[0].selected = true;
		}

		this.$el.trigger(this.context.EVENTS.change);

		if (!this.isMultiSelect) {
			this.closeList();
		}

		this.lastScrollOffset = this.$listWrapperInner[0].scrollTop;
		this.renderAll();
	}

	/**
	 * Open list
	 *
	 */
	openList() {
		this.context.Vent.trigger(this.context.EVENTS.selectReplace.closeAll);

		this.$btn.attr('aria-expanded', true);
		this.$btnWrapper.addClass(this.options.classes.open);
		this.$listWrapper.addClass(this.options.classes.open);
		this.$listWrapper.removeAttr('disabled');

		this.isOpen = true;
	}

	/**
	 * Close list
	 *
	 */
	closeList() {
		this.$listWrapperInner[0].scrollTop = 0;

		this.$btn.attr('aria-expanded', false);
		this.$btnWrapper.removeClass(this.options.classes.open);
		this.$listWrapper.removeClass(this.options.classes.open);
		this.$listWrapper.attr('disabled', 'disabled');

		this.isOpen = false;
	}

	/**
	 * Set width of rendered ui element to width of native select element
	 *
	 */
	setWidth() {
		this.$replaceEl.css({
			width: this.$el.outerWidth()
		});
	}

	/**
	 * Render container for button and list
	 *
	 */
	preRender() {
		let preRenderData = {
			isMultiSelect: this.isMultiSelect,
			instanceId: this.instanceId
		};

		let classes = [].slice.call(this.$el[0].classList);

		this.$replaceEl = $(this.renderTemplate(this.options.templates.preRender, preRenderData));

		for (let i = 0; i < classes.length; i++) {
			if (classes[i].indexOf('is-') === 0) {
				this.modifierClasses.push(classes[i]);
			}
		}

		this.$btnWrapper = this.$replaceEl.find(this.options.selectors.btnWrapper);
		this.$listWrapper = this.$replaceEl.find(this.options.selectors.listWrapper);

		this.el.parentNode.insertBefore(this.$replaceEl[0], this.el.nextSibling);

		if (this.options.useSelectWidth) {
			this.setWidth();
		}

		this.$el.addClass(this.options.classes.uiReplaced);
		this.renderAll();
	}

	/**
	 * Render button and list
	 *
	 */
	renderAll() {
		this.renderBtn(this.data);
		this.renderList(this.data);
	}

	/**
	 * Render button
	 *
	 * @param {Object} data - Object containing data from native select
	 */
	renderBtn(data) {
		let i = 0;
		let text = '';

		let btnData = {
			instanceId: this.instanceId,
			modifierClasses: this.modifierClasses.join(' '),
			ariaExpanded: this.isOpen,
			disabled: data.disabled,
			tabIndex: this.el.tabIndex
		};

		this.$btn = $(this.renderTemplate(this.options.templates.btn, btnData));
		let selectedCount = 0;

		for (i; i < data.items.length; i++) {
			if (data.items[i].selected) {
				text = data.items[i].text;
				selectedCount++;
			}
		}

		if (selectedCount === 0) {
			text = this.options.i18n.placeholder;
			this.$btnWrapper.removeClass(this.options.classes.active);
		} else {
			this.$btnWrapper.addClass(this.options.classes.active);
		}

		if (selectedCount > 1) {
			text = this.options.i18n.placeholder + ' (' + selectedCount + ')';
		}

		this.$btn
			.text(text)
			.attr(
				'aria-label',
				this.options.i18n.selectMenu + ': ' + this.options.i18n.placeholder
			);
		this.$btnWrapper.empty();
		this.$btnWrapper.append(this.$btn);
	}

	/**
	 * Render list
	 *
	 * @param {Object} data - Object containing data from native select
	 */
	renderList(data) {
		this.$listWrapperInner = $(this.renderTemplate(this.options.templates.list, data));
		this.$listWrapperInner[0].scrollTop = this.lastScrollOffset;

		this.$listWrapper.empty();
		this.$listWrapper.append(this.$listWrapperInner);
	}
}

export default SelectReplace;

HTML Output

Simple Select

<form class="c-form--default
	" action="" method="" data-css="c-form">
	<div class="form__select">
		<div class="form__label-wrapper">
			<label for="" class="form__select-label">
				Bitte Land auswählen
			</label>
		</div>
		<div class="form__select-wrapper">
			<!--
				data-placeholder="Bitte Wählen" 			 
				data-reset-text=" - "
				data-has-reset-option="true|false"
			-->
			<select id="select-1" data-placeholder="Bitte Wählen" data-js-module="form-dropdown" name="">
				<option value="" disabled selected>Land</option>
				<option value="Lorem-1">Lorem-2</option>
				<option value="Lorem-2">Lorem-2</option>
				<option value="Lorem-3">Lorem-3</option>
				<option value="Lorem-4">Lorem-4</option>
			</select>
		</div>
	</div>
</form>

Multiple Select

<form class="c-form--default
	" action="" method="" data-css="c-form">
	<div class="form__select">
		<div class="form__label-wrapper">
			<label for="" class="form__select-label">
				Bitte Länder auswählen
			</label>
		</div>
		<div class="form__select-wrapper">
			<!--
				data-placeholder="Bitte Wählen" 			 
				data-reset-text=" - "
				data-has-reset-option="true|false"
			-->
			<select id="select-2" data-placeholder="Bitte Wählen" data-js-module="form-dropdown" name="" multiple="multiple">
				<option value="Lorem-1">Lorem-1</option>
				<option value="Lorem-2">Lorem-2</option>
				<option value="Lorem-3">Lorem-3</option>
				<option value="Lorem-4">Lorem-4</option>
			</select>
		</div>
	</div>
</form>

Wonach suchst du?