|
| 1 | +--- |
| 2 | +title: ChildFieldManager |
| 3 | +summary: Strict management of child fields for advanced use cases |
| 4 | +--- |
| 5 | + |
| 6 | +# `ChildFieldManager` |
| 7 | + |
| 8 | +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. |
| 9 | + |
| 10 | +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. |
| 11 | + |
| 12 | +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. |
| 13 | + |
| 14 | +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. |
| 15 | + |
| 16 | +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: |
| 17 | + |
| 18 | +1. It gets all data fields *including* those managed by a `ChildFieldManager`, which are excluded when passing `false` |
| 19 | +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. |
| 20 | + |
| 21 | +A very simple implementation of this interface would look like this: |
| 22 | + |
| 23 | +> [!WARNING] |
| 24 | +> The below example shows a minimal PHP implementation but not the template. |
| 25 | +> 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. |
| 26 | +
|
| 27 | +```php |
| 28 | +namespace App\Form; |
| 29 | + |
| 30 | +use SilverStripe\Core\Validation\FieldValidation\CompositeFieldValidator; |
| 31 | +use SilverStripe\Forms\ChildFieldManager; |
| 32 | +use SilverStripe\Forms\FormField; |
| 33 | +use SilverStripe\Forms\TextField; |
| 34 | + |
| 35 | +class MyChildFieldManager extends FormField implements ChildFieldManager |
| 36 | +{ |
| 37 | + private static array $field_validators = [ |
| 38 | + CompositeFieldValidator::class, |
| 39 | + ]; |
| 40 | + |
| 41 | + private array $children = []; |
| 42 | + |
| 43 | + public function __construct() |
| 44 | + { |
| 45 | + $this->children = [ |
| 46 | + 'FieldOne' => TextField::create('FieldOne'), |
| 47 | + 'FieldTwo' => TextField::create('FieldTwo'), |
| 48 | + ]; |
| 49 | + parent::__construct('MyManagedField'); |
| 50 | + } |
| 51 | + |
| 52 | + public function isManagedField(string $fieldName): bool |
| 53 | + { |
| 54 | + return array_key_exists($fieldName, $this->children); |
| 55 | + } |
| 56 | + |
| 57 | + public function getManagedFieldByName(string $fieldName): ?FormField |
| 58 | + { |
| 59 | + return $this->children[$fieldName] ?? null; |
| 60 | + } |
| 61 | + |
| 62 | + public function getManagedFields(): iterable |
| 63 | + { |
| 64 | + return $this->children; |
| 65 | + } |
| 66 | + |
| 67 | + public function getValueForValidation(): mixed |
| 68 | + { |
| 69 | + // Ensure child fields get validated by the CompositeFieldValidator |
| 70 | + return $this->children; |
| 71 | + } |
| 72 | + |
| 73 | + public function getSchemaDataDefaults(): array |
| 74 | + { |
| 75 | + $defaults = parent::getSchemaDataDefaults(); |
| 76 | + // Include child schema data for react forms |
| 77 | + $children = $this->getChildren(); |
| 78 | + foreach ($children as $child) { |
| 79 | + $childSchema[] = $child->getSchemaData(); |
| 80 | + } |
| 81 | + $defaults['children'] = $childSchema; |
| 82 | + return $defaults; |
| 83 | + } |
| 84 | +} |
| 85 | +``` |
| 86 | + |
| 87 | +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. |
| 88 | + |
| 89 | +> [!TIP] |
| 90 | +> 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. |
0 commit comments