Problem/Motivation
When a part of the component form is loaded by AJAX after a select change, its checkboxes that should be checked by default arrive unchecked. Nobody touched them, and saving stores false.
Four ways to see it:
- A source with a checkbox setting that defaults to true. Switch the source of a prop to it: the checkbox is unchecked.
- A component prop with "default: true" in its definition. Pick the component in a field formatter, in a view field, or as a component inside a slot: the prop's checkbox is unchecked.
- The Block source. Add it to a slot and pick "Site branding": "Site logo", "Site name" and "Site slogan" are unchecked, although the block checks them by default.
- The Field formatter source. Pick any formatter, then the "Label" formatter: "Link label to the referenced entity" is unchecked, although the formatter links by default.
The same checkbox shows its default when its form is part of the page load, for instance the props of a block whose component is fixed. Only a form loaded by a source, component, block or formatter select change is affected: adding a source to a slot is fine.
Steps to reproduce
1. Place the block of any component having a slot.
2. In the slot, add the "Block" source, then pick "Site branding".
3. "Site logo", "Site name" and "Site slogan" are unchecked. Save the block: the three settings are stored as false and the branding block renders empty.
Same with a custom source:
1. Write a source with a setting "loud" defaulting to TRUE and a checkbox for it in settingsForm().
2. On any string prop, switch the source to it.
3. The checkbox is unchecked. Save: the setting is stored as false.
Why it happens
Core processes an AJAX request twice: once with the submitted values, then a rebuild that is sent back to the browser. Our forms build the newly selected sub-form already in the first pass, from the request input. In that pass core writes an empty value into the input for every element it does not find in the request, and the rebuild takes that empty value for "unchecked" instead of using the default. Core expects a new sub-form to appear only in the rebuild, which our forms cannot do since a normal save has a single pass and needs the input.
The request looks exactly the same for a new checkbox and for a checkbox the user has unchecked: nothing is sent in both cases. Only the select that triggered the request tells them apart.
Proposed resolution
The selects that load a new sub-form next to them (source, component, block plugin, formatter type) declare it with a form property. In the first pass, the empty entries core wrote for the elements next to such a select are dropped from the input, so the rebuild applies their defaults. Real values are kept and elements elsewhere in the form are not touched: a checkbox the user unchecked stays unchecked.
Tests: the test component's "boolean_with_default_true" and "boolean_with_default_false" props get real defaults, a test source with a true and a false default setting is added, and the block and field formatter Playwright specs check the defaults on arrival, after save and reopen, and in the rendered page.
Issue fork ui_patterns-3618664
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
just_like_good_vibesComment #4
pdureau commentedComment #5
pdureau commentedI didn't test in my local environment, but the MR looks OK
Comment #7
just_like_good_vibes