mirror of
https://github.com/Choices-js/Choices.git
synced 2024-05-17 13:06:34 +02:00
Sort choices by label
This commit is contained in:
parent
a862e4a00a
commit
b45715c5be
4
assets/scripts/dist/choices.min.js
vendored
4
assets/scripts/dist/choices.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -44,7 +44,7 @@ export const filterChoices = (results) => {
|
||||||
|
|
||||||
export const activateChoices = (active = true) => {
|
export const activateChoices = (active = true) => {
|
||||||
return {
|
return {
|
||||||
type: 'ACTIVATE_OPTIONS',
|
type: 'ACTIVATE_CHOICES',
|
||||||
active,
|
active,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,7 +13,7 @@ import Store from './store/index.js';
|
||||||
* - Single select box search in dropdown
|
* - Single select box search in dropdown
|
||||||
*/
|
*/
|
||||||
export class Choices {
|
export class Choices {
|
||||||
constructor(element = '[data-choice]', userOptions = {}) {
|
constructor(element = '[data-option]', userConfig = {}) {
|
||||||
|
|
||||||
// If there are multiple elements, create a new instance
|
// If there are multiple elements, create a new instance
|
||||||
// for each element besides the first one (as that already has an instance)
|
// for each element besides the first one (as that already has an instance)
|
||||||
|
@ -22,12 +22,12 @@ export class Choices {
|
||||||
if(elements.length > 1) {
|
if(elements.length > 1) {
|
||||||
for (let i = 1; i < elements.length; i++) {
|
for (let i = 1; i < elements.length; i++) {
|
||||||
const el = elements[i];
|
const el = elements[i];
|
||||||
new Choices(el, userOptions);
|
new Choices(el, userConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultConfig = {
|
||||||
items: [],
|
items: [],
|
||||||
maxItemCount: -1,
|
maxItemCount: -1,
|
||||||
addItems: true,
|
addItems: true,
|
||||||
|
@ -76,7 +76,7 @@ export class Choices {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Merge options with user options
|
// Merge options with user options
|
||||||
this.options = extend(defaultOptions, userOptions);
|
this.config = extend(defaultConfig, userConfig);
|
||||||
|
|
||||||
// Create data store
|
// Create data store
|
||||||
this.store = new Store(this.render);
|
this.store = new Store(this.render);
|
||||||
|
@ -87,17 +87,17 @@ export class Choices {
|
||||||
this.prevState = {};
|
this.prevState = {};
|
||||||
this.currentValue = '';
|
this.currentValue = '';
|
||||||
|
|
||||||
// Retrieve triggering element (i.e. element with 'data-choice' trigger)
|
// Retrieve triggering element (i.e. element with 'data-option' trigger)
|
||||||
this.passedElement = isType('String', element) ? document.querySelector(element) : element;
|
this.passedElement = isType('String', element) ? document.querySelector(element) : element;
|
||||||
|
|
||||||
this.highlightPosition = 0;
|
this.highlightPosition = 0;
|
||||||
this.canSearch = this.options.searchOptions;
|
this.canSearch = this.config.searchOptions;
|
||||||
|
|
||||||
// Assign preset items from passed object first
|
// Assign preset items from passed object first
|
||||||
this.presetItems = this.options.items;
|
this.presetItems = this.config.items;
|
||||||
// Then add any values passed from attribute
|
// Then add any values passed from attribute
|
||||||
if(this.passedElement.value !== '') {
|
if(this.passedElement.value) {
|
||||||
this.presetItems = this.presetItems.concat(this.passedElement.value.split(this.options.delimiter));
|
this.presetItems = this.presetItems.concat(this.passedElement.value.split(this.config.delimiter));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind methods
|
// Bind methods
|
||||||
|
@ -124,10 +124,13 @@ export class Choices {
|
||||||
const canInit = this.passedElement && ['select-one', 'select-multiple', 'text'].includes(this.passedElement.type);
|
const canInit = this.passedElement && ['select-one', 'select-multiple', 'text'].includes(this.passedElement.type);
|
||||||
|
|
||||||
if(canInit) {
|
if(canInit) {
|
||||||
// Let's have it large
|
// If element has already been initalised with Choices
|
||||||
this.init();
|
if(this.passedElement.hasAttribute('data-choice')) return
|
||||||
|
|
||||||
|
// Let's go
|
||||||
|
this.init();
|
||||||
} else {
|
} else {
|
||||||
console.error('Choices: Incompatible input passed');
|
console.error('Incompatible input passed');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,7 +159,7 @@ export class Choices {
|
||||||
this._addEventListeners();
|
this._addEventListeners();
|
||||||
|
|
||||||
// Run callback if it is a function
|
// Run callback if it is a function
|
||||||
if(callback = this.options.callbackOnInit){
|
if(callback = this.config.callbackOnInit){
|
||||||
if(isType('Function', callback)) {
|
if(isType('Function', callback)) {
|
||||||
callback();
|
callback();
|
||||||
} else {
|
} else {
|
||||||
|
@ -172,7 +175,7 @@ export class Choices {
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
destroy() {
|
destroy() {
|
||||||
this.passedElement.classList.remove(this.options.classNames.input, this.options.classNames.hiddenState);
|
this.passedElement.classList.remove(this.config.classNames.input, this.config.classNames.hiddenState);
|
||||||
this.passedElement.tabIndex = '';
|
this.passedElement.tabIndex = '';
|
||||||
this.passedElement.removeAttribute('style', 'display:none;');
|
this.passedElement.removeAttribute('style', 'display:none;');
|
||||||
this.passedElement.removeAttribute('aria-hidden');
|
this.passedElement.removeAttribute('aria-hidden');
|
||||||
|
@ -180,8 +183,8 @@ export class Choices {
|
||||||
this.containerOuter.outerHTML = this.passedElement.outerHTML;
|
this.containerOuter.outerHTML = this.passedElement.outerHTML;
|
||||||
|
|
||||||
this.passedElement = null;
|
this.passedElement = null;
|
||||||
this.userOptions = null;
|
this.userConfig = null;
|
||||||
this.options = null;
|
this.config = null;
|
||||||
this.store = null;
|
this.store = null;
|
||||||
|
|
||||||
this._removeEventListeners();
|
this._removeEventListeners();
|
||||||
|
@ -306,17 +309,17 @@ export class Choices {
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
showDropdown() {
|
showDropdown() {
|
||||||
this.containerOuter.classList.add(this.options.classNames.openState);
|
this.containerOuter.classList.add(this.config.classNames.openState);
|
||||||
this.dropdown.classList.add(this.options.classNames.activeState);
|
this.dropdown.classList.add(this.config.classNames.activeState);
|
||||||
|
|
||||||
const dimensions = this.dropdown.getBoundingClientRect();
|
const dimensions = this.dropdown.getBoundingClientRect();
|
||||||
const shouldFlip = dimensions.top + dimensions.height >= document.body.offsetHeight;
|
const shouldFlip = dimensions.top + dimensions.height >= document.body.offsetHeight;
|
||||||
|
|
||||||
// Whether or not the dropdown should appear above or below input
|
// Whether or not the dropdown should appear above or below input
|
||||||
if(shouldFlip) {
|
if(shouldFlip) {
|
||||||
this.containerOuter.classList.add(this.options.classNames.flippedState);
|
this.containerOuter.classList.add(this.config.classNames.flippedState);
|
||||||
} else {
|
} else {
|
||||||
this.containerOuter.classList.remove(this.options.classNames.flippedState);
|
this.containerOuter.classList.remove(this.config.classNames.flippedState);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
@ -329,13 +332,13 @@ export class Choices {
|
||||||
*/
|
*/
|
||||||
hideDropdown() {
|
hideDropdown() {
|
||||||
// A dropdown flips if it does not have space below the input
|
// A dropdown flips if it does not have space below the input
|
||||||
const isFlipped = this.containerOuter.classList.contains(this.options.classNames.flippedState);
|
const isFlipped = this.containerOuter.classList.contains(this.config.classNames.flippedState);
|
||||||
|
|
||||||
this.containerOuter.classList.remove(this.options.classNames.openState);
|
this.containerOuter.classList.remove(this.config.classNames.openState);
|
||||||
this.dropdown.classList.remove(this.options.classNames.activeState);
|
this.dropdown.classList.remove(this.config.classNames.activeState);
|
||||||
|
|
||||||
if(isFlipped) {
|
if(isFlipped) {
|
||||||
this.containerOuter.classList.remove(this.options.classNames.flippedState);
|
this.containerOuter.classList.remove(this.config.classNames.flippedState);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
@ -347,9 +350,11 @@ export class Choices {
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
toggleDropdown() {
|
toggleDropdown() {
|
||||||
const isActive = this.dropdown.classList.contains(this.options.classNames.activeState);
|
if(this.dropdown.classList.contains(this.config.classNames.activeState)) {
|
||||||
|
this.hideDropdown()
|
||||||
isActive ? this.hideDropdown() : this.showDropdown();
|
} else {
|
||||||
|
this.showDropdown();
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -405,7 +410,7 @@ export class Choices {
|
||||||
this.passedElement.disabled = true;
|
this.passedElement.disabled = true;
|
||||||
if(this.initialised) {
|
if(this.initialised) {
|
||||||
this.input.disabled = true;
|
this.input.disabled = true;
|
||||||
this.containerOuter.classList.add(this.options.classNames.disabledState);
|
this.containerOuter.classList.add(this.config.classNames.disabledState);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -417,29 +422,33 @@ export class Choices {
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
ajax(fn) {
|
ajax(fn) {
|
||||||
if(this.passedElement.type === 'select-one' || this.passedElement.type === 'select-multiple') {
|
if(this.initialised === true) {
|
||||||
this.containerOuter.classList.add('is-loading');
|
if(this.passedElement.type === 'select-one' || this.passedElement.type === 'select-multiple') {
|
||||||
// this.input.placeholder = this.options.loadingText;
|
this.containerOuter.classList.add('is-loading');
|
||||||
|
// this.input.placeholder = this.config.loadingText;
|
||||||
|
|
||||||
const placeholderItem = this._getTemplate('item', { id: -1, value: 'Loading', label: this.options.loadingText, active: true});
|
const placeholderItem = this._getTemplate('item', { id: -1, value: 'Loading', label: this.config.loadingText, active: true});
|
||||||
this.itemList.appendChild(placeholderItem);
|
this.itemList.appendChild(placeholderItem);
|
||||||
|
|
||||||
const callback = (results, value, label) => {
|
const callback = (results, value, label) => {
|
||||||
if(results && results.length) {
|
if(!isType('Array', results) || !value) return;
|
||||||
this.containerOuter.classList.remove('is-loading');
|
|
||||||
this.input.placeholder = "";
|
|
||||||
results.forEach((result, index) => {
|
|
||||||
// Select first choice in list if single select input
|
|
||||||
if(index === 0 && this.passedElement.type === 'select-one') {
|
|
||||||
this._addChoice(true, false, result[value], result[label]);
|
|
||||||
} else {
|
|
||||||
this._addChoice(false, false, result[value], result[label]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fn(callback);
|
if(results && results.length) {
|
||||||
|
this.containerOuter.classList.remove('is-loading');
|
||||||
|
this.input.placeholder = "";
|
||||||
|
results.forEach((result, index) => {
|
||||||
|
// Select first choice in list if single select input
|
||||||
|
if(index === 0 && this.passedElement.type === 'select-one') {
|
||||||
|
this._addChoice(true, false, result[value], result[label]);
|
||||||
|
} else {
|
||||||
|
this._addChoice(false, false, result[value], result[label]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn(callback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
|
@ -468,12 +477,12 @@ export class Choices {
|
||||||
_handleEnter(activeItems, value) {
|
_handleEnter(activeItems, value) {
|
||||||
let canUpdate = true;
|
let canUpdate = true;
|
||||||
|
|
||||||
if(this.options.addItems) {
|
if(this.config.addItems) {
|
||||||
if (this.options.maxItemCount && this.options.maxItemCount > 0 && this.options.maxItemCount <= this.itemList.children.length) {
|
if (this.config.maxItemCount && this.config.maxItemCount > 0 && this.config.maxItemCount <= this.itemList.children.length) {
|
||||||
// If there is a max entry limit and we have reached that limit
|
// If there is a max entry limit and we have reached that limit
|
||||||
// don't update
|
// don't update
|
||||||
canUpdate = false;
|
canUpdate = false;
|
||||||
} else if(this.options.duplicateItems === false && this.passedElement.value) {
|
} else if(this.config.duplicateItems === false && this.passedElement.value) {
|
||||||
// If no duplicates are allowed, and the value already exists
|
// If no duplicates are allowed, and the value already exists
|
||||||
// in the array, don't update
|
// in the array, don't update
|
||||||
canUpdate = !activeItems.some((item) => item.value === value );
|
canUpdate = !activeItems.some((item) => item.value === value );
|
||||||
|
@ -486,7 +495,7 @@ export class Choices {
|
||||||
let canAddItem = true;
|
let canAddItem = true;
|
||||||
|
|
||||||
// If a user has supplied a regular expression filter
|
// If a user has supplied a regular expression filter
|
||||||
if(this.options.regexFilter) {
|
if(this.config.regexFilter) {
|
||||||
// Determine whether we can update based on whether
|
// Determine whether we can update based on whether
|
||||||
// our regular expression passes
|
// our regular expression passes
|
||||||
canAddItem = this._regexFilter(value);
|
canAddItem = this._regexFilter(value);
|
||||||
|
@ -508,13 +517,13 @@ export class Choices {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_handleBackspace(activeItems) {
|
_handleBackspace(activeItems) {
|
||||||
if(this.options.removeItems && activeItems) {
|
if(this.config.removeItems && activeItems) {
|
||||||
const lastItem = activeItems[activeItems.length - 1];
|
const lastItem = activeItems[activeItems.length - 1];
|
||||||
const hasSelectedItems = activeItems.some((item) => item.selected === true);
|
const hasSelectedItems = activeItems.some((item) => item.selected === true);
|
||||||
|
|
||||||
// If editing the last item is allowed and there are not other selected items,
|
// If editing the last item is allowed and there are not other selected items,
|
||||||
// we can edit the item value. Otherwise if we can remove items, remove all selected items
|
// we can edit the item value. Otherwise if we can remove items, remove all selected items
|
||||||
if(this.options.editItems && !hasSelectedItems && lastItem) {
|
if(this.config.editItems && !hasSelectedItems && lastItem) {
|
||||||
this.input.value = lastItem.value;
|
this.input.value = lastItem.value;
|
||||||
this._removeItem(lastItem);
|
this._removeItem(lastItem);
|
||||||
} else {
|
} else {
|
||||||
|
@ -545,7 +554,7 @@ export class Choices {
|
||||||
const activeChoices = this.store.getChoicesFilteredByActive();
|
const activeChoices = this.store.getChoicesFilteredByActive();
|
||||||
|
|
||||||
const hasFocusedInput = this.input === document.activeElement;
|
const hasFocusedInput = this.input === document.activeElement;
|
||||||
const hasActiveDropdown = this.dropdown.classList.contains(this.options.classNames.activeState);
|
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
|
||||||
const hasItems = this.itemList && this.itemList.children;
|
const hasItems = this.itemList && this.itemList.children;
|
||||||
const keyString = String.fromCharCode(event.keyCode);
|
const keyString = String.fromCharCode(event.keyCode);
|
||||||
|
|
||||||
|
@ -554,14 +563,14 @@ export class Choices {
|
||||||
this.showDropdown();
|
this.showDropdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.canSearch = this.options.searchOptions;
|
this.canSearch = this.config.searchOptions;
|
||||||
|
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
case aKey:
|
case aKey:
|
||||||
// If CTRL + A or CMD + A have been pressed and there are items to select
|
// If CTRL + A or CMD + A have been pressed and there are items to select
|
||||||
if(ctrlDownKey && hasItems) {
|
if(ctrlDownKey && hasItems) {
|
||||||
this.canSearch = false;
|
this.canSearch = false;
|
||||||
if(this.options.removeItems && !this.input.value && this.input === document.activeElement) {
|
if(this.config.removeItems && !this.input.value && this.input === document.activeElement) {
|
||||||
// Highlight items
|
// Highlight items
|
||||||
this.highlightAll(this.itemList.children);
|
this.highlightAll(this.itemList.children);
|
||||||
}
|
}
|
||||||
|
@ -576,7 +585,7 @@ export class Choices {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(hasActiveDropdown) {
|
if(hasActiveDropdown) {
|
||||||
const highlighted = this.dropdown.querySelector(`.${this.options.classNames.highlightedState}`);
|
const highlighted = this.dropdown.querySelector(`.${this.config.classNames.highlightedState}`);
|
||||||
|
|
||||||
if(highlighted) {
|
if(highlighted) {
|
||||||
const value = highlighted.getAttribute('data-value');
|
const value = highlighted.getAttribute('data-value');
|
||||||
|
@ -595,16 +604,14 @@ export class Choices {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case escapeKey:
|
case escapeKey:
|
||||||
if(hasActiveDropdown) {
|
if(hasActiveDropdown) this.toggleDropdown();
|
||||||
this.toggleDropdown();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case downKey:
|
case downKey:
|
||||||
case upKey:
|
case upKey:
|
||||||
// If up or down key is pressed, traverse through options
|
// If up or down key is pressed, traverse through options
|
||||||
if(hasActiveDropdown) {
|
if(hasActiveDropdown) {
|
||||||
const currentEl = this.dropdown.querySelector(`.${this.options.classNames.highlightedState}`);
|
const currentEl = this.dropdown.querySelector(`.${this.config.classNames.highlightedState}`);
|
||||||
const directionInt = e.keyCode === downKey ? 1 : -1;
|
const directionInt = e.keyCode === downKey ? 1 : -1;
|
||||||
let nextEl;
|
let nextEl;
|
||||||
|
|
||||||
|
@ -658,29 +665,31 @@ export class Choices {
|
||||||
// We are typing into a text input and have a value, we want to show a dropdown
|
// We are typing into a text input and have a value, we want to show a dropdown
|
||||||
// notice. Otherwise hide the dropdown
|
// notice. Otherwise hide the dropdown
|
||||||
if(this.passedElement.type === 'text') {
|
if(this.passedElement.type === 'text') {
|
||||||
const hasActiveDropdown = this.dropdown.classList.contains(this.options.classNames.activeState);
|
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
|
||||||
let dropdownItem;
|
let dropdownItem;
|
||||||
if(this.input.value) {
|
if(this.input.value) {
|
||||||
const activeItems = this.store.getItemsFilteredByActive();
|
const activeItems = this.store.getItemsFilteredByActive();
|
||||||
const isUnique = !activeItems.some((item) => item.value === this.input.value);
|
const isUnique = !activeItems.some((item) => item.value === this.input.value);
|
||||||
|
|
||||||
if (this.options.maxItemCount && this.options.maxItemCount > 0 && this.options.maxItemCount <= this.itemList.children.length) {
|
if (this.config.maxItemCount && this.config.maxItemCount > 0 && this.config.maxItemCount <= this.itemList.children.length) {
|
||||||
dropdownItem = this._getTemplate('notice', `Only ${ this.options.maxItemCount } options can be added.`);
|
dropdownItem = this._getTemplate('notice', `Only ${ this.config.maxItemCount } options can be added.`);
|
||||||
} else if(!this.options.duplicateItems && !isUnique) {
|
} else if(!this.config.duplicateItems && !isUnique) {
|
||||||
dropdownItem = this._getTemplate('notice', `Only unique values can be added.`);
|
dropdownItem = this._getTemplate('notice', `Only unique values can be added.`);
|
||||||
} else {
|
} else {
|
||||||
dropdownItem = this._getTemplate('notice', `Add "${ this.input.value }"`);
|
dropdownItem = this._getTemplate('notice', `Add "${ this.input.value }"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if((this.options.regexFilter && this._regexFilter(this.input.value)) || !this.options.regexFilter) {
|
if((this.config.regexFilter && this._regexFilter(this.input.value)) || !this.config.regexFilter) {
|
||||||
this.dropdown.innerHTML = dropdownItem.outerHTML;
|
this.dropdown.innerHTML = dropdownItem.outerHTML;
|
||||||
if(!this.dropdown.classList.contains(this.options.classNames.activeState)) {
|
if(!this.dropdown.classList.contains(this.config.classNames.activeState)) {
|
||||||
this.showDropdown();
|
this.showDropdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if(hasActiveDropdown) this.hideDropdown();
|
if(hasActiveDropdown) {
|
||||||
|
this.hideDropdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -717,7 +726,7 @@ export class Choices {
|
||||||
} else if(hasUnactiveChoices) {
|
} else if(hasUnactiveChoices) {
|
||||||
// Otherwise reset options to active
|
// Otherwise reset options to active
|
||||||
this.isSearching = false;
|
this.isSearching = false;
|
||||||
this.store.dispatch(activateChoices());
|
this.store.dispatch(activateChoices(true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -731,7 +740,7 @@ export class Choices {
|
||||||
*/
|
*/
|
||||||
_onInput(e) {
|
_onInput(e) {
|
||||||
if(this.passedElement.type !== 'select-one') {
|
if(this.passedElement.type !== 'select-one') {
|
||||||
this.input.style.width = getWidthOfInput(this.input);
|
this.input.style.width = getWidthOfInput(this.input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -755,7 +764,7 @@ export class Choices {
|
||||||
|
|
||||||
const hasShiftKey = e.shiftKey ? true : false;
|
const hasShiftKey = e.shiftKey ? true : false;
|
||||||
|
|
||||||
if(this.passedElement.type !== 'text' && !this.dropdown.classList.contains(this.options.classNames.activeState)) {
|
if(this.passedElement.type !== 'text' && !this.dropdown.classList.contains(this.config.classNames.activeState)) {
|
||||||
// For select inputs we always want to show the dropdown if it isn't already showing
|
// For select inputs we always want to show the dropdown if it isn't already showing
|
||||||
this.showDropdown();
|
this.showDropdown();
|
||||||
}
|
}
|
||||||
|
@ -766,14 +775,14 @@ export class Choices {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(e.target.hasAttribute('data-button')) {
|
if(e.target.hasAttribute('data-button')) {
|
||||||
if(this.options.removeItems && this.options.removeItemButton) {
|
if(this.config.removeItems && this.config.removeItemButton) {
|
||||||
const itemId = e.target.parentNode.getAttribute('data-id');
|
const itemId = e.target.parentNode.getAttribute('data-id');
|
||||||
const itemToRemove = activeItems.find((item) => item.id === parseInt(itemId));
|
const itemToRemove = activeItems.find((item) => item.id === parseInt(itemId));
|
||||||
this._removeItem(itemToRemove);
|
this._removeItem(itemToRemove);
|
||||||
}
|
}
|
||||||
} else if(e.target.hasAttribute('data-item')) {
|
} else if(e.target.hasAttribute('data-item')) {
|
||||||
// If we are clicking on an item
|
// If we are clicking on an item
|
||||||
if(this.options.removeItems) {
|
if(this.config.removeItems) {
|
||||||
const passedId = e.target.getAttribute('data-id');
|
const passedId = e.target.getAttribute('data-id');
|
||||||
|
|
||||||
// We only want to select one item with a click
|
// We only want to select one item with a click
|
||||||
|
@ -806,17 +815,21 @@ export class Choices {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Click is outside of our element so close dropdown and de-select items
|
// Click is outside of our element so close dropdown and de-select items
|
||||||
const hasActiveDropdown = this.dropdown.classList.contains(this.options.classNames.activeState);
|
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
|
||||||
const hasSelectedItems = activeItems.some((item) => item.selected === true);
|
const hasSelectedItems = activeItems.some((item) => item.selected === true);
|
||||||
|
|
||||||
// De-select any highlighted items
|
// De-select any highlighted items
|
||||||
if(hasSelectedItems) this.unhighlightAll();
|
if(hasSelectedItems) {
|
||||||
|
this.unhighlightAll();
|
||||||
|
}
|
||||||
|
|
||||||
// Remove focus state
|
// Remove focus state
|
||||||
this.containerOuter.classList.remove(this.options.classNames.focusState);
|
this.containerOuter.classList.remove(this.config.classNames.focusState);
|
||||||
|
|
||||||
// Close all other dropdowns
|
// Close all other dropdowns
|
||||||
if(hasActiveDropdown) this.toggleDropdown();
|
if(hasActiveDropdown) {
|
||||||
|
this.toggleDropdown();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -829,10 +842,8 @@ export class Choices {
|
||||||
*/
|
*/
|
||||||
_onMouseOver(e) {
|
_onMouseOver(e) {
|
||||||
// If the dropdown is either the target or one of its children is the target
|
// If the dropdown is either the target or one of its children is the target
|
||||||
if((e.target === this.dropdown || findAncestor(e.target, this.options.classNames.listDropdown))) {
|
if((e.target === this.dropdown || findAncestor(e.target, this.config.classNames.listDropdown))) {
|
||||||
if(e.target.hasAttribute('data-option')) {
|
if(e.target.hasAttribute('data-option')) this._highlightChoice(e.target);
|
||||||
this._highlightChoice(e.target);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -845,8 +856,8 @@ export class Choices {
|
||||||
_onPaste(e) {
|
_onPaste(e) {
|
||||||
if(e.target !== this.input) return;
|
if(e.target !== this.input) return;
|
||||||
// Disable pasting into the input if option has been set
|
// Disable pasting into the input if option has been set
|
||||||
if(!this.options.paste) {
|
if(!this.config.paste) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -858,9 +869,9 @@ export class Choices {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_onFocus(e) {
|
_onFocus(e) {
|
||||||
const hasActiveDropdown = this.dropdown.classList.contains(this.options.classNames.activeState);
|
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
|
||||||
if(e.target === this.input && !hasActiveDropdown) {
|
if(e.target === this.input && !hasActiveDropdown) {
|
||||||
this.containerOuter.classList.add(this.options.classNames.focusState);
|
this.containerOuter.classList.add(this.config.classNames.focusState);
|
||||||
if(this.passedElement.type === 'select-one' || this.passedElement.type === 'select-multiple'){
|
if(this.passedElement.type === 'select-one' || this.passedElement.type === 'select-multiple'){
|
||||||
this.showDropdown();
|
this.showDropdown();
|
||||||
}
|
}
|
||||||
|
@ -874,9 +885,9 @@ export class Choices {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_onBlur(e) {
|
_onBlur(e) {
|
||||||
const hasActiveDropdown = this.dropdown.classList.contains(this.options.classNames.activeState);
|
const hasActiveDropdown = this.dropdown.classList.contains(this.config.classNames.activeState);
|
||||||
if(e.target === this.input && !hasActiveDropdown) {
|
if(e.target === this.input && !hasActiveDropdown) {
|
||||||
this.containerOuter.classList.remove(this.options.classNames.focusState);
|
this.containerOuter.classList.remove(this.config.classNames.focusState);
|
||||||
} else {
|
} else {
|
||||||
this.hideDropdown();
|
this.hideDropdown();
|
||||||
}
|
}
|
||||||
|
@ -891,7 +902,7 @@ export class Choices {
|
||||||
*/
|
*/
|
||||||
_regexFilter(value) {
|
_regexFilter(value) {
|
||||||
if(!value) return;
|
if(!value) return;
|
||||||
const expression = new RegExp(this.options.regexFilter, 'i');
|
const expression = new RegExp(this.config.regexFilter, 'i');
|
||||||
return expression.test(value);
|
return expression.test(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -963,16 +974,16 @@ export class Choices {
|
||||||
const options = Array.from(this.dropdown.querySelectorAll('[data-option-selectable]'));
|
const options = Array.from(this.dropdown.querySelectorAll('[data-option-selectable]'));
|
||||||
|
|
||||||
if(options && options.length) {
|
if(options && options.length) {
|
||||||
const highlightedOptions = Array.from(this.dropdown.querySelectorAll(`.${this.options.classNames.highlightedState}`));
|
const highlightedOptions = Array.from(this.dropdown.querySelectorAll(`.${this.config.classNames.highlightedState}`));
|
||||||
|
|
||||||
// Remove any highlighted options
|
// Remove any highlighted options
|
||||||
highlightedOptions.forEach((el) => {
|
highlightedOptions.forEach((el) => {
|
||||||
el.classList.remove(this.options.classNames.highlightedState);
|
el.classList.remove(this.config.classNames.highlightedState);
|
||||||
});
|
});
|
||||||
|
|
||||||
if(el){
|
if(el){
|
||||||
// Highlight given option
|
// Highlight given option
|
||||||
el.classList.add(this.options.classNames.highlightedState);
|
el.classList.add(this.config.classNames.highlightedState);
|
||||||
this.highlightPosition = options.indexOf(el);
|
this.highlightPosition = options.indexOf(el);
|
||||||
} else {
|
} else {
|
||||||
// Highlight option based on last known highlight location
|
// Highlight option based on last known highlight location
|
||||||
|
@ -987,7 +998,7 @@ export class Choices {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!el) el = options[0];
|
if(!el) el = options[0];
|
||||||
el.classList.add(this.options.classNames.highlightedState);
|
el.classList.add(this.config.classNames.highlightedState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -999,20 +1010,20 @@ export class Choices {
|
||||||
* @return {Object} Class instance
|
* @return {Object} Class instance
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
_addItem(value, label, choiceId = -1, callback = this.options.callbackOnAddItem) {
|
_addItem(value, label, choiceId = -1, callback = this.config.callbackOnAddItem) {
|
||||||
const items = this.store.getItems();
|
const items = this.store.getItems();
|
||||||
let passedValue = value.trim();
|
let passedValue = value.trim();
|
||||||
let passedLabel = label || passedValue;
|
let passedLabel = label || passedValue;
|
||||||
let passedOptionId = choiceId || -1;
|
let passedOptionId = choiceId || -1;
|
||||||
|
|
||||||
// If a prepended value has been passed, prepend it
|
// If a prepended value has been passed, prepend it
|
||||||
if(this.options.prependValue) {
|
if(this.config.prependValue) {
|
||||||
passedValue = this.options.prependValue + passedValue.toString();
|
passedValue = this.config.prependValue + passedValue.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// If an appended value has been passed, append it
|
// If an appended value has been passed, append it
|
||||||
if(this.options.appendValue) {
|
if(this.config.appendValue) {
|
||||||
passedValue = passedValue + this.options.appendValue.toString();
|
passedValue = passedValue + this.config.appendValue.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate unique id
|
// Generate unique id
|
||||||
|
@ -1042,7 +1053,7 @@ export class Choices {
|
||||||
* @return {Object} Class instance
|
* @return {Object} Class instance
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
_removeItem(item, callback = this.options.callbackOnRemoveItem) {
|
_removeItem(item, callback = this.config.callbackOnRemoveItem) {
|
||||||
if(!item || !isType('Object', item)) {
|
if(!item || !isType('Object', item)) {
|
||||||
console.error('removeItem: No item object was passed to be removed');
|
console.error('removeItem: No item object was passed to be removed');
|
||||||
return;
|
return;
|
||||||
|
@ -1115,7 +1126,7 @@ export class Choices {
|
||||||
*/
|
*/
|
||||||
_getTemplate(template, ...args) {
|
_getTemplate(template, ...args) {
|
||||||
if(!template) return;
|
if(!template) return;
|
||||||
const templates = this.options.templates;
|
const templates = this.config.templates;
|
||||||
return templates[template](...args);
|
return templates[template](...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1125,7 +1136,7 @@ export class Choices {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_createTemplates() {
|
_createTemplates() {
|
||||||
const classNames = this.options.classNames;
|
const classNames = this.config.classNames;
|
||||||
const templates = {
|
const templates = {
|
||||||
containerOuter: () => {
|
containerOuter: () => {
|
||||||
return strToEl(`<div class="${ classNames.containerOuter }" data-type="${ this.passedElement.type }"></div>`);
|
return strToEl(`<div class="${ classNames.containerOuter }" data-type="${ this.passedElement.type }"></div>`);
|
||||||
|
@ -1166,7 +1177,7 @@ export class Choices {
|
||||||
`);
|
`);
|
||||||
},
|
},
|
||||||
item: (data) => {
|
item: (data) => {
|
||||||
if(this.options.removeItemButton && this.passedElement.type !== 'select-one') {
|
if(this.config.removeItemButton && this.passedElement.type !== 'select-one') {
|
||||||
return strToEl(`
|
return strToEl(`
|
||||||
<div class="${ classNames.item } ${ data.selected ? classNames.selectedState : ''} ${ !data.disabled ? classNames.itemSelectable : '' }" data-item data-id="${ data.id }" data-value="${ data.value }" data-deletable>
|
<div class="${ classNames.item } ${ data.selected ? classNames.selectedState : ''} ${ !data.disabled ? classNames.itemSelectable : '' }" data-item data-id="${ data.id }" data-value="${ data.value }" data-deletable>
|
||||||
${ data.label }
|
${ data.label }
|
||||||
|
@ -1183,7 +1194,7 @@ export class Choices {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
this.options.templates = extend(this.options.templates, templates);
|
this.config.templates = extend(this.config.templates, templates);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1207,11 +1218,11 @@ export class Choices {
|
||||||
this.dropdown = dropdown;
|
this.dropdown = dropdown;
|
||||||
|
|
||||||
// Hide passed input
|
// Hide passed input
|
||||||
this.passedElement.classList.add(this.options.classNames.input, this.options.classNames.hiddenState);
|
this.passedElement.classList.add(this.config.classNames.input, this.config.classNames.hiddenState);
|
||||||
this.passedElement.tabIndex = '-1';
|
this.passedElement.tabIndex = '-1';
|
||||||
this.passedElement.setAttribute('style', 'display:none;');
|
this.passedElement.setAttribute('style', 'display:none;');
|
||||||
this.passedElement.setAttribute('aria-hidden', 'true');
|
this.passedElement.setAttribute('aria-hidden', 'true');
|
||||||
this.passedElement.removeAttribute('data-choice');
|
this.passedElement.setAttribute('data-choice', '');
|
||||||
|
|
||||||
// Wrap input in container preserving DOM ordering
|
// Wrap input in container preserving DOM ordering
|
||||||
wrap(this.passedElement, containerInner);
|
wrap(this.passedElement, containerInner);
|
||||||
|
@ -1220,15 +1231,15 @@ export class Choices {
|
||||||
wrap(containerInner, containerOuter);
|
wrap(containerInner, containerOuter);
|
||||||
|
|
||||||
// If placeholder has been enabled and we have a value
|
// If placeholder has been enabled and we have a value
|
||||||
if (this.options.placeholder && (this.options.placeholderValue || this.passedElement.placeholder)) {
|
if (this.config.placeholder && (this.config.placeholderValue || this.passedElement.placeholder)) {
|
||||||
const placeholder = this.options.placeholderValue || this.passedElement.placeholder;
|
const placeholder = this.config.placeholderValue || this.passedElement.placeholder;
|
||||||
input.placeholder = placeholder;
|
input.placeholder = placeholder;
|
||||||
if(this.passedElement.type !== 'select-one') {
|
if(this.passedElement.type !== 'select-one') {
|
||||||
input.style.width = getWidthOfInput(input);
|
input.style.width = getWidthOfInput(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!this.options.addItems) this.disable();
|
if(!this.config.addItems) this.disable();
|
||||||
|
|
||||||
containerOuter.appendChild(containerInner);
|
containerOuter.appendChild(containerInner);
|
||||||
containerOuter.appendChild(dropdown);
|
containerOuter.appendChild(dropdown);
|
||||||
|
@ -1237,7 +1248,7 @@ export class Choices {
|
||||||
|
|
||||||
if(this.passedElement.type === 'select-multiple' || this.passedElement.type === 'text') {
|
if(this.passedElement.type === 'select-multiple' || this.passedElement.type === 'text') {
|
||||||
containerInner.appendChild(input);
|
containerInner.appendChild(input);
|
||||||
} else if(this.options.searchOptions) {
|
} else if(this.config.searchOptions) {
|
||||||
dropdown.insertBefore(input, dropdown.firstChild);
|
dropdown.insertBefore(input, dropdown.firstChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1346,7 +1357,7 @@ export class Choices {
|
||||||
|
|
||||||
if(this.passedElement.type === 'text') {
|
if(this.passedElement.type === 'text') {
|
||||||
// Assign hidden input array of values
|
// Assign hidden input array of values
|
||||||
this.passedElement.setAttribute('value', itemsFiltered.join(this.options.delimiter));
|
this.passedElement.setAttribute('value', itemsFiltered.join(this.config.delimiter));
|
||||||
} else {
|
} else {
|
||||||
const selectedOptionsFragment = document.createDocumentFragment();
|
const selectedOptionsFragment = document.createDocumentFragment();
|
||||||
|
|
||||||
|
|
|
@ -439,4 +439,13 @@ export const getWidthOfInput = (input) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${width}px`;
|
return `${width}px`;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export const sortByAlpha = (a, b) => {
|
||||||
|
const labelA = a.label.toLowerCase();
|
||||||
|
const labelB = b.label.toLowerCase();
|
||||||
|
|
||||||
|
if (labelA < labelB) return -1;
|
||||||
|
if (labelA > labelB) return 1;
|
||||||
|
return 0;
|
||||||
|
};
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { sortByAlpha } from './../lib/utils.js';
|
||||||
|
|
||||||
const choices = (state = [], action) => {
|
const choices = (state = [], action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'ADD_CHOICE':
|
case 'ADD_CHOICE':
|
||||||
|
@ -10,7 +12,7 @@ const choices = (state = [], action) => {
|
||||||
selected: false,
|
selected: false,
|
||||||
active: true,
|
active: true,
|
||||||
score: 9999,
|
score: 9999,
|
||||||
}];
|
}].sort(sortByAlpha);
|
||||||
|
|
||||||
case 'ADD_ITEM':
|
case 'ADD_ITEM':
|
||||||
// When an item is added and it has an associated choice,
|
// When an item is added and it has an associated choice,
|
||||||
|
@ -63,9 +65,8 @@ const choices = (state = [], action) => {
|
||||||
case 'ACTIVATE_CHOICES':
|
case 'ACTIVATE_CHOICES':
|
||||||
return state.map((choice) => {
|
return state.map((choice) => {
|
||||||
choice.active = action.active;
|
choice.active = action.active;
|
||||||
|
|
||||||
return choice;
|
return choice;
|
||||||
});
|
}).sort(sortByAlpha);
|
||||||
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
12
index.html
12
index.html
|
@ -42,14 +42,14 @@
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<label for="choices-9">With pre-selected option</label>
|
<label for="choices-9">With pre-selected option</label>
|
||||||
<select id="choices-9" name="choices-9" data-choice placeholder="Choose an option" multiple>
|
<select id="choices-9" name="choices-9" placeholder="Choose an option" multiple>
|
||||||
<option value="Dropdown item 1">Dropdown item 1</option>
|
<option value="Dropdown item 1">Dropdown item 1</option>
|
||||||
<option value="Dropdown item 2" selected>Dropdown item 2</option>
|
<option value="Dropdown item 2" selected>Dropdown item 2</option>
|
||||||
<option value="Dropdown item 3">Dropdown item 3</option>
|
<option value="Dropdown item 3">Dropdown item 3</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<label for="choices-10">Option groups</label>
|
<label for="choices-10">Option groups</label>
|
||||||
<select id="choices-10" name="choices-10" data-choice placeholder="This is a placeholder" multiple>
|
<select id="choices-10" name="choices-10" placeholder="This is a placeholder" multiple>
|
||||||
<optgroup label="UK">
|
<optgroup label="UK">
|
||||||
<option value="London">London</option>
|
<option value="London">London</option>
|
||||||
<option value="Manchester">Manchester</option>
|
<option value="Manchester">Manchester</option>
|
||||||
|
@ -86,17 +86,17 @@
|
||||||
|
|
||||||
<h2>Single select input</h2>
|
<h2>Single select input</h2>
|
||||||
<label for="choices-11">Default</label>
|
<label for="choices-11">Default</label>
|
||||||
<select id="choices-11" name="choices-11" data-choice placeholder="This is a placeholder">
|
<select id="choices-11" name="choices-11" placeholder="This is a placeholder">
|
||||||
<option value="Dropdown item 1">Dropdown item 1</option>
|
<option value="Dropdown item 1">Dropdown item 1</option>
|
||||||
<option value="Dropdown item 2">Dropdown item 2</option>
|
<option value="Dropdown item 2">Dropdown item 2</option>
|
||||||
<option value="Dropdown item 3">Dropdown item 3</option>
|
<option value="Dropdown item 3">Dropdown item 3</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<label for="choices-12">Options from remote source</label>
|
<label for="choices-12">Options from remote source</label>
|
||||||
<select name="choices-12" id="choices-12" data-choice placeholder="Pick an Arctic Monkeys record"></select>
|
<select name="choices-12" id="choices-12" placeholder="Pick an Arctic Monkeys record"></select>
|
||||||
|
|
||||||
<label for="choices-13">Option groups</label>
|
<label for="choices-13">Option groups</label>
|
||||||
<select id="choices-13" name="choices-13" data-choice placeholder="This is a placeholder">
|
<select id="choices-13" name="choices-13" placeholder="This is a placeholder">
|
||||||
<optgroup label="UK">
|
<optgroup label="UK">
|
||||||
<option value="London">London</option>
|
<option value="London">London</option>
|
||||||
<option value="Manchester">Manchester</option>
|
<option value="Manchester">Manchester</option>
|
||||||
|
@ -180,7 +180,7 @@
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
|
||||||
const choicesMultiple = new Choices('[data-choice]', {
|
const choicesMultiple = new Choices('select', {
|
||||||
placeholderValue: 'This is a placeholder set in the config',
|
placeholderValue: 'This is a placeholder set in the config',
|
||||||
removeButton: true
|
removeButton: true
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue