mirror of
https://github.com/Choices-js/Choices.git
synced 2024-05-17 21:16:34 +02:00
Render options without a group even if groups are present.
Closes #615. This pushes the conversion of OPTION/OPTGROUP elements to Choice objects into the WrappedSelect class and unifies the code paths a little between groups-present and groups-not-present. Some work towards possibly fixing #615
This commit is contained in:
parent
bc835aa44d
commit
942306572a
|
@ -702,6 +702,19 @@ describe('Choices - select multiple', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('some options with a group and some without', () => {
|
||||||
|
it('Shows all options, whether they are in a group or not', () => {
|
||||||
|
cy.get('[data-test-hook=mixed-groups]')
|
||||||
|
.find('.choices__input--cloned')
|
||||||
|
.focus();
|
||||||
|
cy.get('[data-test-hook=mixed-groups]')
|
||||||
|
.find('.choices__list--dropdown .choices__item')
|
||||||
|
.should(($choices) => {
|
||||||
|
expect($choices.length).to.equal(3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('custom properties', () => {
|
describe('custom properties', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.get('[data-test-hook=custom-properties]')
|
cy.get('[data-test-hook=custom-properties]')
|
||||||
|
|
|
@ -283,6 +283,23 @@
|
||||||
</optgroup>
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div data-test-hook="mixed-groups">
|
||||||
|
<label for="choices-groups"
|
||||||
|
>Choice groups with some elements without a group</label
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
class="form-control"
|
||||||
|
name="mixed-choices-groups"
|
||||||
|
id="mixed-choices-groups"
|
||||||
|
multiple
|
||||||
|
>
|
||||||
|
<optgroup label="UK">
|
||||||
|
<option value="London">London</option>
|
||||||
|
</optgroup>
|
||||||
|
<option value="Paris">Paris</option>
|
||||||
|
<option value="Lyon">Lyon</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div data-test-hook="custom-properties">
|
<div data-test-hook="custom-properties">
|
||||||
<label for="choices-custom-properties">Custom properties</label>
|
<label for="choices-custom-properties">Custom properties</label>
|
||||||
|
@ -481,6 +498,10 @@
|
||||||
allowHTML: true,
|
allowHTML: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
new Choices('#mixed-choices-groups', {
|
||||||
|
allowHTML: false,
|
||||||
|
});
|
||||||
|
|
||||||
new Choices('#choices-custom-properties', {
|
new Choices('#choices-custom-properties', {
|
||||||
allowHTML: true,
|
allowHTML: true,
|
||||||
searchFields: ['label', 'value', 'customProperties.country'],
|
searchFields: ['label', 'value', 'customProperties.country'],
|
||||||
|
|
|
@ -46,7 +46,6 @@ import {
|
||||||
isType,
|
isType,
|
||||||
sortByScore,
|
sortByScore,
|
||||||
strToEl,
|
strToEl,
|
||||||
parseCustomProperties,
|
|
||||||
} from './lib/utils';
|
} from './lib/utils';
|
||||||
import { defaultState } from './reducers';
|
import { defaultState } from './reducers';
|
||||||
import Store from './store/store';
|
import Store from './store/store';
|
||||||
|
@ -283,19 +282,10 @@ class Choices implements Choices {
|
||||||
}
|
}
|
||||||
// Create array of choices from option elements
|
// Create array of choices from option elements
|
||||||
if ((this.passedElement as WrappedSelect).options) {
|
if ((this.passedElement as WrappedSelect).options) {
|
||||||
(this.passedElement as WrappedSelect).options.forEach((option) => {
|
const choicesFromOptions = (
|
||||||
this._presetChoices.push({
|
this.passedElement as WrappedSelect
|
||||||
value: option.value,
|
).optionsAsChoices();
|
||||||
label: option.innerHTML,
|
this._presetChoices.push(...choicesFromOptions);
|
||||||
selected: !!option.selected,
|
|
||||||
disabled: option.disabled || option.parentNode.disabled,
|
|
||||||
placeholder:
|
|
||||||
option.value === '' || option.hasAttribute('placeholder'),
|
|
||||||
customProperties: parseCustomProperties(
|
|
||||||
option.dataset.customProperties,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._render = this._render.bind(this);
|
this._render = this._render.bind(this);
|
||||||
|
@ -903,6 +893,13 @@ class Choices implements Choices {
|
||||||
groups.sort(this.config.sorter);
|
groups.sort(this.config.sorter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add Choices without group first, regardless of sort, otherwise they won't be distinguishable
|
||||||
|
// from the last group
|
||||||
|
const choicesWithoutGroup = choices.filter((c) => c.groupId == -1);
|
||||||
|
if (choicesWithoutGroup.length > 0) {
|
||||||
|
this._createChoicesFragment(choicesWithoutGroup, fragment, false);
|
||||||
|
}
|
||||||
|
|
||||||
groups.forEach((group) => {
|
groups.forEach((group) => {
|
||||||
const groupChoices = getGroupChoices(group);
|
const groupChoices = getGroupChoices(group);
|
||||||
if (groupChoices.length >= 1) {
|
if (groupChoices.length >= 1) {
|
||||||
|
@ -2216,12 +2213,7 @@ class Choices implements Choices {
|
||||||
this._highlightPosition = 0;
|
this._highlightPosition = 0;
|
||||||
this._isSearching = false;
|
this._isSearching = false;
|
||||||
this._startLoading();
|
this._startLoading();
|
||||||
|
this._addPredefinedChoices(this._presetChoices);
|
||||||
if (this._presetGroups.length) {
|
|
||||||
this._addPredefinedGroups(this._presetGroups);
|
|
||||||
} else {
|
|
||||||
this._addPredefinedChoices(this._presetChoices);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._stopLoading();
|
this._stopLoading();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
|
import { Choice } from '../interfaces/choice';
|
||||||
|
import { parseCustomProperties } from '../lib/utils';
|
||||||
import { ClassNames } from '../interfaces/class-names';
|
import { ClassNames } from '../interfaces/class-names';
|
||||||
import { Item } from '../interfaces/item';
|
import { Item } from '../interfaces/item';
|
||||||
import WrappedElement from './wrapped-element';
|
import WrappedElement from './wrapped-element';
|
||||||
|
import { isHTMLOptgroup } from '../lib/htmlElementGuards';
|
||||||
|
import { isHTMLOption } from '../lib/htmlElementGuards';
|
||||||
|
|
||||||
export default class WrappedSelect extends WrappedElement {
|
export default class WrappedSelect extends WrappedElement {
|
||||||
element: HTMLSelectElement;
|
element: HTMLSelectElement;
|
||||||
|
@ -53,6 +57,42 @@ export default class WrappedSelect extends WrappedElement {
|
||||||
this.appendDocFragment(fragment);
|
this.appendDocFragment(fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
optionsAsChoices(): Partial<Choice>[] {
|
||||||
|
const choices: Partial<Choice>[] = [];
|
||||||
|
|
||||||
|
for (const e of Array.from(this.element.querySelectorAll(':scope > *'))) {
|
||||||
|
if (isHTMLOption(e)) {
|
||||||
|
choices.push(this._optionToChoice(e as HTMLOptionElement));
|
||||||
|
} else if (isHTMLOptgroup(e)) {
|
||||||
|
choices.push(this._optgroupToChoice(e as HTMLOptGroupElement));
|
||||||
|
}
|
||||||
|
// There should only be those two in a <select> and we wouldn't care about others anyways
|
||||||
|
}
|
||||||
|
|
||||||
|
return choices;
|
||||||
|
}
|
||||||
|
|
||||||
|
_optionToChoice(option: HTMLOptionElement): Choice {
|
||||||
|
return {
|
||||||
|
value: option.value,
|
||||||
|
label: option.innerHTML,
|
||||||
|
selected: !!option.selected,
|
||||||
|
disabled: option.disabled || this.element.disabled,
|
||||||
|
placeholder: option.value === '' || option.hasAttribute('placeholder'),
|
||||||
|
customProperties: parseCustomProperties(option.dataset.customProperties),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_optgroupToChoice(optgroup: HTMLOptGroupElement): Partial<Choice> {
|
||||||
|
return {
|
||||||
|
label: optgroup.label || '',
|
||||||
|
disabled: !!optgroup.disabled,
|
||||||
|
choices: Array.from(optgroup.querySelectorAll('option')).map((option) =>
|
||||||
|
this._optionToChoice(option),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
appendDocFragment(fragment: DocumentFragment): void {
|
appendDocFragment(fragment: DocumentFragment): void {
|
||||||
this.element.innerHTML = '';
|
this.element.innerHTML = '';
|
||||||
this.element.appendChild(fragment);
|
this.element.appendChild(fragment);
|
||||||
|
|
5
src/scripts/lib/htmlElementGuards.ts
Normal file
5
src/scripts/lib/htmlElementGuards.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export const isHTMLOption = (e: Element): e is HTMLOptionElement =>
|
||||||
|
e.tagName === 'OPTION';
|
||||||
|
|
||||||
|
export const isHTMLOptgroup = (e: Element): e is HTMLOptGroupElement =>
|
||||||
|
e.tagName === 'OPTGROUP';
|
Loading…
Reference in a new issue