Examples

Autonomous Box Select Component

To offer a simple select alternative to radio buttons, mirroring the same attributes as other form controls. Using attributes such as value, disabled to offer a drop in control for configuring something off or on. Bootstraped from a bootstrap file, by importing into a bigger application or direct as html script (ensure you build fallback for IE support).

<my-box-select></my-box-select>

MyBoxSelect Usage <my-box-select>


<!-- Polyfill -->
<script src="/node_modules/promise-polyfill/dist/polyfill.min.js"></script>
<script src="/node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script>

<!-- Bootstrap component only, ensure you have built fallback for IE support too (.js)! -->
<script type="module" src="./your/path/my-box-select.mjs"></script>
<script nomodule src="./your/path/my-box-select.js"></script>

<!-- Or add your switch import to a bootstrap js file/app using ES6 import and use that instead of the component directly... -->
<script type="module" src="./your/path/index.mjs"></script>
<script nomodule src="./your/path/index.js"></script>

<!-- example basic fouc resolution -->
<style>
	[fouc] { opacity: 0; transition: opacity 200ms ease-in-out; }
	[fouc="loaded"] { opacity: 1; }
</style>

...

<div class="info">
	<div class="class">
		<div class="row" fouc>
			<div class="col-sm-4">
				<my-box-select label="Hello" value="1">
					<option value="1">One</option>
					<option value="2">Two</option>
					<option value="3">Three</option>
					<option value="4">Four</option>
				</my-box-select>
			</div>
			<div class="col-sm-4">
				<my-box-select label="Hello" value="2">
					<option value="1">One</option>
					<option value="2">Two</option>
					<option value="3">Three</option>
					<option value="4">Four</option>
				</my-box-select>
			</div>
			<div class="col-sm-4">
				<my-box-select label="Hello" value="3" disabled>
					<option value="1">One</option>
					<option value="2">Two</option>
					<option value="3">Three</option>
					<option value="4">Four</option>
				</my-box-select>
			</div>
		</div>
	</div>
</div>

...

<!-- fouc for individual components -->
<script> setTimeout(() => document.querySelector('[fouc]').setAttribute('fouc', 'loaded'), 1000); </script>
						

MyBoxSelect Class my-box-select.mjs


import { CustomHTMLElement, html } from '../../../node_modules/custom-web-component/index.js';

/**
 * @public @name MyBoxSelect
 * @extends CustomHTMLElement
 * @description Box select control, kinda like a radio button, but boxes instead!
 */
class MyBoxSelect extends CustomHTMLElement {

	/**
     * @public @constructor @name constructor
	 * @description Triggered when component is instantiated (but not ready or in DOM, must call super() first)
	 */
	constructor() {
		super();

		this.value;
		this.invalid;
		this.options = [];
	}

	/**
	 * @public @static @name template
	 * @description Template function to return web component UI
	 * @return {TemplateResult} HTML template result
	 */
	static template() {
		return html`
			<style>
                :host {
					display: inline-block;
					width: 100%;
					min-height: 62px;
					box-sizing: border-box;
				}

				.container {
					display: inline-block;
					width: 100%;
					height: inherit;
					padding: 6px 0 6px 0;
					box-sizing: border-box;
					position: relative;
				}

				.container [invisible] { opacity: 0; }
				
				.container label {
					display: block;
					min-height: 20px;
					color: var(--my-box-select--label--color, #222);
					font-weight: var(--my-box-select--label--font-weight, normal);
					text-align: var(--my-box-select--label--text-align, left);
					font-size: 14px;
					overflow: hidden;
				}
				
				.container .boxer-buttons {
					display: flex;
					padding: 1px;
					width: 100%;
					flex-flow: row;
					border: var(--my-box-select--border, 1px solid #222);
					border-radius: var(--my-box-select--border-radius, 0);
					box-sizing: border-box;
				}
				
 				.container .boxer-buttons .option {
 					display: block;
 					padding: 0 5px;
 					height: 28px;
 					flex: 1 1;
 					background: var(--my-box-select--option--background, white);
 					color: var(--my-box-select--option--color, #222);
 					font-size: var(--my-box-select--option--font-size, 13px);
 					font-family: var(--my-box-select--option--font-family, inherit);
					cursor: var(--my-box-select--option--cursor, default);
 					text-align: center;
 					line-height: 30px;
 					cursor: default;
 					white-space: nowrap;
 					border-radius: var(--my-box-select--option--border-radius, 0);
 				}
				
 				.container .boxer-buttons .option[selected] { background: var(--my-box-select--option--background--selected, #222); color: var(--my-box-select--option--color--selected, white); cursor: default; }
				:host([disabled]) .container { pointer-events: none; cursor: not-allowed; opacity: var(--my-box-select--disabled--opacity, 0.6); }
			</style>

			<div class="container" ?invalid="${this.invalid}">
				<label ?invisible="${!this.hasAttribute('label')}">${this.getAttribute('label')}</label>
				<div id="boxer-buttons" class="boxer-buttons" @change="${this._changeEvent.bind(this)}">
					${this.options.length > 0 ? this.options.map((option) => html`
						<span class="option" tabindex="0" value="${option.value}" ?selected="${option.selected}" @click="${this._changeEvent.bind(this)}">${option.label}</span>
					`) : ''}
				</div>
			</div>
		`;
	}

