Can I create virtual pages from a structure field?

I have collected around 100 quotations in a structure field. These are all displayed on one page.
Now I would like to make each quote clickable and open the quote in a new page, e.g. to send it via a link. Can I create virtual pages from a structure field, or is there another solution?

Yes, it doesn’t matter what you convert into virtual pages.

The alternative would be a route (e.g. 'quotations/(:any)').

I do that with the blogroll on my site. I have a short howto post how I do this. If you want, I can send you the link (I don’t want to spam the forum without asking).

Guess this one: Blogroll - Maurice Renck

1 Like

I am already very close to the goal, but the generated link is faulty and does not pass the slug: https://mywebsite/zitate/

<a href="<?= url('zitat/' . $item->zitatSlug()->value()) ?>">

I have never worked with virtual pages before and I am not yet completely familiar with routes. That’s why I can’t find the error.

[
'pattern' => 'zitat/(:any)',
'action'  => function ($slug) {
		$page = page('zitate');
		$zitate = $page->zitate()->toStructure()->filter(function($z) use ($zitat) {
								return Str::slug($z->zitat()->value()) === $zitat;
						}); 

		if ($zitate->count() > 0) {
				$zitat = $zitate->first();

				$content = [
						'slug' => $slug,
						'template' => 'zitat',
						'content' => [
								'zitat' => $zitat->zitat()->value(),
								'title' => $zitat->zitatPerson()->value(),
						]
				];
				return Page::factory($content);
		} else {
				return site()->visit('error');
		}
}
]

Blueprint:

zitate:
  label: Zitate
  type: structure
  fields:
    zitat:
      label: Zitat
      type: textarea
      buttons: false
      autofocus: true
    zitatPerson:
      label: Person
      type: text

Thank you! I found the link, too. But you have still integrated multilanguage, which I don’t need and therefore confuses me a bit in the realization. :face_with_peeking_eye:

Perhaps it is also easier to output the ID as a slug. Because each quote has a consecutive number: id="<?= $item->id() ?> That would perhaps simplify things. I do not need the quote as a slug

Result:

https://mywebsite/zitate/1
https://mywebsite/zitate/2
https://mywebsite/zitate/3
…

This variable is not defined, you need to use $slug!

:partying_face: Perfect! That works.

How can I shorten the slug to 20 than 128 characters, for example, as the complete quote is currently displayed in the link:


https://mywebsite/zitat/die-tatsache-dass-menschen-mit-zwei-augen-aber-nur-einem-mund-geboren-werden-laesst-darauf-schliessen-dass-sie-zweimal-so-viel-s

$slug = Str::substr(Str::slug($slug), 0, 20);
doesn’t work

<a href="<?= url('zitat/' . Str::slug($item->zitat()->value())) ?>" >

[
'pattern' => 'zitat/(:any)',
'action'  => function ($slug) {
		$page = page('zitate');
		$zitate = $page->zitate()->toStructure()->filter(function($z) use ($slug) {
								return Str::slug($z->zitat()->value()) === $slug;
						}); 

		if ($zitate->count() > 0) {
				$zitat = $zitate->first();

				$content = [
						'slug' => $slug,
						'template' => 'zitat',
						'content' => [
								'zitat' => $zitat->zitat()->value(),
								'title' => $zitat->zitatPerson()->value(),
						]
				];
				return Page::factory($content);
		} else {
				return site()->visit('error');
		}
}
]

Now I have tried and tested for (too) long, but without a solution.

I have these questions:

  • Where do I insert: Str::substr() to shorten the slug? I have tried several possibilities, but without success.

  • How do I get the virtual pages to be displayed as subpages?
    This is desirable:
    www.mywebsite.com/zitate/was-du-heute-kannst-besorgen…
    This is current:
    www.mywebsite.com/zitat/was-du-heute-kannst-besorgen…

  • Then I saw that the title contains the slug, as with hyphen instead of space and with converted umlauts etc.

I’ve read the guide about routes and virtual pages a few times, but I haven’t got a 100% understanding yet :face_with_spiral_eyes:

When you generate your url and then again when comparing your strings while filtering.

Now I understand it.
I had assumed that this only had to be defined once in the route.
It works with this code. Thank you!

<a href="<?= url('zitat/' . Str::substr(Str::slug($item->zitat()->value()), 0, 20)) ?>">

[
'pattern' => 'zitat/(:any)',
'action'  => function ($slug) {
$page = page('zitate');
$zitate = $page->zitate()->toStructure()->filter(function($z) use ($slug) {
		return Str::substr(Str::slug($z->zitat()->value()), 0, 20) === $slug;
}); 

if ($zitate->count() > 0) {
		$zitat = $zitate->first();
		$slug = Str::substr($slug, 0, 20);

		$content = [
				'slug' => $slug,
				'template' => 'zitat',
				'content' => [
						'zitat' => $zitat->zitat()->value(),
						'person' => $zitat->zitatPerson()->value(),
				]
		];
		return Page::factory($content);
} else {
		return site()->visit('error');
}
}
]

],

How do I assign a blueprint to a virtual page? There are no physical pages that can be edited in the panel. I aim to define settings for the background and font color. All other templates and header/footer snippets also refer to this. Of course, I have a template for the virtual page, but there are also queries here that get their information from the blueprint.

= blueprint zitat

But this only works if you define them as children in your parent model, otherwise they won’t appear as children in the Panel, because Kirby won’t know about pages defined in routes.

I had to compromise on the template and invest some manual labour.
But I’m happy with it this way. Here is a “virtual” quote

In the navigation, the active “Zitate” menu is no longer highlighted in red color when a virtual page is active. This is because only children are considered:

<a <?php e($child->isOpen(), ' class="active"') ?> href="<?= $child->url() ?>">

How do I get the “Zitate” menu to be displayed actively?

You could add an additional condition, if child is open or current url string contains $child->url()

I have now discarded the idea of virtual pages and made each quote into a child.
I had the following problems with the virtual pages:

  • no assignment option for a blueprint to define individual options (font colour, background colour, etc.)
  • the slug was used as the page title and I couldn’t find out where to change it
  • virtual pages do not appear in the sitemap, which is bad for Google

I think virtual pages are great for a photo gallery or for external data. Unfortunately, it wasn’t the right solution for my case. But thanks to your help @texnixe I have :student: learnt something new and can apply the knowledge to future decisions.

Should probably stress that there are basically three types of virtual pages, as outlines in the guide:

  1. simple route, no matter what you return to it, this does not become part of the site index.
  2. Virtual page registered via the pages extension, same problem as 1.
  3. virtual pages defined as children in a model: they become part of the index, and you can deal with them basically like pages in the file system, although CRUD operations require additional work, as you basically have to overwrite everything.
  4. Mix of file system and information from other sources.
1 Like