Blocks on settings #849
Replies: 8 comments
-
Ok so I find a solution for recursive blocks. I'm overriding the HandleBlock repository behavior like: CLICK ME
yes, even hidden code blocks!<?php
namespace App\Repositories\Behaviors;
use A17\Twill\Models\Behaviors\HasMedias;
use A17\Twill\Repositories\BlockRepository;
trait HandleBlocks
{
public function hydrateHandleBlocks($object, $fields)
{
if ($this->shouldIgnoreFieldBeforeSave('blocks')) {
return;
}
$blocksCollection = collect();
$blocksFromFields = $this->getBlocks($object, $fields);
$blockRepository = app(BlockRepository::class);
$blocksFromFields->each(function ($block, $key) use ($blocksCollection, $blockRepository) {
$newBlock = $blockRepository->createForPreview($block);
$newBlock->id = $key + 1;
$childBlocksCollection = collect();
$block['blocks']->each(function ($childBlock) use ($newBlock, $blocksCollection, $blockRepository, $childBlocksCollection) {
$childBlock['parent_id'] = $newBlock->id;
$newChildBlock = $blockRepository->createForPreview($childBlock);
$blocksCollection->push($newChildBlock);
$childBlocksCollection->push($newChildBlock);
});
$newBlock->setRelation('children', $childBlocksCollection);
$blocksCollection->push($newBlock);
});
$object->setRelation('blocks', $blocksCollection);
return $object;
}
public function afterSaveHandleBlocks($object, $fields)
{
if ($this->shouldIgnoreFieldBeforeSave('blocks')) {
return;
}
$blockRepository = app(BlockRepository::class);
$blockRepository->bulkDelete($object->blocks()->pluck('id')->toArray());
$this->getBlocks($object, $fields)->each(function ($block) use ($object, $blockRepository) {
$blockCreated = $blockRepository->create($block);
$this->afterSaveHandleRecursiveBlocks($object, $blockRepository, $block, $blockCreated->id);
});
}
private function afterSaveHandleRecursiveBlocks($object, $blockRepository, $block, $parent_id) {
$block['blocks']->each(function ($childBlock) use ($object, $parent_id, $blockRepository) {
$childBlock['parent_id'] = $parent_id;
$blockCreated = $blockRepository->create($childBlock);
if ($childBlock['blocks']) {
$childBlockRepository = app(BlockRepository::class);
$this->afterSaveHandleRecursiveBlocks($object, $childBlockRepository, $childBlock, $blockCreated->id);
}
});
}
private function getBlocks($object, $fields)
{
$blocks = collect();
if (isset($fields['blocks']) && is_array($fields['blocks'])) {
foreach ($fields['blocks'] as $index => $block) {
$block = $this->buildBlock($block, $object);
$block['position'] = $index + 1;
$block['blocks'] = $this->getRecursiveBlocks($object, $block);
$blocks->push($block);
}
}
return $blocks;
}
public function getRecursiveBlocks($object, $block) {
$childBlocksList = collect();
foreach ($block['blocks'] as $childKey => $childBlocks) {
foreach ($childBlocks as $index => $childBlock) {
$childBlock = $this->buildBlock($childBlock, $object, true);
$childBlock['child_key'] = $childKey;
$childBlock['position'] = $index + 1;
if ($childBlock['blocks']) {
$childBlock['blocks'] = $this->getRecursiveBlocks($object, $childBlock);
}
$childBlocksList->push($childBlock);
}
}
return $childBlocksList;
}
private function buildBlock($block, $object, $repeater = false)
{
$block['blockable_id'] = $object->id;
$block['blockable_type'] = $object->getMorphClass();
return app(BlockRepository::class)->buildFromCmsArray($block, $repeater);
}
public function getFormFieldsHandleBlocks($object, $fields)
{
$fields['blocks'] = null;
if ($object->has('blocks')) {
$blocksConfig = config('twill.block_editor');
foreach ($object->blocks as $block) {
$isInRepeater = isset($block->parent_id);
$configKey = $isInRepeater ? 'repeaters' : 'blocks';
$blockTypeConfig = $blocksConfig[$configKey][$block->type] ?? null;
if (is_null($blockTypeConfig)) {
continue;
}
$blockItem = [
'id' => $block->id,
'type' => $blockTypeConfig['component'],
'title' => $blockTypeConfig['title'],
'attributes' => $blockTypeConfig['attributes'] ?? [],
];
if ($isInRepeater) {
$fields['blocksRepeaters']["blocks-{$block->parent_id}_{$block->child_key}"][] = $blockItem + [
'max' => $blockTypeConfig['max'],
'trigger' => $blockTypeConfig['trigger'],
];
} else {
$fields['blocks'][] = $blockItem + [
'icon' => $blockTypeConfig['icon'],
];
}
$fields['blocksFields'][] = collect($block['content'])->filter(function ($value, $key) {
return $key !== "browsers";
})->map(function ($value, $key) use ($block) {
return [
'name' => "blocks[$block->id][$key]",
'value' => $value,
];
})->filter()->values()->toArray();
$blockFormFields = app(BlockRepository::class)->getFormFields($block);
$medias = $blockFormFields['medias'];
if ($medias) {
$fields['blocksMedias'][] = collect($medias)->mapWithKeys(function ($value, $key) use ($block) {
return [
"blocks[$block->id][$key]" => $value,
];
})->filter()->toArray();
}
$files = $blockFormFields['files'];
if ($files) {
collect($files)->each(function ($rolesWithFiles, $locale) use (&$fields, $block) {
$fields['blocksFiles'][] = collect($rolesWithFiles)->mapWithKeys(function ($files, $role) use ($locale, $block) {
return [
"blocks[$block->id][$role][$locale]" => $files,
];
})->toArray();
});
}
if (isset($block['content']['browsers'])) {
$fields['blocksBrowsers'][] = $this->getBlockBrowsers($block);
}
}
if ($fields['blocksFields'] ?? false) {
$fields['blocksFields'] = call_user_func_array('array_merge', $fields['blocksFields'] ?? []);
}
if ($fields['blocksMedias'] ?? false) {
$fields['blocksMedias'] = call_user_func_array('array_merge', $fields['blocksMedias'] ?? []);
}
if ($fields['blocksFiles'] ?? false) {
$fields['blocksFiles'] = call_user_func_array('array_merge', $fields['blocksFiles'] ?? []);
}
if ($fields['blocksBrowsers'] ?? false) {
$fields['blocksBrowsers'] = call_user_func_array('array_merge', $fields['blocksBrowsers'] ?? []);
}
}
return $fields;
}
protected function getBlockBrowsers($block)
{
return collect($block['content']['browsers'])->mapWithKeys(function ($ids, $relation) use ($block) {
$relationRepository = $this->getModelRepository($relation);
$relatedItems = $relationRepository->get([], ['id' => $ids], [], -1);
$sortedRelatedItems = array_flip($ids);
foreach ($relatedItems as $item) {
$sortedRelatedItems[$item->id] = $item;
}
$items = collect(array_values($sortedRelatedItems))->filter(function ($value) {
return is_object($value);
})->map(function ($relatedElement) use ($relation) {
return [
'id' => $relatedElement->id,
'name' => $relatedElement->titleInBrowser ?? $relatedElement->title,
'edit' => moduleRoute($relation, config('twill.block_editor.browser_route_prefixes.' . $relation), 'edit', $relatedElement->id),
] + (classHasTrait($relatedElement, HasMedias::class) ? [
'thumbnail' => $relatedElement->defaultCmsImage(['w' => 100, 'h' => 100]),
] : []);
})->toArray();
return [
"blocks[$block->id][$relation]" => $items,
];
})->filter()->toArray();
}
} Maybe it can deserve a PR? |
Beta Was this translation helpful? Give feedback.
-
Hey @bastienrobert, This is an interesting approach. I'm also curious to know if you though about how this would affect the Best. |
Beta Was this translation helpful? Give feedback.
-
Hi @sauron, I'm working on I was working on this for a complex menu for an e-shop website I'm working on for a customer, which needs embedded blocks, something like:
Finally, it's the only thing I had to edit. The front-end is already working well 👌 |
Beta Was this translation helpful? Give feedback.
-
Here is how I finally got my recursive blocks to send them as a JSON response from an external API controller: protected function getOrderedBlocks($blocks, $parent_id = null) {
return array_values(json_decode(json_encode($blocks->where('parent_id', $parent_id)->map(function ($block) use($blocks) {
$children = array_values(json_decode(json_encode($blocks->where('parent_id', $block->id)), true));
foreach ($children as &$child) {
$child['children'] = $this->getOrderedBlocks($blocks, $child['id']);
}
$block->children = $children;
return $block;
})), true));
} |
Beta Was this translation helpful? Give feedback.
-
Hi @bastienrobert, Cool use case! And interesting way of building a menu too. I would tend to generate menus out of the actual content but I can see how what you're building can be useful. Do you plan on attaching those menus items to actual content records? Maybe through a browser field? Regarding the recursive repeaters, wow! I'm curious to see the UI it produces with your setup. We didn't consider going down more levels in design and I'm actually surprised to hear that the frontend seem to already work with more levels down! As for supporting blocks in settings, I'm definitely not against it and it should be fairly straightforward to implement. |
Beta Was this translation helpful? Give feedback.
-
Yep, already did that! My menu links (in the bottom of my tree) are just two fields: a label (translated) and a browser field to link pages. The front-end looks like this (for me, it rocks 😍): Cool news! |
Beta Was this translation helpful? Give feedback.
-
"Embedded blocks" or "nested repeaters" are now supported thanks to #521. Keeping this open and renaming to Blocks on settings only as this is definitely something that should be possible. |
Beta Was this translation helpful? Give feedback.
-
This discussion came up when i was researching how to use the block editor within twill setting. I managed to get it to work myself. It's a bit hacky though. Here is an example (twil 3.3.1):
@twillBlockTitle('Footer')
@twillBlockIcon('text')
@twillBlockGroup('app')
@php
$blocks = [
'image',
'text',
];
@endphp
<h2>Footer Top</h2>
<x-twill::block-editor
:blocks="$blocks"
name="footer_top"
/>
<h2>Footer Middle Columns</h2>
<x-twill::block-editor
:blocks="$blocks"
name="footer_columns"
/>
<h2>Footer Bottom</h2>
<x-twill::block-editor
:blocks="$blocks"
name="footer_bottom"
/>
class PageDisplayController extends Controller
{
public function show(string $slug, PageRepository $pageRepository): View
{
$page = $pageRepository->forSlug($slug);
if (!$page || !$page->published) {
abort(404);
}
$theme_settings = TwillAppSettings::getGroupForName('theme')->getSettingsModel();
$footer = TwillAppSettings::getGroupDataForSectionAndName('theme', 'footer');
$renderFooterBlocks = function($child_key = 'default') use ($footer, $theme_settings) {
$renderer = new BlockRenderer();
foreach($footer->children as $child) {
if ($child->child_key === $child_key) {
$renderer->rootBlocks[] = BlockRenderer::getNestedBlocksForBlock($child, $theme_settings, 'default');
}
}
return $renderer->render();
};
return view('site.page', [
'item' => $page,
'renderFooterBlocks' => $renderFooterBlocks,
]);
}
}
...
<footer class="bg-gray-800">
<div class="max-w-7xl p-6 mx-auto text-white text-center">
<div class="border-b-4 border-red-500 py-4 text-xl">
{!! $renderFooterBlocks('footer_top') !!}
</div>
<div class="flex flex-row justify-around items-center my-4">
{!! $renderFooterBlocks('footer_columns') !!}
</div>
<div class="py-4">
{!! $renderFooterBlocks('footer_bottom') !!}
</div>
</div>
</footer>
...
class AppServiceProvider extends ServiceProvider
{
public function boot()
{
...
TwillAppSettings::registerSettingsGroup(
SettingsGroup::make()->name('theme')->label('Theme')
);
}
} |
Beta Was this translation helpful? Give feedback.
-
I'm coming with 2 questions, I can't find the answer in the documentation:
Beta Was this translation helpful? Give feedback.
All reactions