Skip to content

Commit 8a2a88d

Browse files
committed
Reset TomSelect when updating controller attributes
1 parent cddafa8 commit 8a2a88d

File tree

4 files changed

+94
-2
lines changed

4 files changed

+94
-2
lines changed

src/Autocomplete/assets/dist/controller.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export default class extends Controller {
3636
private isObserving;
3737
private hasLoadedChoicesPreviously;
3838
private originalOptions;
39+
private isRemoteOptions;
3940
initialize(): void;
4041
connect(): void;
4142
initializeTomSelect(): void;

src/Autocomplete/assets/dist/controller.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class default_1 extends Controller {
3737
this.isObserving = false;
3838
this.hasLoadedChoicesPreviously = false;
3939
this.originalOptions = [];
40+
this.isRemoteOptions = false;
4041
}
4142
initialize() {
4243
if (!this.mutationObserver) {
@@ -57,6 +58,8 @@ class default_1 extends Controller {
5758
}
5859
if (this.urlValue) {
5960
this.tomSelect = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_createAutocompleteWithRemoteData).call(this, this.urlValue, this.hasMinCharactersValue ? this.minCharactersValue : null);
61+
this.isRemoteOptions = true;
62+
this.startMutationObserver();
6063
return;
6164
}
6265
if (this.optionsAsHtmlValue) {
@@ -180,11 +183,15 @@ class default_1 extends Controller {
180183
}
181184
break;
182185
}
186+
if (mutation.target === this.element && mutation.attributeName.includes('data-symfony--ux-autocomplete')) {
187+
requireReset = true;
188+
break;
189+
}
183190
break;
184191
}
185192
});
186193
const newOptions = this.selectElement ? this.createOptionsDataStructure(this.selectElement) : [];
187-
const areOptionsEquivalent = this.areOptionsEquivalent(newOptions);
194+
const areOptionsEquivalent = this.isRemoteOptions || this.areOptionsEquivalent(newOptions);
188195
if (!areOptionsEquivalent || requireReset) {
189196
this.originalOptions = newOptions;
190197
this.resetTomSelect();

src/Autocomplete/assets/src/controller.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export default class extends Controller {
5252
private isObserving = false;
5353
private hasLoadedChoicesPreviously = false;
5454
private originalOptions: Array<OptionDataStructure> = [];
55+
private isRemoteOptions = false;
5556

5657
initialize() {
5758
if (!this.mutationObserver) {
@@ -82,6 +83,9 @@ export default class extends Controller {
8283
this.hasMinCharactersValue ? this.minCharactersValue : null
8384
);
8485

86+
this.isRemoteOptions = true;
87+
this.startMutationObserver();
88+
8589
return;
8690
}
8791

@@ -449,12 +453,18 @@ export default class extends Controller {
449453
break;
450454
}
451455

456+
if (mutation.target === this.element && mutation.attributeName.match(/data-(symfony--ux-)?autocomplete/)) {
457+
requireReset = true;
458+
459+
break;
460+
}
461+
452462
break;
453463
}
454464
});
455465

456466
const newOptions = this.selectElement ? this.createOptionsDataStructure(this.selectElement) : [];
457-
const areOptionsEquivalent = this.areOptionsEquivalent(newOptions);
467+
const areOptionsEquivalent = this.isRemoteOptions || this.areOptionsEquivalent(newOptions);
458468
if (!areOptionsEquivalent || requireReset) {
459469
this.originalOptions = newOptions;
460470
this.resetTomSelect();

src/Autocomplete/assets/test/controller.test.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,80 @@ describe('AutocompleteController', () => {
138138
expect(fetchMock.requests()[1].url).toEqual('/path/to/autocomplete?query=foo');
139139
});
140140

141+
it('resets when ajax URL attribute on a select element changes', async () => {
142+
const { container, tomSelect} = await startAutocompleteTest(`
143+
<label for="the-select">Items</label>
144+
<select
145+
id="the-select"
146+
data-testid="main-element"
147+
data-controller="autocomplete"
148+
data-autocomplete-url-value="/path/to/autocomplete"
149+
></select>
150+
`);
151+
152+
const selectElement = getByTestId(container, 'main-element') as HTMLSelectElement;
153+
154+
// initial Ajax request on focus
155+
fetchMock.mockResponseOnce(
156+
JSON.stringify({
157+
results: [
158+
{
159+
value: 3,
160+
text: 'salad'
161+
},
162+
]
163+
}),
164+
);
165+
166+
fetchMock.mockResponseOnce(
167+
JSON.stringify({
168+
results: [
169+
{
170+
value: 1,
171+
text: 'pizza'
172+
},
173+
{
174+
value: 2,
175+
text: 'popcorn'
176+
}
177+
]
178+
}),
179+
);
180+
181+
const controlInput = tomSelect.control_input;
182+
183+
// wait for the initial Ajax request to finish
184+
userEvent.click(controlInput);
185+
await waitFor(() => {
186+
expect(container.querySelectorAll('.option[data-selectable]')).toHaveLength(1);
187+
});
188+
189+
controlInput.value = 'foo';
190+
controlInput.dispatchEvent(new Event('input'));
191+
192+
await waitFor(() => {
193+
expect(container.querySelectorAll('.option[data-selectable]')).toHaveLength(2);
194+
});
195+
196+
expect(selectElement.value).toBe('');
197+
tomSelect.addItem('2');
198+
expect(selectElement.value).toBe('2');
199+
200+
selectElement.outerHTML = `
201+
<select
202+
id="the-select"
203+
data-testid="main-element"
204+
data-controller="autocomplete"
205+
data-autocomplete-url-value="/path/to/autocomplete2"
206+
></select>
207+
`;
208+
209+
// wait for the MutationObserver to flush these changes
210+
await shortDelay(10);
211+
212+
expect(selectElement.value).toBe('');
213+
});
214+
141215
it('connect with ajax URL on an input element', async () => {
142216
const { container, tomSelect } = await startAutocompleteTest(`
143217
<label for="the-input">Items</label>

0 commit comments

Comments
 (0)