Skip to content

Commit 3c5fc3e

Browse files
committed
DOC Document new ChildFieldManager interface
1 parent 19ade0a commit 3c5fc3e

File tree

2 files changed

+94
-2
lines changed

2 files changed

+94
-2
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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::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\Core\Validation\FieldValidation\CompositeFieldValidator;
29+
use SilverStripe\Forms\ChildFieldManager;
30+
use SilverStripe\Forms\FormField;
31+
use SilverStripe\Forms\TextField;
32+
33+
class MyChildFieldManager extends FormField implements ChildFieldManager
34+
{
35+
private static array $field_validators = [
36+
CompositeFieldValidator::class,
37+
];
38+
39+
private array $children = [];
40+
41+
public function __construct()
42+
{
43+
$this->children = [
44+
'FieldOne' => TextField::create('FieldOne'),
45+
'FieldTwo' => TextField::create('FieldTwo'),
46+
];
47+
parent::__construct('MyManagedField');
48+
}
49+
50+
public function isManagedField(string $fieldName): bool
51+
{
52+
return array_key_exists($fieldName, $this->children);
53+
}
54+
55+
public function getManagedFieldByName(string $fieldName): ?FormField
56+
{
57+
return $this->children[$fieldName] ?? null;
58+
}
59+
60+
public function getManagedFields(): iterable
61+
{
62+
return $this->children;
63+
}
64+
65+
public function getValueForValidation(): mixed
66+
{
67+
// Ensure child fields get validated by the CompositeFieldValidator
68+
return $this->children;
69+
}
70+
71+
public function getSchemaDataDefaults(): array
72+
{
73+
$defaults = parent::getSchemaDataDefaults();
74+
// Include child schema data for react forms
75+
$children = $this->getChildren();
76+
foreach ($children as $child) {
77+
$childSchema[] = $child->getSchemaData();
78+
}
79+
$defaults['children'] = $childSchema;
80+
return $defaults;
81+
}
82+
}
83+
```
84+
85+
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.
86+
87+
> [!TIP]
88+
> 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.

en/08_Changelogs/6.2.0.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,14 @@ Note that some third-party modules may not yet support PHP 8.5, so PHP deprecati
105105

106106
### Move elemental blocks
107107

108-
In the [dnadesign/silverstripe-elemental](/optional_features/elemental/) module, functionality has been added to move elemental blocks from one parent to another, including from one elemental area to another on the same parent record.
108+
The [dnadesign/silverstripe-elemental](/optional_features/elemental/) module now includes functionality that allows elemental blocks to be moved from one parent record to another. This includes moving blocks between different elemental areas on the same parent record.
109109

110110
![move block button](./_images/move-block-button.png)
111111

112-
The move form provides a lot of flexibility for various configurations, and fields will be hidden if there's only one option to select from. For example, if there's only one elemental area relation on your selected parent record, no dropdown to select an elemental area will be displayed - but if there's multiple, a dropdown will be there for you.
112+
The move form has been designed for flexibility and will automatically hide fields if there's only one option available. For example:
113+
114+
- If your selected parent record has only one elemental area relation, the dropdown for selecting an elemental area won't be displayed.
115+
- If there are multiple elemental area relations, the dropdown will be available.
113116

114117
![move block form modal](./_images/move-block-modal.png)
115118

@@ -142,6 +145,7 @@ You can filter by the name and description of the campaign, its status, and the
142145

143146
### Other new features and enhancements {#other-new}
144147

148+
- A new [`ChildFieldManager`](api:SilverStripe\Forms\ChildFieldManager) interface has been added to allow a parent form field to strictly control its children, but still allow setting/getting values for those fields let them handle AJAX requests. See [`ChildFieldManager` docs](/developer_guides/forms/field_types/childfieldmanager/) for more details.
145149
- The `help` plugin is now added by default to all [`TinyMCEConfig`](api:SilverStripe\TinyMCE\TinyMCEConfig) instances. If you were adding it manually, you can remove that custom code. If for some reason you don't want that plugin in one of your configs, you can use [`TinyMCEConfig::disablePlugins()`](api:SilverStripe\TinyMCE\TinyMCEConfig::disablePlugins()) to remove it - but be aware that it is extremely useful for screen reader users to keep this plugin installed.
146150
- [`HTML::createTag()`](api:SilverStripe\View\HTML::createTag()) now supports value-less boolean attributes. For example if you pass `HTML::create('input', ['readonly' => true])`, the result will be `<input readonly>`. Previously it would output as `<input readonly="1">`.
147151
- The built-in session handlers [`FileSessionHandler`](api:SilverStripe\Control\SessionHandler\FileSessionHandler), [`CacheSessionHandler`](api:SilverStripe\Control\SessionHandler\CacheSessionHandler), and [`DatabaseSessionHandler`](api:SilverStripe\Control\SessionHandler\DatabaseSessionHandler) all now validate the session ID `PHPSESSID` to ensure it matches a valid format, otherwise a `RuntimeException` will be thrown.

0 commit comments

Comments
 (0)