|
| 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::validate()`](api:SilverStripe\Forms\Form::validate()), [`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::getAllDataFields()`](api:SilverStripe\Forms\FieldList::getAllDataFields()). This differs from [`FieldList::dataFields()`](api:SilverStripe\Forms\FieldList::dataFields()) in two ways: |
| 15 | + |
| 16 | +1. It gets all data fields *including* those managed by a `ChildFieldManager`, which `dataFields()` excludes |
| 17 | +1. It only caches the same fields that `dataFields()` caches - i.e. fields returned from a `ChildFieldManager` are not cached when calling `getAllDataFields()`. |
| 18 | + |
| 19 | +A very simple implementation of this interface would look like this: |
| 20 | + |
| 21 | +> [!WARNING] |
| 22 | +> The below example shows a minimal PHP implementation but not the template. |
| 23 | +> 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. |
| 24 | +
|
| 25 | +```php |
| 26 | +namespace App\Form; |
| 27 | + |
| 28 | +use SilverStripe\Forms\ChildFieldManager; |
| 29 | +use SilverStripe\Forms\FormField; |
| 30 | +use SilverStripe\Forms\TextField; |
| 31 | + |
| 32 | +class MyChildFieldManager extends FormField implements ChildFieldManager |
| 33 | +{ |
| 34 | + private array $children = []; |
| 35 | + |
| 36 | + public function __construct() |
| 37 | + { |
| 38 | + $this->children = [ |
| 39 | + 'FieldOne' => TextField::create('FieldOne'), |
| 40 | + 'FieldTwo' => TextField::create('FieldTwo'), |
| 41 | + ]; |
| 42 | + parent::__construct('MyManagedField'); |
| 43 | + } |
| 44 | + |
| 45 | + public function isManagedField(string $fieldName): bool |
| 46 | + { |
| 47 | + return array_key_exists($fieldName, $this->children); |
| 48 | + } |
| 49 | + |
| 50 | + public function getManagedFieldByName(string $fieldName): ?FormField |
| 51 | + { |
| 52 | + return $this->children[$fieldName] ?? null; |
| 53 | + } |
| 54 | + |
| 55 | + public function getManagedFields(): iterable |
| 56 | + { |
| 57 | + return $this->children; |
| 58 | + } |
| 59 | + |
| 60 | + public function getSchemaDataDefaults(): array |
| 61 | + { |
| 62 | + $defaults = parent::getSchemaDataDefaults(); |
| 63 | + // Include child schema data for react forms |
| 64 | + $children = $this->getChildren(); |
| 65 | + foreach ($children as $child) { |
| 66 | + $childSchema[] = $child->getSchemaData(); |
| 67 | + } |
| 68 | + $defaults['children'] = $childSchema; |
| 69 | + return $defaults; |
| 70 | + } |
| 71 | +} |
| 72 | +``` |
| 73 | + |
| 74 | +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. |
| 75 | + |
| 76 | +> [!TIP] |
| 77 | +> 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