Skip to content

Commit

Permalink
Render options without a group even if groups are present.
Browse files Browse the repository at this point in the history
Closes Choices-js#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 Choices-js#615
  • Loading branch information
voidus committed Apr 5, 2023
1 parent bc835aa commit 9423065
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 20 deletions.
13 changes: 13 additions & 0 deletions cypress/e2e/select-multiple.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
beforeEach(() => {
cy.get('[data-test-hook=custom-properties]')
Expand Down
21 changes: 21 additions & 0 deletions public/test/select-multiple/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,23 @@ <h2>Select multiple inputs</h2>
</optgroup>
</select>
</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">
<label for="choices-custom-properties">Custom properties</label>
Expand Down Expand Up @@ -481,6 +498,10 @@ <h2>Select multiple inputs</h2>
allowHTML: true,
});

new Choices('#mixed-choices-groups', {
allowHTML: false,
});

new Choices('#choices-custom-properties', {
allowHTML: true,
searchFields: ['label', 'value', 'customProperties.country'],
Expand Down
32 changes: 12 additions & 20 deletions src/scripts/choices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ import {
isType,
sortByScore,
strToEl,
parseCustomProperties,
} from './lib/utils';
import { defaultState } from './reducers';
import Store from './store/store';
Expand Down Expand Up @@ -283,19 +282,10 @@ class Choices implements Choices {
}
// Create array of choices from option elements
if ((this.passedElement as WrappedSelect).options) {
(this.passedElement as WrappedSelect).options.forEach((option) => {
this._presetChoices.push({
value: option.value,
label: option.innerHTML,
selected: !!option.selected,
disabled: option.disabled || option.parentNode.disabled,
placeholder:
option.value === '' || option.hasAttribute('placeholder'),
customProperties: parseCustomProperties(
option.dataset.customProperties,
),
});
});
const choicesFromOptions = (
this.passedElement as WrappedSelect
).optionsAsChoices();
this._presetChoices.push(...choicesFromOptions);
}

this._render = this._render.bind(this);
Expand Down Expand Up @@ -903,6 +893,13 @@ class Choices implements Choices {
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) => {
const groupChoices = getGroupChoices(group);
if (groupChoices.length >= 1) {
Expand Down Expand Up @@ -2216,12 +2213,7 @@ class Choices implements Choices {
this._highlightPosition = 0;
this._isSearching = false;
this._startLoading();

if (this._presetGroups.length) {
this._addPredefinedGroups(this._presetGroups);
} else {
this._addPredefinedChoices(this._presetChoices);
}
this._addPredefinedChoices(this._presetChoices);

this._stopLoading();
}
Expand Down
40 changes: 40 additions & 0 deletions src/scripts/components/wrapped-select.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { Choice } from '../interfaces/choice';
import { parseCustomProperties } from '../lib/utils';
import { ClassNames } from '../interfaces/class-names';
import { Item } from '../interfaces/item';
import WrappedElement from './wrapped-element';
import { isHTMLOptgroup } from '../lib/htmlElementGuards';
import { isHTMLOption } from '../lib/htmlElementGuards';

export default class WrappedSelect extends WrappedElement {
element: HTMLSelectElement;
Expand Down Expand Up @@ -53,6 +57,42 @@ export default class WrappedSelect extends WrappedElement {
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 {
this.element.innerHTML = '';
this.element.appendChild(fragment);
Expand Down
5 changes: 5 additions & 0 deletions src/scripts/lib/htmlElementGuards.ts
Original file line number Diff line number Diff line change
@@ -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';

0 comments on commit 9423065

Please sign in to comment.