Part 1 of our series about creating a conference website with Joomla core covered everything there is to know about preparing and defining the desired functionality. Part 2 was about the process of submitting and approving Talks. In the last part of the series I will detail the process of creating the conference schedule.
Sorting talks
Following our collection of talks in the ‘Talks’ category, we began planning sessions: four parallel talks (two in English and two in German) for each time slot, running from 9 am to 6 pm on Friday and Saturday, and a slightly shorter schedule for Sunday.. We also planned coffee and lunch breaks.
Each time slot is a category. Here’s a breakdown of the structure:
- Talks
- Friday
- 09:00-09:15
- 09:15-09:45
- …
- Saturday
- 09:00-09:15
- 09:15-09:45
- …
- Sunday
- 09:00-09:15
- 09:15-09:45
- ...
- Friday
To organize the talks effectively, we utilized tags for key aspects such as:
- Rooms (Room 1, Room 2, Room 3, Room 4)
- Keynote presentations
- Online talks
- Language (English, German)
To keep the talks ordered by room we manually ordered the articles in each category.
Building the schedule
We created the schedule using a dedicated Articles Module for each day, with the corresponding category (day) selected and including child categories. In the display options we selected a vertical layout (we will change that later using CSS), with article titles as h4, author, tags and “Read More” link. As mentioned above is the ordering “Article Order” (ascending). The most important part is the grouping: we choose grouping by Category (ascending).
Since we are using Articles module elsewhere, we have created an alternative layout called “schedule”. Each module uses the module class “schedule” and is displayed as html5 style.
All three modules are loaded on an article. The title of the modules are showed as h2, that makes the grouping title automatically to a h3.
The result looks like this:

