If you are like me and you make heavy use of snippets the following Component
might prevent you from some headaches.
Most of my Kirby sites use many different content types, the visual representation however is often quite similar. I have a Drupal background and somehow like that you have the change to overwrite a template based on the maschine name of a content type. For example to customize the markup for a custom post type article
you can β in Drupal β overwrite the default template (node.tpl.php
) by copying the file and renaming it to node--article.tpl.php
. I wanted to have something similar in Kirby and since version 2.3 it is possible to implement that behavior with a custom Snippet Component
.
// site/plugins/theme/components/snippet.php
namespace Weidner\Theme\Component;
use A;
use PageAbstract;
use Str;
use Tpl;
use Traversable;
/**
* Custom Snippet Builder Component
*
* @author Daniel Weidner <hallo@danielweidner.de>
* @package Weidner\Theme
* @since 1.0
*/
class Snippet extends \Kirby\Component\Snippet {
/**
* Renders the snippet with the given data.
*
* @param string $name
* @param array $data
* @param boolean $return
* @return string
*/
public function render($name, $data = [], $return = false) {
// Split snippet information
$file = basename($name);
$dir = str::contains($name, '/') ? dirname($name) . DS : '';
// Collect file suggestions based on properties of the page object
$suggestions = [];
if ($item = $this->item($data)) {
$id = $item->id();
$type = $item->intendedTemplate();
$suggestions[] = $dir . $file . '--' . str::slug($id);
$suggestions[] = $dir . $file . '--' . $type;
$suggestions = array_filter(array_unique($suggestions));
}
// Ensure the default snippet file is always the last candidate in the list
$suggestions[] = $name;
// Load the first candidate that actually exists in the siteβs snippet folder
foreach ($suggestions as $suggestion) {
if ($file = $this->kirby->registry->get('snippet', $suggestion)) {
return tpl::load($file, $data, $return);
}
}
// Snippet file not found
return '';
}
/**
* Try to find a page object in the variable scope of the snippet.
*
* @param array $data Collection of snippet variables.
* @return Page|boolean
*/
protected function item($data) {
// Test whether the given item is a page object
$item = a::get($data, 'item');
if ($item instanceof PageAbstract) {
return $item;
}
// Test whether one of the given items is a page object
$items = a::get($data, 'items');
if (is_array($items) || $items instanceof Traversable) {
foreach ($items as $item) {
if ($item instanceof PageAbstract) {
return $item;
}
}
}
return false;
}
}
In order to use the new component you have to register this new Component somewhere in a custom plugin:
// site/plugins/theme/theme.php
load([
'weidner\\theme\\component\\snippet' => __DIR__ . DS . 'components' . DS . 'snippet.php',
]);
$kirby->set('component', 'snippet', 'Weidner\Theme\Component\Snippet');
Did you know, you can pass the snippet
function a page object and it will allow your template file to access it via a variable called $item
? The proposed component uses the same convention. Every time you pass a page object as item
or a page collection as items
the component tries to identify the type of your page objects ($page->indentedTemplate()
) and tries to load a custom snippet for you if it exists. If it does not, it uses the default snippet name as fallback.
Now you can write the following in your template:
// site/templates/default.php
<?php snippet('header'); ?>
<main>
<?php snippet('page', [ 'item' => $page]); // or simply snippet('page', $page) ?>
</main>
<?php snippet('footer'); ?>
If the template specified for your page object is book
and the uri is books/moby-dick
, Kirby tries to load one of the following snippets in the given order:
site/snippets/page--books-moby-dick.php
site/snippets/page--book.php
site/snippets/page.php
Now my different content types can share the same snippet file, but I can decide for each type individually whether I want to use the default or some custom markup . It would be easy to extend the component and include custom field values like a format
field in the hierarchy as well:
// site/plugins/theme/components/snippet.php
// ...
if ($item->format()->exists() && $item->format()->isNotEmpty()) {
$suggestions[] = $suggestions[] = $dir . $file . '--' . str::slug($item->format()->value());
}
// ..
Hope this might be useful for someone else. I would appreciate your feedback.