In der September Joomla Override Challenge #JOC geht es darum mit Joomla Core ein Verzeichnis zu erstellen, z.B. für eine Stadt.

Ein Verzeichnis von Geschäften in einer Stadt kann man als Bloglayout mit Unterkategorien anzeigen:

Kategorien

  • Verzeichnis
    • Apotheken
    • Ärzte
    • Lebensmittel
    • Restaurants
    • ...

Wir können die Beiträge mit Custom Fields erweitern, z.B. Adresse, Telefonnummer, Webseite, Öffnungszeiten...

Das Bloglayout von Joomla ist manchmal kompliziert einzustellen. Man kann alle Beiträge einer Kategorie anzeigen. Man kann auch die Unterkategorien einbeziehen, dann werden alle Beiträge je nach Einstellung der Sortierung angezeigt. Oder die Unterkategorien werden als Unterpunkte dargestellt. Eine Filterfunktion steht uns im Core leider nicht zur Verfügung.

Für diese Challenge wollte ich was Dynamisches erstellen, wenn möglich ohne Einsatz von Javascript. Auf der Suche nach Inspiration bin ich auf diesem Tutorial gestoßen: How to Build a Filtering Component in Pure CSS und schon war die Idee für ein Override von Bloglayout geboren.

Da die Änderungen tiefgreifend sind und wir eventuell auf unserer Seite auch ein normales Bloglayout verwenden wollen, werden wir ein alternatives Layout erstellen. Dafür brauchen wir nicht nur Kopien von den PHP Files sondern auch ein eigenes XML File um den Menüpunkt anlegen zu können:

blog.php -> filter.php

blog_childern.php -> filter_children.php

blog_item.php -> filter_item.php

blog_links.php -> filter_links.php

blog.xml -> filter.xml

Das XML File müssen wir vom Core kopieren, das wird nicht über den normalen Weg eines Overrides (Erweiterungen -> Templates -> Templates -> Details & Dateien -> Override erstellen) angelegt.

Damit wir im Menü den neuen Typ erkennen können, verändern wir die ersten Zeilen im XML File:

blog.xml

<?xml version="1.0" encoding="utf-8"?>
<metadata>
	<layout title="COM_CONTENT_CATEGORY_VIEW_BLOG_TITLE" option="COM_CONTENT_CATEGORY_VIEW_BLOG_OPTION">
		<help key = "JHELP_MENUS_MENU_ITEM_ARTICLE_CATEGORY_BLOG" />
		<message>
			<![CDATA[COM_CONTENT_CATEGORY_VIEW_BLOG_DESC]]>
		</message>
	</layout>

 

filter.xml

<?xml version="1.0" encoding="utf-8"?>
<metadata>
	<layout title="Verzeichnis mit Filter" option="Verzeichnis mit Filter">
		<help key = "JHELP_MENUS_MENU_ITEM_ARTICLE_CATEGORY_BLOG" />
		<message>
			<![CDATA[Verzeichnis mit Filter+]]>
		</message>
	</layout>

Das ist die einfache Variante. Man kann natürlich auch mit Sprachstrings arbeiten, wie im original Datei und so das Override mehrsprachfähig machen.

 

In filter.php habe ich verschiedene Sachen geändert: Itemscope entfernt (wird in den Beiträgen anders gesetzt), das Laden von "loadTemplate('children')" nach oben geschoben, Lead Items entfernt und Intro Item vereinfacht.

<?php
/**
 * @package     Joomla.Site
 * @subpackage  com_content
 *
 * @copyright   Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('_JEXEC') or die;

JHtml::addIncludePath(JPATH_COMPONENT . '/helpers');

JHtml::_('behavior.caption');

$dispatcher = JEventDispatcher::getInstance();

$this->category->text = $this->category->description;
$dispatcher->trigger('onContentPrepare', array($this->category->extension . '.categories', &$this->category, &$this->params, 0));
$this->category->description = $this->category->text;

$results = $dispatcher->trigger('onContentAfterTitle', array($this->category->extension . '.categories', &$this->category, &$this->params, 0));
$afterDisplayTitle = trim(implode("\n", $results));

$results = $dispatcher->trigger('onContentBeforeDisplay', array($this->category->extension . '.categories', &$this->category, &$this->params, 0));
$beforeDisplayContent = trim(implode("\n", $results));

$results = $dispatcher->trigger('onContentAfterDisplay', array($this->category->extension . '.categories', &$this->category, &$this->params, 0));
$afterDisplayContent = trim(implode("\n", $results));

?>
<div class="directory<?php echo $this->pageclass_sfx; ?>">
	<?php if ($this->params->get('show_page_heading')) : ?>
		<div class="page-header">
			<h1> <?php echo $this->escape($this->params->get('page_heading')); ?> </h1>
		</div>
	<?php endif; ?>

	<?php if ($this->params->get('show_category_title', 1) or $this->params->get('page_subheading')) : ?>
		<h2> <?php echo $this->escape($this->params->get('page_subheading')); ?>
			<?php if ($this->params->get('show_category_title')) : ?>
				<span class="subheading-category"><?php echo $this->category->title; ?></span>
			<?php endif; ?>
		</h2>
	<?php endif; ?>
	<?php echo $afterDisplayTitle; ?>

	<?php if ($this->params->get('show_cat_tags', 1) && !empty($this->category->tags->itemTags)) : ?>
		<?php $this->category->tagLayout = new JLayoutFile('joomla.content.tags'); ?>
		<?php echo $this->category->tagLayout->render($this->category->tags->itemTags); ?>
	<?php endif; ?>

	<?php if ($beforeDisplayContent || $afterDisplayContent || $this->params->get('show_description', 1) || $this->params->def('show_description_image', 1)) : ?>
		<div class="category-desc clearfix">
			<?php if ($this->params->get('show_description_image') && $this->category->getParams()->get('image')) : ?>
				<img src="/<?php echo $this->category->getParams()->get('image'); ?>" alt="<?php echo htmlspecialchars($this->category->getParams()->get('image_alt'), ENT_COMPAT, 'UTF-8'); ?>"/>
			<?php endif; ?>
			<?php echo $beforeDisplayContent; ?>
			<?php if ($this->params->get('show_description') && $this->category->description) : ?>
				<?php echo JHtml::_('content.prepare', $this->category->description, '', 'com_content.category'); ?>
			<?php endif; ?>
			<?php echo $afterDisplayContent; ?>
		</div>
	<?php endif; ?>

	<?php if ($this->maxLevel != 0 && !empty($this->children[$this->category->id])) : ?>
		<?php if ($this->params->get('show_category_heading_title_text', 1) == 1) : ?>
			<p class="filter-title"><?php echo JText::_('TPL_CITY_FILTER'); ?></p>
		<?php endif; ?>
		<?php echo $this->loadTemplate('children'); ?>
	<?php endif; ?>

	<?php if (empty($this->lead_items) && empty($this->link_items) && empty($this->intro_items)) : ?>
		<?php if ($this->params->get('show_no_articles', 1)) : ?>
			<p><?php echo JText::_('COM_CONTENT_NO_ARTICLES'); ?></p>
		<?php endif; ?>
	<?php endif; ?>

	<?php if (!empty($this->intro_items)) : ?>
		<div class="posts">
			<?php foreach ($this->intro_items as $key => &$item) : ?>
				<?php
					$this->item = &$item;
					echo $this->loadTemplate('item');
				?>
			<?php endforeach; ?>
		</div>
	<?php endif; ?>

	<?php if (!empty($this->link_items)) : ?>
		<div class="items-more">
			<?php echo $this->loadTemplate('links'); ?>
		</div>
	<?php endif; ?>

	<?php if (($this->params->def('show_pagination', 1) == 1 || ($this->params->get('show_pagination') == 2)) && ($this->pagination->get('pages.total') > 1)) : ?>
		<div class="pagination">
			<?php if ($this->params->def('show_pagination_results', 1)) : ?>
				<p class="counter pull-right"> <?php echo $this->pagination->getPagesCounter(); ?> </p>
			<?php endif; ?>
			<?php echo $this->pagination->getPagesLinks(); ?> </div>
	<?php endif; ?>
</div>

 

filter_children.php wurde so geändert, dass als "Filter" fungieren wird:

<?php
/**
 * @package     Joomla.Site
 * @subpackage  com_content
 *
 * @copyright   Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('_JEXEC') or die;

JHtml::_('bootstrap.tooltip');

$lang   = JFactory::getLanguage();
$user   = JFactory::getUser();
$groups = $user->getAuthorisedViewLevels();

if ($this->maxLevel != 0 && count($this->children[$this->category->id]) > 0) : ?>

    <input type="radio" id="All" name="categories" value="All" checked>
	<?php foreach ($this->children[$this->category->id] as $id => $child) : ?>
		<?php // Check whether category access level allows access to subcategories. ?>
		<?php if (in_array($child->access, $groups)) : ?>
            <input type="radio" id="<?php echo $this->escape($child->title); ?>" name="categories" value="<?php echo $this->escape($child->title); ?>">
		<?php endif; ?>
	<?php endforeach; ?>
	<ol class="filters">
		<li>
		<label for="All"><?php echo JText::_('TPL_CITY_ALL'); ?></label>
		</li>
		<?php foreach ($this->children[$this->category->id] as $id => $child) : ?>
			<?php // Check whether category access level allows access to subcategories. ?>
			<?php if (in_array($child->access, $groups)) : ?>
               <li>
                <label for="<?php echo $this->escape($child->title); ?>"><?php echo $this->escape($child->title); ?></label>
              </li>
			<?php endif; ?>
		<?php endforeach; ?>
	</ol>
<?php endif;

 

In filter_item.php habe ich den Itemscope neu gesetzt, einige <div> und Klassen ergänzt und was sehr wichtig ist, damit unser Filter funktioniert, ein Attribut "data-category" definiert:

<?php
/**
 * @package     Joomla.Site
 * @subpackage  com_content
 *
 * @copyright   Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('_JEXEC') or die;

// Create a shortcut for params.
$params = $this->item->params;
JHtml::addIncludePath(JPATH_COMPONENT . '/helpers/html');
$canEdit = $this->item->params->get('access-edit');
$info    = $params->get('info_block_position', 0);

// Check if associations are implemented. If they are, define the parameter.
$assocParam = (JLanguageAssociations::isEnabled() && $params->get('show_associations'));

?>
<div class="post" itemscope itemtype="http://schema.org/LocalBusiness" data-category="<?php echo $this->item->category_title; ?>">

	<?php if ($this->item->state == 0 || strtotime($this->item->publish_up) > strtotime(JFactory::getDate())
		|| ((strtotime($this->item->publish_down) < strtotime(JFactory::getDate())) && $this->item->publish_down != JFactory::getDbo()->getNullDate())) : ?>
		<div class="system-unpublished">
	<?php endif; ?>

	<?php echo JLayoutHelper::render('joomla.content.blog_style_default_item_title', $this->item); ?>

	<?php if ($canEdit || $params->get('show_print_icon') || $params->get('show_email_icon')) : ?>
		<?php echo JLayoutHelper::render('joomla.content.icons', array('params' => $params, 'item' => $this->item, 'print' => false)); ?>
	<?php endif; ?>

	<?php // Todo Not that elegant would be nice to group the params ?>
	<?php $useDefList = ($params->get('show_modify_date') || $params->get('show_publish_date') || $params->get('show_create_date')
		|| $params->get('show_hits') || $params->get('show_category') || $params->get('show_parent_category') || $params->get('show_author') || $assocParam); ?>

	<?php if ($useDefList && ($info == 0 || $info == 2)) : ?>
		<?php // Todo: for Joomla4 joomla.content.info_block.block can be changed to joomla.content.info_block ?>
		<?php echo JLayoutHelper::render('joomla.content.info_block.block', array('item' => $this->item, 'params' => $params, 'position' => 'above')); ?>
	<?php endif; ?>
	<?php if ($info == 0 && $params->get('show_tags', 1) && !empty($this->item->tags->itemTags)) : ?>
		<?php echo JLayoutHelper::render('joomla.content.tags', $this->item->tags->itemTags); ?>
	<?php endif; ?>

	<?php echo JLayoutHelper::render('joomla.content.intro_image', $this->item); ?>

	<?php if (!$params->get('show_intro')) : ?>
		<?php // Content is generated by content plugin event "onContentAfterTitle" ?>
		<?php echo $this->item->event->afterDisplayTitle; ?>
	<?php endif; ?>

	<?php // Content is generated by content plugin event "onContentBeforeDisplay" ?>
	<?php echo $this->item->event->beforeDisplayContent; ?>

	<?php echo $this->item->introtext; ?>

	<?php if ($info == 1 || $info == 2) : ?>
		<?php if ($useDefList) : ?>
			<?php // Todo: for Joomla4 joomla.content.info_block.block can be changed to joomla.content.info_block ?>
			<?php echo JLayoutHelper::render('joomla.content.info_block.block', array('item' => $this->item, 'params' => $params, 'position' => 'below')); ?>
		<?php endif; ?>
		<?php if ($params->get('show_tags', 1) && !empty($this->item->tags->itemTags)) : ?>
			<?php echo JLayoutHelper::render('joomla.content.tags', $this->item->tags->itemTags); ?>
		<?php endif; ?>
	<?php endif; ?>

	<?php if ($params->get('show_readmore') && $this->item->readmore) :
		if ($params->get('access-view')) :
			$link = JRoute::_(ContentHelperRoute::getArticleRoute($this->item->slug, $this->item->catid, $this->item->language));
		else :
			$menu = JFactory::getApplication()->getMenu();
			$active = $menu->getActive();
			$itemId = $active->id;
			$link = new JUri(JRoute::_('index.php?option=com_users&view=login&Itemid=' . $itemId, false));
			$link->setVar('return', base64_encode(ContentHelperRoute::getArticleRoute($this->item->slug, $this->item->catid, $this->item->language)));
		endif; ?>

		<?php echo JLayoutHelper::render('joomla.content.readmore', array('item' => $this->item, 'params' => $params, 'link' => $link)); ?>

	<?php endif; ?>

	<?php if ($this->item->state == 0 || strtotime($this->item->publish_up) > strtotime(JFactory::getDate())
		|| ((strtotime($this->item->publish_down) < strtotime(JFactory::getDate())) && $this->item->publish_down != JFactory::getDbo()->getNullDate())) : ?>
	</div>
	<?php endif; ?>

	<?php // Content is generated by content plugin event "onContentAfterDisplay" ?>
	<?php echo $this->item->event->afterDisplayContent; ?>
</div>

 

Ein letztes Override brauchen wir noch und zwar für die Detailansicht der Beiträge (com_content/article). Hier erstellen wir auch ein alternatives Layout: filter.php statt default.php

<?php
/**
 * @package     Joomla.Site
 * @subpackage  com_content
 *
 * @copyright   Copyright (C) 2005 - 2020 Open Source Matters, Inc. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE.txt
 */

defined('_JEXEC') or die;

JHtml::addIncludePath(JPATH_COMPONENT . '/helpers');

// Create shortcuts to some parameters.
$params  = $this->item->params;
$urls    = json_decode($this->item->urls);
$canEdit = $params->get('access-edit');
$user    = JFactory::getUser();
$info    = $params->get('info_block_position', 0);

// Check if associations are implemented. If they are, define the parameter.
$assocParam = (JLanguageAssociations::isEnabled() && $params->get('show_associations'));
JHtml::_('behavior.caption');

?>
<div class="item-page<?php echo $this->pageclass_sfx; ?>" itemscope itemtype="https://schema.org/Article">
	<meta itemprop="inLanguage" content="<?php echo ($this->item->language === '*') ? JFactory::getConfig()->get('language') : $this->item->language; ?>" />
	<?php if ($this->params->get('show_page_heading')) : ?>
	<div class="page-header">
		<h1> <?php echo $this->escape($this->params->get('page_heading')); ?> </h1>
	</div>
	<?php endif;
	if (!empty($this->item->pagination) && $this->item->pagination && !$this->item->paginationposition && $this->item->paginationrelative)
	{
		echo $this->item->pagination;
	}
	?>

	<?php // Todo Not that elegant would be nice to group the params ?>
	<?php $useDefList = ($params->get('show_modify_date') || $params->get('show_publish_date') || $params->get('show_create_date')
	|| $params->get('show_hits') || $params->get('show_category') || $params->get('show_parent_category') || $params->get('show_author') || $assocParam); ?>

	<?php if (!$useDefList && $this->print) : ?>
		<div id="pop-print" class="btn hidden-print">
			<?php echo JHtml::_('icon.print_screen', $this->item, $params); ?>
		</div>
		<div class="clearfix"> </div>
	<?php endif; ?>
	<?php if ($params->get('show_title')) : ?>
	<div class="page-header">
		<h2 itemprop="headline">
			<?php echo $this->escape($this->item->title); ?>
		</h2>
		<?php if ($this->item->state == 0) : ?>
			<span class="label label-warning"><?php echo JText::_('JUNPUBLISHED'); ?></span>
		<?php endif; ?>
		<?php if (strtotime($this->item->publish_up) > strtotime(JFactory::getDate())) : ?>
			<span class="label label-warning"><?php echo JText::_('JNOTPUBLISHEDYET'); ?></span>
		<?php endif; ?>
		<?php if ((strtotime($this->item->publish_down) < strtotime(JFactory::getDate())) && $this->item->publish_down != JFactory::getDbo()->getNullDate()) : ?>
			<span class="label label-warning"><?php echo JText::_('JEXPIRED'); ?></span>
		<?php endif; ?>
	</div>
	<?php endif; ?>
	<?php if (!$this->print) : ?>
		<?php if ($canEdit || $params->get('show_print_icon') || $params->get('show_email_icon')) : ?>
			<?php echo JLayoutHelper::render('joomla.content.icons', array('params' => $params, 'item' => $this->item, 'print' => false)); ?>
		<?php endif; ?>
	<?php else : ?>
		<?php if ($useDefList) : ?>
			<div id="pop-print" class="btn hidden-print">
				<?php echo JHtml::_('icon.print_screen', $this->item, $params); ?>
			</div>
		<?php endif; ?>
	<?php endif; ?>

	<?php // Content is generated by content plugin event "onContentAfterTitle" ?>
	<?php echo $this->item->event->afterDisplayTitle; ?>

	<?php if ($useDefList && ($info == 0 || $info == 2)) : ?>
		<?php // Todo: for Joomla4 joomla.content.info_block.block can be changed to joomla.content.info_block ?>
		<?php echo JLayoutHelper::render('joomla.content.info_block.block', array('item' => $this->item, 'params' => $params, 'position' => 'above')); ?>
	<?php endif; ?>

	<?php if ($info == 0 && $params->get('show_tags', 1) && !empty($this->item->tags->itemTags)) : ?>
		<?php $this->item->tagLayout = new JLayoutFile('joomla.content.tags'); ?>

		<?php echo $this->item->tagLayout->render($this->item->tags->itemTags); ?>
	<?php endif; ?>

	<?php // Content is generated by content plugin event "onContentBeforeDisplay" ?>
	<?php echo $this->item->event->beforeDisplayContent; ?>

	<?php if (isset($urls) && ((!empty($urls->urls_position) && ($urls->urls_position == '0')) || ($params->get('urls_position') == '0' && empty($urls->urls_position)))
		|| (empty($urls->urls_position) && (!$params->get('urls_position')))) : ?>
	<?php echo $this->loadTemplate('links'); ?>
	<?php endif; ?>
	<?php if ($params->get('access-view')) : ?>
	<?php echo JLayoutHelper::render('joomla.content.full_image', $this->item); ?>
	<?php
	if (!empty($this->item->pagination) && $this->item->pagination && !$this->item->paginationposition && !$this->item->paginationrelative) :
		echo $this->item->pagination;
	endif;
	?>
	<?php if (isset ($this->item->toc)) :
		echo $this->item->toc;
	endif; ?>
	<div itemprop="articleBody">
		<?php echo $this->item->text; ?>
		<?php if (!empty($this->item->jcfields[1]->value)) : ?>
			<div class="info-box">
				<div>
					<h4 class="n-utitel">Adresse:</h4>
					<?php echo $this->item->jcfields[1]->value; ?><br />
					<?php echo $this->item->jcfields[2]->value; ?> <?php echo $this->item->jcfields[3]->value; ?>
				</div>
				<div>
					<h4 class="n-utitel">Kontakt:</h4>
					<?php echo $this->item->jcfields[4]->value; ?><br />
					<?php echo $this->item->jcfields[5]->value; ?>
				</div>
			</div>
		<?php endif; ?>
	</div>

	<?php if ($info == 1 || $info == 2) : ?>
		<?php if ($useDefList) : ?>
				<?php // Todo: for Joomla4 joomla.content.info_block.block can be changed to joomla.content.info_block ?>
			<?php echo JLayoutHelper::render('joomla.content.info_block.block', array('item' => $this->item, 'params' => $params, 'position' => 'below')); ?>
		<?php endif; ?>
		<?php if ($params->get('show_tags', 1) && !empty($this->item->tags->itemTags)) : ?>
			<?php $this->item->tagLayout = new JLayoutFile('joomla.content.tags'); ?>
			<?php echo $this->item->tagLayout->render($this->item->tags->itemTags); ?>
		<?php endif; ?>
	<?php endif; ?>

	<?php
	if (!empty($this->item->pagination) && $this->item->pagination && $this->item->paginationposition && !$this->item->paginationrelative) :
		echo $this->item->pagination;
	?>
	<?php endif; ?>
	<?php if (isset($urls) && ((!empty($urls->urls_position) && ($urls->urls_position == '1')) || ($params->get('urls_position') == '1'))) : ?>
	<?php echo $this->loadTemplate('links'); ?>
	<?php endif; ?>
	<?php // Optional teaser intro text for guests ?>
	<?php elseif ($params->get('show_noauth') == true && $user->get('guest')) : ?>
	<?php echo JLayoutHelper::render('joomla.content.intro_image', $this->item); ?>
	<?php echo JHtml::_('content.prepare', $this->item->introtext); ?>
	<?php // Optional link to let them register to see the whole article. ?>
	<?php if ($params->get('show_readmore') && $this->item->fulltext != null) : ?>
	<?php $menu = JFactory::getApplication()->getMenu(); ?>
	<?php $active = $menu->getActive(); ?>
	<?php $itemId = $active->id; ?>
	<?php $link = new JUri(JRoute::_('index.php?option=com_users&view=login&Itemid=' . $itemId, false)); ?>
	<?php $link->setVar('return', base64_encode(ContentHelperRoute::getArticleRoute($this->item->slug, $this->item->catid, $this->item->language))); ?>
	<p class="readmore">
		<a href="/<?php echo $link; ?>" class="register">
			<?php if ($params->get('alternative_readmore', '') === '') : ?>
				<?php echo JText::_('COM_CONTENT_REGISTER_TO_READ_MORE'); ?>
			<?php else : ?>
				<?php echo $params->get('alternative_readmore'); ?>
				<?php if ($params->get('show_readmore_title', 0) != 0) : ?>
					<?php echo JHtml::_('string.truncate', $this->item->title, $params->get('readmore_limit')); ?>
				<?php endif; ?>
			<?php endif; ?>
		</a>
	</p>
	<?php endif; ?>
	<?php endif; ?>
	<?php
	if (!empty($this->item->pagination) && $this->item->pagination && $this->item->paginationposition && $this->item->paginationrelative) :
		echo $this->item->pagination;
	?>
	<?php endif; ?>
	<?php // Content is generated by content plugin event "onContentAfterDisplay" ?>
	<?php echo $this->item->event->afterDisplayContent; ?>
