If we want to show single files on a page together with some additional information, we can create a parent page with subpages, and upload a single image to each subpage. However, in the Panel those pages would be pretty empty when the information about the image is actually stored in the image meta data. Wouldn't it be cool and less time consuming if we could just upload a bunch of images to the parent page, fill in the meta data—maybe even automatically add some EXIF data at upload (no, that's not part of this recipe)—,and the child pages would be automagically created from these images?
Thanks to virtual pages in Kirby, we can, and that with little more code than what we would need for the parent/subpage approach. Let's work out how.
- A working Kirby installation. In this example, we use a Starterkit to make use of the existing styles, but you might as well use a Plainkit or your own setup.
- A code editor of your choice
- Optionally, some music that gets you into coding mood
At the end of this recipe we will wrap all this code into a plugin. So if you prefer to start with that structure right from the beginning, you will find the folder structure below.
Let's start with creating a blueprint for our new gallery overview page with a files section to which we can upload the images and assign a files blueprint.
site.yml, we have to adapt the
pages section and add the
gallery template as a new template option. We also change the
create option, so that this part of the blueprint now looks like this:
Now, log in to Panel and create a new gallery page using the
gallery.yml blueprint. Upload some files (if you use the Starterkit, you can borrow somes images from the subpages of the
photography page). Then publish the page.
Before we visit the image gallery on the frontend, we need a new template—
gallery.php—with the following code:
We are ready to view our spectacular little gallery on the frontend.
If you look closely, you will notice that we currently output the images of the page and the
href attribute points to the image URL itself. If you click on any of the links, the browser will open the single file in a new tab or window, but not in the context of a new page. That's not really what we want, and we'll of course fix that later.
So next, let's go tell Kirby that we want these images to be children of the gallery page.
We can fetch child pages of a given page in Kirby with the
children() method, and by default this method returns the subfolders of the given page.
In a Page Model, we can however overwrite this method and thus change what Kirby regards as children. Let's create such a model in the
We loop through the images, store the page properties for each image in the
$images array and pass it to the
Note that we use the file's
name instead of the
filename (i.e. without the extension) for the page slug. This can have certain downsides if you happen to have multiple files with the same filename but a different extension, which also use the same file template. On the other hand, we probably don't want to use the extension in the URL, particularly if we want to provide different file formats, e.g. support for
It's up to you to adapt the slug as fits your use case. If you change the slug, you will have to adapt the image method in the
gallery-image.php page model, though.
If you want to use the unsluggified filename with extension (e.g.
attentions-sharks.jpg), you will need an additional route to prevent Kirby redirecting the URL to the media folder.
With this model in place, we can now modify our
gallery.php template and output the child pages instead of the images. Let's do it!
Instead of looping through the images as before, we now loop through the children (and just output the URL for the moment).
But if we open this page in the browser, all we see is some url strings (or black rectangles if you used the Starterkit) instead of the images we had before. But if we click on one of the links, the new virtual subpage opens with the file's name as title (and otherwise almost empty). We are getting there…
How do we get the images back? We need another page model for the child pages that fetches the correct image from the images of the parent page based on the page slug.
Here we redefine the
image() method and return the image that matches the page slug if no file name is passed to the method. Otherwise, we fall back to the parent method. If necessary, you can modify this code to also filter by file type or extension (e.g. if you use different versions of the same file).
We can now adjust the
gallery.php template again:
And hurray! The images show up on the frontend again.
Let's quickly set up the file blueprint with some fields so that we can add some meta data to the images.
Since we want to access the file meta data as if it was page data, we have to map each image's meta data to the page content object, which we do inside the
children method of the
gallery.php model from above.
With the page model complete, let's set out to create a template for the children pages.
Provided you have filled in some data for each image, you can now lean back and admire the result. Feel free to adjust the styles or the content fields to your liking.
There's only one thing left to do…
At this point, when we update a file's metadata, we cannot preview the resulting page like a normal page. If we click on the
Open button in the file view, we are just redirected to the file. We could now add a pages section to our
gallery.yml together with a blueprint for the subpages and extend the model, but that would only complicate matters without really adding any advantages.
Instead, we can use a
file::url component that changes the file URL depending on the request header. In this case, we want to modify the URL only if the visitor wants a JSON response.
This component might actually interfere with JSON requests from the frontend, so only add this component if it works for you.
/plugins folder, create a folder
virtual-gallery with the following
Done! The virtual pages from files in the parent page are now working exactly like we wanted.
Here is the final code again, this time all nicely tucked up into a plugin.