Ability to disable sorting of choices/groups

This commit is contained in:
Josh Johnson 2016-08-31 19:18:46 +01:00
parent 59977af5d3
commit aff165e4a1
6 changed files with 332 additions and 285 deletions

View file

@ -41,6 +41,7 @@ A vanilla, lightweight (~15kb gzipped 🎉), configurable select box/text input
search: true, search: true,
flip: true, flip: true,
regexFilter: null, regexFilter: null,
shouldSort: true,
sortFilter: sortByAlpha, sortFilter: sortByAlpha,
sortFields: ['label', 'value'], sortFields: ['label', 'value'],
placeholder: true, placeholder: true,
@ -225,6 +226,13 @@ Pass an array of objects:
**Usage:** A filter that will need to pass for a user to successfully add an item. **Usage:** A filter that will need to pass for a user to successfully add an item.
### shouldSort
**Type:** `Boolean` **Default:** `true`
**Input types affected:** `select-one`, `select-multiple`
**Usage:** Whether choices should be sorted. If false, choices will appear in the order they were given.
### sortFilter ### sortFilter
**Type:** `Function` **Default:** sortByAlpha **Type:** `Function` **Default:** sortByAlpha

View file

@ -104,6 +104,12 @@
} }
} }
// Retrieve triggering element (i.e. element with 'data-choice' trigger)
this.passedElement = (0, _utils.isType)('String', element) ? document.querySelector(element) : element;
// If element has already been initalised with Choices, return it silently
if (this.passedElement.getAttribute('data-choice') === 'active') return;
var defaultConfig = { var defaultConfig = {
items: [], items: [],
choices: [], choices: [],
@ -118,6 +124,7 @@
search: true, search: true,
flip: true, flip: true,
regexFilter: null, regexFilter: null,
shouldSort: true,
sortFilter: _utils.sortByAlpha, sortFilter: _utils.sortByAlpha,
sortFields: ['label', 'value'], sortFields: ['label', 'value'],
placeholder: true, placeholder: true,
@ -172,12 +179,14 @@
this.currentState = {}; this.currentState = {};
this.prevState = {}; this.prevState = {};
this.currentValue = ''; this.currentValue = '';
// Retrieve triggering element (i.e. element with 'data-choice' trigger)
this.passedElement = (0, _utils.isType)('String', element) ? document.querySelector(element) : element;
this.highlightPosition = 0; this.highlightPosition = 0;
// Track searching
this.canSearch = this.config.search; this.canSearch = this.config.search;
// Track tapping
this.wasTap = true;
// Focus containerOuter but not show dropdown if true
this.focusAndHideDropdown = false;
// Assing preset choices from passed object // Assing preset choices from passed object
this.presetChoices = this.config.choices; this.presetChoices = this.config.choices;
@ -209,25 +218,17 @@
this._onPaste = this._onPaste.bind(this); this._onPaste = this._onPaste.bind(this);
this._onInput = this._onInput.bind(this); this._onInput = this._onInput.bind(this);
// Focus containerOuter but not show dropdown if true
this.focusAndHideDropdown = false;
// Monitor touch taps/scrolls
this.wasTap = true;
// Cutting the mustard // Cutting the mustard
var cuttingTheMustard = 'querySelector' in document && 'addEventListener' in document && 'classList' in document.createElement('div'); var cuttingTheMustard = 'querySelector' in document && 'addEventListener' in document && 'classList' in document.createElement('div');
if (!cuttingTheMustard) console.error('Choices: Your browser doesn\'t support Choices'); if (!cuttingTheMustard) console.error('Choices: Your browser doesn\'t support Choices');
// Input type check // Input type check
var canInit = this.passedElement && (0, _utils.isElement)(this.passedElement) && ['select-one', 'select-multiple', 'text'].some(function (type) { var isValidElement = this.passedElement && (0, _utils.isElement)(this.passedElement);
var isValidType = ['select-one', 'select-multiple', 'text'].some(function (type) {
return type === _this.passedElement.type; return type === _this.passedElement.type;
}); });
if (canInit) { if (isValidElement && isValidType) {
// If element has already been initalised with Choices
if (this.passedElement.getAttribute('data-choice') === 'active') return;
// Let's go // Let's go
this.init(); this.init();
} else { } else {
@ -247,31 +248,31 @@
value: function init() { value: function init() {
var callback = arguments.length <= 0 || arguments[0] === undefined ? this.config.callbackOnInit : arguments[0]; var callback = arguments.length <= 0 || arguments[0] === undefined ? this.config.callbackOnInit : arguments[0];
if (this.initialised !== true) { if (this.initialised === true) return;
// Set initialise flag
this.initialised = true;
// Create required elements // Set initialise flag
this._createTemplates(); this.initialised = true;
// Generate input markup // Create required elements
this._createInput(); this._createTemplates();
this.store.subscribe(this.render); // Generate input markup
this._createInput();
// Render any items this.store.subscribe(this.render);
this.render();
// Trigger event listeners // Render any items
this._addEventListeners(); this.render();
// Run callback if it is a function // Trigger event listeners
if (callback) { this._addEventListeners();
if ((0, _utils.isType)('Function', callback)) {
callback(); // Run callback if it is a function
} else { if (callback) {
console.error('callbackOnInit: Callback is not a function'); if ((0, _utils.isType)('Function', callback)) {
} callback();
} else {
console.error('callbackOnInit: Callback is not a function');
} }
} }
} }
@ -285,6 +286,8 @@
}, { }, {
key: 'destroy', key: 'destroy',
value: function destroy() { value: function destroy() {
if (this.initialised !== true) return;
this._removeEventListeners(); this._removeEventListeners();
this.passedElement.classList.remove(this.config.classNames.input, this.config.classNames.hiddenState); this.passedElement.classList.remove(this.config.classNames.input, this.config.classNames.hiddenState);
@ -600,29 +603,29 @@
value: function setValue(args) { value: function setValue(args) {
var _this8 = this; var _this8 = this;
if (this.initialised === true) { if (this.initialised !== true) return;
// Convert args to an itterable array
var values = [].concat(_toConsumableArray(args));
values.forEach(function (item) { // Convert args to an itterable array
if ((0, _utils.isType)('Object', item)) { var values = [].concat(_toConsumableArray(args));
if (!item.value) return;
// If we are dealing with a select input, we need to create an option first values.forEach(function (item) {
// that is then selected. For text inputs we can just add items normally. if ((0, _utils.isType)('Object', item)) {
if (_this8.passedElement.type !== 'text') { if (!item.value) return;
_this8._addChoice(true, false, item.value, item.label, -1); // If we are dealing with a select input, we need to create an option first
} else { // that is then selected. For text inputs we can just add items normally.
_this8._addItem(item.value, item.label, item.id); if (_this8.passedElement.type !== 'text') {
} _this8._addChoice(true, false, item.value, item.label, -1);
} else if ((0, _utils.isType)('String', item)) { } else {
if (_this8.passedElement.type !== 'text') { _this8._addItem(item.value, item.label, item.id);
_this8._addChoice(true, false, item, item, -1);
} else {
_this8._addItem(item);
}
} }
}); } else if ((0, _utils.isType)('String', item)) {
} if (_this8.passedElement.type !== 'text') {
_this8._addChoice(true, false, item, item, -1);
} else {
_this8._addItem(item);
}
}
});
return this; return this;
} }
@ -639,31 +642,29 @@
value: function setValueByChoice(value) { value: function setValueByChoice(value) {
var _this9 = this; var _this9 = this;
if (this.passedElement.type !== 'text') { if (this.passedElement.type === 'text') return;
(function () {
var choices = _this9.store.getChoices();
// If only one value has been passed, convert to array
var choiceValue = (0, _utils.isType)('Array', value) ? value : [value];
// Loop through each value and var choices = this.store.getChoices();
choiceValue.forEach(function (val) { // If only one value has been passed, convert to array
var foundChoice = choices.find(function (choice) { var choiceValue = (0, _utils.isType)('Array', value) ? value : [value];
// Check 'value' property exists and the choice isn't already selected
return choice.value === val;
});
if (foundChoice) { // Loop through each value and
if (!foundChoice.selected) { choiceValue.forEach(function (val) {
_this9._addItem(foundChoice.value, foundChoice.label, foundChoice.id); var foundChoice = choices.find(function (choice) {
} else { // Check 'value' property exists and the choice isn't already selected
console.warn('Attempting to select choice already selected'); return choice.value === val;
} });
} else {
console.warn('Attempting to select choice that does not exist'); if (foundChoice) {
} if (!foundChoice.selected) {
}); _this9._addItem(foundChoice.value, foundChoice.label, foundChoice.id);
})(); } else {
} console.warn('Attempting to select choice already selected');
}
} else {
console.warn('Attempting to select choice that does not exist');
}
});
return this; return this;
} }
@ -681,20 +682,20 @@
value: function setChoices(choices, value, label) { value: function setChoices(choices, value, label) {
var _this10 = this; var _this10 = this;
if (this.initialised === true) { if (this.initialised !== true) return;
if (this.passedElement.type === 'select-one' || this.passedElement.type === 'select-multiple') {
if (!(0, _utils.isType)('Array', choices) || !value) return;
if (choices && choices.length) { if (this.passedElement.type === 'select-one' || this.passedElement.type === 'select-multiple') {
this.containerOuter.classList.remove(this.config.classNames.loadingState); if (!(0, _utils.isType)('Array', choices) || !value) return;
choices.forEach(function (result, index) {
if (result.choices) { if (choices && choices.length) {
_this10._addGroup(result, index); this.containerOuter.classList.remove(this.config.classNames.loadingState);
} else { choices.forEach(function (result, index) {
_this10._addChoice(result.selected ? result.selected : false, result.disabled ? result.disabled : false, result[value], result[label]); if (result.choices) {
} _this10._addGroup(result, index);
}); } else {
} _this10._addChoice(result.selected ? result.selected : false, result.disabled ? result.disabled : false, result[value], result[label]);
}
});
} }
} }
return this; return this;
@ -785,43 +786,40 @@
value: function ajax(fn) { value: function ajax(fn) {
var _this11 = this; var _this11 = this;
if (this.initialised === true) { if (this.initialised !== true) return;
if (this.passedElement.type === 'select-one' || this.passedElement.type === 'select-multiple') {
this.containerOuter.classList.add(this.config.classNames.loadingState);
this.containerOuter.setAttribute('aria-busy', 'true');
if (this.passedElement.type === 'select-one') {
var placeholderItem = this._getTemplate('placeholder', this.config.loadingText);
this.itemList.appendChild(placeholderItem);
} else {
this.input.placeholder = this.config.loadingText;
}
var callback = function callback(results, value, label) { if (this.passedElement.type === 'select-one' || this.passedElement.type === 'select-multiple') {
if (!(0, _utils.isType)('Array', results) || !value) return; this.containerOuter.classList.add(this.config.classNames.loadingState);
if (results && results.length) { this.containerOuter.setAttribute('aria-busy', 'true');
// Remove loading states/text if (this.passedElement.type === 'select-one') {
_this11.containerOuter.classList.remove(_this11.config.classNames.loadingState); var placeholderItem = this._getTemplate('placeholder', this.config.loadingText);
if (_this11.passedElement.type === 'select-multiple') { this.itemList.appendChild(placeholderItem);
var placeholder = _this11.config.placeholder ? _this11.config.placeholderValue || _this11.passedElement.getAttribute('placeholder') : false; } else {
if (placeholder) { this.input.placeholder = this.config.loadingText;
_this11.input.placeholder = placeholder;
}
}
// Add each result as a choice
results.forEach(function (result, index) {
// Select first choice in list if single select input
if (index === 0 && _this11.passedElement.type === 'select-one') {
_this11._addChoice(true, false, result[value], result[label]);
} else {
_this11._addChoice(false, false, result[value], result[label]);
}
});
}
_this11.containerOuter.removeAttribute('aria-busy');
};
fn(callback);
} }
var callback = function callback(results, value, label) {
if (!(0, _utils.isType)('Array', results) || !value) return;
if (results && results.length) {
// Remove loading states/text
_this11.containerOuter.classList.remove(_this11.config.classNames.loadingState);
if (_this11.passedElement.type === 'select-multiple') {
var placeholder = _this11.config.placeholder ? _this11.config.placeholderValue || _this11.passedElement.getAttribute('placeholder') : false;
if (placeholder) {
_this11.input.placeholder = placeholder;
}
}
// Add each result as a choice
results.forEach(function (result, index) {
_this11._addChoice(false, false, result[value], result[label]);
});
}
_this11.containerOuter.removeAttribute('aria-busy');
};
fn(callback);
} }
return this; return this;
} }
@ -1064,6 +1062,7 @@
var _this14 = this; var _this14 = this;
if (!value) return; if (!value) return;
if (this.input === document.activeElement) { if (this.input === document.activeElement) {
var choices = this.store.getChoices(); var choices = this.store.getChoices();
var hasUnactiveChoices = choices.some(function (option) { var hasUnactiveChoices = choices.some(function (option) {
@ -1086,7 +1085,6 @@
include: 'score' include: 'score'
}); });
var results = fuse.search(needle); var results = fuse.search(needle);
_this14.currentValue = newValue; _this14.currentValue = newValue;
_this14.highlightPosition = 0; _this14.highlightPosition = 0;
_this14.isSearching = true; _this14.isSearching = true;
@ -2157,6 +2155,7 @@
// Join choices with preset choices and add them // Join choices with preset choices and add them
allChoices.concat(_this19.presetChoices).forEach(function (o, index) { allChoices.concat(_this19.presetChoices).forEach(function (o, index) {
// Pre-select first choice if it's a single select
if (index === 0 && _this19.passedElement.type === 'select-one') { if (index === 0 && _this19.passedElement.type === 'select-one') {
_this19._addChoice(true, o.disabled ? o.disabled : false, o.value, o.label); _this19._addChoice(true, o.disabled ? o.disabled : false, o.value, o.label);
} else { } else {
@ -2195,7 +2194,12 @@
var groupFragment = fragment || document.createDocumentFragment(); var groupFragment = fragment || document.createDocumentFragment();
var filter = this.config.sortFilter; var filter = this.config.sortFilter;
groups.sort(filter).forEach(function (group) { // If sorting is enabled, filter groups
if (this.config.shouldSort) {
groups.sort(filter);
}
groups.forEach(function (group) {
// Grab options that are children of this group // Grab options that are children of this group
var groupChoices = choices.filter(function (choice) { var groupChoices = choices.filter(function (choice) {
if (_this20.passedElement.type === 'select-one') { if (_this20.passedElement.type === 'select-one') {
@ -2233,11 +2237,15 @@
var choicesFragment = fragment || document.createDocumentFragment(); var choicesFragment = fragment || document.createDocumentFragment();
var filter = this.isSearching ? _utils.sortByScore : this.config.sortFilter; var filter = this.isSearching ? _utils.sortByScore : this.config.sortFilter;
choices.sort(filter).forEach(function (choice) { // If sorting is enabled or the user is searching, filter choices
if (this.config.shouldSort || this.isSearching) {
choices.sort(filter);
}
choices.forEach(function (choice) {
var dropdownItem = _this21._getTemplate('choice', choice); var dropdownItem = _this21._getTemplate('choice', choice);
if (_this21.passedElement.type === 'select-one') { var shouldRender = _this21.passedElement.type === 'select-one' || !choice.selected;
choicesFragment.appendChild(dropdownItem); if (shouldRender) {
} else if (!choice.selected) {
choicesFragment.appendChild(dropdownItem); choicesFragment.appendChild(dropdownItem);
} }
}); });

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -42,6 +42,12 @@ export default class Choices {
} }
} }
// Retrieve triggering element (i.e. element with 'data-choice' trigger)
this.passedElement = isType('String', element) ? document.querySelector(element) : element;
// If element has already been initalised with Choices, return it silently
if (this.passedElement.getAttribute('data-choice') === 'active') return;
const defaultConfig = { const defaultConfig = {
items: [], items: [],
choices: [], choices: [],
@ -111,12 +117,14 @@ export default class Choices {
this.currentState = {}; this.currentState = {};
this.prevState = {}; this.prevState = {};
this.currentValue = ''; this.currentValue = '';
// Retrieve triggering element (i.e. element with 'data-choice' trigger)
this.passedElement = isType('String', element) ? document.querySelector(element) : element;
this.highlightPosition = 0; this.highlightPosition = 0;
// Track searching
this.canSearch = this.config.search; this.canSearch = this.config.search;
// Track tapping
this.wasTap = true;
// Focus containerOuter but not show dropdown if true
this.focusAndHideDropdown = false;
// Assing preset choices from passed object // Assing preset choices from passed object
this.presetChoices = this.config.choices; this.presetChoices = this.config.choices;
@ -148,23 +156,15 @@ export default class Choices {
this._onPaste = this._onPaste.bind(this); this._onPaste = this._onPaste.bind(this);
this._onInput = this._onInput.bind(this); this._onInput = this._onInput.bind(this);
// Focus containerOuter but not show dropdown if true
this.focusAndHideDropdown = false;
// Monitor touch taps/scrolls
this.wasTap = true;
// Cutting the mustard // Cutting the mustard
const cuttingTheMustard = 'querySelector' in document && 'addEventListener' in document && 'classList' in document.createElement('div'); const cuttingTheMustard = 'querySelector' in document && 'addEventListener' in document && 'classList' in document.createElement('div');
if (!cuttingTheMustard) console.error('Choices: Your browser doesn\'t support Choices'); if (!cuttingTheMustard) console.error('Choices: Your browser doesn\'t support Choices');
// Input type check // Input type check
const canInit = this.passedElement && isElement(this.passedElement) && ['select-one', 'select-multiple', 'text'].some(type => type === this.passedElement.type); const isValidElement = this.passedElement && isElement(this.passedElement);
const isValidType = ['select-one', 'select-multiple', 'text'].some(type => type === this.passedElement.type);
if (canInit) {
// If element has already been initalised with Choices
if (this.passedElement.getAttribute('data-choice') === 'active') return;
if (isValidElement && isValidType) {
// Let's go // Let's go
this.init(); this.init();
} else { } else {
@ -178,31 +178,31 @@ export default class Choices {
* @public * @public
*/ */
init(callback = this.config.callbackOnInit) { init(callback = this.config.callbackOnInit) {
if (this.initialised !== true) { if (this.initialised === true) return;
// Set initialise flag
this.initialised = true;
// Create required elements // Set initialise flag
this._createTemplates(); this.initialised = true;
// Generate input markup // Create required elements
this._createInput(); this._createTemplates();
this.store.subscribe(this.render); // Generate input markup
this._createInput();
// Render any items this.store.subscribe(this.render);
this.render();
// Trigger event listeners // Render any items
this._addEventListeners(); this.render();
// Run callback if it is a function // Trigger event listeners
if (callback) { this._addEventListeners();
if (isType('Function', callback)) {
callback(); // Run callback if it is a function
} else { if (callback) {
console.error('callbackOnInit: Callback is not a function'); if (isType('Function', callback)) {
} callback();
} else {
console.error('callbackOnInit: Callback is not a function');
} }
} }
} }
@ -213,6 +213,8 @@ export default class Choices {
* @public * @public
*/ */
destroy() { destroy() {
if (this.initialised !== true) return;
this._removeEventListeners(); this._removeEventListeners();
this.passedElement.classList.remove(this.config.classNames.input, this.config.classNames.hiddenState); this.passedElement.classList.remove(this.config.classNames.input, this.config.classNames.hiddenState);
@ -472,29 +474,29 @@ export default class Choices {
* @public * @public
*/ */
setValue(args) { setValue(args) {
if (this.initialised === true) { if (this.initialised !== true) return;
// Convert args to an itterable array
const values = [...args];
values.forEach((item) => { // Convert args to an itterable array
if (isType('Object', item)) { const values = [...args];
if (!item.value) return;
// If we are dealing with a select input, we need to create an option first values.forEach((item) => {
// that is then selected. For text inputs we can just add items normally. if (isType('Object', item)) {
if (this.passedElement.type !== 'text') { if (!item.value) return;
this._addChoice(true, false, item.value, item.label, -1); // If we are dealing with a select input, we need to create an option first
} else { // that is then selected. For text inputs we can just add items normally.
this._addItem(item.value, item.label, item.id); if (this.passedElement.type !== 'text') {
} this._addChoice(true, false, item.value, item.label, -1);
} else if (isType('String', item)) { } else {
if (this.passedElement.type !== 'text') { this._addItem(item.value, item.label, item.id);
this._addChoice(true, false, item, item, -1);
} else {
this._addItem(item);
}
} }
}); } else if (isType('String', item)) {
} if (this.passedElement.type !== 'text') {
this._addChoice(true, false, item, item, -1);
} else {
this._addItem(item);
}
}
});
return this; return this;
} }
@ -506,29 +508,29 @@ export default class Choices {
* @public * @public
*/ */
setValueByChoice(value) { setValueByChoice(value) {
if (this.passedElement.type !== 'text') { if (this.passedElement.type === 'text') return;
const choices = this.store.getChoices();
// If only one value has been passed, convert to array
const choiceValue = isType('Array', value) ? value : [value];
// Loop through each value and const choices = this.store.getChoices();
choiceValue.forEach((val) => { // If only one value has been passed, convert to array
const foundChoice = choices.find((choice) => { const choiceValue = isType('Array', value) ? value : [value];
// Check 'value' property exists and the choice isn't already selected
return choice.value === val;
});
if (foundChoice) { // Loop through each value and
if (!foundChoice.selected) { choiceValue.forEach((val) => {
this._addItem(foundChoice.value, foundChoice.label, foundChoice.id); const foundChoice = choices.find((choice) => {
} else { // Check 'value' property exists and the choice isn't already selected
console.warn('Attempting to select choice already selected'); return choice.value === val;
}
} else {
console.warn('Attempting to select choice that does not exist');
}
}); });
}
if (foundChoice) {
if (!foundChoice.selected) {
this._addItem(foundChoice.value, foundChoice.label, foundChoice.id);
} else {
console.warn('Attempting to select choice already selected');
}
} else {
console.warn('Attempting to select choice that does not exist');
}
});
return this; return this;
} }
@ -541,20 +543,20 @@ export default class Choices {
* @public * @public
*/ */
setChoices(choices, value, label) { setChoices(choices, value, label) {
if (this.initialised === true) { if (this.initialised !== true) return;
if (this.passedElement.type === 'select-one' || this.passedElement.type === 'select-multiple') {
if (!isType('Array', choices) || !value) return;
if (choices && choices.length) { if (this.passedElement.type === 'select-one' || this.passedElement.type === 'select-multiple') {
this.containerOuter.classList.remove(this.config.classNames.loadingState); if (!isType('Array', choices) || !value) return;
choices.forEach((result, index) => {
if (result.choices) { if (choices && choices.length) {
this._addGroup(result, index); this.containerOuter.classList.remove(this.config.classNames.loadingState);
} else { choices.forEach((result, index) => {
this._addChoice(result.selected ? result.selected : false, result.disabled ? result.disabled : false, result[value], result[label]); if (result.choices) {
} this._addGroup(result, index);
}); } else {
} this._addChoice(result.selected ? result.selected : false, result.disabled ? result.disabled : false, result[value], result[label]);
}
});
} }
} }
return this; return this;
@ -628,43 +630,40 @@ export default class Choices {
* @public * @public
*/ */
ajax(fn) { ajax(fn) {
if (this.initialised === true) { if (this.initialised !== true) return;
if (this.passedElement.type === 'select-one' || this.passedElement.type === 'select-multiple') {
this.containerOuter.classList.add(this.config.classNames.loadingState);
this.containerOuter.setAttribute('aria-busy', 'true');
if (this.passedElement.type === 'select-one') {
const placeholderItem = this._getTemplate('placeholder', this.config.loadingText);
this.itemList.appendChild(placeholderItem);
} else {
this.input.placeholder = this.config.loadingText;
}
const callback = (results, value, label) => { if (this.passedElement.type === 'select-one' || this.passedElement.type === 'select-multiple') {
if (!isType('Array', results) || !value) return; this.containerOuter.classList.add(this.config.classNames.loadingState);
if (results && results.length) { this.containerOuter.setAttribute('aria-busy', 'true');
// Remove loading states/text if (this.passedElement.type === 'select-one') {
this.containerOuter.classList.remove(this.config.classNames.loadingState); const placeholderItem = this._getTemplate('placeholder', this.config.loadingText);
if (this.passedElement.type === 'select-multiple') { this.itemList.appendChild(placeholderItem);
const placeholder = this.config.placeholder ? this.config.placeholderValue || this.passedElement.getAttribute('placeholder') : false; } else {
if (placeholder) { this.input.placeholder = this.config.loadingText;
this.input.placeholder = placeholder;
}
}
// Add each result as a choice
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]);
}
});
}
this.containerOuter.removeAttribute('aria-busy');
};
fn(callback);
} }
const callback = (results, value, label) => {
if (!isType('Array', results) || !value) return;
if (results && results.length) {
// Remove loading states/text
this.containerOuter.classList.remove(this.config.classNames.loadingState);
if (this.passedElement.type === 'select-multiple') {
const placeholder = this.config.placeholder ? this.config.placeholderValue || this.passedElement.getAttribute('placeholder') : false;
if (placeholder) {
this.input.placeholder = placeholder;
}
}
// Add each result as a choice
results.forEach((result, index) => {
this._addChoice(false, false, result[value], result[label]);
});
}
this.containerOuter.removeAttribute('aria-busy');
};
fn(callback);
} }
return this; return this;
} }
@ -867,6 +866,7 @@ export default class Choices {
*/ */
_searchChoices(value) { _searchChoices(value) {
if (!value) return; if (!value) return;
if (this.input === document.activeElement) { if (this.input === document.activeElement) {
const choices = this.store.getChoices(); const choices = this.store.getChoices();
const hasUnactiveChoices = choices.some((option) => option.active !== true); const hasUnactiveChoices = choices.some((option) => option.active !== true);
@ -1880,7 +1880,6 @@ export default class Choices {
const passedOptions = Array.from(this.passedElement.options); const passedOptions = Array.from(this.passedElement.options);
const allChoices = []; const allChoices = [];
// Create array of options from option elements // Create array of options from option elements
passedOptions.forEach((o) => { passedOptions.forEach((o) => {
allChoices.push({ allChoices.push({
@ -1895,6 +1894,7 @@ export default class Choices {
allChoices allChoices
.concat(this.presetChoices) .concat(this.presetChoices)
.forEach((o, index) => { .forEach((o, index) => {
// Pre-select first choice if it's a single select
if (index === 0 && this.passedElement.type === 'select-one') { if (index === 0 && this.passedElement.type === 'select-one') {
this._addChoice(true, o.disabled ? o.disabled : false, o.value, o.label); this._addChoice(true, o.disabled ? o.disabled : false, o.value, o.label);
} else { } else {
@ -1927,26 +1927,29 @@ export default class Choices {
const groupFragment = fragment || document.createDocumentFragment(); const groupFragment = fragment || document.createDocumentFragment();
const filter = this.config.sortFilter; const filter = this.config.sortFilter;
groups // If sorting is enabled, filter groups
.sort(filter) if (this.config.shouldSort) {
.forEach((group) => { groups.sort(filter);
// Grab options that are children of this group }
const groupChoices = choices.filter((choice) => {
if (this.passedElement.type === 'select-one') {
return choice.groupId === group.id;
}
return choice.groupId === group.id && !choice.selected; groups.forEach((group) => {
}); // Grab options that are children of this group
const groupChoices = choices.filter((choice) => {
if (groupChoices.length >= 1) { if (this.passedElement.type === 'select-one') {
const dropdownGroup = this._getTemplate('choiceGroup', group); return choice.groupId === group.id;
groupFragment.appendChild(dropdownGroup);
this.renderChoices(groupChoices, groupFragment);
} }
return choice.groupId === group.id && !choice.selected;
}); });
if (groupChoices.length >= 1) {
const dropdownGroup = this._getTemplate('choiceGroup', group);
groupFragment.appendChild(dropdownGroup);
this.renderChoices(groupChoices, groupFragment);
}
});
return groupFragment; return groupFragment;
} }
@ -1960,20 +1963,20 @@ export default class Choices {
renderChoices(choices, fragment) { renderChoices(choices, fragment) {
// Create a fragment to store our list items (so we don't have to update the DOM for each item) // Create a fragment to store our list items (so we don't have to update the DOM for each item)
const choicesFragment = fragment || document.createDocumentFragment(); const choicesFragment = fragment || document.createDocumentFragment();
const filter = this.isSearching ? sortByScore : this.config.sortFilter;
// If sorting is enabled or the user is searching, filter choices
if (this.config.shouldSort || this.isSearching) { if (this.config.shouldSort || this.isSearching) {
const filter = this.isSearching ? sortByScore : this.config.sortFilter;
choices.sort(filter); choices.sort(filter);
} }
choices
.forEach((choice) => { choices.forEach((choice) => {
const dropdownItem = this._getTemplate('choice', choice); const dropdownItem = this._getTemplate('choice', choice);
if (this.passedElement.type === 'select-one') { const shouldRender = this.passedElement.type === 'select-one' || !choice.selected;
choicesFragment.appendChild(dropdownItem); if (shouldRender) {
} else if (!choice.selected) { choicesFragment.appendChild(dropdownItem);
choicesFragment.appendChild(dropdownItem); }
} });
});
return choicesFragment; return choicesFragment;
} }

View file

@ -41,7 +41,9 @@
<h1 class="visible-ie">Choices.js</h1> <h1 class="visible-ie">Choices.js</h1>
<p>Choices.js is a lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency.</p> <p>Choices.js is a lightweight, configurable select box/text input plugin. Similar to Select2 and Selectize but without the jQuery dependency.</p>
<p>For all config options, visit the <a href="https://github.com/jshjohnson/Choices">GitHub repo</a>.</p> <p>For all config options, visit the <a href="https://github.com/jshjohnson/Choices">GitHub repo</a>.</p>
<hr> <hr>
<h2>Text inputs</h2> <h2>Text inputs</h2>
<label for="choices-1">Limited to 5 values with remove button</label> <label for="choices-1">Limited to 5 values with remove button</label>
<input class="form-control" id="choices-1" type="text" value="preset-1,preset-2" placeholder="Enter something"> <input class="form-control" id="choices-1" type="text" value="preset-1,preset-2" placeholder="Enter something">
@ -170,6 +172,28 @@
<label for="choices-17">Option selected via config</label> <label for="choices-17">Option selected via config</label>
<select class="form-control" name="choices-17" id="choices-17" placeholder="This is a placeholder"></select> <select class="form-control" name="choices-17" id="choices-17" placeholder="This is a placeholder"></select>
<label for="choices-18">Options without sorting</label>
<select class="form-control" name="choices-18" id="choices-18" placeholder="This is a placeholder">
<option value="Madrid">Madrid</option>
<option value="Toronto">Toronto</option>
<option value="Vancouver">Vancouver</option>
<option value="London">London</option>
<option value="Manchester">Manchester</option>
<option value="Liverpool">Liverpool</option>
<option value="Paris">Paris</option>
<option value="Malaga">Malaga</option>
<option value="Washington" disabled>Washington</option>
<option value="Lyon">Lyon</option>
<option value="Marseille">Marseille</option>
<option value="Hamburg">Hamburg</option>
<option value="Munich">Munich</option>
<option value="Barcelona">Barcelona</option>
<option value="Berlin">Berlin</option>
<option value="Montreal">Montreal</option>
<option value="New York">New York</option>
<option value="Michigan">Michigan</option>
</select>
<p>Below is an example of how you could have two select inputs depend on eachother. 'Boroughs' will only be enabled if the value of 'States' is 'New York'</p> <p>Below is an example of how you could have two select inputs depend on eachother. 'Boroughs' will only be enabled if the value of 'States' is 'New York'</p>
<label for="cities">States</label> <label for="cities">States</label>
<select class="form-control" name="cities" id="cities" placeholder="Choose a state"> <select class="form-control" name="cities" id="cities" placeholder="Choose a state">
@ -335,6 +359,10 @@
], ],
}).setValueByChoice('Two'); }).setValueByChoice('Two');
var example16 = new Choices('#choices-18', {
shouldSort: false,
});
var cities = new Choices(document.getElementById('cities'), { var cities = new Choices(document.getElementById('cities'), {
callbackOnChange: function(value) { callbackOnChange: function(value) {
if(value === 'New York') { if(value === 'New York') {