</div>

 

Damit haben wir das Grundgerüst für unsere Beiträge erstellt. Die nächsten Schritte sind:

  • Sprachoverrides anlegen:
    COM_CONTENT_READ_MORE_TITLE -> Mehr Infos (statt Weiterlesen...)
    TPL_CITY_ALL -> Alle (für die Filterfunktion)
    TPL_CITY_FILTER -> Finden in Meine City
  • Custom Fields anlegen (Feldgruppe: Weitere Infos):
    • Adresse (text)
    • PLZ (text)
    • Ort (text)
    • Telefonnummer (text)
    • Website (url )
  • Beiträge erstellen (verschiedene Beiträge in den gewünschten Kategorien)
  • Menüpunkt anlegen, hier sind die Einstellungen wichtig:

verzeichnis menu 1

verzeichnis menu 2

verzeichnis menu 3

verzeichnis menu 4

 

Und jetzt kommt das Wichtigste: CSS! Die ganze funktionalität des Filters beruht auf CSS. Wie im Tutorial von George Martsoukos gut erklärt ist, arbeiten wir mit versteckten radio Buttons. Die radio Buttons haben wir in filter_children.php angelegt, so dass jede Unterkategorie von "Verzeichnis" ein radio darstellt. Das Label vom Radio-Button ist der Name der Kategorie. Für "Alle" haben wir ein Label fest codiert und dafür ein Sprachstring angelegt.

