Objectives:
template
fieldVersions used at the time of writing:
Version | |
---|---|
PHP | 8.0 |
Laravel | 8.x |
1php 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.
We'll add the template
field to the generated migration:
File:
database/migrations/2021_09_19_131244_create_articles_tables.php
1<?php 2 3use Illuminate\Database\Migrations\Migration; 4use Illuminate\Database\Schema\Blueprint; 5 6return new class extends Migration 7{ 8 public function up() 9 {10 Schema::create('articles', function (Blueprint $table) {11 createDefaultTableFields($table);12 13 $table->string('title', 200)->nullable();14 $table->text('description')->nullable();15 $table->integer('position')->unsigned()->nullable();16 $table->text('template')->nullable();17 });18 19 Schema::create('article_slugs', function (Blueprint $table) {20 createDefaultSlugsTableFields($table, 'article');21 });22 23 Schema::create('article_revisions', function (Blueprint $table) {24 createDefaultRevisionsTableFields($table, 'article');25 });26 }27 28 public function down()29 {30 Schema::dropIfExists('article_revisions');31 Schema::dropIfExists('article_slugs');32 Schema::dropIfExists('articles');33 }34};
Then, we'll run the migrations:
1php artisan migrate
and add the module to our routes/admin.php
and config/twill-navigation.php
.
In this example, we imagine 3 templates that our authors can choose from:
We'll start by adding our new field to the fillables:
File:
app/Models/Article.php
1protected $fillable = [2 'published',3 'title',4 'description',5 'position',6 'template',7];
Then, we'll define some constants for our template options:
File:
app/Models/Article.php
1public const DEFAULT_TEMPLATE = 'full_article'; 2 3public const AVAILABLE_TEMPLATES = [ 4 [ 5 'value' => 'full_article', 6 'label' => 'Full Article', 7 'block_selection' => ['article-header', 'article-paragraph', 'article-references'], 8 ], 9 [10 'value' => 'linked_article',11 'label' => 'Linked Article',12 'block_selection' => ['article-header', 'linked-article'],13 ],14 [15 'value' => 'empty',16 'label' => 'Empty',17 'block_selection' => [],18 ],19];
We'll add an attribute accessor to get the template name for the currently selected template value:
File:
app/Models/Article.php
1public function getTemplateLabelAttribute()2{3 $template = collect(static::AVAILABLE_TEMPLATES)->firstWhere('value', $this->template);4 5 return $template['label'] ?? '';6}
This will be useful in our create.blade.php
view below.
template
field to the create modalWhen 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:
File:
resources/views/admin/articles/create.blade.php
1<x-twill::input 2 :name="$titleFormKey ?? 'title'" 3 :label="$titleFormKey === 'title' ? twillTrans('twill::lang.modal.title-field') : ucfirst($titleFormKey)" 4 :required="true" 5 on-change="formatPermalink" 6/> 7 8@if ($item->template ?? false) 9 {{--10 On update, we show the selected template in a disabled field.11 For simplicity, templates cannot be modified once an item has been created.12 --}}13 <x-twill::input14 name="template_label"15 label="Template"16 :disabled="true"17 />18@else19 {{--20 On create, we show a select field with all possible templates.21 --}}22 <x-twill::select23 name="template"24 label="Template"25 :default="\App\Models\Article::DEFAULT_TEMPLATE"26 :options="\App\Models\Article::AVAILABLE_TEMPLATES"27 />28@endif29 30@if ($permalink ?? true)31 <x-twill::input32 name="slug"33 :label="twillTrans('twill::lang.modal.permalink-field')"34 ref="permalink"35 :prefix="$permalinkPrefix ?? ''"36 />37@endif
1php artisan twill:make:block article-header2php artisan twill:make:block article-paragraph3php artisan twill:make:block article-references4php artisan twill:make:block linked-article
File:
resources/views/twill/blocks/article-header.blade.php
1@twillBlockTitle('Article Header') 2@twillBlockIcon('text') 3@twillBlockGroup('app') 4 5<x-twill::input 6 name="subtitle" 7 label="Subtitle" 8/> 9 10<x-twill::input11 name="author"12 label="Author"13/>14 15<x-twill::input16 name="reading_time"17 label="Estimated Reading Time"18/>
File:
resources/views/twill/blocks/article-paragraph.blade.php
1@twillBlockTitle('Article Paragraph') 2@twillBlockIcon('text') 3@twillBlockGroup('app') 4 5<x-twill::wysiwyg 6 name="text" 7 label="Text" 8 placeholder="Text" 9 :toolbar-options="['bold', 'italic', 'link', 'clean']"10/>
File:
resources/views/twill/blocks/article-references.blade.php
1@twillBlockTitle('Article References') 2@twillBlockIcon('text') 3@twillBlockGroup('app') 4 5<x-twill::wysiwyg 6 name="text" 7 label="Text" 8 placeholder="Text" 9 :toolbar-options="['bold', 'italic', 'link', 'clean']"10/>
File:
resources/views/twill/blocks/linked-post.blade.php
1@twillBlockTitle('Linked Article') 2@twillBlockIcon('text') 3@twillBlockGroup('app') 4 5<x-twill::input 6 name="title" 7 label="Article title" 8/> 9 10<x-twill::input11 name="description"12 label="Article link"13 type="textarea"14 :rows="4"15/>16 17<x-twill::input18 name="url"19 label="Article URL"20/>
We'll add the block editor field to our form:
File:
resources/views/admin/articles/form.blade.php
1@extends('twill::layouts.form') 2 3@section('contentFields') 4 <x-twill::input 5 name="description" 6 label="Description" 7 :maxlength="100" 8 /> 9 10 <x-twill::block-editor 11 :blocks="\App\Models\Article::AVAILABLE_BLOCKS"12 />13@stop
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:
File:
app/Models/Article.php
1<?php 2 3namespace App\Models; 4 5use A17\Twill\Models\Behaviors\HasBlocks;
6use A17\Twill\Models\Behaviors\HasFiles; 7use A17\Twill\Models\Behaviors\HasMedias; 8use A17\Twill\Models\Behaviors\HasPosition; 9use A17\Twill\Models\Behaviors\HasRevisions;10use A17\Twill\Models\Behaviors\HasSlug;11use A17\Twill\Models\Behaviors\Sortable;12use A17\Twill\Models\Model;13use A17\Twill\Repositories\BlockRepository;14 15class Article extends Model implements Sortable16{17 use HasBlocks;
18 use HasSlug;19 use HasMedias;20 use HasFiles;21 use HasRevisions;22 use HasPosition;23 24 // #region constants25 public const DEFAULT_TEMPLATE = 'full_article';26 27 public const AVAILABLE_TEMPLATES = [28 [29 'value' => 'full_article',30 'label' => 'Full Article',31 'block_selection' => ['article-header', 'article-paragraph', 'article-references'],32 ],33 [34 'value' => 'linked_article',35 'label' => 'Linked Article',36 'block_selection' => ['article-header', 'linked-article'],37 ],38 [39 'value' => 'empty',40 'label' => 'Empty',41 'block_selection' => [],42 ],43 ];44 45 // #endregion constants46 47 public const AVAILABLE_BLOCKS = ['article-header', 'article-paragraph', 'article-references', 'linked-article'];48 49 // #region fillable50 protected $fillable = [
51 'published',52 'title',53 'description',54 'position',55 'template',56 ];57 58 // #endregion fillable59 60 public $slugAttributes = [
61 'title',62 ];63 64 // #region accessor65 public function getTemplateLabelAttribute()
66 {67 $template = collect(static::AVAILABLE_TEMPLATES)->firstWhere('value', $this->template);68 69 return $template['label'] ?? '';70 }71 72 // #endregion accessor73 74 // #region prefill75 public function getTemplateBlockSelectionAttribute()76 {77 $template = collect(static::AVAILABLE_TEMPLATES)->firstWhere('value', $this->template);78 79 return $template['block_selection'] ?? [];80 }81 82 public function prefillBlockSelection()83 {84 $i = 1;85 86 foreach ($this->template_block_selection as $blockType) {87 app(BlockRepository::class)->create([88 'blockable_id' => $this->id,89 'blockable_type' => static::class,90 'position' => $i++,91 'content' => '{}',92 'type' => $blockType,93 ]);94 }95 }96 97 // #endregion prefill98}
Then, we'll hook into the repository's afterSave()
:
File:
app/Repositories/ArticleRepository.php
1<?php 2 3namespace App\Repositories; 4 5use A17\Twill\Repositories\Behaviors\HandleBlocks;
6use A17\Twill\Repositories\Behaviors\HandleSlugs; 7use A17\Twill\Repositories\Behaviors\HandleMedias; 8use A17\Twill\Repositories\Behaviors\HandleFiles; 9use A17\Twill\Repositories\Behaviors\HandleRevisions;10use A17\Twill\Repositories\ModuleRepository;11use App\Models\Article;12 13class ArticleRepository extends ModuleRepository14{15 use HandleBlocks, HandleSlugs, HandleMedias, HandleFiles, HandleRevisions;16 17 public function __construct(Article $model)
18 {19 $this->model = $model;20 }21 22 public function afterSave($model, $fields)23 {24 parent::afterSave($model, $fields);25 26 if ($model->wasRecentlyCreated) {27 $model->prefillBlockSelection();28 }29 }30}
The check on $object->wasRecentlyCreated
ensures the prefill operation will only run when the record is first created.
And there we have it, a templating mechanism for our block editor: