Sort choices by label

This commit is contained in:
Josh Johnson 2016-06-29 14:47:58 +01:00
parent a862e4a00a
commit b45715c5be
6 changed files with 144 additions and 123 deletions

File diff suppressed because one or more lines are too long

View file

@ -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,
} }
}; };

View file

@ -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();

View file

@ -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;
};

View file

@ -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:

View file

@ -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
}); });