Create a Custom Frontend Adapter
Every entry in the adapter mapping pairs a frontend adapter with a backend adapter. The frontend adapter decides
which input controls the tree level configuration renders for that data type; the backend adapter resolves the value
used to build the tree.
The bundle ships four frontend adapters: SimpleValueAdapter, LocalizedFieldAdapter, RelationAdapter and
ObjectBrickAdapter. Custom frontend adapters are registered differently in Pimcore Studio and in the Admin Classic UI,
but both use the same frontend name in the mapping.
Pimcore Studio
Frontend adapters are registered in the frontend adapter registry, a service the bundle binds to the Studio UI
container under the id BackendPowerTools/AetConfiguration/FrontendAdapterRegistry. Your Studio UI bundle can add its
own adapter in three steps:
- Create a React component that renders the controls;
- Register the component in the frontend adapter registry;
- Add the adapter to the mapping.
Create the Component
An adapter is a plain React component. It renders additional Form.Items for one tree level and receives the tree
level context as props:
| Prop | Description |
|---|---|
classId | Id of the data-object class the tree is built for. |
classFieldName | Name of the class field selected for this tree level. |
classField | The selected class field as delivered by the backend, including its additionalData. |
adapterConfiguration | The adapter configuration currently stored for this tree level. |
Every control must live under the adapterConfiguration form path. Whatever the controls collect is stored as the
adapter configuration of the tree level and passed to the backend adapter's setConfiguration(). Values must be
scalars (string, number or boolean); the backend configuration schema rejects arrays and nested objects, so a
multi-select has to be stored as e.g. a comma-separated string.
import React from 'react'
import { useTranslation } from '@pimcore/studio-ui-bundle/app'
import { Form, Input } from '@pimcore/studio-ui-bundle/components'
export interface AetFrontendAdapterProps {
classId: string
classFieldName: string
classField?: { name: string, title: string, fieldType: string, adapterType: string, additionalData?: unknown }
adapterConfiguration?: Record<string, string | number | boolean | null | undefined>
}
export const FooAdapter = (props: AetFrontendAdapterProps): React.JSX.Element => {
const { t } = useTranslation()
return (
<>
<Form.Item
label={ t('aet-configuration.tree-level.icon-class') }
name={ ['adapterConfiguration', 'treeLevelIconClass'] }
>
<Input />
</Form.Item>
<Form.Item
label="Placeholder"
name={ ['adapterConfiguration', 'placeholder'] }
>
<Input />
</Form.Item>
</>
)
}
Register the Component
Register the adapter in the onInit hook of one of your bundle's Studio UI modules. The registry is bound and seeded
with the built-in adapters while the Backend Power Tools plugin initializes, which happens before any module's onInit
runs. Registering and overriding therefore behave the same regardless of the order in which the bundles' modules run.
import type React from 'react'
import { container, type AbstractModule } from '@pimcore/studio-ui-bundle'
import { FooAdapter, type AetFrontendAdapterProps } from './foo-adapter'
interface AetFrontendAdapterRegistry {
registerAdapter: (adapter: { type: string, component: React.ComponentType<AetFrontendAdapterProps> }) => void
overrideAdapter: (adapter: { type: string, component: React.ComponentType<AetFrontendAdapterProps> }) => boolean
hasAdapter: (type: string) => boolean
}
export const FooModule: AbstractModule = {
onInit: (): void => {
const registry = container.get<AetFrontendAdapterRegistry>('BackendPowerTools/AetConfiguration/FrontendAdapterRegistry')
registry.registerAdapter({
type: 'FooAdapter', //= reflects the "frontend" adapter in the config
component: FooAdapter
})
}
}
registerAdapter() refuses to register a type twice and reports the collision through the Studio error handler, so one
bundle cannot silently replace another bundle's adapter. To replace a registered adapter on purpose — for example to
take over one of the built-in adapters — call overrideAdapter() instead.
Add the Adapter to the Mapping
The last step is to add the adapter to the mapping in the config.yaml of your bundle. The string provided to
frontend has to match the type you registered.
pimcore_backend_power_tools:
pimcore_alternative_object_trees:
adapters:
input: #= the datatype of the corresponding class field
frontend: FooAdapter
backend: Pimcore\Bundle\BackendPowerToolsBundle\Adapter\AlternativeElementTree\DataObject\SimpleValueAdapter
If the frontend value names a type that no bundle registered, the tree level renders the class field selector only
and no adapter-specific controls.
Admin Classic UI
To create a custom frontend adapter for the Admin Classic UI, you need to follow three steps:
- Create a new JS class for your custom frontend adapter;
- Register the class via an event;
- Add the adapter to the mapping.
Create the JS class
Each adapter has to extend the pimcore.bundle.backendpowertools.alternativeelementtree.configuration.adapter.abstract class.
The only thing you have to do is to set your adapters type and implement the setFields method. This method has to return an array of ExtJS fields.
pimcore.registerNS("your.custom.adapter.fooAdapter");
your.custom.adapter.fooAdapter = Class.create(
pimcore.bundle.backendpowertools.alternativeelementtree.configuration.adapter.abstract, {
type: 'FooAdapter',
setFields: function () {
this.fields = [
this.getTextField()
];
return this.fields;
},
getTextField: function () {
return new Ext.form.TextField({
name: 'placeholder',
fieldLabel: "Placeholder",
value: this.config.placeholder ?? null
});
}
});
your.custom.adapter.fooAdapter.type = 'FooAdapter'; //reflects the "frontend" adapter in the config
Register the Class via an Event
After creating the class itself, you must register it via the pimcore.backendpowertools.adt.registerAdtAdapter event.
...
initialize: function () {
document.addEventListener(pimcore.bundle.backendpowertools.events.registerAetAdapter, this.registerAdapters.bind(this));
},
registerAdapters: function (event) {
const adapters = event.detail.adapters;
adapters.push(your.custom.adapter.fooAdapter);
...
Add the Adapter to the Mapping
The last step is to add the adapter to the mapping. This can be done in the config.yaml of your bundle.
Be aware that the string provided to frontend has to match the type of your adapter.
pimcore_backend_power_tools:
pimcore_alternative_object_trees:
adapters:
input: #= the datatype of the corresponding class field
frontend: FooAdapter
backend: SimpleValueAdapter