Der Filter bewirkt, dass wenn man auf eine Kategorie klickt, die passenden Beiträge angezeigt werden. Um das zu erreichen nutzen wir eine Kombination von CSS Anweisungen:

  • Pseudo-Klasse :checked
  • Die Pseudo-Klasse Negation (:not())
  • Attributen
  • Der "Geschwister-Selektor" (~) bekannt als “subsequent-sibling selector” in CSS4

Wenn wir auf "Alle" klicken, werden alle Beiträge mit dem Attribut data-category angezeigt (der Radio-Button "All" ist per default checked):

[value="All"]:checked ~ .posts [data-category] {
    display: flex;
}

Wenn wir auf irgendeine Kategorie klicken, werden die passenden Beitrgäge angezeigt:

[value="Arztpraxis"]:checked ~ .posts .post:not([data-category~="Arztpraxis"]),
[value="Apotheke"]:checked ~ .posts .post:not([data-category~="Apotheke"]),
[value="Restaurant"]:checked ~ .posts .post:not([data-category~="Restaurant"]),
[value="Lebensmittel"]:checked ~ .posts .post:not([data-category~="Lebensmittel"]) {
    display: none;
}

Was sagt uns der Selektor genau?

Der erste Teil [value="Arztpraxis"]:checked schaut ob der Radio-Button mit dem Wert "Arztpraxis" gecheckt ist.

Danach ist die Tilde (~) auch bekannt als “subsequent-sibling selector”. Damit wird das Element .post ausgewählt, das .posts als Elternteil hat. Wir verfeinern die Auswahl mit :not([data-category~="Arztpraxis"]) nur auf diejenigen .post-Elemente, die kein data-category-Attribut haben, das irgendwo innerhalb einer durch Leerzeichen getrennten Liste einen Arztpraxis-Wert enthält. Dann setzen wir display: none; für alle Elemente, die diesen Kriterien entsprechen. Somit sind nur die Beiträge der Kategorie "Arztpraxis" sichtbar.

Hier die vollständigen CSS Anweisungen

/* VERZEICHNIS
–––––––––––––––––––––––––––––––––––––––––––––––––– */
.directory ol {
    list-style: none;
}
.directory input[type="radio"] {
    position: absolute;
    left: -9999px;
}
.directory .filter-title {
	text-align: center;
	font-weight: 700;
}

/* FILTERS
–––––––––––––––––––––––––––––––––––––––––––––––––– */
.filters {
    text-align: center;
    margin-bottom: var(--s2);
}
.filters * {
    display: inline-block;
}
.filters label {
    padding: 0.5rem 1rem;
    margin-bottom: 0.25rem;
    border: 1px dotted var(--color-dark);
    border-radius: 2rem;
    min-width: 50px;
	line-height: normal;
	font-weight: 400;
    cursor: pointer;
    transition: all 0.1s;
}
.filters label:hover {
    background: var(--color-dark);
    color: #fff;
}
.filters label:focus {
    border: 1px dotted #000;
}

