You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/5.x/extend/element-types.md
+63-17Lines changed: 63 additions & 17 deletions
Original file line number
Diff line number
Diff line change
@@ -452,6 +452,8 @@ Event::on(
452
452
453
453
`mandatory` here means that the layout element _must_ be present in the field layout, not that a value is required. Even if a developer has not customized the field layout, Craft will ensure this layout element is added to the first tab. The `TitleField` layout element is mandatory by default, and assumes the title comes from a `title` attribute, so there’s nothing to customize!
454
454
455
+
Field layout elements that map to native attributes on your element should be [declared as “safe”](guide:structure-models#safe-attributes) so that they can be mass-assigned by the `elements/save` action. If you maintain your own controller, you will need to manually assign each attribute (i.e: `$element->price = Craft::$app->getRequest()->getRequiredBody('price'))`).
456
+
455
457
Take a look at the existing [field layout element types](repo:craftcms/cms/blob/5.x/src/fieldlayoutelements) to see which makes the most sense for your attribute. Field layout elements that exist only to provide feedback or information should extend <craft5:craft\fieldlayoutelements\BaseUiElement>.
456
458
457
459
::: tip
@@ -462,7 +464,7 @@ Custom fields are handled for you, automatically—they’ll appear just below y
462
464
463
465
### Saving Custom Field Values
464
466
465
-
When saving values to a custom field, you may use the [`setFieldValue()`](craft5:craft\base\ElementInterface::setFieldValue()) and [`setFieldValues()`](craft5:craft\base\ElementInterface::setFieldValues()) methods or assign directly to a property corresponding to its handle.
467
+
When saving values to a custom field attached to your element, can use any combination of [`setFieldValue()`](craft5:craft\base\ElementInterface::setFieldValue()), [`setFieldValues()`](craft5:craft\base\ElementInterface::setFieldValues()), and direct assignment to a property corresponding to its instance handle.
466
468
467
469
::: code
468
470
```php Single Value
@@ -489,6 +491,8 @@ $product->setFieldValues([
489
491
```
490
492
:::
491
493
494
+
When using Craft’s default `elements/save` controller action, field values are automatically assigned from POST data.
495
+
492
496
#### Validating Required Custom Fields
493
497
494
498
Required custom fields are only enforced when the element is saved using the `live`[validation scenario](guide:structure-models#scenarios). Set the scenario before calling `saveElement()`:
@@ -532,12 +536,17 @@ Elements that support multiple sites will have their `afterSave()` method called
532
536
All that is required to support [element indexes](../system/elements.md#indexes) is a route that points to a template containing this:
533
537
534
538
```twig
539
+
{# This template is provided by Craft: #}
535
540
{% extends '_layouts/elementindex.twig' %}
541
+
542
+
{# Set a page title: #}
536
543
{% set title = 'Products'|t('my-plugin') %}
537
-
{% set elementType = 'ns\\prefix\\elements\\Product' %}
544
+
545
+
{# Let Craft know what element type the index is for: #}
546
+
{% set elementType = 'mynamespace\\elements\\Product' %}
538
547
```
539
548
540
-
To create a new element from this page, define an `actionButton` block:
549
+
To let authors create new elements from this page, define an `actionButton` block:
541
550
542
551
```twig
543
552
{% block actionButton %}
@@ -555,7 +564,7 @@ To create a new element from this page, define an `actionButton` block:
555
564
Some element types may require more information to properly initialize, or will enforce permissions based on initial configuration. Entries, for example, are always created in a particular _section_—and a user may not be [permitted](#permissions) to create them in every section they’re allowed to view or publish in. In these cases, you may need to define extra params in the action URL, or <badgevertical="baseline"type="verb">POST</badge> to your own controller.
556
565
:::
557
566
558
-
Your route should be registered via [`EVENT_REGISTER_CP_URL_RULES`](craft5:craft\web\UrlManager::EVENT_REGISTER_CP_URL_RULES):
567
+
Your index’s route should be registered via [`EVENT_REGISTER_CP_URL_RULES`](craft5:craft\web\UrlManager::EVENT_REGISTER_CP_URL_RULES):
559
568
560
569
```php
561
570
use craft\events\RegisterUrlRulesEvent;
@@ -571,6 +580,10 @@ Event::on(
571
580
);
572
581
```
573
582
583
+
A controller is not necessary, here—the route maps directly to a template.
584
+
585
+
At this point, your element index will be empty. You can skip down to the [Editing Elements](#editing-elements) section to learn more about populating and persisting elements!
586
+
574
587
### Sources
575
588
576
589
Element sources are sets of criteria that form the basis of how users interact with your index and [relation fields](#relation-field). Default sources can be defined by your element type by implementing the protected static [defineSources()](craft5:craft\base\Element::defineSources()) method:
@@ -948,7 +961,11 @@ Craft makes editing elements frictionless by providing turn-key edit screens as
948
961
949
962
To give your elements dedicated edit pages, you must define a route that agrees with their `getCpEditUrl()` method. Collocate this rule with the one that defines your [index](#element-index):
@@ -994,7 +1011,7 @@ If you are interested in rendering context-agnostic views for your element (or o
994
1011
995
1012
### Saving
996
1013
997
-
If your element does not require any special processing, you may be able to use the generic `elements/save` controller action. Craft will use the <badgevertical="baseline"type="verb">POST</badge> body to bulk-assign and typecast [safe attributes](guide:structure-models#safe-attributes) with <craft5:craft\base\Model::setAttributes()>, populate custom fields with <craft5:craft\base\Element::setFieldValues()>, check permissions, validate, and eventually save the element.
1014
+
Most element types can use the generic `elements/save` controller action to persist data. Craft will use the <badgevertical="baseline"type="verb">POST</badge> body to bulk-assign and typecast [safe attributes](guide:structure-models#safe-attributes) with <craft5:craft\base\Model::setAttributes()>, populate custom fields with <craft5:craft\base\Element::setFieldValues()>, check permissions, validate, and eventually save the element.
998
1015
999
1016
Any time you _do_ require some special handling (or need to do more sophisticated authorization than is encapsulated by the element’s [`canSave()`](#permissions) method), you should implement a custom [controller](./controllers.md) action. To programmatically save an element, you will need to do at least the following:
The elements service will in turn call your element’s [`beforeSave()` and `afterSave()` methods](#save-hooks), in which you must persist any additional custom properties.
1051
+
The elements service will in turn call your element’s [`beforeSave()` and `afterSave()` methods](#save-hooks), in which you must persist any native attributes.
1035
1052
1036
1053
::: tip
1037
1054
This process is discussed in greater depth in the [controllers documentation](./controllers.md#model-lifecycle).
The default implementation of these methods in <craft5:craft\base\Element> is typically _restrictive_, meaning users will be _denied_ access by default.
1054
1071
1055
-
Your element should always call the parent method to ensure that events are emitted when a permission check is taking place.
1072
+
Your element should always call the parent method to ensure that [events](events.md) are emitted when a permission check is taking place.
1056
1073
:::
1057
1074
1058
1075
If your element would benefit from a user-manageable permissions structure, you must [register each relevant permission](./user-permissions.md) and check them in the corresponding methods—or as part of custom [controller actions](#saving).
@@ -1063,7 +1080,7 @@ If your element would benefit from a user-manageable permissions structure, you
1063
1080
1064
1081
You can give your element its own relation field by creating a new [field type](field-types.md) that extends <craft5:craft\fields\BaseRelationField>.
1065
1082
1066
-
That base class does most of the grunt work for you, so you can get your field up and running by implementing three simple methods:
1083
+
That base class does most of the grunt work for you, so you can get your field up and running by implementing three methods:
1067
1084
1068
1085
```php
1069
1086
<?php
@@ -1091,11 +1108,13 @@ class Products extends BaseRelationField
1091
1108
}
1092
1109
```
1093
1110
1111
+
Be sure and [register your field type](field-types.md#registering-custom-field-types), so that developers can select it!
1112
+
1094
1113
## Eager-Loading
1095
1114
1096
-
If your element type has its own [relation field](#relation-field), it is already eager-loadable through that. Furthermore, if you have declared support for [content](#fields--content), any elements that are selected as relations via other relation fields will be eager-loadable from your element.
1115
+
If your element type has its own [relation field](#relation-field), it is already eager-loadable through that. Furthermore, if you have declared support for [content](#fields--content), any elements that are selected as relations via other relational fields will be eager-loadable from your element.
1097
1116
1098
-
The only case where eager-loading support is _not_ provided for free is if your element type has any “hard-coded” relations with other elements. For example, entries have authors (users), but those relations are defined in a dedicated `authorId` column in the `entries` table—not the `relations` table.
1117
+
Eager-loading support is _not_ provided automatically for “hard-coded” relations with other elements. For example, entries have authors (user elements), but those relationships are stored in a separate `entries_authors` table.
1099
1118
1100
1119
If your elements maintain this kind of relationship to other elements, make them eager-loadable by adding an `eagerLoadingMap()` method to your element class:
1101
1120
@@ -1137,27 +1156,54 @@ public static function eagerLoadingMap(array $sourceElements, string $handle): a
1137
1156
}
1138
1157
```
1139
1158
1140
-
This function takes an array of already-queried elements (the “source” elements) and an eager-loading handle. It returns a map of which _source_ element IDs should eager-load which _target_ element IDs.
1159
+
This function takes an array of already-queried elements (the “source” elements) and an eager-loading handle. It returns a map of which _source_ element IDs should eager-load which _target_ element IDs. The structure of that array should look something like this:
1160
+
1161
+
```php
1162
+
[
1163
+
'elementType' => User::class,
1164
+
'map' => [
1165
+
// Source: Product ID; Target: User ID
1166
+
['source' => 8735, 'target' => 385],
1167
+
['source' => 7319, 'target' => 1388],
1168
+
['source' => 6684, 'target' => 139],
1169
+
['source' => 6693, 'target' => 139],
1170
+
// ...
1171
+
]
1172
+
]
1173
+
```
1174
+
1175
+
Note that user ID `139` appears twice, for product IDs `6684` and `6693`! That’s to be expected—Craft knows to only load that element _once_, but will make it available on both of those products.
1141
1176
1142
1177
::: tip
1143
-
You may be able to create the source-target map without another database query, if the target IDs have already been loaded along with the elements!
1178
+
You may be able to create the source-target map without another database query, if the target IDs have already been loaded along with the elements! In the example, if products are owned by a single vendor, the vendor ID could be stored an [selected](#element-query-class) such that the map can be built in memory:
1179
+
1180
+
```php
1181
+
$map = array_map(function($src) {
1182
+
return [
1183
+
'source' => $src->id,
1184
+
'target' => $src->vendorId,
1185
+
];
1186
+
}, $sourceElements);
1187
+
```
1144
1188
:::
1145
1189
1146
-
If you need to override where eager-loaded elements are stored, add a `setEagerLoadedElements()` method to your element class as well:
1190
+
To assign eager-loaded elements to a specific attribute (or process the value in some other way), add a `setEagerLoadedElements()` method to your element class:
1147
1191
1148
1192
```php
1149
1193
public function setEagerLoadedElements(string $handle, array $elements): void
1150
1194
{
1151
1195
// The handle can be anything, so long as it matches what is used in `eagerLoadingMap()`:
Otherwise, Craft stashes the results in a generic `_eagerLoadedElements` array by handle, which you must retrieve later. Be sure and handle each eager-loadable native attribute in this method.
0 commit comments