# 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:

01-create-modal

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:

02-edit-form