mirror of
https://github.com/Choices-js/Choices.git
synced 2024-05-19 14:06:33 +02:00
Better sort handling + sort tests
This commit is contained in:
parent
34bce568a2
commit
ca86265731
|
@ -42,12 +42,6 @@ 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 = {
|
||||
items: [],
|
||||
choices: [],
|
||||
|
@ -117,14 +111,12 @@ export default class Choices {
|
|||
this.currentState = {};
|
||||
this.prevState = {};
|
||||
this.currentValue = '';
|
||||
this.highlightPosition = 0;
|
||||
|
||||
// Track searching
|
||||
// Retrieve triggering element (i.e. element with 'data-choice' trigger)
|
||||
this.passedElement = isType('String', element) ? document.querySelector(element) : element;
|
||||
|
||||
this.highlightPosition = 0;
|
||||
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
|
||||
this.presetChoices = this.config.choices;
|
||||
|
@ -156,15 +148,23 @@ export default class Choices {
|
|||
this._onPaste = this._onPaste.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
|
||||
const cuttingTheMustard = 'querySelector' in document && 'addEventListener' in document && 'classList' in document.createElement('div');
|
||||
if (!cuttingTheMustard) console.error('Choices: Your browser doesn\'t support Choices');
|
||||
|
||||
// Input type check
|
||||
const isValidElement = this.passedElement && isElement(this.passedElement);
|
||||
const isValidType = ['select-one', 'select-multiple', 'text'].some(type => type === this.passedElement.type);
|
||||
const canInit = this.passedElement && isElement(this.passedElement) && ['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
|
||||
this.init();
|
||||
} else {
|
||||
|
@ -174,11 +174,11 @@ export default class Choices {
|
|||
|
||||
/**
|
||||
* Initialise Choices
|
||||
* @return {Object} Class instance
|
||||
* @return
|
||||
* @public
|
||||
*/
|
||||
init(callback = this.config.callbackOnInit) {
|
||||
if (this.initialised === false) {
|
||||
if (this.initialised !== true) {
|
||||
// Set initialise flag
|
||||
this.initialised = true;
|
||||
|
||||
|
@ -205,32 +205,27 @@ export default class Choices {
|
|||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy Choices and nullify values
|
||||
* @return {Object} Class instance
|
||||
* @return
|
||||
* @public
|
||||
*/
|
||||
destroy() {
|
||||
if (this.initialised === true) {
|
||||
this._removeEventListeners();
|
||||
this._removeEventListeners();
|
||||
|
||||
this.passedElement.classList.remove(this.config.classNames.input, this.config.classNames.hiddenState);
|
||||
this.passedElement.tabIndex = '';
|
||||
this.passedElement.removeAttribute('style', 'display:none;');
|
||||
this.passedElement.removeAttribute('aria-hidden');
|
||||
this.passedElement.classList.remove(this.config.classNames.input, this.config.classNames.hiddenState);
|
||||
this.passedElement.tabIndex = '';
|
||||
this.passedElement.removeAttribute('style', 'display:none;');
|
||||
this.passedElement.removeAttribute('aria-hidden');
|
||||
|
||||
this.containerOuter.outerHTML = this.passedElement.outerHTML;
|
||||
this.containerOuter.outerHTML = this.passedElement.outerHTML;
|
||||
|
||||
this.passedElement = null;
|
||||
this.userConfig = null;
|
||||
this.config = null;
|
||||
this.store = null;
|
||||
this.initialised = false;
|
||||
}
|
||||
return this;
|
||||
this.passedElement = null;
|
||||
this.userConfig = null;
|
||||
this.config = null;
|
||||
this.store = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -500,6 +495,7 @@ export default class Choices {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -510,29 +506,29 @@ export default class Choices {
|
|||
* @public
|
||||
*/
|
||||
setValueByChoice(value) {
|
||||
if (this.passedElement.type === 'text') return;
|
||||
if (this.passedElement.type !== 'text') {
|
||||
const choices = this.store.getChoices();
|
||||
// If only one value has been passed, convert to array
|
||||
const choiceValue = isType('Array', value) ? value : [value];
|
||||
|
||||
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
|
||||
choiceValue.forEach((val) => {
|
||||
const foundChoice = choices.find((choice) => {
|
||||
// Check 'value' property exists and the choice isn't already selected
|
||||
return choice.value === val;
|
||||
});
|
||||
|
||||
// Loop through each value and
|
||||
choiceValue.forEach((val) => {
|
||||
const foundChoice = choices.find((choice) => {
|
||||
// Check 'value' property exists and the choice isn't already selected
|
||||
return choice.value === val;
|
||||
});
|
||||
|
||||
if (foundChoice) {
|
||||
if (!foundChoice.selected) {
|
||||
this._addItem(foundChoice.value, foundChoice.label, foundChoice.id);
|
||||
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 already selected');
|
||||
console.warn('Attempting to select choice that does not exist');
|
||||
}
|
||||
} else {
|
||||
console.warn('Attempting to select choice that does not exist');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -595,7 +591,7 @@ export default class Choices {
|
|||
*/
|
||||
disable() {
|
||||
this.passedElement.disabled = true;
|
||||
if (this.initialised === true) {
|
||||
if (this.initialised) {
|
||||
if (!this.containerOuter.classList.contains(this.config.classNames.disabledState)) {
|
||||
this._removeEventListeners();
|
||||
this.passedElement.setAttribute('disabled', '');
|
||||
|
@ -613,7 +609,7 @@ export default class Choices {
|
|||
*/
|
||||
enable() {
|
||||
this.passedElement.disabled = false;
|
||||
if (this.initialised === true) {
|
||||
if (this.initialised) {
|
||||
if (this.containerOuter.classList.contains(this.config.classNames.disabledState)) {
|
||||
this._addEventListeners();
|
||||
this.passedElement.removeAttribute('disabled');
|
||||
|
@ -648,7 +644,6 @@ export default class Choices {
|
|||
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) {
|
||||
|
@ -867,7 +862,6 @@ export default class Choices {
|
|||
*/
|
||||
_searchChoices(value) {
|
||||
if (!value) return;
|
||||
|
||||
if (this.input === document.activeElement) {
|
||||
const choices = this.store.getChoices();
|
||||
const hasUnactiveChoices = choices.some((option) => option.active !== true);
|
||||
|
@ -888,6 +882,7 @@ export default class Choices {
|
|||
include: 'score',
|
||||
});
|
||||
const results = fuse.search(needle);
|
||||
|
||||
this.currentValue = newValue;
|
||||
this.highlightPosition = 0;
|
||||
this.isSearching = true;
|
||||
|
@ -1879,7 +1874,8 @@ export default class Choices {
|
|||
});
|
||||
} else {
|
||||
const passedOptions = Array.from(this.passedElement.options);
|
||||
const allChoices = [];
|
||||
const filter = this.config.sortFilter;
|
||||
const allChoices = this.presetChoices;
|
||||
|
||||
// Create array of options from option elements
|
||||
passedOptions.forEach((o) => {
|
||||
|
@ -1891,17 +1887,34 @@ export default class Choices {
|
|||
});
|
||||
});
|
||||
|
||||
// Join choices with preset choices and add them
|
||||
allChoices
|
||||
.concat(this.presetChoices)
|
||||
.forEach((o, index) => {
|
||||
// Pre-select first choice if it's a single select
|
||||
if (index === 0 && this.passedElement.type === 'select-one') {
|
||||
this._addChoice(true, o.disabled ? o.disabled : false, o.value, o.label);
|
||||
// If sorting is enabled or the user is searching, filter choices
|
||||
if (this.config.shouldSort) {
|
||||
allChoices.sort(filter);
|
||||
}
|
||||
|
||||
// Determine whether there is a selected choice
|
||||
const hasSelectedChoice = allChoices.some((choice) => {
|
||||
return choice.selected === true;
|
||||
});
|
||||
|
||||
// Add each choice
|
||||
allChoices.forEach((choice, index) => {
|
||||
const isDisabled = choice.disabled ? choice.disabled : false;
|
||||
const isSelected = choice.selected ? choice.selected : false;
|
||||
// Pre-select first choice if it's a single select
|
||||
if (this.passedElement.type === 'select-one') {
|
||||
if (hasSelectedChoice || (!hasSelectedChoice && index > 0)) {
|
||||
// If there is a selected choice already or the choice is not
|
||||
// the first in the array, add each choice normally
|
||||
this._addChoice(isSelected, isDisabled, choice.value, choice.label);
|
||||
} else {
|
||||
this._addChoice(o.selected ? o.selected : false, o.disabled ? o.disabled : false, o.value, o.label);
|
||||
// Otherwise pre-select the first choice in the array
|
||||
this._addChoice(true, false, choice.value, choice.label);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this._addChoice(isSelected, isDisabled, choice.value, choice.label);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (this.passedElement.type === 'text') {
|
||||
// Add any preset values seperated by delimiter
|
||||
|
@ -1946,7 +1959,6 @@ export default class Choices {
|
|||
if (groupChoices.length >= 1) {
|
||||
const dropdownGroup = this._getTemplate('choiceGroup', group);
|
||||
groupFragment.appendChild(dropdownGroup);
|
||||
|
||||
this.renderChoices(groupChoices, groupFragment);
|
||||
}
|
||||
});
|
||||
|
@ -2005,7 +2017,6 @@ export default class Choices {
|
|||
items.forEach((item) => {
|
||||
// Create a standard select option
|
||||
const option = this._getTemplate('option', item);
|
||||
|
||||
// Append it to fragment
|
||||
selectedOptionsFragment.appendChild(option);
|
||||
});
|
||||
|
|
10
index.html
10
index.html
|
@ -195,13 +195,13 @@
|
|||
</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>
|
||||
<label for="cities">States</label>
|
||||
<select class="form-control" name="cities" id="cities" placeholder="Choose a state">
|
||||
<label for="states">States</label>
|
||||
<select class="form-control" name="states" id="states" placeholder="Choose a state">
|
||||
<option value="Michigan">Michigan</option>
|
||||
<option value="Texas">Texas</option>
|
||||
<option value="Chicago">Chicago</option>
|
||||
<option value="New York">New York</option>
|
||||
<option value="Washington">Washington</option>
|
||||
<option value="Michigan">Michigan</option>
|
||||
</select>
|
||||
|
||||
<label for="boroughs">Boroughs</label>
|
||||
|
@ -353,7 +353,7 @@
|
|||
|
||||
var example15 = new Choices('#choices-17', {
|
||||
choices: [
|
||||
{value: 'One', label: 'Label One'},
|
||||
{value: 'One', label: 'Label One', selected: true},
|
||||
{value: 'Two', label: 'Label Two', disabled: true},
|
||||
{value: 'Three', label: 'Label Three'},
|
||||
],
|
||||
|
@ -363,7 +363,7 @@
|
|||
shouldSort: false,
|
||||
});
|
||||
|
||||
var cities = new Choices(document.getElementById('cities'), {
|
||||
var states = new Choices(document.getElementById('states'), {
|
||||
callbackOnChange: function(value) {
|
||||
if(value === 'New York') {
|
||||
boroughs.enable();
|
||||
|
|
|
@ -133,7 +133,6 @@ describe('Choices', function() {
|
|||
this.input.placeholder = 'Placeholder text';
|
||||
|
||||
document.body.appendChild(this.input);
|
||||
|
||||
});
|
||||
|
||||
it('should accept a user inputted value', function() {
|
||||
|
@ -236,28 +235,27 @@ describe('Choices', function() {
|
|||
const option = document.createElement('option');
|
||||
|
||||
option.value = `Value ${i}`;
|
||||
option.innerHTML = `Value ${i}`;
|
||||
option.innerHTML = `Label ${i}`;
|
||||
|
||||
this.input.appendChild(option);
|
||||
}
|
||||
|
||||
document.body.appendChild(this.input);
|
||||
|
||||
this.choices = new Choices(this.input, {
|
||||
removeItemButton: true
|
||||
});
|
||||
});
|
||||
|
||||
it('should open the choice list on focussing', function() {
|
||||
this.choices = new Choices(this.input);
|
||||
this.choices.input.focus();
|
||||
expect(this.choices.dropdown.classList).toContain(this.choices.config.classNames.activeState);
|
||||
});
|
||||
|
||||
it('should select the first choice', function() {
|
||||
this.choices = new Choices(this.input);
|
||||
expect(this.choices.currentState.items[0].value).toContain('Value 1');
|
||||
});
|
||||
|
||||
it('should highlight the choices on keydown', function() {
|
||||
this.choices = new Choices(this.input);
|
||||
this.choices.input.focus();
|
||||
|
||||
for (var i = 0; i < 2; i++) {
|
||||
|
@ -274,6 +272,7 @@ describe('Choices', function() {
|
|||
});
|
||||
|
||||
it('should select choice on enter key press', function() {
|
||||
this.choices = new Choices(this.input);
|
||||
this.choices.input.focus();
|
||||
|
||||
// Key down to second choice
|
||||
|
@ -295,6 +294,7 @@ describe('Choices', function() {
|
|||
});
|
||||
|
||||
it('should trigger a change callback on selection', function() {
|
||||
this.choices = new Choices(this.input);
|
||||
spyOn(this.choices.config, 'callbackOnChange');
|
||||
this.choices.input.focus();
|
||||
|
||||
|
@ -317,6 +317,7 @@ describe('Choices', function() {
|
|||
});
|
||||
|
||||
it('should open the dropdown on click', function() {
|
||||
this.choices = new Choices(this.input);
|
||||
const container = this.choices.containerOuter;
|
||||
this.choices._onClick({
|
||||
target: container,
|
||||
|
@ -328,6 +329,7 @@ describe('Choices', function() {
|
|||
});
|
||||
|
||||
it('should close the dropdown on double click', function() {
|
||||
this.choices = new Choices(this.input);
|
||||
const container = this.choices.containerOuter;
|
||||
|
||||
this.choices._onClick({
|
||||
|
@ -346,6 +348,7 @@ describe('Choices', function() {
|
|||
});
|
||||
|
||||
it('should filter choices when searching', function() {
|
||||
this.choices = new Choices(this.input);
|
||||
this.choices.input.focus();
|
||||
this.choices.input.value = 'Value 3';
|
||||
|
||||
|
@ -360,6 +363,32 @@ describe('Choices', function() {
|
|||
|
||||
expect(this.choices.isSearching && mostAccurateResult.value === 'Value 3').toBeTruthy;
|
||||
});
|
||||
|
||||
it('shouldn\'t sort choices if shouldSort is false', function() {
|
||||
this.choices = new Choices(this.input, {
|
||||
shouldSort: false,
|
||||
choices: [
|
||||
{value: 'Value 5', label: 'Label Five'},
|
||||
{value: 'Value 6', label: 'Label Six'},
|
||||
{value: 'Value 7', label: 'Label Seven'},
|
||||
],
|
||||
});
|
||||
|
||||
expect(this.choices.currentState.choices[0].value).toEqual('Value 5');
|
||||
});
|
||||
|
||||
it('should sort choices if shouldSort is false', function() {
|
||||
this.choices = new Choices(this.input, {
|
||||
shouldSort: true,
|
||||
choices: [
|
||||
{value: 'Value 5', label: 'Label Five'},
|
||||
{value: 'Value 6', label: 'Label Six'},
|
||||
{value: 'Value 7', label: 'Label Seven'},
|
||||
],
|
||||
});
|
||||
|
||||
expect(this.choices.currentState.choices[0].value).toEqual('Value 1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('should accept multiple select inputs', function() {
|
||||
|
|
Loading…
Reference in a new issue