Using Laravel Livewire to render dynamic Gutenberg blocks

Livewire is a framework for Laravel that enables developers to create interactive web content without using JavaScript. WordPress has a very easy to use method of content creation and these things can be used together. The demo below shows the block editor being used for content creation, and Livewire is updating the view on the left every 250 milliseconds to refresh the content. The vast majority of this is done only in PHP, we just needed a tiny bit of JS to help the block editor. #UseLessJS

The first thing we need is for Laravel to be able to access the WordPress database to refresh the content. Corcel is providing this interface to Eloquent.

<?php

namespace App\Http\Livewire;

use Livewire\Component;
use Corcel\Model\Post;

class Blocks extends Component
{
    public function render()
    {
        // Just use the first post
        $post = Post::find(1);
    }
}

Next we need to parse the blocks in the post, and we can import WP_Block_Parser to do this:

<?php

namespace App\Http\Livewire;

use Corcel\Model\Post;
use Livewire\Component;
use WP_Block_Parser;

class Blocks extends Component
{
    public $blocks;
    public $title;

    public function render()
    {
        $post = Post::find(1);

        $parser = new WP_Block_Parser();
        $this->title = $post->post_title;
        $this->blocks = $parser->parse($post->post_content);

        return view('livewire.blocks');
    }
}

Now we have the blocks in a structure, and they can be rendered on the frontend using a Livewire component that updates its content every 250ms. For each block we use a blade component to render the actual HTML of the block:

<div class="entry-content" wire:poll.250ms>
    @foreach ($blocks as $block)
        @include('components.block', ['block'=>$block])
    @endforeach
</div>

We’re rendering different kinds of blocks differently, and we want to be able to handle nested blocks. The space between blocks can contain “empty” blocks, these are handled with the null case. Columns and groups need wrappers and their innerBlocks then rendered with the same block component. The Button is rendered with a Livewire component, which just stores the number of times you clicked on it:

@switch($block['blockName'])
    @case(null)
    @break

    @case('core/columns')
    <div class="wp-block-columns">
        @foreach ($block['innerBlocks'] as $innerBlock)
            @include('components.block', ['block' => $innerBlock])
        @endforeach
    </div>
    @break

    @case('core/buttons')
    @livewire('block-button', ['block' => $block], key($block['attrs']['blockId']))
    @break

    @case('core/group')
    @case('core/column')
    @foreach ($block['innerBlocks'] as $innerBlock)
        <div class="wp-block-column">
        @include('components.block', ['block'=>$innerBlock])
        </div>
    @endforeach
    @break

    @default
    <div wire:key="{{$block['attrs']['blockId']}}">
        {!! $block['innerHTML'] !!}
    </div>
    @break
@endswitch

The tricky bit, which is hinted at above, is that Livewire can only keep track of which block is which using a key. Otherwise if you swap the blocks around it doesn’t really know which one has changed. WordPress doesn’t key the blocks, they’re just stored in (effectively) a standard array. To help Livewire we need the block editor to attach a unique key to each block when it’s created. This very-quickly-written code does this by attaching a Math.random() key to each block.

wp.domReady(function() {
  wp.hooks.addFilter(
    "blocks.registerBlockType",
    "wcll/blockidattr",
    function(settings) {
        settings.attributes = _objectSpread({}, settings.attributes, {
          blockId: {
            type: "string",
            default: ''
          }
        });

      return settings;
    }
  );
  wp.hooks.addFilter(
    "editor.BlockEdit",
    "wcll/blockidInput",
    wp.compose.createHigherOrderComponent(function(BlockEdit) {
      return function(props) {
          return React.createElement(
            wp.element.Fragment,
            null,
            React.createElement(BlockEdit, props),
              React.createElement(
                wp.blockEditor.InspectorControls,
                null,
                React.createElement(
                  wp.components.PanelBody,
                  {
                    title: "Block ID Plugin"
                  },
                  React.createElement(wp.components.TextControl, {
                    label: "Block ID",
                    value: props.attributes.blockId,
                    onChange: function onChange(nextId) {
                      return props.setAttributes({
                        blockId: nextId
                      });
                    }
                  })
                )
              )
          );

        return React.createElement(BlockEdit, props);
      };
    }, "withBlockIdInput")
  );

  wp.hooks.addFilter("blocks.getSaveElement", "wcll/blockid", function(
    element,
    block,
    attributes
  ) {
  attributes.blockId = attributes.blockId ? attributes.blockId : 'abc'+Math.random();
  return element;
  });
});

This is also how (in the video) we’re able to move a button around in the DOM without Livewire losing track of it.

I don’t really know of a use case for this dynamically updating, interactive web page. Possibly some sort of interactive digital sign? Ideas in the comments!

Comments