/* FILTERED ELEMENTS (POSTS)
–––––––––––––––––––––––––––––––––––––––––––––––––– */
.posts {
    display: grid;
    grid-gap: 1.5rem;
	grid-template-columns: repeat(auto-fit, minmax(330px, 1fr));
	margin-top: var(--s2);
}
.posts .post {
    background: var(--color-middle);
	border-radius: var(--radius);
	padding: var(--s1);
	position: relative;
    box-shadow: var(--card-shadow);
	display: flex;
	flex-direction: column;
}
.posts .post .readmore {
	margin-top: auto;
	text-align: right;
}
.post:before {
    content: "";
    pointer-events: none;
    width: 100%;
    height: 100%;
    position: absolute;
    top: -.3rem;
    left: -.3rem;
	border: .2rem solid var(--color-contrast);
	border-radius: .25rem;
    z-index: 1;
}
.posts .page-header {
    font-size: 0.8em;
}
.posts .post-title:hover {
    text-decoration: underline;
}
.posts figcaption {
    padding: 1rem;
}
.posts .post-categories {
    margin-bottom: 0.75rem;
    font-size: 0.75rem;
}
.posts .post-categories * {
    display: inline-block;
}
.posts .post-categories li {
    margin-bottom: 0.2rem;
}
.posts .post-categories a {
    padding: 0.2rem 0.5rem;
    border-radius: 1rem;
    border: 1px solid;
    line-height: normal;
    transition: all 0.1s;
}
.posts .post-categories a:hover {
    background: var(--color-dark);
    color: #fff;
}

