Skip to content

Commit 9d6c9cc

Browse files
committed
bug #910 [LiveComponent] Fix array valued checkboxes change event handling (welcoMattic)
This PR was squashed before being merged into the 2.x branch. Discussion ---------- [LiveComponent] Fix array valued checkboxes change event handling | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes <!-- please update src/**/CHANGELOG.md files --> | Tickets | Fix #906 | License | MIT As described in the linked issue, the event `change` of checkboxes weren't fully triggered (it didn't trigger AJAX request). It was because of the always-equals values of the pre-checked value attribute of the element and the after-change-event value of the element. Thanks `@Jim56450` for the help! Commits ------- 0e94fec [LiveComponent] Fix array valued checkboxes change event handling
2 parents b31ef35 + 0e94fec commit 9d6c9cc

File tree

4 files changed

+75
-11
lines changed

4 files changed

+75
-11
lines changed

src/LiveComponent/assets/dist/live_controller.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -311,18 +311,19 @@ function getElementAsTagText(element) {
311311
: element.outerHTML;
312312
}
313313
const getMultipleCheckboxValue = function (element, currentValues) {
314+
const finalValues = [...currentValues];
314315
const value = inputValue(element);
315316
const index = currentValues.indexOf(value);
316317
if (element.checked) {
317318
if (index === -1) {
318-
currentValues.push(value);
319+
finalValues.push(value);
319320
}
320-
return currentValues;
321+
return finalValues;
321322
}
322323
if (index > -1) {
323-
currentValues.splice(index, 1);
324+
finalValues.splice(index, 1);
324325
}
325-
return currentValues;
326+
return finalValues;
326327
};
327328
const inputValue = function (element) {
328329
return element.dataset.value ? element.dataset.value : element.value;

src/LiveComponent/assets/src/dom_utils.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -266,24 +266,25 @@ export function getElementAsTagText(element: HTMLElement): string {
266266
}
267267

268268
const getMultipleCheckboxValue = function (element: HTMLInputElement, currentValues: Array<string>): Array<string> {
269+
const finalValues = [...currentValues];
269270
const value = inputValue(element);
270271
const index = currentValues.indexOf(value);
271272

272273
if (element.checked) {
273274
// Add value to an array if it's not in it already
274275
if (index === -1) {
275-
currentValues.push(value);
276+
finalValues.push(value);
276277
}
277278

278-
return currentValues;
279+
return finalValues;
279280
}
280281

281282
// Remove value from an array
282283
if (index > -1) {
283-
currentValues.splice(index, 1);
284+
finalValues.splice(index, 1);
284285
}
285286

286-
return currentValues;
287+
return finalValues;
287288
};
288289

289290
const inputValue = function (element: HTMLInputElement): string {

src/LiveComponent/assets/test/controller/model.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,38 @@ describe('LiveController data-model Tests', () => {
335335
expect(test.component.valueStore.getOriginalProps()).toEqual({form: {check: ['foo', 'bar']}});
336336
});
337337

338+
it('sends correct data for array valued checkbox fields with non-form object', async () => {
339+
const test = await createTest({ check: [] }, (data: any) => `
340+
<div ${initComponent(data)}>
341+
<form data-model="*">
342+
<label>
343+
Checkbox 1: <input type="checkbox" name="check[]" value="foo" ${data.check.indexOf('foo') > -1 ? 'checked' : ''} />
344+
</label>
345+
346+
<label>
347+
Checkbox 2: <input type="checkbox" name="check[]" value="bar" ${data.check.indexOf('bar') > -1 ? 'checked' : ''} />
348+
</label>
349+
</form>
350+
351+
Checkbox 2 is ${data.check.indexOf('bar') > -1 ? 'checked' : 'unchecked' }
352+
</div>
353+
`);
354+
355+
const check1Element = getByLabelText(test.element, 'Checkbox 1:');
356+
const check2Element = getByLabelText(test.element, 'Checkbox 2:');
357+
358+
// only 1 Ajax call will be made thanks to debouncing
359+
test.expectsAjaxCall()
360+
.expectUpdatedData({ 'check': ['foo', 'bar'] });
361+
362+
await userEvent.click(check1Element);
363+
await userEvent.click(check2Element);
364+
365+
await waitFor(() => expect(test.element).toHaveTextContent('Checkbox 2 is checked'));
366+
367+
expect(test.component.valueStore.getOriginalProps()).toEqual({check: ['foo', 'bar']});
368+
});
369+
338370
it('sends correct data for array valued checkbox fields with initial data', async () => {
339371
const test = await createTest({ form: { check: ['foo']} }, (data: any) => `
340372
<div ${initComponent(data)}>
@@ -367,6 +399,36 @@ describe('LiveController data-model Tests', () => {
367399
expect(test.component.valueStore.getOriginalProps()).toEqual({form: {check: ['bar']}});
368400
});
369401

402+
it('sends correct data for array valued checkbox fields with non-form object and with initial data', async () => {
403+
const test = await createTest({ check: ['foo'] }, (data: any) => `
404+
<div ${initComponent(data)}>
405+
<label>
406+
Checkbox 1: <input type="checkbox" data-model="check[]" value="foo" ${data.check.indexOf('foo') > -1 ? 'checked' : ''} />
407+
</label>
408+
409+
<label>
410+
Checkbox 2: <input type="checkbox" data-model="check[]" value="bar" ${data.check.indexOf('bar') > -1 ? 'checked' : ''} />
411+
</label>
412+
413+
Checkbox 1 is ${data.check.indexOf('foo') > -1 ? 'checked' : 'unchecked' }
414+
</div>
415+
`);
416+
417+
const check1Element = getByLabelText(test.element, 'Checkbox 1:');
418+
const check2Element = getByLabelText(test.element, 'Checkbox 2:');
419+
420+
// only 1 Ajax call will be made thanks to debouncing
421+
test.expectsAjaxCall()
422+
.expectUpdatedData({ 'check': ['bar'] });
423+
424+
await userEvent.click(check1Element);
425+
await userEvent.click(check2Element);
426+
427+
await waitFor(() => expect(test.element).toHaveTextContent('Checkbox 1 is unchecked'));
428+
429+
expect(test.component.valueStore.getOriginalProps()).toEqual({check: ['bar']});
430+
});
431+
370432
it('sends correct data for select multiple field', async () => {
371433
const test = await createTest({ form: { select: []} }, (data: any) => `
372434
<div ${initComponent(data)}>

src/LiveComponent/doc/index.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -531,9 +531,9 @@ value on checked. If no ``value`` is set, the checkbox will set a boolean value:
531531

532532
<input type="checkbox" data-model="agreeToTerms">
533533

534-
<input type="checkbox" data-model="foods" value="pizza">
535-
<input type="checkbox" data-model="foods" value="tacos">
536-
<input type="checkbox" data-model="foods" value="sushi">
534+
<input type="checkbox" data-model="foods[]" value="pizza">
535+
<input type="checkbox" data-model="foods[]" value="tacos">
536+
<input type="checkbox" data-model="foods[]" value="sushi">
537537

538538
``select`` and ``radio`` elements are a bit easier: use these to either set a
539539
single value or an array of values::

0 commit comments

Comments
 (0)