Skip to content

Commit 49e4239

Browse files
committed
Reset TomSelect when updating controller attributes
1 parent ed31702 commit 49e4239

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
@@ -48,6 +48,7 @@ export default class extends Controller {
4848
private isObserving = false;
4949
private hasLoadedChoicesPreviously = false;
5050
private originalOptions: Array<{ value: string; text: string; group: string | null }> = [];
51+
private isRemoteOptions = false;
5152

5253
initialize() {
5354
if (!this.mutationObserver) {
@@ -78,6 +79,9 @@ export default class extends Controller {
7879
this.hasMinCharactersValue ? this.minCharactersValue : null
7980
);
8081

82+
this.isRemoteOptions = true;
83+
this.startMutationObserver();
84+
8185
return;
8286
}
8387

@@ -398,12 +402,18 @@ export default class extends Controller {
398402
break;
399403
}
400404

405+
if (mutation.target === this.element && mutation.attributeName.match(/data-(symfony--ux-)?autocomplete/)) {
406+
requireReset = true;
407+
408+
break;
409+
}
410+
401411
break;
402412
}
403413
});
404414

405415
const newOptions = this.selectElement ? this.createOptionsDataStructure(this.selectElement) : [];
406-
const areOptionsEquivalent = this.areOptionsEquivalent(newOptions);
416+
const areOptionsEquivalent = this.isRemoteOptions || this.areOptionsEquivalent(newOptions);
407417
if (!areOptionsEquivalent || requireReset) {
408418
this.originalOptions = newOptions;
409419
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)