Hi,
This might be a complex one, but I will explain as simply as I can.
I am using Kirby’s blocks as a page builder. I have one block called “Artist Profiles”, which contains a structure field called “artists”. The fields in this structure are an image, an artist name and a writer field for a profile of the artist.
On a page where this block is used, I would like to output markup a bit like this for each artist:
<a href="https://website.com/page/{artist-name-as-url}-artist">
Artist Name
</a>
And to have the sub-pages be created as virtual pages. I would like these to use a defined template (e.g. artist-profile.php
), and display the information entered into the structure field.
I have set up the structure, but am unsure what my next step should be to get this wired up. If anyone has any pointers that would be super, or if an alternative approach would be more appropriate
Thanks as always
Mike
I’d set up a route with a pattern the desired pattern and then return the page according to this guide example: Simple virtual page | Kirby CMS
With the diffference that you first have to fetch the correct data from the block in the given page.
But I think your URL should also contain the id of the block to easily find the data.
Hi @texnixe thanks for the pointer - I have made some progress with this. I have changed the url for the links to:
<a href="<?= $page->url() . '/artist/' . $block->id() . '/' . Str::slug($artist->name()) ?>">
Artist Name
</a>
And added this route to my config.php:
[
'pattern' => '(:all)/artist/(:any)/(:any)',
'action' => function () {
return Page::factory([
'slug' => 'artist-page',
'template' => 'artist-page'
]);
}
]
And created the artist-page.php
template. I am now not sure how to set the slug correctly (so it reflects that the virtual page sits as a child to the page the structure is on), or how to access the data from the structure to populate the page. Would you be able to offer any advice on this?
The slug would be the artist name, so the second (:any)
Note that you have to pass the variable as params to the closure
[
'pattern' => '(:all)/artist/(:any)/(:any)',
'action' => function ($parent, $id, $slug) {
$parent = page($parent);
$content = []; // fetch the content from the block of the given page
return Page::factory([
'slug' => $slug,
'parent' => $parent,
'template' => 'artist-page',
'content' => $content,
]);
}
]
Hi,
Thanks for all the help, I am getting closer. I now have this:
[
'pattern' => '(:all)/artist/(:any)/(:any)/(:any)',
'action' => function ($parent, $id, $itemId, $slug) {
$parent = page($parent);
$content = page($parent)->pageBuilder()->toBlocks()->find($id)->artists()->toArray();
return Page::factory([
'slug' => $slug,
'parent' => $parent,
'template' => 'artist-page',
'content' => $content,
]);
}
]
Which is sending an array of the artists
structure from the correct blocks field to the new page. However I would like to pull out the specific entry for the artist that has been selected within that structure. To that end I have added $itemId
to the pattern, which is the id
of the item from the structure. I am now not sure how to use this to just pick out that specific artist from the structure. Is there a way to do this?
Yes, of course. What does $content give you if you do a dump($content)
?
I get the following:
Array
(
[artists] => Array
(
[0] => Array
(
[image] => Array
(
[0] => alex-vann-1.jpg
)
[name] => Alex Vann
[description] => Graphic Designer, Artist and Musician
)
[1] => Array
(
[image] => Array
(
[0] => ana-maria-lines.jpg
)
[name] => Ana Maria Lines
[description] => Storyteller
)
[2] => Array
(
[image] => Array
(
[0] => ann-walker.jpg
)
[name] => Ann Walker
[description] => Fine Artist
)
)
)
Instead of turning this whole thing into an array, I’d find the item:
$content = page($parent)->pageBuilder()->toBlocks()->find($id)->artists()->findBy('name', ...);
$itemId
: What do you mean, the structure items don’t have an id, it seems?
Thanks, I think I understand.
$item->id()
was giving me the index of the structure item, which I thought I could use. But I can skip that if I can use the artist name. However I only have it in a slugified form, is there a way I can work with this?
This is how I am constructing the URL currently:
<?php $artistUrl = $page->url() . '/artist/' . $block->id() . '/' . $item->id() . '/'. Str::slug($item->name()); ?>
Sorry for all the questions, I really appreciate the help
Unfortunately, $findBy()
doesn’t accept a callback, so we have to use filterBy()
and then get the first item. This should work (but better check what $content now gives you):
$item = page($parent)->pageBuilder()->toBlocks()->find($id)->artists()->filter(function($item) {
return Str::slug($item->name()->value()) === $slug;
})->first();
$content = $item ? $item->toArray() : [];
I am still getting the same array as previously (all the artists) from my dump($content)
. This is my route now:
[
'pattern' => '(:all)/artist/(:any)/(:any)',
'action' => function ($parent, $id, $slug) {
$parent = page($parent);
$item = page($parent)->pageBuilder()->toBlocks()->find($id)->artists()->filter(function($item) {
return Str::slug($item->name()->value()) === $slug;
})->first();
$content = $item ? $item->toArray() : [];
dump($content);
return Page::factory([
'slug' => $slug,
'parent' => $parent,
'template' => 'artist-page',
'content' => $content,
]);
}
]
The dump looks like this:
Array
(
[artists] => Array
(
[0] => Array
(
[image] => Array
(
[0] => alex-vann-1.jpg
)
[name] => Alex Vann
[description] => Graphic Designer, Artist and Musician
)
[1] => Array
(
[image] => Array
(
[0] => ana-maria-lines.jpg
)
[name] => Ana Maria Lines
[description] => Storyteller
)
[2] => Array
(
[image] => Array
(
[0] => ann-walker.jpg
)
[name] => Ann Walker
[description] => Fine Artist
)
)
)
Could you send me a excerpt from the project as zip via PM, please? I think I need to see it…
Posted the solution on Discord, but just for completeness if anyone comes across this:
[
'pattern' => '(:all)/artist/(:any)/(:any)',
'action' => function ($parent, $id, $slug) {
$parent = page($parent);
$item = page($parent)->pageBuilder()->toBlocks()->find($id)->artists()->toStructure()->filter(function($item) use($slug){
return Str::slug($item->name()->value()) === $slug;
})->first();
$content = $item ? $item->toArray() : [];
return Page::factory([
'slug' => $slug,
'parent' => $parent,
'template' => 'artist-page',
'content' => $content,
]);
}
]
],
1 Like
I have implemented this, and it is working perfectly for creating the pages and pulling the information through.
My issue now is images used in the structure entries for the artists - they don’t work on the virtual pages, I guess because they are associated with the parent page. What would be the best approach for pulling these through to be used on the virtual page as well?
Hm, since you have access to the parent page inside the route, you might as well get the image from there.