The title of the modules (e. g. Friday, 16 October) are h2, the groups (categories / time slots) are h3, the titles of the talks (articles) are h4. We have three different kind of slots:
- Generic ones (registration, coffee break, lunch): don’t take place on a specific room and they don’t need to display an author (it is me on all of them ;-) )
- Keynotes: there is only one talk in the main room (Room 1)
- Slots with four parallel sessions
Here is the complete code of the override:
schedule.php
<?php
/**
* @package Joomla.Site
* @subpackage mod_articles
*
* @copyright (C) 2024 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\CMS\Helper\ModuleHelper;
use Joomla\CMS\Language\Text;
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $app->getDocument()->getWebAssetManager();
$wa->registerAndUseStyle('mod_articles', 'mod_articles/mod-articles.css');
if (!$list) {
return;
}
$groupHeading = 'h4';
if ((bool) $module->showtitle) {
$modTitle = $params->get('header_tag');
if ($modTitle == 'h1') {
$groupHeading = 'h2';
} elseif ($modTitle == 'h2') {
$groupHeading = 'h3';
}
}
?>
<?php if ($grouped) : ?>
<?php foreach ($list as $groupName => $items) : ?>
<div class="mod-articles-group">
<<?php echo $groupHeading; ?>><?php echo Text::_($groupName); ?></<?php echo $groupHeading; ?>>
<?php require ModuleHelper::getLayoutPath('mod_articles', $params->get('layout', 'default') . $layoutSuffix); ?>
</div>
<?php endforeach; ?>
<?php else : ?>
<?php $items = $list; ?>
<?php require ModuleHelper::getLayoutPath('mod_articles', 'schedule_items'); ?>
<?php endif;
schedule_items.php
<?php
/**
* @package Joomla.Site
* @subpackage mod_articles
*
* @copyright (C) 2024 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Layout\LayoutHelper;
if ($params->get('articles_layout') == 1) {
$gridCols = 'grid-cols-' . $params->get('layout_columns');
}
$currentDate = Factory::getDate()->format('Y-m-d H:i:s');
?>
<ul class="mod-articles-items<?php echo ($params->get('articles_layout') == 1 ? ' mod-articles-grid ' . $gridCols : ''); ?> mod-list">
<?php foreach ($items as $item) : ?>
<?php
$displayInfo = $item->displayHits || $item->displayAuthorName || $item->displayCategoryTitle || $item->displayDate;
$canEdit = $item->params->get('access-edit');
?>
<li class="card">
<?php if ($params->get('show_tags', 0) && $item->tags->itemTags) : ?>
<div class="mod-articles-tags">
<?php echo LayoutHelper::render('joomla.content.tags', $item->tags->itemTags); ?>
</div>
<?php endif; ?>
<?php if ($params->get('item_title')) : ?>
<?php $item_heading = $params->get('item_heading', 'h4'); ?>
<<?php echo $item_heading; ?> class="mod-articles-title">
<?php if ($params->get('link_titles') == 1) : ?>
<?php $attributes = ['class' => 'mod-articles-link ' . $item->active, 'itemprop' => 'url']; ?>
<?php $link = htmlspecialchars($item->link, ENT_COMPAT, 'UTF-8', false); ?>
<?php $title = htmlspecialchars($item->title, ENT_COMPAT, 'UTF-8', false); ?>
<?php echo HTMLHelper::_('link', $link, $title, $attributes); ?>
<?php else : ?>
<?php echo htmlspecialchars($item->title, ENT_QUOTES, 'UTF-8'); ?>
<?php endif; ?>
</<?php echo $item_heading; ?>>
<?php endif; ?>
<?php if ($item->state === 0) : ?>
<span class="badge bg-warning"><?php echo Text::_('JUNPUBLISHED'); ?></span>
<?php endif; ?>
<?php if ($item->publish_up > $currentDate) : ?>
<span class="badge bg-warning"><?php echo Text::_('JNOTPUBLISHEDYET'); ?></span>
<?php endif; ?>
<?php if ($item->publish_down !== null && $item->publish_down < $currentDate) : ?>
<span class="badge bg-warning"><?php echo Text::_('JEXPIRED'); ?></span>
<?php endif; ?>
<?php echo $item->event->afterDisplayTitle; ?>
<?php if ($displayInfo && $item->created_by != 807) : ?>
<?php $listClass = ($params->get('info_layout') == 1) ? 'list-inline' : 'list-unstyled'; ?>
<dl class="<?php echo $listClass; ?>">
<dt class="article-info-term">
<span class="visually-hidden">
<?php echo Text::_('MOD_ARTICLES_INFO'); ?>
</span>
</dt>
<?php if ($item->displayAuthorName && $item->created_by != 807): ?>
<dd class="mod-articles-writtenby <?php echo ($params->get('info_layout') == 1 ? 'list-inline-item' : ''); ?>">
<?php echo LayoutHelper::render('joomla.icon.iconclass', ['icon' => 'icon-user icon-fw']); ?>
<?php echo htmlspecialchars($item->displayAuthorName, ENT_QUOTES, 'UTF-8'); ?>
</dd>
<?php endif; ?>
<?php if ($item->displayCategoryTitle) : ?>
<dd class="mod-articles-category <?php echo ($params->get('info_layout') == 1 ? 'list-inline-item' : ''); ?>">
<?php echo LayoutHelper::render('joomla.icon.iconclass', ['icon' => 'icon-folder-open icon-fw']); ?>
<?php if ($item->displayCategoryLink) : ?>
<a href="/<?php echo $item->displayCategoryLink; ?>">
<?php echo htmlspecialchars($item->displayCategoryTitle, ENT_QUOTES, 'UTF-8'); ?>
</a>
<?php else : ?>
<?php echo htmlspecialchars($item->displayCategoryTitle, ENT_QUOTES, 'UTF-8'); ?>
<?php endif; ?>
</dd>
<?php endif; ?>
<?php if ($item->displayDate) : ?>
<dd class="mod-articles-date <?php echo ($params->get('info_layout') == 1 ? 'list-inline-item' : ''); ?>">
<?php echo LayoutHelper::render('joomla.icon.iconclass', ['icon' => 'icon-calendar icon-fw']); ?>
<?php echo htmlspecialchars($item->displayDate, ENT_QUOTES, 'UTF-8'); ?>
</dd>
<?php endif; ?>
<?php if ($item->displayHits) : ?>
<dd class="mod-articles-hits <?php echo ($params->get('info_layout') == 1 ? 'list-inline-item' : ''); ?>">
<?php echo LayoutHelper::render('joomla.icon.iconclass', ['icon' => 'icon-eye icon-fw']); ?>
<?php echo $item->displayHits; ?>
</dd>
<?php endif; ?>
</dl>
<?php endif; ?>
<?php if (in_array($params->get('img_intro_full'), ['intro', 'full']) && !empty($item->imageSrc)) : ?>
<?php echo LayoutHelper::render('joomla.content.' . $params->get('img_intro_full') . '_image', $item); ?>
<?php endif; ?>
<?php echo $item->event->beforeDisplayContent; ?>
<?php if ($params->get('show_introtext', 1)) : ?>
<?php echo $item->displayIntrotext; ?>
<?php endif; ?>
<?php echo $item->event->afterDisplayContent; ?>
<?php if ($params->get('show_readmore') && !empty($item->fulltext)) : ?>
<?php if ($params->get('show_readmore_title', '') !== '') : ?>
<?php $item->params->set('show_readmore_title', $params->get('show_readmore_title')); ?>
<?php $item->params->set('readmore_limit', $params->get('readmore_limit')); ?>
<?php endif; ?>
<?php echo LayoutHelper::render('joomla.content.readmore', ['item' => $item, 'params' => $item->params, 'link' => $item->link]); ?>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
To hide the author in the generic program points I wrote a little “hack” in the module layout: I check the id of the author and hide the info if the article has been created by me:
<?php if ($displayInfo && $item->created_by != 807) : ?>
CSS
Without changes in the CSS of your template (we are using Cassiopeia) the result is a list of articles grouped by category. But that is not how a conference schedule normally looks like. And here comes some CSS to the rescue.
The articles are displayed as list elements (ul > li). I defined the list as grid: one column on small displays, two columns in medium displays and four columns in big displays. For the generic slots and the keynotes I display the only element in full width:
.mod-articles-items:has(> :last-child:nth-child(1)) {
grid-template-columns: 1fr;
}
The li-elements have the class “card” that is already in use elsewhere. To position the elements (tags, title, author, read more) inside each “card” I’m using subgrid (I wrote about subgrid some months ago: CSS Shorts: Subgrid).

The longest part of this specific CSS is the definition of the tags :-) .
Here is the added CSS for the schedule:
.schedule {
.mod-articles-group:not(:first-of-type) {
margin-block: 3rem;
}
.mod-articles-items {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: auto max-content auto auto;
row-gap: .5rem;
padding-inline-start: 3rem;
> li {
grid-row: span 4;
display: grid;
grid-template-rows: subgrid;
padding: .5rem 1rem;
}
h4 {
font-size: 18px;
&:only-child {
grid-row: 1 / -1;
margin-block: 1rem;
}
}
@media (min-width: 768px) {
grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 1200px) {
grid-template-columns: repeat(4, 1fr);
}
}
.mod-articles-items:has(> :last-child:nth-child(1)) {
grid-template-columns: 1fr;
}
}
.btn-room,
.btn-online,
.btn-keynote,
.btn-eng,
.btn-de {
display: inline-block;
padding: .4rem .8rem;
color: #ffffff;
font-weight: 600 !important;
border-radius: 6px;
text-decoration: none;
transition: background-color .25s ease;
&:hover {
color: #fff;
}
}
.btn-room1 {
background-color: #08406d;
&:hover {
background-color: #063356;
}
}
.btn-room2 {
background-color: #0f6a52;
&:hover {
background-color: #0c5441;
}
}
.btn-room3 {
background-color: #a85c1c;
&:hover {
background-color: #8a4b17;
}
}
.btn-room4 {
background-color: #5a3a8f;
&:hover {
background-color: #4a3077;
}
}
.btn-online {
background-color: #1b7ea3;
&:hover {
background-color: #166684;
}
}
.btn-keynote {
background-color: #8e1f1f;
&:hover {
background-color: #741919;
}
}
.btn-eng,
.btn-de {
color: var(--body-color);
border: 1px solid var(--body-color);
&:hover {
background-color: #b7b7b7;
color: var(--body-color);
}
}
You can see the schedule live here: https://conference.joomla.org/schedule
Conclusion
I hope through this series of articles I could show you what is possible with Joomla core, some overrides and CSS.
We at the Joomla World Conference hope that our program exceeds your expectations, and we're looking forward to seeing you in Potsdam in October!

Comments