Hi there! I am a seasoned WordPress Developer and am trying to re-create the ACF flexible content field with Kirby’s layout field, to create a simple page builder.
So far, it’s working great except for one thing: I can’t find a way to disallow empty rows in the panel. Basically I’m looking for a way to make filling in at least one block per row/column mandatory:
Ok, an empty layout doesn’t look nice in the Panel, however, in reality it doesn’t matter because you can just ignore it on the frontend. So this is basically a visual issue, I think.
There is no built-in way to enforce adding blocks to a layout. One thing you could do is use a page.update:before hook and check if the fields contains empty layouts and throw an error.
@texnixe Thanks for the hint with page.update:before! I was able to solve it like this in site/config/config.php:
return [
'hooks' => [
'page.update:before' => function (Kirby\Cms\Page $page, array $values) {
// Bail early if this is not a project
if ($page->intendedTemplate()->name() !== 'project') return;
// Bail early if there are no details (the layout field's name)
if (empty($values['details'])) return;
// Parse $details
$details = json_decode($values['details']);
foreach ($details as $row) {
if (allColumnsAreEmpty($row->columns)) throw new Exception('Please delete empty rows');
}
}
]
];
/**
* Checks if all columns in a Layout row are empty
*/
function allColumnsAreEmpty(array $columns): bool
{
foreach ($columns as $column) {
if (count($column->blocks)) return false;
}
return true;
}
I first tried to use Kirby’s API but got lost since I couldn’t find how I could cast $values['details'] to a layout field. Any pointers there? Something like this maybe?
I also first tried validating the field from $page, but it seems like that doesn‘t catch newly added rows. It will run on the current layout field, ignoring any changes that are about to be saved, doesn‘t it?
Is there some extensive documentation on the filterBy method in the docs somewhere? I couldn’t find any. This is what I ended up with instead:
return [
'hooks' => [
'page.update:before' => function (Kirby\Cms\Page $page, array $values) {
// Bail early if this is not a project
if ($page->intendedTemplate()->name() !== 'project') return;
// Bail early if there are no details
if (empty($values['details'])) return;
$field = new \Kirby\Cms\Field($page, 'details', $values['details']);
foreach($field->toLayouts() as $layout) {
if (allColumnsAreEmpty($layout->columns())) throw new Exception('Please delete empty rows');
}
}
]
];
/**
* Checks if all columns in a Layout row are empty
*/
function allColumnsAreEmpty(Kirby\Cms\LayoutColumns $columns): bool
{
foreach ($columns as $column) {
if ($column->blocks()->count() > 0) return false;
}
return true;
}
In my case, I only want to prevent completely empty layouts. So if at least one column contains at least one block, it should pass.
A friend pointed me towards the filter compendium page. Using that, I was able to condense the above code to this:
return [
'hooks' => [
'page.update:before' => function (Kirby\Cms\Page $page, array $values) {
// Bail early if this is not a project
if ($page->intendedTemplate()->name() !== 'project') return;
// Bail early if there are no details
if (empty($values['details'])) return;
$field = new \Kirby\Cms\Field($page, 'details', $values['details']);
if ($field->toLayouts()->filterBy('isEmpty', '==', true)->count() > 0) {
throw new Exception('Please delete empty rows');
}
}
]
];
@texnixe Is the filterBy('isEmpty, true') you suggested some kind of short hand or a typo?