# 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
Versions used at the time of writing:
Version | |
---|---|
PHP | 8.0 |
Laravel | 8.x |
# 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:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateArticlesTables extends Migration
{
public function up()
{
Schema::create('articles', function (Blueprint $table) {
createDefaultTableFields($table);
$table->string('title', 200)->nullable();
$table->text('description')->nullable();
$table->integer('position')->unsigned()->nullable();
$table->text('template')->nullable();
});
Schema::create('article_slugs', function (Blueprint $table) {
createDefaultSlugsTableFields($table, 'article');
});
Schema::create('article_revisions', function (Blueprint $table) {
createDefaultRevisionsTableFields($table, 'article');
});
}
public function down()
{
Schema::dropIfExists('article_revisions');
Schema::dropIfExists('article_slugs');
Schema::dropIfExists('articles');
}
}
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:
app/Models/Article.php
protected $fillable = [
'published',
'title',
'description',
'position',
'template',
];
Then, we'll define some constants for our template options:
public const DEFAULT_TEMPLATE = 'full_article';
public 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' => [],
],
];
We'll add an attribute accessor to get the template name for the currently selected template value:
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.
# Add the template
field to the create modal
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:
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'),
'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
blocks/article-header.blade.php
@twillBlockTitle('Article Header')
@twillBlockIcon('text')
@twillBlockGroup('app')
@formField('input', [
'name' => 'subtitle',
'label' => 'Subtitle',
])
@formField('input', [
'name' => 'author',
'label' => 'Author',
])
@formField('input', [
'name' => 'reading_time',
'label' => 'Estimated Reading Time',
])
blocks/article-paragraph.blade.php
@twillBlockTitle('Article Paragraph')
@twillBlockIcon('text')
@twillBlockGroup('app')
@formField('wysiwyg', [
'name' => 'text',
'label' => 'Text',
'placeholder' => 'Text',
'toolbarOptions' => ['bold', 'italic', 'link', 'clean'],
])
blocks/article-references.blade.php
@twillBlockTitle('Article References')
@twillBlockIcon('text')
@twillBlockGroup('app')
@formField('wysiwyg', [
'name' => 'text',
'label' => 'Text',
'placeholder' => 'Text',
'toolbarOptions' => ['bold', 'italic', 'link', 'clean'],
])
blocks/linked-post.blade.php
@twillBlockTitle('Linked Article')
@twillBlockIcon('text')
@twillBlockGroup('app')
@formField('input', [
'name' => 'title',
'label' => 'Article Title',
])
@formField('input', [
'name' => 'description',
'label' => 'Article Link',
'type' => 'textarea',
'rows' => 4,
])
@formField('input', [
'name' => 'url',
'label' => 'Article URL',
])
# Add the editor to our form
We'll add the block editor field to our form:
resources/views/admin/articles/form.blade.php
@extends('twill::layouts.form')
@section('contentFields')
@formField('input', [
'name' => 'description',
'label' => 'Description',
'maxlength' => 100
])
@formField('block_editor', [
'blocks' => \App\Models\Article::AVAILABLE_BLOCKS,
])
@stop
# 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:
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(BlockRepository::class)->create([
'blockable_id' => $this->id,
'blockable_type' => static::class,
'position' => $i++,
'content' => '{}',
'type' => $blockType,
]);
}
}
Then, we'll hook into the repository's afterSave()
:
<?php
namespace App\Repositories;
use A17\Twill\Repositories\Behaviors\HandleBlocks;
use A17\Twill\Repositories\Behaviors\HandleSlugs;
use A17\Twill\Repositories\Behaviors\HandleMedias;
use A17\Twill\Repositories\Behaviors\HandleFiles;
use A17\Twill\Repositories\Behaviors\HandleRevisions;
use A17\Twill\Repositories\ModuleRepository;
use App\Models\Article;
class ArticleRepository extends ModuleRepository
{
use HandleBlocks, HandleSlugs, HandleMedias, HandleFiles, HandleRevisions;
public function __construct(Article $model)
{
$this->model = $model;
}
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: