Skip to content

Commit

Permalink
[Autocomplete] Fix grouped options order
Browse files Browse the repository at this point in the history
  • Loading branch information
zavarock committed May 3, 2024
1 parent 3962582 commit 32c5cdb
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 12 deletions.
38 changes: 35 additions & 3 deletions src/Autocomplete/assets/dist/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,9 @@ class default_1 extends Controller {
}
createOptionsDataStructure(selectElement) {
return Array.from(selectElement.options).map((option) => {
const optgroup = option.closest('optgroup');
return {
value: option.value,
text: option.text,
group: optgroup ? optgroup.label : null,
};
});
}
Expand All @@ -209,7 +207,7 @@ class default_1 extends Controller {
if (filteredOriginalOptions.length !== filteredNewOptions.length) {
return false;
}
const normalizeOption = (option) => `${option.value}-${option.text}-${option.group}`;
const normalizeOption = (option) => `${option.value}-${option.text}`;
const originalOptionsSet = new Set(filteredOriginalOptions.map(normalizeOption));
const newOptionsSet = new Set(filteredNewOptions.map(normalizeOption));
return (originalOptionsSet.size === newOptionsSet.size &&
Expand Down Expand Up @@ -240,6 +238,40 @@ _default_1_instances = new WeakSet(), _default_1_getCommonConfig = function _def
this.tomSelect.setTextboxValue('');
},
closeAfterSelect: true,
onOptionAdd: (value, data) => {
let parentElement = this.tomSelect.input;
let optgroupData = null;
const optgroup = data[this.tomSelect.settings.optgroupField];
if (optgroup && this.tomSelect.optgroups) {
optgroupData = this.tomSelect.optgroups[optgroup];
if (optgroupData) {
const optgroupElement = parentElement.querySelector(`optgroup[label="${optgroupData.label}"]`);
if (optgroupElement) {
parentElement = optgroupElement;
}
}
}
const optionElement = document.createElement('option');
optionElement.value = value;
optionElement.text = data[this.tomSelect.settings.labelField];
const optionOrder = data['$order'];
let orderedOption = null;
for (const [, tomSelectOption] of Object.entries(this.tomSelect.options)) {
if (tomSelectOption['$order'] === optionOrder) {
orderedOption = parentElement.querySelector(`:scope > option[value="${tomSelectOption[this.tomSelect.settings.valueField]}"]`);
break;
}
}
if (orderedOption) {
orderedOption.insertAdjacentElement('afterend', optionElement);
}
else if (optionOrder >= 0) {
parentElement.append(optionElement);
}
else {
parentElement.prepend(optionElement);
}
},
};
if (!this.selectElement && !this.urlValue) {
config.shouldLoad = () => false;
Expand Down
58 changes: 49 additions & 9 deletions src/Autocomplete/assets/src/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ export interface AutocompleteConnectOptions {
tomSelect: TomSelect;
options: any;
}
interface OptionDataStructure {
value: string;
text: string;
}

export default class extends Controller {
static values = {
Expand Down Expand Up @@ -38,7 +42,7 @@ export default class extends Controller {
private mutationObserver: MutationObserver;
private isObserving = false;
private hasLoadedChoicesPreviously = false;
private originalOptions: Array<{ value: string; text: string; group: string | null }> = [];
private originalOptions: Array<OptionDataStructure> = [];

initialize() {
if (!this.mutationObserver) {
Expand Down Expand Up @@ -146,6 +150,47 @@ export default class extends Controller {
this.tomSelect.setTextboxValue('');
},
closeAfterSelect: true,
// fix positioning (in the dropdown) of options added through addOption()
onOptionAdd: (value: string, data: { [key: string]: any }) => {
let parentElement = this.tomSelect.input as Element;
let optgroupData = null;

const optgroup = data[this.tomSelect.settings.optgroupField];
if (optgroup && this.tomSelect.optgroups) {
optgroupData = this.tomSelect.optgroups[optgroup];
if (optgroupData) {
const optgroupElement = parentElement.querySelector(`optgroup[label="${optgroupData.label}"]`);
if (optgroupElement) {
parentElement = optgroupElement;
}
}
}

const optionElement = document.createElement('option');
optionElement.value = value;
optionElement.text = data[this.tomSelect.settings.labelField];

const optionOrder = data['$order'];
let orderedOption = null;

for (const [, tomSelectOption] of Object.entries(this.tomSelect.options)) {
if (tomSelectOption['$order'] === optionOrder) {
orderedOption = parentElement.querySelector(
`:scope > option[value="${tomSelectOption[this.tomSelect.settings.valueField]}"]`
);

break;
}
}

if (orderedOption) {
orderedOption.insertAdjacentElement('afterend', optionElement);
} else if (optionOrder >= 0) {
parentElement.append(optionElement);
} else {
parentElement.prepend(optionElement);
}
},
};

// for non-autocompleting input elements, avoid the "No results" message that always shows
Expand Down Expand Up @@ -411,20 +456,16 @@ export default class extends Controller {
}
}

private createOptionsDataStructure(
selectElement: HTMLSelectElement
): Array<{ value: string; text: string; group: string | null }> {
private createOptionsDataStructure(selectElement: HTMLSelectElement): Array<OptionDataStructure> {
return Array.from(selectElement.options).map((option) => {
const optgroup = option.closest('optgroup');
return {
value: option.value,
text: option.text,
group: optgroup ? optgroup.label : null,
};
});
}

private areOptionsEquivalent(newOptions: Array<{ value: string; text: string; group: string | null }>): boolean {
private areOptionsEquivalent(newOptions: Array<OptionDataStructure>): boolean {
// remove the empty option, which is added by TomSelect so may be missing from new options
const filteredOriginalOptions = this.originalOptions.filter((option) => option.value !== '');
const filteredNewOptions = newOptions.filter((option) => option.value !== '');
Expand All @@ -444,8 +485,7 @@ export default class extends Controller {
return false;
}

const normalizeOption = (option: { value: string; text: string; group: string | null }) =>
`${option.value}-${option.text}-${option.group}`;
const normalizeOption = (option: OptionDataStructure) => `${option.value}-${option.text}`;
const originalOptionsSet = new Set(filteredOriginalOptions.map(normalizeOption));
const newOptionsSet = new Set(filteredNewOptions.map(normalizeOption));

Expand Down

0 comments on commit 32c5cdb

Please sign in to comment.