Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
16 changes: 16 additions & 0 deletions en/08_Changelogs/6.2.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ title: 6.2.0 (unreleased)
- [Accessibility improvements](#accessibility-improvements)
- [Unsaved changes indicator](#unsaved-changes-indicator)
Copy link
Member Author

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

- [PHP 8.5 support](#php-8-5-support)
- [Move elemental blocks](#move-elemental-blocks)
- [Pass arbitrary attributes with requirements API](#requirements-attributes)
- [Filter archived records](#filter-archived-records)
- [Filter campaigns](#filter-campaigns)
Expand Down Expand Up @@ -102,6 +103,19 @@ All [supported modules](/project_governance/supported_modules/) have been update

Note that some third-party modules may not yet support PHP 8.5, so PHP deprecation warnings may still show for those if your PHP error reporting is set to report all deprecations.

### Move elemental blocks

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.

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

The move form has been designed for flexibility and will automatically hide fields if there's only one option available. For example:

- If your selected parent record has only one elemental area relation, the dropdown for selecting an elemental area won't be displayed.
- If there are multiple elemental area relations, the dropdown will be available.

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

### Pass arbitrary attributes with requirements API {#requirements-attributes}

When using [`Requirements_Backend`](api:SilverStripe\View\Requirements_Backend) as your requirements API backend (which is the default), you can now pass arbitrary attributes for JavaScript and CSS (`<script>` and `<link>` tags) using the `$options` argument in various methods.
Expand Down Expand Up @@ -131,6 +145,7 @@ You can filter by the name and description of the campaign, its status, and the

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

- 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.
- 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.
- [`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">`.
- 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.
Expand Down Expand Up @@ -167,6 +182,7 @@ It's best practice to avoid using deprecated code where possible, but sometimes
- The [`EagerLoadedList::getIDList()`](api:SilverStripe\ORM\EagerLoadedList::getIDList()) method has been deprecated. Use `$list->column('ID')` instead.
- The [`UnsavedRelationList::getIDList()`](api:SilverStripe\ORM\UnsavedRelationList::getIDList()) method has been deprecated. Use `$list->column('ID')` instead.
- The `IconHOC` react component has been deprecated and will be removed in a future major release without a replacement. The `Button` component's `icon` prop will still remain.
- The [`FieldList::dataFields()`](api:SilverStripe\Forms\FieldList::dataFields()) method has been deprecated. Use[`FieldList::getDataFields()`](api:SilverStripe\Forms\FieldList::getDataFields()) instead.

## Bug fixes

Expand Down
Binary file added en/08_Changelogs/_images/move-block-button.png
Copy link
Member Author

@GuySartorelli GuySartorelli Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically the Move button would never be blue like this, and the Content button would.... I've cheated a bit to make it less confusing 😅

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added en/08_Changelogs/_images/move-block-modal.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.