Boards & Summary Templates
Boards are a way to aggregate content on your site.
Boards offer a dynamic way to group chronological content on the front-end of your site. Think of boards like page lists, express lists or calendar event lists – but with the ability to switch between different content form factors, to aggregate different types of content together in the same list, and more.
Before moving into Boards and how to develop custom Summary templates and manage these templates in Concrete, we will strongly encourage you to walk through with our Boards Editor’s documentation if you haven’t gone through with it as yet.
Using Summary Templates to Display Content
Summary templates let you choose how content is displayed. They disconnect content from specific Block types, allowing various blocks to use the same template. For instance, a single summary template can be utilized by both a page list and other blocks.
In the Atomik theme, installing the theme also sets up summary templates. The XML below showcases how to define these when adding your theme:
<summarytemplates>
<template handle="blog_image_left" name="Blog Image Left" icon="blank.png" package="">
<categories>
<category handle="page"/>
</categories>
<fields>
<field required="1">title</field>
<field required="1">link</field>
<field required="1">thumbnail</field>
<field required="1">date</field>
</fields>
</template>
</summarytemplates>
After this XML is processed, a new summary template named "Blog Image Left" becomes available for pages that have a title, link, thumbnail, and date.
Categories in Summary
Every summary template can link to multiple categories. The default categories in Concrete version 9 are:
- Page
- Calendar Event
They determine which type of content the template relates to.
Summary Fields
Concrete includes built-in fields, defined during installation. These are in:
/path/to/install_directory/concrete/config/install/base/summary.xml
The sample fields:
<summaryfields>
<field handle="title" name="Title" package=""/>
<field handle="date" name="Date" package=""/>
<field handle="date_start" name="Start Date" package=""/>
<field handle="date_end" name="End Date" package=""/>
<field handle="link" name="Link" package=""/>
<field handle="description" name="Description" package=""/>
<field handle="thumbnail" name="Thumbnail" package=""/>
<field handle="categories" name="Categories" package=""/>
<field handle="author" name="Author" package=""/>
</summaryfields>
These
When setting up your summary template, you can specify which fields are essential.
Template Application
After a template is defined in the system, it can be used across all linked categories, like calendar events or pages. This flexibility means a template might show content from both a calendar event and a page.
Here's a blog_entry_card
template sample from Atomik Theme:
<div class="ccm-summary-template-blog-entry-thumbnail">
<a href="<?=$link?>" class="card">
<div class="position-relative">
<div class="card-img-top ccm-summary-template-blog-entry-thumbnail-image-overlay"></div>
<img class="card-img-top" src="<?=$thumbnail->getThumbnailURL('blog_entry_thumbnail')?>">
</div>
<div class="card-body">
<h5 class="card-title"><?=$title?></h5>
<?php if ($date) { ?>
<p class="card-text text-center"><small class="text-muted"><?=date('F d, Y', (string) $date)?></small></p>
<?php } ?>
</div>
</a>
</div>
Note: For any existing content when a new summary template is added, run the reindex content task to update the database with the new templates.
Board Templates
When making a board, you pick a template for it. Board templates, like the summary ones, are added via XML. Here's an example using the Atomik theme:
<boardtemplates>
<template handle="blog" name="Blog"
icon="blank.png" package="">
</template>
</boardtemplates>
After adding the Atomik content, a "Blog" template is ready for use.
Driver for the Board
Board templates differ from summary templates because they require a driver class. The driver class tells the system about the board's slots and how content fills them. The Atomik theme has its driver setup, but if you're making your own, you might use code like this:
use Concrete\Core\Board\Template\Driver\Manager as BoardTemplateManager;
use Foo\Bar\Board\Template\Driver\FooBarDriver;
...
$boardTemplateManager = $this->app->make(BoardTemplateManager::class);
$boardTemplateManager->extend('foo_bar', function() {
return $this->app->make(FooBarDriver::class);
});
This makes sure a template with the handle foo_bar
uses FooBarDriver
as its driver.
Template File Details
Let's look at a board template from the Atomik theme:
<?php defined('C5_EXECUTE') or die("Access Denied.");?>
<div class="container ccm-board-blog">
<div class="row pb-4 mb-4">
<div class="col-md-12 blog-featured-post">
<?php
$slot->display(1);
?>
<hr class="d-md-none d-block mb-0">
</div>
</div>
<div class="row gx-8">
<div class="col-md-8">
<?php
if ($slot->hasContents(2)) {
?>
<div class="row">
<div class="col-md-12">
<?php
$slot->display(2);
?>
</div>
</div>
<?php } ?>
<?php
if ($slot->hasContents(3)) {
?>
<div class="row">
<div class="col-md-12">
<hr class="d-none d-md-block">
<?php
$slot->display(3);
?>
</div>
</div>
<?php } ?>
<?php
if ($slot->hasContents(4)) {
?>
<div class="row">
<div class="col-md-12">
<hr class="d-none d-md-block">
<?php
$slot->display(4);
?>
</div>
</div>
<?php } ?>
<?php
if ($slot->hasContents(5)) {
?>
<div class="row">
<div class="col-md-12">
<hr class="d-none d-md-block">
<?php
$slot->display(5);
?>
</div>
</div>
<?php } ?>
</div>
<div class="col-md-4 col-blog-sidebar mt-3 mt-md-0">
<?php
$stack = Stack::getByName('Blog Sidebar');
if ($stack) {
$stack->display();
}
?>
</div>
</div>
</div>
This board template is standard HTML & PHP with styling from Bootstrap 5. The only unique part for boards is the $slot
object, which interacts with the content. The board driver and this file work in tandem to show your board's content.
Simple Board Slot Templates
For every board, there are spaces called slots, marked like $slot->display(1)
. These slots hold content that follows a Slot Template.
Board slot templates are added with XML. Here's an example from the Atomik theme:
<boardslottemplates>
<template handle="blog_image_left" name="Blog Image Left" icon="blank.png">
</template>
...
</boardslottemplates>
Slot Template Drivers
Just like board templates, slot templates need a driver class. The Atomik theme sets up the "Blog Image Left" slot template with its driver. But if you're making your own, you might do:
use Concrete\Core\Board\Template\Slot\Driver\Manager as BoardSlotTemplateManager;
...
$boardSlotTemplateManager = $this->app->make(BoardSlotTemplateManager::class);
$boardSlotTemplateManager->extend('foo_bar_slot', function() {
return $this->app->make(FooBarSlotDriver::class);
});
This makes sure the "Foo Bar Slot" slot template uses FooBarSlotDriver
.
What's the job of these slot drivers? They tell the system about the board's content spaces and decide how content fits into them.
Slot Template File Examples
Look at these sample slot template files:
Blog Image Left
Location: concrete/themes/atomik/elements/boards/slots/blog_image_left.php
$slot->display(1);
Not much, right? Sometimes slot templates just show a single content piece.
Blog Two Up
<div class="row">
<div class="col-md-6">
<?php $slot->display(1); ?>
</div>
<div class="col-md-6">
<?php $slot->display(2); ?>
</div>
</div>
This one has two slots, shown as columns.
Blog Three Up
<div class="row">
<div class="col-md-4"><?php $slot->display(1); ?></div>
...
</div>
This one has three slots.
To sum up: Boards use templates to arrange content. Sometimes, you might see content lined up one after the other. Other times, they might be side by side in columns.Boards are meant to be dynamic and flexible – but are guaranteed to always look great.
Blog Template Driver
How do boards consistently maintain such a polished appearance? It's all thanks to driver objects, which dictate the layout of board content. Examine the configuration of Atomik's Blog board:
The Concrete\Core\Board\Template\Driver\BlogDriver
class informs Concrete that it itself contains 5 slots:
public function getTotalSlots(): int
{
return 5;
}
Additionally, it controls the display of slot number 1 in a very particular way, by implementing the getLayoutPlanner
method. This is an optional method available to the blog driver class.
public function getLayoutPlanner(): ?PlannerInterface
{
return new SlotLayoutPlanner([
'1' => ['blog_image_left']
]);
}
The SlotLayoutPlanner
class is a simple class meant to tie a particular slot in a board template to a particular board slot template. What does this mean? It means that no matter how many times you regenerate the Atomik blog board, you'll aways get a left-image content element in the first slot, because the only slot template that layout planner will allow to be placed within the slot 1 is the blog_image_left
slot template.
So let's look at the blog_image_left
slot template driver, found at Concrete\Core\Board\Template\Slot\Driver\BlogImageLeftDriver
. It informs the system that it contains one content slot:
public function getTotalContentSlots(): int
{
return 1;
}
And it also informs the system that within its single content slow, only the content objects with the blog_image_left
summary templates may be placed. This is through the use of the slot filterer object:
public function getSlotFilterer(): ?FiltererInterface
{
$filterer = new SummaryObjectFilterer();
$filterer->registerSlot(1, [
'blog_image_left',
]);
return $filterer;
}
This is similar to the board planner object, but it works with summary templates.
Let's contrast this with the Concrete\Core\Boared|Template\Slot\Driver\BlogThreeUpDriver
class. When the slot template blog_three_up
is placed within a board template slot, **three* content objects will be placed within its content slots:
public function getTotalContentSlots(): int
{
return 3;
}
Additionally, not just any summary templates may be used for these content objects – only the blog_entry_card
summary template will be used for the items in these content slots, because that's what the slot filterer decrees:
public function getSlotFilterer(): ?FiltererInterface
{
$filterer = new SummaryObjectFilterer();
$filterer->registerSlot(1, [
'blog_entry_card',
]);
$filterer->registerSlot(2, [
'blog_entry_card',
]);
$filterer->registerSlot(3, [
'blog_entry_card',
]);
return $filterer;
}
When you get down to the details of the blog_entry_card
template, it's relatively straightforward. From concrete/themes/atomik/elements/summary/templates/blog_entry_card.php
:
<?php defined('C5_EXECUTE') or die("Access Denied."); ?>
<div class="ccm-summary-template-blog-entry-thumbnail">
<a href="<?=$link?>" class="card">
<div class="position-relative">
<div class="card-img-top ccm-summary-template-blog-entry-thumbnail-image-overlay"></div>
<img class="card-img-top" src="<?=$thumbnail->getThumbnailURL('blog_entry_thumbnail')?>">
</div>
<div class="card-body">
<h5 class="card-title"><?=$title?></h5>
<?php if ($date) { ?>
<p class="card-text text-center"><small class="text-muted"><?=date('F d, Y', (string) $date)?></small></p>
<?php } ?>
</div>
</a>
</div>