	/**
	 * @public @static @get @name observedProperties
	 * @description Provide properties to watch for changes
	 * @return {Array} Array of property names as strings
	 */
	static get observedProperties() { return ['value'] }

	/**
	 * @public @name propertyChanged
	 * @description Callback run when a custom elements properties change
	 * @param {String} property The property name
	 * @param {Mixed} oldValue The old value
	 * @param {Mixed} newValue The new value
	 */
	propertyChanged(property, oldValue, newValue) {
		this.updateTemplate();
	}

	/**
	 * @public @static @get @name observedAttributes
	 * @description Provide attributes to watch for changes
	 * @return {Array} Array of attribute names as strings
	 */
	static get observedAttributes() { return ['name', 'label', 'disabled'] }

	/**
	 * @public @name attributeChanged
	 * @description Callback run when a custom elements attributes change
	 * @param {String} attribute The attribute name
	 * @param {Mixed} oldValue The old value
	 * @param {Mixed} newValue The new value
	 */
	attributeChanged(attribute, oldValue, newValue) {
		this.updateTemplate();
	}

	/**
	 * @public @name constructed
	 * @description Lifecycle hook that gets called when the element is finished contructing, perfect for setting up properties
	 */
	constructed() {
		this.value = this.hasAttribute('value') ? this.getAttribute('value') : this.value;
	}

	/**
	 * @public @name connected
	 * @description Callback run once the custom element has been added to the DOM and template is rendered
	 */
	connected() { }

	/**
	 * @public @name templateUpdated
	 * @description Callback run once the template has complete re-render
	 */
	templateUpdated() {
		this.rerender();
	}

	/**
	 * @public @name templateUpdated
	 * @description Callback run once the custom element has complete a re-render
	 */
	rerender() {
		setTimeout(() => {
			this.options = [];
			let options = this.querySelectorAll('option');
			for (let i = 0; i < options.length; i++) {
				let value = options[i].hasAttribute('value') ? options[i].getAttribute('value') : options[i].innerText;
				this.options.push({
					label: options[i].innerText,
					value: value,
					selected: value === this.value
				});				
			}
			this.updateTemplate();
		}, 1);
	}

	/**
	 * @private @name _event
	 * @description Detect an event, update a property and dispatch an event
     * @param {Event} ev Any event that kicks the function
	 */
	_changeEvent(ev) {
		if (this.hasAttribute('disabled')) return;

		this.value = ev.target.hasAttribute('value') ? ev.target.getAttribute('value') : ev.target.innerText;
		this._select(this.value);
		this.updateTemplate();
		this.dispatchEvent(new CustomEvent('change', { detail: ev }));
		
		ev.stopPropagation();
	}

	/**
	 * @private @name _select
	 * @description Select a value
     * @param {Event} ev Any event that kicks the function
	 */
	_select(value) {
		for (let i = 0; i < this.options.length; i++) this.options[i].selected = this.options[i].value === this.value;
		this.updateTemplate();
	}
}

// bootstrap the class as a new web component
customElements.define('my-box-select', MyBoxSelect);