Introducing DropBlockEditor

by Jeffrey van Rossum

About a month ago, I tweeted that I was working on drag and drop editor for Laravel and showed a little demo video.

Last week, I released version 0.1.0 (pre release) of what is now called DropBlockEditor. In this article I would like to tell you more about it and give you an idea of how you could use it yourself.

DropBlockEditor demo screenshot

Why another editor?

There are quite some editors around, so why build a new one - you might ask. There are a couple of reasons.

Newsletter templates

The editor is mainly created to work well for newsletter templates. To create more advanced newsletter templates, one might use tools like MJML. MJML is a framework that provides you with clean HTML-like syntax, but generates the complex HTML and CSS needed for email clients. And it's responsive.

That being said, you can also just use regular HTML. And using the editor is not necessarily limited to newsletters.

The TALL-stack

I love working with Livewire, Alpine.js and Tailwind CSS. I really liked the idea of being able to create custom blocks for an editor, with the power of Livewire at my disposal.

How to use the editor

It is quite easy to setup. You install the package in your Laravel app and in a blade view, you render the editor through a Livewire component.

@livewire('dropblockeditor', [
    'title' => 'Your example campaign',
])

If you already use Alpine in your app, the editor should already be working. If not, simply add Alpine.

By default, an example block is added to the editor - as well as an example save button (topbar on the right).

Both of course can be removed through the config/dropblockeditor.php file. This is also where you can register your own blocks and buttons.

Alternatively, you can also define those, by passing them to the component.

The base html that you drop your blocks in, can be set by either providing the name of a blade template or a string.

@livewire('dropblockeditor', [
    'title' => 'Your example campaign',
  	'blocks' => [
        Jeffreyvr\DropBlockEditor\Blocks\Example::class,
    ],
  	'buttons' => [
        'example-button'
    ],
  	'base' => 'your-base-blade-template'
])

How blocks work

A block has a visual part (which is the Block class) and an optional modified Livewire component (BlockEditComponent) to make it editable.

Let's say we are creating a very simple article block. This might look like this:

namespace App\DropBlockEditor\Blocks;

use Jeffreyvr\DropBlockEditor\Blocks\Block;

class Article extends Block
{
    public $title = 'Article';

    public $data = [
        'title' => 'Hi!',
        'content' => 'This is just a very basic example of an article block.'
    ];

    public $blockEditComponent = 'article';

    public function render()
    {
        return <<<'blade'
        <mj-section background-color="#ffffff">
            <mj-column width="400px">
                <mj-text font-size="20px"
                        font-family="Figtree"
                        color="#141B33">{{ $title }}</mj-text>

                <mj-text color="#141B33" font-size="16px" line-height="26px" font-family="Figtree, Arial">
                    {{ $content }}
                </mj-text>
            </mj-column>
        </mj-section>
        blade;
    }
}

A block has a title, contains data, references the Livewire component that will be used to edit the block - and of course has a render method.

Notice I'm using MJML here? If you want to use MJML too, check the documentation on how to set it up.

So we have the block, how do we now make it editable? In comes the (slightly modified) Livewire component:

namespace App\Http\Livewire;

use Jeffreyvr\DropBlockEditor\Components\BlockEditComponent;

class Article extends BlockEditComponent
{
    public function render()
    {
        return <<<'blade'
        <div class="space-y-4">
            <div>
                <label for="title" class="mb-1">Title</label>
                <input type="text" id="title" wire:model.debounce.500ms="data.title" class="w-full border border-gray-200 px-3 py-1 rounded-md">
            </div>
            <div wire:ignore>
                <label for="content" class="mb-1">Content</label>
                <textarea id="content" wire:model.debounce.500ms="data.content" class="w-full border border-gray-200 px-3 py-1 rounded-md"></textarea>
            </div>
        </div>
        blade;
    }
}

The BlockEditComponent already knows about the data attribute. This is needed to make sure your block receives it's content. Other then that, this is really just a basic run of the mill Livewire component :)

The package has a handy artisan comment to generate blocks and the Livewire component:

php artisan dropblockeditor:make Article --with-edit-component

Adding a save button

Of course when you're working in the editor, at one point, you would probably want to save your work.

Earlier in the article, I already mentioned how you can register buttons. But now let's see how we actually make one.

namespace App\Http\Livewire;

use Livewire\Component;

class SaveButton extends Component
{
	public $properties;

    protected $listeners = [
        'editorIsUpdated' => 'editorIsUpdated',
    ];

    public function editorIsUpdated($properties)
    {
        $this->properties = $properties;
    }

    public function save()
    {
        // Example of getting a json string of the active blocks.
        $activeBlocks = collect($this->properties['activeBlocks'])
        	->toJson();

        // If you want to generate the output, you can do:
        $output = Parse::execute([
             'activeBlocks' => $this->properties['activeBlocks'],
             'base' => $this->properties['base'],
             'context' => 'rendered',
             'parsers' => $this->properties['parsers'],
    	]);
    }

    public function render()
    {
        return <<<'blade'
            <div>
                <button wire:click="save" class="bg-blue-200 text-blue-900 rounded px-3 py-1 text-sm">Save</button>
            </div>
        blade;
    }
}

A button is a basic Livewire component. If you want to store the output of the editor, either rendered or as JSON - you need to add the properties attribute and the editorIsUpdated listener.

In the example code, you'll see how you can generate the JSON code and/or the rendered output. It's then up to you where you want to store it.

To make the editor render existing content, you can pass the an array of the resulting JSON as activeBlocks:

@livewire('dropblockeditor', [
    'title' => 'Your example campaign',
   'activeBlocks' => json_decode($json, true)
])

What about inline editing?

To be able to use the desired stack and allow for rendering in MJML, I made the choice not to add support for inline editing. All editing of a block happens in the sidebar on the right. This is quite common for page builder editors, though most allow for some form of minor inline editing, like a headline or body text. I did spend some time on getting that to work, but it felt hacky the payoff was to high. It did not work great and made the code more complicated. For my use case, it wasn't the way to go. Though I'd of course be open to suggestions on how we could achieve it without these drawbacks.

MJML's performance

In this article I have mentioned MJML a couple of times. It's really a great tool to generate newsletter code. However, wheter you use their API or use it through Node - the performance isn't great.

Locally it works pretty fase, but on the most basic Digital Ocean Droplet, the rendering can take around a second. If that doesn't sounds slow, it feels a little slow when you are rendering a lot.

Alternatively, you can pre render the MJML and just use the resulting HTML in your blocks. I'm thinking of adding some form of automatic pre rendering in the future.

Mailcoach

I've also been working on a package to use the editor inside of Mailcoach. Mailcoach is Spatie's cool newsletter package. I will probably write an article about that in the near future, but you can already check out it's documentation and try it out.

In closing

As the package is in pre-release, please note that I'm still working on it and current updates might break backwards compatibility. Feel free to test and try it out, but it's good to be aware of that :)

If you are interested in trying out the editor, I recommend you to take a look at the documentation. If you find bugs or have suggestions, feel free to shoot me a message on Twitter or contribute on GitHub.

Comments

Talk about this article on X.
Did you like this post?

If you sign up for my newsletter, I can keep you up to date on more posts like this when they are published.