<div class="form-item form-item--select ">
<label for="select-multi" class="form-item__label">
Select
</label>
<div class="select-multiple">
<div class="select-multiple__inner">
<select id="select-multi" multiple class="select-multiple__select " name="Select-multiple" data-placeholder="Choose categories">
<option value="option-1">Option 1</option>
<option value="option-2">Option 2</option>
<option value="option-3">Option 3</option>
<option value="option-4">Option 4</option>
<option value="option-5">Option 5</option>
<option value="option-6">Option 6</option>
<option value="option-7">Option 7</option>
<option value="option-8">Option 8</option>
<option value="option-9">Option 9</option>
<option value="option-10">Option 10</option>
</select>
</div>
</div>
</div>
<div class="form-item form-item--select {{getmodifiers modifiers "form-item"}}">
{{#if label}}
<label for="{{id}}" class="form-item__label">
{{label}}
{{#if required}}
*
{{/if}}
</label>
{{/if}}
<div class="select-multiple">
<div class="select-multiple__inner">
<select id="{{id}}" multiple class="select-multiple__select {{ additionalClasses }}" {{{ getattributes attr }}}>
{{#each options}}
<option value="{{value}}" {{#if selected}}selected{{/if}}>{{name}}</option>
{{/each}}
</select>
</div>
</div>
{{#if errorMsg}}
<div class="form-item__error-msg">{{ errorMsg }}</div>
{{/if}}
</div>
{
"label": "Select",
"id": "select-multi",
"attr": {
"name": "Select-multiple",
"data-placeholder": "Choose categories"
},
"options": [
{
"name": "Option 1",
"value": "option-1"
},
{
"name": "Option 2",
"value": "option-2"
},
{
"name": "Option 3",
"value": "option-3"
},
{
"name": "Option 4",
"value": "option-4"
},
{
"name": "Option 5",
"value": "option-5"
},
{
"name": "Option 6",
"value": "option-6"
},
{
"name": "Option 7",
"value": "option-7"
},
{
"name": "Option 8",
"value": "option-8"
},
{
"name": "Option 9",
"value": "option-9"
},
{
"name": "Option 10",
"value": "option-10"
}
]
}
import getEvent from '../../../functions/getEvent';
const getDistanceToBottom = (top, elementHeight, windowHeight) => {
return windowHeight - top - elementHeight;
};
var SelectMultiple = function(el) {
this.opts = {
maxHeight: 400,
breakpoints: {
769: {
maxHeight: 200
}
}
};
this.el = el;
this.open = false;
this.options = [];
this.navigationIndex = -1;
this.clickOnOutside = this.closeOnOutside.bind(this);
this.init();
};
SelectMultiple.prototype = {
getOptionsByBreakpoint: function(windowWidth) {
let options = this.opts;
for (const key in this.opts.breakpoints) {
if (key > windowWidth) {
options = options.breakpoints[key];
}
}
return options;
},
alignDropdown: function() {
const elementHeight = this.el.offsetHeight;
const windowHeight = window.innerHeight;
const rect = this.el.getBoundingClientRect();
const options = this.getOptionsByBreakpoint(window.innerWidth);
const distanceToTop = rect.top || 0;
const distanceToBottom = getDistanceToBottom(rect.top, elementHeight, windowHeight) || 0;
let maxHeight = 0;
if (distanceToTop > distanceToBottom) {
this.optionsElement.classList.add('select-multiple__options--up');
maxHeight = distanceToTop;
} else {
this.optionsElement.classList.remove('select-multiple__options--up');
maxHeight = distanceToBottom;
}
if (maxHeight > options.maxHeight) {
maxHeight = options.maxHeight;
}
this.optionsElement.style.maxHeight = maxHeight + 'px';
},
insertWrapper: function() {
this.wrapper = document.createElement('div');
this.wrapper.classList.add('select-multiple__wrapper');
this.el.appendChild(this.wrapper);
},
insertToggler: function() {
this.toggler = document.createElement('button');
this.toggler.classList.add('select-multiple__toggler');
this.toggler.textContent = this.select.getAttribute('data-placeholder') || '';
this.wrapper.appendChild(this.toggler);
},
insertOptionAndLabel: function(optionElement) {
const option = document.createElement('a');
option.setAttribute('data-value', optionElement.getAttribute('value'));
if (optionElement.selected) {
option.setAttribute('data-checked', true);
}
option.classList.add('select-multiple__option');
option.innerHTML = optionElement.innerHTML;
this.optionsElement.appendChild(option);
this.options.push({
name: optionElement.innerHTML.toLowerCase(),
optionElement: option,
originalElement: optionElement
});
},
insertListOfOptions: function() {
const options = this.select.querySelectorAll('option');
this.optionsElement = document.createElement('div');
this.optionsElement.classList.add('select-multiple__options');
this.el.appendChild(this.optionsElement);
for (let i = 0; i < options.length; i++) {
this.insertOptionAndLabel(options[i]);
}
this.select.style.display = 'none';
},
selectOption: function(e) {
const focusedOption = this.el.querySelector('.select-multiple__option--focused');
const event = getEvent('click');
if (focusedOption) {
focusedOption.dispatchEvent(event);
e.preventDefault();
}
},
onKeydown: function(e) {
const pressedKey = e.code || e.key;
if (pressedKey === 'ArrowUp') {
this.navigateOptions('up');
e.preventDefault();
} else if (pressedKey === 'ArrowDown') {
this.navigateOptions('down');
e.preventDefault();
} else if (pressedKey === 'Enter') {
this.selectOption(e);
} else if (pressedKey === 'Backspace') {
this.removeLastPill(e);
} else if (pressedKey === 'Escape' || pressedKey === 'Tab') {
this.closeOptions();
}
},
fitOptionInParent: function(option) {
const label = option;
const labelHeight = label.offsetHeight;
const labelOffset = label.getBoundingClientRect();
const parentOffset = this.optionsElement.getBoundingClientRect();
const parentHeight = this.optionsElement.offsetHeight;
if (labelOffset.top + labelHeight > parentOffset.top + parentHeight) {
this.optionsElement.scrollTop = label.offsetTop + label.offsetHeight - this.optionsElement.offsetHeight;
} else if (labelOffset.top < parentOffset.top) {
this.optionsElement.scrollTop = label.offsetTop;
}
},
navigateOptions: function(direction) {
const self = this;
const previousFocused = self.optionsElement.querySelector('.select-multiple__option--focused');
const notChecked = self.optionsElement.querySelector('.select-multiple__option:not([data-checked]):not(.searchable-select__option--hidden)');
let next;
if (!notChecked || notChecked.length < 1) {
return;
}
const findNextVisible = function(el) {
let next = el.nextSibling;
// If on end select the first one
if (!next) {
next = self.optionsElement.querySelector('.select-multiple__option');
}
if (next.getAttribute('data-checked') || next.classList.contains('select-multiple__option--hidden')) {
return findNextVisible(next);
} else {
return next;
}
};
const findPreviousVisible = function(el) {
let previous = el.previousSibling;
// If on first select the last one
if (!previous) {
previous = self.optionsElement.querySelectorAll('.select-multiple__option');
previous = previous[previous.length - 1];
}
if (previous.getAttribute('data-checked') || previous.classList.contains('select-multiple__option--hidden')) {
return findPreviousVisible(previous);
} else {
return previous;
}
};
if (!previousFocused) {
next = self.optionsElement.querySelector('.select-multiple__option:not([data-checked])');
} else if (direction === 'down') {
previousFocused.classList.remove('select-multiple__option--focused');
next = findNextVisible(previousFocused);
} else if (direction === 'up') {
previousFocused.classList.remove('select-multiple__option--focused');
next = findPreviousVisible(previousFocused);
}
next.classList.add('select-multiple__option--focused');
self.fitOptionInParent(next);
},
addPills: function() {
const checked = this.optionsElement.querySelectorAll('[data-checked]');
for (let i = 0; i < checked.length; i++) {
this.addPill(checked[i]);
}
},
addPill: function(option) {
const pill = option.cloneNode(true);
pill.setAttribute('data-checked', true);
pill.setAttribute('class', '');
pill.classList.add('select-multiple__pill');
this.wrapper.insertBefore(pill, this.wrapper.firstChild);
pill.addEventListener('click', this.optionChanged.bind(this));
},
removePill: function(option) {
const pill = this.wrapper.querySelector('.select-multiple__pill[data-value="' + option.getAttribute('data-value') + '"]');
if (pill) {
this.wrapper.removeChild(pill);
}
},
removeLastPill: function(e) {
const pills = this.wrapper.querySelectorAll('.select-multiple__pill');
const event = getEvent('click');
// Only do this if search input is 0 and there is pills present.
if (pills.length > 0) {
pills[pills.length - 1].dispatchEvent(event);
e.preventDefault();
}
},
optionChanged: function(e, noFocus) {
let target = e.target;
let original = this.el.querySelector('[value="' + e.target.getAttribute('data-value') + '"]');
let checked = target.getAttribute('data-checked');
const event = getEvent('change');
if (target.classList.contains('select-multiple__pill')) {
target = this.optionsElement.querySelector('[data-value="' + target.getAttribute('data-value') + '"]');
}
checked = !checked;
if (target.classList.contains('select-multiple__option--focused')) {
this.navigateOptions('down');
}
e.preventDefault();
if (checked) {
original.selected = true;
} else {
original.selected = false;
}
this.select.dispatchEvent(event);
},
updateSelected: function() {
for (let i = 0; i < this.options.length; i++) {
if (this.options[i].originalElement.selected && !this.options[i].optionElement.getAttribute('data-checked')) {
this.addPill(this.options[i].optionElement);
this.options[i].optionElement.setAttribute('data-checked', true);
} else if (!this.options[i].originalElement.selected && this.options[i].optionElement.getAttribute('data-checked')) {
this.removePill(this.options[i].optionElement);
this.options[i].optionElement.removeAttribute('data-checked');
}
}
},
openOptions: function() {
if (!this.open) {
this.open = true;
this.optionsElement.classList.add('select-multiple__options--open');
this.navigationIndex = -1;
document.removeEventListener('click', this.clickOnOutside);
setTimeout(() => {
document.addEventListener('click', this.clickOnOutside);
}, 150);
}
},
closeOptions: function() {
if (this.open) {
/* Remove focused option when closing */
const focusedOption = this.el.querySelector('.select-multiple__option--focused');
if (focusedOption) {
focusedOption.classList.remove('select-multiple__option--focused');
}
document.removeEventListener('click', this.clickOnOutside);
this.open = false;
this.optionsElement.classList.remove('select-multiple__options--open');
this.navigationIndex = -1;
}
},
closeOnOutside: function(e) {
const { target } = e;
if (target !== this.optionsElement &&
target.parentNode !== this.optionsElement &&
target !== this.wrapper &&
target.parentNode !== this.wrapper &&
!target.classList.contains('select-multiple__pill')) {
this.closeOptions();
}
},
addEventListeners: function() {
const self = this;
window.addEventListener('resize', self.alignDropdown.bind(self));
window.addEventListener('scroll', () => {
if (!self.open) {
self.alignDropdown();
}
});
self.wrapper.addEventListener('keydown', self.onKeydown.bind(self));
self.toggler.addEventListener('click', function(e) {
if (!self.open) {
self.openOptions();
} else {
self.closeOptions();
}
e.preventDefault();
});
for (let i = 0; i < self.options.length; i++) {
self.options[i].optionElement.addEventListener('click', self.optionChanged.bind(self));
}
self.select.addEventListener('change', function(e) {
self.updateSelected();
});
},
init: function() {
this.select = this.el.querySelector('.select-multiple__select');
this.insertWrapper();
this.insertListOfOptions();
this.addPills();
this.insertToggler();
this.alignDropdown();
this.addEventListeners();
}
};
export default SelectMultiple;
import SelectMultiple from './SelectMultiple';
const els = document.querySelectorAll('.select-multiple__inner');
for (let i = 0; i < els.length; i++) {
new SelectMultiple(els[i]);
}
.select-multiple {
position: relative;
margin-bottom: size(2);
}
.select-multiple__wrapper {
border: 1px solid $color-gray-2;
width: 100%;
padding: size(1) size(4) size(0.5) size(2);
background: $color-white;
min-height: 50px;
}
.select-multiple__toggler {
width: size(2.5);
width: 100%;
height: 100%;
display: block;
position: absolute;
right: 12px;
top: 0;
background-image: url('./img/icon-chevron-down.svg');
background-size: size(2) size(2);
background-position: center center;
background-repeat: no-repeat;
background-position: calc(100% - #{size(2)}) center;
right: 0;
text-align: left;
padding: 0 size(2);
font-weight: $font-weight-bold;
}
/* Hides button text when pills exist */
.select-multiple__pill + .select-multiple__toggler {
font-size: 0;
}
.select-multiple__pill {
background: $color-green-dark;
color: $color-white;
font-size: size(1.75);
line-height: size(2.25);
font-weight: bold;
padding: size(1) 30px size(0.75) size(1);
display: inline-block;
margin: 0 4px 4px 0;
position: relative;
z-index: $z-promoted;
cursor: default;
&:hover {
text-decoration: none;
}
&:after {
content: ' ';
position: absolute;
right: size(1.5);
top: 50%;
margin-top: -5px;
width: size(1.5);
height: size(1.5);
background-image: url('./img/close.svg');
background-repeat: no-repeat;
background-position: center center;
}
}
.select-multiple__options {
position: absolute;
top: 100%;
width: 100%;
background: $color-white;
margin-top: -1px;
border: 1px solid $color-gray-2;
border-top-width: 0px;
overflow-x: hidden;
overflow-y: auto;
left: -9999px;
z-index: 999;
opacity: 0;
transition: left 0ms, opacity 100ms;
transition-delay: 100ms, 0ms;
-webkit-overflow-scrolling: touch;
&--up {
top: auto;
bottom: 100%;
border-bottom-width: 0;
border-top-width: 1px;
}
}
.select-multiple__options--open {
opacity: 1;
left: 0;
transition-delay: 0ms, 0ms;
}
.select-multiple__option {
display: block;
padding: size(0.75) size(2);
cursor: default;
@include breakpoint($s) {
padding: size(1) size(2);
}
&--hidden {
display: none;
}
}
.select-multiple__option--focused {
background: $color-gray-2;
}
.select-multiple__option:hover {
background: $color-gray-1;
color: $color-black;
text-decoration: none;
}
.select-multiple__option[data-checked] {
display: none;
}
.select-multiple__option--checked {
background: $color-green-dark;
color: $color-white;
font-weight: bold;
}
No notes defined.