-
Notifications
You must be signed in to change notification settings - Fork 72
DOC Document new ChildFieldManager interface and move block functionality.
#853
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
emteknetnz
merged 2 commits into
silverstripe:6
from
creative-commoners:pulls/6/move-blocks
Dec 8, 2025
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
90 changes: 90 additions & 0 deletions
90
en/02_Developer_Guides/03_Forms/Field_types/05_ChildFieldManager.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| --- | ||
| title: ChildFieldManager | ||
| summary: Strict management of child fields for advanced use cases | ||
| --- | ||
|
|
||
| # `ChildFieldManager` | ||
|
|
||
| In most cases, child form fields can be grouped with a [`CompositeField`](api:SilverStripe\Forms\CompositeField) or one of its subclasses - but sometimes you need more control. | ||
|
|
||
| For example, you may have a use case where specific form fields *must* be present in the child field list. In that case you won't want methods like [`FieldList::removeByName()`](api:SilverStripe\Forms\FieldList::removeByName()) or [`FieldList::replaceField()`](api:SilverStripe\Forms\FieldList::replaceField()) to know about your child fields, but you'll still want to expose them for things like [`Form::loadDataFrom()`](api:SilverStripe\Forms\Form::loadDataFrom()), [`Form::saveInto()`](api:SilverStripe\Forms\Form::saveInto()), and allow them to manage their own AJAX requests. | ||
|
|
||
| To achieve this, your custom [`FormField`](api:SilverStripe\Forms\FormField) can implement the [`ChildFieldManager`](api:SilverStripe\Forms\ChildFieldManager) interface. The methods declared in this interface allow the form to access your fields for critical functionality, but doesn't let anyone remove or replace fields in your managed child field list. | ||
|
|
||
| In order to get managed fields from a `FieldList`, call [`FieldList::getDataFields(true)`](api:SilverStripe\Forms\FieldList::getDataFields()).This method replaces the now deprecated [`FieldList::dataFields()`](api:SilverStripe\Forms\FieldList::dataFields()) method. | ||
|
|
||
| Calling `FieldList::getDataFields()` with no arguments, or passing in `false` explicitly, is the same as calling the old `FieldList::dataFields()` method. If you pass in `true`, that differs in two ways: | ||
|
|
||
| 1. It gets all data fields *including* those managed by a `ChildFieldManager`, which are excluded when passing `false` | ||
| 1. Fields returned from a `ChildFieldManager` are not cached. This allows child field managers to swap out the form field implementation if their logic requires it. | ||
|
|
||
| A very simple implementation of this interface would look like this: | ||
|
|
||
| > [!WARNING] | ||
| > The below example shows a minimal PHP implementation but not the template. | ||
| > The assumption is if you're implementing this interface, you already have an advanced use case and know what you need to do in your template to get your use case broadly working. | ||
|
|
||
| ```php | ||
| namespace App\Form; | ||
|
|
||
| use SilverStripe\Core\Validation\FieldValidation\CompositeFieldValidator; | ||
| use SilverStripe\Forms\ChildFieldManager; | ||
| use SilverStripe\Forms\FormField; | ||
| use SilverStripe\Forms\TextField; | ||
|
|
||
| class MyChildFieldManager extends FormField implements ChildFieldManager | ||
| { | ||
| private static array $field_validators = [ | ||
| CompositeFieldValidator::class, | ||
| ]; | ||
|
|
||
| private array $children = []; | ||
|
|
||
| public function __construct() | ||
| { | ||
| $this->children = [ | ||
| 'FieldOne' => TextField::create('FieldOne'), | ||
| 'FieldTwo' => TextField::create('FieldTwo'), | ||
| ]; | ||
| parent::__construct('MyManagedField'); | ||
| } | ||
|
|
||
| public function isManagedField(string $fieldName): bool | ||
| { | ||
| return array_key_exists($fieldName, $this->children); | ||
| } | ||
|
|
||
| public function getManagedFieldByName(string $fieldName): ?FormField | ||
| { | ||
| return $this->children[$fieldName] ?? null; | ||
| } | ||
|
|
||
| public function getManagedFields(): iterable | ||
| { | ||
| return $this->children; | ||
| } | ||
|
|
||
| public function getValueForValidation(): mixed | ||
| { | ||
| // Ensure child fields get validated by the CompositeFieldValidator | ||
| return $this->children; | ||
| } | ||
|
|
||
| public function getSchemaDataDefaults(): array | ||
| { | ||
| $defaults = parent::getSchemaDataDefaults(); | ||
| // Include child schema data for react forms | ||
| $children = $this->getChildren(); | ||
| foreach ($children as $child) { | ||
| $childSchema[] = $child->getSchemaData(); | ||
| } | ||
| $defaults['children'] = $childSchema; | ||
| return $defaults; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| You can then implement whatever logic you want and rely on those specific fields being there no matter what anyone else is doing with your form. | ||
|
|
||
| > [!TIP] | ||
| > It's usually a good idea to ensure that if [`FormField::setForm()`](api:SilverStripe\Forms\FormField::setForm()), [`FormField::performReadonlyTransformation()`](api:SilverStripe\Forms\FormField::performReadonlyTransformation()), [`FormField::performDisabledTransformation()`](api:SilverStripe\Forms\FormField::performDisabledTransformation()), [`FormField::setReadonly()`](api:SilverStripe\Forms\FormField::setReadonly()), or [`FormField::setDisabled()`](api:SilverStripe\Forms\FormField::setDisabled()) is called on your parent form, the appropriate methods are called on your child fields a well. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Technically the |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was missing though the section was added. Figured I may as well just add it in rather than spawning a whole separate issue just for this