Write an Embed for the Editor field
Form's Editor field is a full-featured wysiwyg / markdown field which can contain images, files, and user defined embeds; tou should first look at the Editor field documentation to understand the concept, since here's the details on how to write an Embed class.
How does an Embed work?
First a quick presentation: an embed is not a structured data, meant to be stored in a database or to be represented by a Model. I will be stored in the content of an Editor text, as a custom HTML tag, with attributes. Here's an example of how a RelatedPostEmbed
can be presented in the Editor field:
Here's how it can be edited, in Sharp:
And here's how it could be stored, as a reference:
<x-related-post post=\"25\"></x-related-post><p>She was aware that things could go wrong. [...]</p>
The purpose of this class is to define all this: how to present, edit and store the embed.
Write the class
Required methods
The class must extend Code16\Sharp\Form\Fields\Embeds\SharpFormEditorEmbed
; you'll have to implement at least these two methods:
buildFormFields(FieldsContainer $formFields): void
Here you can declare the form fields of the embed; the API is the same as building a standard Form ( see Building an Entity Form). This form will appear in a modal when the use creates a new embed, or clicks in the edit button of an existing one.
TIP
You can choose to name one (and only one) field slot
: it will be stored as the component content, rather than in an attribute. This could be easier to handle complex data (an Editor HTML text for instance) this way, in the public site, where you can use the standard {{ $slot }}
attribute to display it.
updateContent(array $data = []): array
This method is called on posting the form. Here you should validate the input if needed, and return the data.
public function updateContent(array $data = []): array
{
$this->validate($data, [
'post' => [
'required',
Rule::exists('posts', 'id')
],
]);
return $data;
}
Configure the embed
This is not required, but you should implement buildEmbedConfig(): void
, where you can call:
configureLabel(string $label): self
: to define the name of the embed (should be short)configureTagName(string $tagName): self
: to define the tag name (typically starting withx-
)configureFormInlineTemplate(string $template): self
andconfigureFormTemplatePath(string $templatePath): self
: to define the .vue template for displaying the embed in the Editor field in the formconfigureShowInlineTemplate(string $template): self
andconfigureShowTemplatePath(string $templatePath): self
: same, but for the show page, only you need to have a different version for it.
Here's a complete example:
public function buildEmbedConfig(): void
{
$this
->configureLabel('Related Post')
->configureTagName('x-related-post')
->configureFormInlineTemplate('<div><span v-if="online" style="color: blue">●</span><i v-if="!online" style="color: orange">●</i> <i class="fa fa-link"></i> <em>{{ title }}</em></div>');
}
Templates are vue-based, and work the same as for autocompletes or html form fields. We chose to write it inline here, but we could refer to a .vue
file, with configureFormTemplatePath
instead. The same template will be used in Show page, if the editor text should be displayed there, since we didn't configure anything ( via configureShowInlineTemplate
or configureShowTemplatePath
).
Additional useful methods
Two more methods can be implemented, in case you need more control of data transformation:
transformDataForTemplate(array $data, bool $isForm): array
This method is called before the template rendering. This is where you have a chance to format data for the template, which could even mean to make a DB query, in some cases.
Here's an example, matching the template seen above:
public function transformDataForTemplate(array $data, bool $isForm): array
{
$post = Post::find($data['post']);
return $this
->setCustomTransformer('title', function ($value) use ($post) {
return $post?->title;
})
->setCustomTransformer('online', function ($value) use ($post) {
return $post?->state === 'online';
})
->transformForTemplate($data);
}
The embed data is simply post
, which is an id. So we find the related post, and return attributes needed by the template, leveraging Sharp's transformation API (see how to transform data) — but we could instead directly build and return an array, as always.
WARNING
There is a catch on transformation: instead of simply using ->transform()
, we used ->transformForTemplate()
: although this is not needed in all cases, this will ensure that field formatters are not called, since this could lead to unwanted transformation of the templates.
Notice the $isForm
param, which allows differentiating data depending on the context (Form or Show).
transformDataForFormFields(array $data): array
Similarly, this method is called to transform the data before displaying the form. This can be required in case your form includes fields like autocompletes, or uploads. You can refer once again to the documentation on how to transform data, and this time be sure to use the regular ->transform()
method, since data needs to be formatted for fields.
Configure the fields in Form and Show
At this stage, the only remaining step is to declare the embed in the related fields, meaning:
- in the Form
SharpFormEditorField
, where it would be inserted / edited - and maybe in a Show
SharpShowTextField
, if the embed should be presented in a show page
As this is detailed in documentations of these two fields, the way to achieve this is to call allowEmbeds
:
// In a Form
public function buildFormFields(FieldsContainer $formFields): void
{
$formFields
->addField(
SharpFormEditorField::make('content')
->allowEmbeds([
RelatedPostEmbed::class,
])
);
// [...]
}
// In a Show
protected function buildShowFields(FieldsContainer $showFields): void
{
$showFields
->addField(
SharpShowTextField::make('content')
->allowEmbeds([
RelatedPostEmbed::class,
])
);
// [...]
}
Display the embed in the public section
The embed should be treated like any regular Laravel blade component. Here's an example:
@props([
'post',
])
@if($post = \App\Models\Post::find($post))
<div class="card mb-3">
<div class="card-body">
<h4 class="h6">Related post</h4>
<h3>{{ $post->title }}</h3>
<div class="text-muted">
{{ Str::limit(strip_tags($post->content), 200) }}
</div>
</div>
</div>
@endif