/* FILTERING RULES
–––––––––––––––––––––––––––––––––––––––––––––––––– */
[value="All"]:checked ~ .filters [for="All"],
[value="Arztpraxis"]:checked ~ .filters [for="Arztpraxis"],
[value="Apotheke"]:checked ~ .filters [for="Apotheke"],
[value="Restaurant"]:checked ~ .filters [for="Restaurant"],
[value="Lebensmittel"]:checked ~ .filters [for="Lebensmittel"] {
    background: var(--color-dark);
    color: #fff;
}
[value="All"]:checked ~ .posts [data-category] {
    display: flex;
}
[value="Arztpraxis"]:checked ~ .posts .post:not([data-category~="Arztpraxis"]),
[value="Apotheke"]:checked ~ .posts .post:not([data-category~="Apotheke"]),
[value="Restaurant"]:checked ~ .posts .post:not([data-category~="Restaurant"]),
[value="Lebensmittel"]:checked ~ .posts .post:not([data-category~="Lebensmittel"]) {
    display: none;
}

/* Item */
.info-box {
	display: grid;
	grid-template-columns: repeat(auto-fill,minmax(300px,1fr));
	grid-gap: 2rem;
	background-color: #fff;
	padding: var(--s1);
	position: relative;
	border-radius: .25rem;
    box-shadow: var(--card-shadow);
}
.info-box::before {
    content: "";
    pointer-events: none;
    width: 100%;
    height: 100%;
    position: absolute;
    top: -.3rem;
    left: -.3rem;
	border: .2rem solid var(--color-contrast);
	border-radius: .25rem;
    z-index: 1;
}
.info-box h4 {
	margin-top: 0;
}

 

Das Ergebnis kann man hier "live in action" sehen: https://www.dr-menzel-it.de/overrides/verzeichnis

 

Für die Detailansicht der Beiträge haben wir auch ein Override angelegt und die Custom Fields als extra Box dargestellt:

verzeichnis beitrag

 

Da der CSS Code manuell angepasst werden muss, wenn man neue Kategorien anlegt, ist diese Lösung eine nette Spielerei für kleine Verzeichnisse. Aber es zeigt wieder wie flexibel Joomla und wie mächtig CSS ist.

 

Hier geht es zu Daniels Lösung