Skip to content

Commit 135ab6b

Browse files
committed
Reset TomSelect when updating controller attributes
1 parent aec04ad commit 135ab6b

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
@@ -34,6 +34,7 @@ export default class extends Controller {
3434
private isObserving;
3535
private hasLoadedChoicesPreviously;
3636
private originalOptions;
37+
private isRemoteOptions;
3738
initialize(): void;
3839
connect(): void;
3940
initializeTomSelect(): void;

src/Autocomplete/assets/dist/controller.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ class default_1 extends Controller {
3030
this.isObserving = false;
3131
this.hasLoadedChoicesPreviously = false;
3232
this.originalOptions = [];
33+
this.isRemoteOptions = false;
3334
}
3435
initialize() {
3536
if (!this.mutationObserver) {
@@ -50,6 +51,8 @@ class default_1 extends Controller {
5051
}
5152
if (this.urlValue) {
5253
this.tomSelect = __classPrivateFieldGet(this, _default_1_instances, "m", _default_1_createAutocompleteWithRemoteData).call(this, this.urlValue, this.hasMinCharactersValue ? this.minCharactersValue : null);
54+
this.isRemoteOptions = true;
55+
this.startMutationObserver();
5356
return;
5457
}
5558
if (this.optionsAsHtmlValue) {
@@ -167,11 +170,15 @@ class default_1 extends Controller {
167170
requireReset = true;
168171
break;
169172
}
173+
if (mutation.target === this.element && mutation.attributeName.includes('data-symfony--ux-autocomplete')) {
174+
requireReset = true;
175+
break;
176+
}
170177
break;
171178
}
172179
});
173180
const newOptions = this.selectElement ? this.createOptionsDataStructure(this.selectElement) : [];
174-
const areOptionsEquivalent = this.areOptionsEquivalent(newOptions);
181+
const areOptionsEquivalent = this.isRemoteOptions || this.areOptionsEquivalent(newOptions);
175182
if (!areOptionsEquivalent || requireReset) {
176183
this.originalOptions = newOptions;
177184
this.resetTomSelect();

src/Autocomplete/assets/src/controller.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export default class extends Controller {
3939
private isObserving = false;
4040
private hasLoadedChoicesPreviously = false;
4141
private originalOptions: Array<{ value: string; text: string; group: string | null }> = [];
42+
private isRemoteOptions = false;
4243

4344
initialize() {
4445
if (!this.mutationObserver) {
@@ -69,6 +70,9 @@ export default class extends Controller {
6970
this.hasMinCharactersValue ? this.minCharactersValue : null
7071
);
7172

73+
this.isRemoteOptions = true;
74+
this.startMutationObserver();
75+
7276
return;
7377
}
7478

@@ -389,12 +393,18 @@ export default class extends Controller {
389393
break;
390394
}
391395

396+
if (mutation.target === this.element && mutation.attributeName.match(/data-(symfony--ux-)?autocomplete/)) {
397+
requireReset = true;
398+
399+
break;
400+
}
401+
392402
break;
393403
}
394404
});
395405

396406
const newOptions = this.selectElement ? this.createOptionsDataStructure(this.selectElement) : [];
397-
const areOptionsEquivalent = this.areOptionsEquivalent(newOptions);
407+
const areOptionsEquivalent = this.isRemoteOptions || this.areOptionsEquivalent(newOptions);
398408
if (!areOptionsEquivalent || requireReset) {
399409
this.originalOptions = newOptions;
400410
this.resetTomSelect();

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

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

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

0 commit comments

Comments
 (0)