Skip to content

Commit 04f35ee

Browse files
committed
Field layout elements!
1 parent 1c7681c commit 04f35ee

File tree

3 files changed

+358
-9
lines changed

3 files changed

+358
-9
lines changed

docs/.vuepress/sets/craft-cms.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ module.exports = {
149149
"element-types",
150150
"element-exporter-types",
151151
"field-types",
152+
"field-layout-element-types",
152153
"filesystem-types",
153154
"widget-types",
154155
]

docs/5.x/extend/element-types.md

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -354,14 +354,7 @@ Event::on(
354354

355355
## Fields + Content
356356

357-
Add a static `hasContent()` method to your element class to give them their own rows in the `content` table. This is required to support [titles](#titles) and [custom fields](#field-layouts).
358-
359-
```php
360-
public static function hasContent(): bool
361-
{
362-
return true;
363-
}
364-
```
357+
Every element comes with automatic support for custom fields and multi-site content.
365358

366359
### Titles
367360

@@ -418,7 +411,9 @@ Read about managing [multiple field layouts](#variations-multiple-field-layouts)
418411

419412
#### Native Layout Elements
420413

421-
Native element properties (like our Product’s `price`) can be made editable via the [sidebar](#edit-screen), or as customizable field layout elements. Craft has some pre-packaged field layout elements (like <craft5:craft\fieldlayoutelements\TitleField>), but you are responsible for configuring others—either with the existing classes, or by implementing new layout elements:
414+
<See path="field-layout-element-types.md" />
415+
416+
Native element properties (like our Product’s `price`) can be made editable via the [sidebar](#edit-screen), or as customizable [field layout elements](field-layout-elements.md). Craft has some pre-packaged field layout elements (like <craft5:craft\fieldlayoutelements\TitleField>), but you are responsible for configuring others—either with the existing classes, or by implementing new layout elements:
422417

423418
```php
424419
use craft\models\FieldLayout;
Lines changed: 353 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
---
2+
description: Create composable editing and UI components for field layouts.
3+
sidebarDepth: 2
4+
related:
5+
- uri: ../system/fields.md
6+
label: Fields + Field Layouts
7+
- uri: ../system/elements.md
8+
label: Introduction to Elements
9+
- uri: element-types.md
10+
label: Element Types
11+
- uri: field-types.md
12+
label: Field Types
13+
---
14+
15+
# Field Layout Elements
16+
17+
In addition to content fields, developers can add a handful of other built-in _field layout elements_ to their [field layouts](../system/fields.md#field-layouts) to optimize the author’s attention or productivity. Craft uses field layout elements for titles, some [address](../reference/element-types/addresses.md) components, alt text on [assets](../reference/element-types/assets.md), native [user](../reference/element-types/users.md) attributes, and more. They also power “UI” elements like horizontal rules, breaks, and templates.
18+
19+
Plugins can supplement the built-in field layout elements by creating and registering a class that extends <craft5:craft\base\FieldLayoutElement>. Broadly speaking, field layout elements come in two flavors:
20+
21+
- **Fields** — Features that enhance (or are essential to) collecting data for an element. **Title** fields, users’ **Email** field, and Commerce’s **Variants** nested element management interface are examples of fields. Fields typically extend <craft5:craft\fieldlayoutelements\BaseField> (or one of its subclasses).
22+
- **UI** — Optional features that can be added any number of times, and are not part of the critical authoring path. The built-in **Template** and **Tip** components are examples of UI elements, and are based on <craft5:craft\fieldlayoutelements\BaseUiElement> (or one of its subclasses).
23+
24+
All layout elements are rendered in the context of an element, and support user and element conditions in addition to their own custom settings. We’ll look at how you can adopt these behaviors via your class’s [inheritance](#base-definitions), [public properties](#settings-features), and configuration.
25+
26+
::: tip
27+
Plugins that provide field types _do not_ need to maintain their own field layout element! Instead, they should [return HTML appropriate to the editing context](field-types.md#inputs), which will be wrapped in an instance of <craft5:craft\fieldlayoutelements\CustomField>.
28+
:::
29+
30+
Field layout elements are only used to build the body of the element editor. The sidebar (or “metadata” column), header, and toolbar are managed by each [element type](element-types.md), individually, but can be customized using [events](events.md).
31+
32+
## Class + Registration
33+
34+
Create a new class that extends <craft5:craft\base\FieldLayoutElement> (or one of the [other existing types](#base-definitions)). Our example will display some sort of read-only status information from an external source:
35+
36+
```php
37+
namespace myplugin\fieldlayoutelements;
38+
39+
use Craft;
40+
use craft\base\ElementInterface;
41+
use craft\fieldlayoutelements\BaseUiElement;
42+
use craft\helpers\Html;
43+
44+
class SynchronizationStatus extends BaseUiElement
45+
{
46+
public function selectorLabel(): string
47+
{
48+
return Craft::t('site', 'Synchronization Status');
49+
}
50+
51+
public function formHtml(?ElementInterface $element = null, bool $static = false): ?string
52+
{
53+
return Html::tag('div', 'Hello, world!');
54+
}
55+
56+
protected function selectorIcon(): ?string
57+
{
58+
return 'satellite-dish';
59+
}
60+
}
61+
```
62+
63+
This is only the minimum implementation for a _UI_ field layout element. Explore deeper customization options in the [base definitions](#base-definitions) and [settings](#settings) sections.
64+
65+
To register a field layout element, add the appropriate event listener:
66+
67+
- For native fields, use <craft5:craft\models\FieldLayout::EVENT_DEFINE_NATIVE_FIELDS>;
68+
- For UI elements, use <craft5:craft\models\FieldLayout::EVENT_DEFINE_UI_ELEMENTS>;
69+
70+
::: code
71+
```php Fields
72+
use craft\events\DefineFieldLayoutElementsEvent;
73+
use craft\models\FieldLayout;
74+
use yii\base\Event;
75+
76+
Event::on(
77+
FieldLayout::class,
78+
FieldLayout::EVENT_DEFINE_NATIVE_FIELDS,
79+
function(DefineFieldLayoutFieldsEvent $event) {
80+
/** @var FieldLayout $layout */
81+
$layout = $event->sender;
82+
83+
if ($layout->type === MyElementType::class) {
84+
$event->fields[] = MyNativeField::class;
85+
}
86+
}
87+
);
88+
```
89+
```php UI Elements
90+
use craft\events\DefineFieldLayoutElementsEvent;
91+
use craft\models\FieldLayout;
92+
use yii\base\Event;
93+
94+
Event::on(
95+
FieldLayout::class,
96+
FieldLayout::EVENT_DEFINE_UI_ELEMENTS,
97+
function(DefineFieldLayoutFieldsEvent $event) {
98+
$event->elements[] = SynchronizationStatus::class;
99+
}
100+
);
101+
```
102+
:::
103+
104+
In the first example (registering a “native field”), we perform a check to make sure the layout element is only available in layout designers that target a specific element type.
105+
106+
## Base Definitions
107+
108+
Craft includes a handful of abstract types that can be extended to leverage specific features and provide hints as to the layout element’s purpose:
109+
110+
- <craft5:craft\fieldlayoutelements\BaseUiElement> — A static component that typically does not correspond to a specific element property or accept input. Its appearance and output can still
111+
- <craft5:craft\fieldlayoutelements\BaseField> — Common features for native and custom fields, including a label, instructions, tip, warning, whether or not input is “required,” and whether its underlying attribute can be rendered in [element cards](../system/elements.md#chips--cards) or provide [thumbnails](../system/elements.md#chips--cards). HTML inputs that are rendered into layout elements that extend this class are automatically factored in to change tracking, and their values are sent to Craft when saving an element.
112+
- <craft5:craft\fieldlayoutelements\BaseNativeField> — A subclass of `BaseField` that is intended for use with native element attributes (or an attribute provided via a [behavior](behaviors.md)).
113+
- <craft5:craft\fieldlayoutelements\TextField> — General-purpose input element suitable for most scalar values. You can customize many of the underlying HTML element’s attributes via class properties—see below for an example.
114+
115+
::: tip
116+
Somewhat counterintuitively, the <craft5:craft\fieldlayoutelements\CustomField> class is _not_ intended to be extended. Craft automatically wraps each custom field instance with this layout element, exposing
117+
:::
118+
119+
### Ad-Hoc Elements
120+
121+
In some cases, you may not need to create a dedicated class at all! Suppose we were creating a custom element type to manage currencies and exchange rates—the element type might have a property like `$symbol` to hold a three-character ISO code, which we want to let administrators edit alongside other custom fields. Instead of a custom class, we could register a suitable layout element based on the `TextField` class:
122+
123+
```php
124+
namespace myplugin;
125+
126+
use craft\events\DefineFieldLayoutElementsEvent;
127+
use craft\fieldlayoutelements\TextField;
128+
use craft\models\FieldLayout;
129+
use yii\base\Event;
130+
use myplugin\elements\Currency;
131+
132+
Event::on(
133+
FieldLayout::class,
134+
FieldLayout::EVENT_DEFINE_NATIVE_FIELDS,
135+
function(DefineFieldLayoutFieldsEvent $event) {
136+
/** @var FieldLayout $layout */
137+
$layout = $event->sender;
138+
139+
if ($layout->type === Currency::class) {
140+
$event->fields[] = [
141+
'class' => TextField::class,
142+
'type' => 'text',
143+
'title' => Craft::t('my-plugin', 'Currency Symbol'),
144+
'maxlength' => 3,
145+
'placeholder' => 'USD',
146+
'autocorrect' => false,
147+
// ...
148+
];
149+
}
150+
}
151+
);
152+
```
153+
154+
You can register any value compatible with <craft5:Craft::createObject()>, including strings (like the fully-qualified class name in the previous section), a config object (as above), or an already- instantiated object.
155+
156+
## Settings + Features
157+
158+
For a comprehensive list of methods and properties you can use to customize a field layout element’s behavior, refer to the classes that extend <craft5:craft\base\FieldLayoutElement>. The most important divide in feature sets is between _UI Elements_ (classes that extend <craft5:craft\fieldlayoutelements\BaseUiElement>) and _Fields_ (classes that extend <craft5:craft\fieldlayoutelements\BaseField>). Features available to one or the other are noted.
159+
160+
### Cosmetic
161+
162+
These settings exist primarily to improve the usability and accessibility of fields and UI elements in field layouts.
163+
164+
#### Selector Label
165+
166+
The label displayed when viewing a field layout element in the selector palette, and when it appears within a field layout designer.
167+
168+
```php
169+
protected function selectorLabel(): string
170+
```
171+
172+
#### Icon
173+
174+
An icon that helps identify layout elements of the same type. In mosts cases, this return value should be consistent—but Craft makes use of it to bubble up [dynamic icons](craft5:craft\fieldlayoutelements\CustomField::selectorIcon()) from custom fields.
175+
176+
```php
177+
protected function selectorIcon(): ?string
178+
```
179+
180+
#### Width
181+
182+
By default, field layout elements are full-width. Opt-in to customizable width using `hasCustomWidth()`:
183+
184+
```php
185+
public function hasCustomWidth(): bool
186+
{
187+
return true;
188+
}
189+
```
190+
191+
#### Indicators (Fields)
192+
193+
To make field layout elements more useful at-a-glance, they support icon-based “indicators.” Craft handles many built-in features (like fields marked as _Required_, or elements with configured conditions), but you can supplement them with your own indicators.
194+
195+
```php
196+
protected function selectorIndicators(): array
197+
{
198+
// Preserve built-in indicators:
199+
$indicators = parent::selectorIndicators();
200+
201+
if ($this->someCustomSetting) {
202+
$indicators[] = [
203+
'label' => Craft::t('my-plugin', 'This field layout element may behave differently!'),
204+
'icon' => 'triangle-exclamation',
205+
'iconColor' => 'fuchsia',
206+
];
207+
}
208+
209+
return $indicators;
210+
}
211+
```
212+
213+
#### Label and Instructions (Fields)
214+
215+
Bind assistive text to a native field input.
216+
217+
```php
218+
public ?string $label = null;
219+
public ?string $instructions = null;
220+
```
221+
222+
Override the `defaultLabel()` and `defaultInstructions()` methods instead, if you wish to allow users to customize labels and instructions—or to provide translatable defaults.
223+
224+
#### Tips + Warnings (Fields)
225+
226+
In addition to [labels and instructions](#label-and-instructions-fields), fields can provide a `tip` and `warning` box below their markup.
227+
228+
```php
229+
public ?string $tip = null;
230+
public ?string $warning = null;
231+
```
232+
233+
These strings should be plain text or Markdown; HTML is encoded before parsing.
234+
235+
### Functional
236+
237+
These settings govern the actual behavior and configurability of your field layout element, throughout the system.
238+
239+
#### Multi-instance
240+
241+
Whether your field layout element can be added to a layout more than once. Typically, native fields _do not_ support this, as they represent an underlying element property. Most UI elements _do_, except in cases where multiple instances would be redundant or confusing. Consider that semi-dynamic UI elements may be rendered at different times—say, in response to a new tab becoming visible—and therefore show different information.
242+
243+
```php
244+
public function isMultiInstance(): bool
245+
{
246+
return true;
247+
}
248+
```
249+
250+
#### Settings Support
251+
252+
If your field layout element has aspects that should be configurable on a per-instance basis, you should return `true` from `hasSettings()`:
253+
254+
```php
255+
public function hasSettings()
256+
{
257+
return true;
258+
}
259+
```
260+
261+
It is then your responsibility to render additional settings HTML:
262+
263+
```php
264+
protected function settingsHtml(): ?string
265+
{
266+
return Cp::lightswitchFieldHtml([
267+
'label' => Craft::t('my-plugin', 'Refresh automatically?'),
268+
'instructions' => Craft::t('my-plugin', 'Periodically refresh the sync status while the element editor is open and in the foreground.'),
269+
'id' => 'autoRefresh',
270+
'name' => 'autoRefresh',
271+
'on' => $this->autoRefresh,
272+
]);
273+
}
274+
```
275+
276+
This markup is prepended to any default settings, like the user and element condition builders. Craft hides the **Settings** action for a field layout element if it doesn’t support [conditions](#conditions) and doesn’t explicitly return `true` from `hasSettings()`.
277+
278+
#### Conditions
279+
280+
All field layout elements support conditions, by default. Opt-out by returning `false` from a `conditional()` method:
281+
282+
```php
283+
protected function conditional(): bool
284+
{
285+
return false;
286+
}
287+
```
288+
289+
#### Mandatory (Fields)
290+
291+
You can force a field layout element to be present in any field layout it is supported in by marking it as “mandatory:”
292+
293+
```php
294+
public function mandatory(): bool
295+
{
296+
return false;
297+
}
298+
```
299+
300+
If a mandatory field layout element has not been explicitly added to a field layout via a field layout designer, Craft injects it at the bottom of the first tab (when rendering the layout) to ensure it is available to authors.
301+
302+
#### Requirable (Fields)
303+
304+
When added to a layout, you can include a **Make required/optional** action (along with a corresponding [indicator](#indicators-fields)). The state of this selection is available when rendering a field layout element as the `required` property, and can be used in your `inputHtml()` method.
305+
306+
#### Thumbnail + Card Support (Fields)
307+
308+
Field layout elements also underpin Craft’s handling of [element chips and cards](../system/elements.md#chips-cards).
309+
310+
To make your field layout element available for selection as element thumbnails, implement the `thumbable()` and `thumbHtml()` methods:
311+
312+
```php
313+
public function thumbable(): bool
314+
{
315+
return true;
316+
}
317+
318+
public function thumbHtml(ElementInterface $element, int $size): ?string
319+
{
320+
$value = $element->{$this->attribute()};
321+
322+
return Html::tag('div', Cp::fallbackIconSvg($value), ['class' => 'cp-icon']);
323+
}
324+
```
325+
326+
Any other kind of value can be exposed to element cards by returning `true` from a `previewable()` method:
327+
328+
```php
329+
public function previewable(): bool
330+
{
331+
return true;
332+
}
333+
```
334+
335+
You must then implement `previewHtml()` to return a representation of your data. By default, Craft will attempt to access a property of the element corresponding to your field layout element’s `attribute`.
336+
337+
```php
338+
public function previewHtml(ElementInterface $element): string
339+
{
340+
return Html::tag('code', Html::encode($element->{$this->attribute()}));
341+
}
342+
```
343+
344+
#### Fieldset (Fields)
345+
346+
Wrap the input HTML in a `fieldset`, and use special handling for labels, instructions, and errors.
347+
348+
```php
349+
protected function useFieldset(): bool
350+
{
351+
return true;
352+
}
353+
```

0 commit comments

Comments
 (0)