Prefill a block editor from a selection of templates
Objectives:
- Create a new module with a
template
field - Prefill the block editor for new items according to the selected template
Requirements:
Versions used at the time of writing:
Version | |
---|---|
PHP | 8.0 |
Laravel | 8.61 |
Twill | 2.5.2 |
Create the new module
php artisan twill:make:module articles -B
We'll make sure to enable blocks on the module, everything else is optional. In this example, we won't be using translations, but they can be added with minor changes.
Update the migration
We'll add the template
field to the generated migration:
// update file: database/migrations/xxxx_xx_xx_xxxxxx_create_articles_tables.php
public function up()
{
Schema::create('articles', function (Blueprint $table) {
// ...
$table->text('template')->nullable();
});
}
Then, we'll run the migrations:
php artisan migrate
and add the module to our routes/admin.php
and config/twill-navigation.php
.
Update the model
In this example, we imagine 3 templates that our authors can choose from:
- Full Article — an original article on our blog
- Linked Article — a short article to summarize and share interesting articles from other blogs
- Empty — a blank canvas
We'll start by adding our new field to the fillables:
// update file: app/Models/Article.php
class Article extends Model
{
protected $fillable = [
// ...
'template',
];
}
Then, we'll define some constants for our template options:
// update file: app/Models/Article.php
const DEFAULT_TEMPLATE = 'full_article';
const AVAILABLE_TEMPLATES = [
[
'value' => 'full_article',
'label' => 'Full Article',
],
[
'value' => 'linked_article',
'label' => 'Linked Article',
],
[
'value' => 'empty',
'label' => 'Empty',
],
];
We'll add an attribute accessor to get the template name for the currently selected template value:
// update file: app/Models/Article.php
public function getTemplateLabelAttribute()
{
$template = collect(static::AVAILABLE_TEMPLATES)
->firstWhere('value', $this->template);
return $template['label'] ?? '';
}
This will be useful in our create.blade.php
view below.
template
field to the create modal
Add the When running php artisan twill:make:module
, we get a form.blade.php
to define the main form for our module. In addition, it's also possible to redefine the fields that are displayed in the create modal, before the form:
We'll copy Twill's built-in view from vendor/area17/twill/views/partials/create.blade.php
into our project, then add our template
field:
// create file: resources/views/admin/articles/create.blade.php
@formField('input', [
'name' => $titleFormKey ?? 'title',
'label' => $titleFormKey === 'title' ?
twillTrans('twill::lang.modal.title-field') : ucfirst($titleFormKey),
'required' => true,
'onChange' => 'formatPermalink'
])
@if ($item->template ?? false)
{{--
On update, we show the selected template in a disabled field.
For simplicity, templates cannot be modified once an item has been created.
--}}
@formField('input', [
'name' => 'template_label',
'label' => 'Template',
'disabled' => true,
])
@else
{{--
On create, we show a select field with all possible templates.
--}}
@formField('select', [
'name' => 'template',
'label' => 'Template',
'default' => \App\Models\Article::DEFAULT_TEMPLATE,
'options' => \App\Models\Article::AVAILABLE_TEMPLATES,
])
@endif
@if ($permalink ?? true)
@formField('input', [
'name' => 'slug',
'label' => twillTrans('twill::lang.modal.permalink-field'),
'translated' => true, //Twill 2.6.0 return [Object objet] on slug field without this as true
'ref' => 'permalink',
'prefix' => $permalinkPrefix ?? ''
])
@endif
Create some blocks
php artisan twill:make:block article-header
php artisan twill:make:block article-paragraph
php artisan twill:make:block article-references
php artisan twill:make:block linked-article
You'll find some example views for each block at the end of this recipe.
Define the block selection for each template
We'll update our AVAILABLE_TEMPLATES
with the list of blocks that should be prefilled for each template, then add the AVAILABLE_BLOCKS
for our form:
// update file: app/Models/Article.php
const DEFAULT_TEMPLATE = 'full_article';
const AVAILABLE_TEMPLATES = [
[
'value' => 'full_article',
'label' => 'Full Article',
'block_selection' => ['article-header', 'article-paragraph', 'article-references'],
],
[
'value' => 'linked_article',
'label' => 'Linked Article',
'block_selection' => ['article-header', 'linked-article'],
],
[
'value' => 'empty',
'label' => 'Empty',
'block_selection' => [],
],
];
const AVAILABLE_BLOCKS = [
'article-header', 'article-paragraph', 'article-references', 'linked-article'
];
Then, we'll add the block editor field to our form:
// update file: resources/views/admin/articles/form.blade.php
@formField('block_editor', [
'blocks' => \App\Models\Article::AVAILABLE_BLOCKS,
])
Prefill the blocks on create
With this, all that's needed is to initialize the block editor from the selected template. We'll update our model to add the prefill operation:
// update file: app/Models/Article.php
public function getTemplateBlockSelectionAttribute()
{
$template = collect(static::AVAILABLE_TEMPLATES)
->firstWhere('value', $this->template);
return $template['block_selection'] ?? [];
}
public function prefillBlockSelection()
{
$i = 1;
foreach ($this->template_block_selection as $blockType) {
app(\A17\Twill\Repositories\BlockRepository)->create([
'blockable_id' => $this->id,
'blockable_type' => static::class,
'position' => $i++,
'content' => '{}',
'type' => $blockType,
]);
}
}
Then, we'll hook into the repository's afterSave()
:
// update file: app/Repositories/ArticleRepository.php
class ArticleRepository extends ModuleRepository
{
// ...
public function afterSave($object, $fields)
{
parent::afterSave($object, $fields);
if ($object->wasRecentlyCreated) {
$object->prefillBlockSelection();
}
}
}
The check on $object->wasRecentlyCreated
ensures the prefill operation will only run when the record is first created.
Finished result
And there we have it — a templating mechanism for our block editor:
Thanks for reading and have fun :)