(Geschätzte Lesezeit: 9 - 17 Minuten)

Hast du Seiten mit viel Inhalt, oder möchtest du eine Seite erstellen, auf der der Besucher selbst entscheiden kann, welche Informationen er sehen möchte (wie eine FAQ-Seite)? Ein Akkordeon ist dafür perfekt geeignet. Der Besucher erhält einen Überblick über die Themen, klickt auf die interessanten Themen und der Inhalt wird angezeigt. Und das Gute daran ist: Es braucht dafür keine Erweiterung, man kann es mit einer kleinen Anpassung - dem Erstellen eines Template Overrides - einer der Joomla-Core-Ansichten machen. Hier kommt nun, wie du das anstellen kannst!

Dies ist die Übersetzung des Artikels Collapsible sections with Bootstrap aus dem Joomla Community Magazine 06/24 von Viviana Menzel

In einem früheren Artikel (siehe Fallstudie 2) habe ich gezeigt, wie man eine FAQ-Seite mit dem Seitenumbruch - Plugin erstellt. Dies funktioniert gut mit wenig Inhalt, aber wenn man komplexere FAQs (oder andere Arten von klappbaren Inhalten) erstellen muss, gibt es einen besseren Weg, es zu tun, nämlich mit einem Template Override.

In diesem Artikel zeige ich, wie man das Layout des Kategorie-Blogs umgestalten kann, um Artikel als „ausklappbare Abschnitte“ oder auch „Akkordeons“ mit Bootstrap anzuzeigen. Ich habe den Code aus dem Bootstrap-Beispiel angepasst und einige Extras hinzugefügt, um das Akkordeon zugänglicher zu machen, wie es von Bootstrap nach dem Beispiel der WAI empfohlen wird.

Da das Layout des Kategorie-Blogs wahrscheinlich mehrmals auf einer Seite verwendet werden soll, und dabei nicht gewollt wird, dass alle Seiten wie eine FAQ aussehen, erstellen wir einen neuen Menütyp für die Akkordeonansicht. Auf diese Weise können wir unser Layout für diese spezielle Seite wählen. Das bedeutet, dass wir das Blog-Layout überschreiben, einschließlich der XML-Datei:

blog.xml -> akkordeon.xml
blog.php -> akkordeon.php

Diese Dateien befinden sich in deinem Template Dateien. Gehe zu System -> Site Templates, suche dein Template und klicke auf den Link Details und Dateien. Klicke auf die Registerkarte TOverrides erstellen , suche com_content (unter Komponenten), klicke auf category und Joomla erstellt automatisch einen Template Override. Du finden sie unter Details und Dateien in der Seitenleiste links neben deinem Template Editor, unter html.

Es gibt ein paar Dateien, die nicht gebraucht werden, ich habe sie für dieses Tutorial entfernt: blog-item.php, blog-children.php und blog-links.php.

Accordion.xml, unsere blog.xml-Überlagerungsdatei

In der XML-Datei entfernen wir einige Parameter, die für eine FAQ nicht benötigt werden, und fügen eine Option hinzu, um das erste Element des Akkordeons als offen oder geschlossen einzustellen:

 <?xml version="1.0" encoding="UTF-8"?>
<metadata>
<layout title="Akkordeon" option="COM_CONTENT_CATEGORY_VIEW_BLOG_OPTION">
<help key = "Menu_Item:_Category_Blog" />
<message>
<![CDATA[Zeigt Beiträge einer Kategorie als Akkordeon]]>
</message>
</layout>


<!-- Add fields to the request variables for the layout. -->
<fields name="request">
<fieldset name="request"
addfieldprefix="Joomla\Component\Categories\Administrator\Field"
>

<field
name="id"
type="modal_category"
label="JGLOBAL_CHOOSE_CATEGORY_LABEL"
extension="com_content"
required="true"
select="true"
new="true"
edit="true"
clear="true"
/>

<field
name="filter_tag"
type="tag"
label="JTAG"
multiple="true"
mode="nested"
custom="deny"
/>
</fieldset>
</fields>

<!-- Add fields to the parameters object for the layout. -->
<fields name="params">
<fieldset name="basic" label="JGLOBAL_CATEGORY_OPTIONS">
<field
name="layout_type"
type="hidden"
default="blog"
/>

<field
name="show_category_title"
type="list"
label="JGLOBAL_SHOW_CATEGORY_TITLE"
useglobal="true"
class="form-select-color-state"
validate="options"
>
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>

<field
name="show_description"
type="list"
label="JGLOBAL_SHOW_CATEGORY_DESCRIPTION_LABEL"
useglobal="true"
class="form-select-color-state"
validate="options"
>
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>

<field
name="show_description_image"
type="list"
label="JGLOBAL_SHOW_CATEGORY_IMAGE_LABEL"
useglobal="true"
class="form-select-color-state"
validate="options"
>
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>

<field
name="maxLevel"
type="list"
label="JGLOBAL_MAXLEVEL_LABEL"
description="JGLOBAL_MAXLEVEL_DESC"
useglobal="true"
validate="options"
>
<option value="-1">JALL</option>
<option value="0">JNONE</option>
<option value="1">J1</option>
<option value="2">J2</option>
<option value="3">J3</option>
<option value="4">J4</option>
<option value="5">J5</option>
</field>

<field
name="show_empty_categories"
type="list"
label="JGLOBAL_SHOW_EMPTY_CATEGORIES_LABEL"
useglobal="true"
class="form-select-color-state"
validate="options"
>
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>

<field
name="show_no_articles"
type="list"
label="COM_CONTENT_NO_ARTICLES_LABEL"
useglobal="true"
class="form-select-color-state"
validate="options"
>
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>

<field
name="show_cat_tags"
type="list"
label="COM_CONTENT_FIELD_SHOW_CAT_TAGS_LABEL"
useglobal="true"
class="form-select-color-state"
validate="options"
>
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>

</fieldset>

<fieldset name="advanced" label="JGLOBAL_BLOG_LAYOUT_OPTIONS" description="JGLOBAL_SUBSLIDER_BLOG_LAYOUT_LABEL">

<field
name="num_leading_articles"
type="hidden"
default="0"
/>

<field
name="num_intro_articles"
type="number"
label="JGLOBAL_NUM_INTRO_ARTICLES_LABEL"
filter="integer"
validate="number"
min="0"
useglobal="true"
parentclass="stack span-1"
/>

<field
name="collapse_first_item"
type="radio"
layout="joomla.form.field.radio.switcher"
label="Erstes Element geöffnet"
parentclass="stack span-2-inline"
validate="options"
default="1"
>
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>

<field
name="show_featured"
type="list"
label="JGLOBAL_SHOW_FEATURED_ARTICLES_LABEL"
default=""
useglobal="true"
class="form-select-color-state"
validate="options"
parentclass="stack span-1"
>
<option value="show">JSHOW</option>
<option value="hide">JHIDE</option>
<option value="only">JONLY</option>
</field>

<field
name="show_subcategory_content"
type="list"
label="JGLOBAL_SHOW_SUBCATEGORY_CONTENT_LABEL"
useglobal="true"
validate="options"
parentclass="stack span-1-inline"
>
<option value="0">JNONE</option>
<option value="-1">JALL</option>
<option value="1">J1</option>
<option value="2">J2</option>
<option value="3">J3</option>
<option value="4">J4</option>
<option value="5">J5</option>
</field>
<field
name="orderby_pri"
type="list"
label="JGLOBAL_CATEGORY_ORDER_LABEL"
useglobal="true"
validate="options"
parentclass="stack span-2"
>
<option value="none">JGLOBAL_NO_ORDER</option>
<option value="alpha">JGLOBAL_TITLE_ALPHABETICAL</option>
<option value="ralpha">JGLOBAL_TITLE_REVERSE_ALPHABETICAL</option>
<option value="order">JGLOBAL_CATEGORY_MANAGER_ORDER</option>
</field>

<field
name="orderby_sec"
type="list"
label="JGLOBAL_ARTICLE_ORDER_LABEL"
useglobal="true"
validate="options"
parentclass="stack span-2-inline"
>
<option value="front">COM_CONTENT_FEATURED_ORDER</option>
<option value="rdate">JGLOBAL_MOST_RECENT_FIRST</option>
<option value="date">JGLOBAL_OLDEST_FIRST</option>
<option value="alpha">JGLOBAL_TITLE_ALPHABETICAL</option>
<option value="ralpha">JGLOBAL_TITLE_REVERSE_ALPHABETICAL</option>
<option value="author">JGLOBAL_AUTHOR_ALPHABETICAL</option>
<option value="rauthor">JGLOBAL_AUTHOR_REVERSE_ALPHABETICAL</option>
<option value="hits" requires="hits">JGLOBAL_MOST_HITS</option>
<option value="rhits" requires="hits">JGLOBAL_LEAST_HITS</option>
<option value="random">JGLOBAL_RANDOM_ORDER</option>
<option value="order">JGLOBAL_ORDERING</option>
<option value="rorder">JGLOBAL_REVERSE_ORDERING</option>
<option value="vote" requires="vote">JGLOBAL_VOTES_DESC</option>
<option value="rvote" requires="vote">JGLOBAL_VOTES_ASC</option>
<option value="rank" requires="vote">JGLOBAL_RATINGS_DESC</option>
<option value="rrank" requires="vote">JGLOBAL_RATINGS_ASC</option>
</field>

<field
name="order_date"
type="list"
label="JGLOBAL_ORDERING_DATE_LABEL"
useglobal="true"
validate="options"
parentclass="stack span-2-inline"
>
<option value="created">JGLOBAL_CREATED</option>
<option value="modified">JGLOBAL_MODIFIED</option>
<option value="published">JPUBLISHED</option>
<option value="unpublished">JUNPUBLISHED</option>
</field>

<field
name="show_pagination"
type="list"
label="JGLOBAL_PAGINATION_LABEL"
useglobal="true"
class="form-select-color-state"
validate="options"
parentclass="stack span-1"
>
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
<option value="2">JGLOBAL_AUTO</option>
</field>

<field
name="show_pagination_results"
type="list"
label="JGLOBAL_PAGINATION_RESULTS_LABEL"
useglobal="true"
class="form-select-color-state"
validate="options"
parentclass="stack span-1-inline"
>
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>

</fieldset>

<fieldset name="integration" label="COM_MENUS_INTEGRATION_FIELDSET_LABEL">
<field
name="show_feed_link"
type="list"
label="JGLOBAL_SHOW_FEED_LINK_LABEL"
useglobal="true"
class="form-select-color-state"
validate="options"
>
<option value="0">JHIDE</option>
<option value="1">JSHOW</option>
</field>

<field
name="feed_summary"
type="list"
label="JGLOBAL_FEED_SUMMARY_LABEL"
useglobal="true"
validate="options"
>
<option value="0">JGLOBAL_INTRO_TEXT</option>
<option value="1">JGLOBAL_FULL_TEXT</option>
</field>
</fieldset>
</fields>
</metadata>

 

Accordion.php, unsere blog.php override Datei

Die Datei accordion.php wird ebenfalls stark vereinfacht und lässt nur den Block für die Intro-Elemente übrig:

<?php
/**
* @package Joomla.Site
* @subpackage com_content
*
* @copyright (C) 2006 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\FileLayout;
use Joomla\CMS\Layout\LayoutHelper;

$app = Factory::getApplication();

$this->category->text = $this->category->description;
$app->triggerEvent('onContentPrepare', [$this->category->extension . '.categories', &$this->category, &$this->params, 0]);
$this->category->description = $this->category->text;

$results = $app->triggerEvent('onContentAfterTitle', [$this->category->extension . '.categories', &$this->category, &$this->params, 0]);
$afterDisplayTitle = trim(implode("\n", $results));

$results = $app->triggerEvent('onContentBeforeDisplay', [$this->category->extension . '.categories', &$this->category, &$this->params, 0]);
$beforeDisplayContent = trim(implode("\n", $results));

$results = $app->triggerEvent('onContentAfterDisplay', [$this->category->extension . '.categories', &$this->category, &$this->params, 0]);
$afterDisplayContent = trim(implode("\n", $results));

$htag = $this->params->get('show_page_heading') ? 'h2' : 'h1';
$htagart = $this->params->get('show_page_heading') && $this->params->get('show_category_title') ? 'h3' : 'h2';

HTMLHelper::_('bootstrap.collapse');
?>


<div class="com-content-category-blog blog">
<?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)) : ?>
<<?php echo $htag; ?>>
<?php echo $this->category->title; ?>
</<?php echo $htag; ?>>
<?php endif; ?>
<?php echo $afterDisplayTitle; ?>

<?php if ($this->params->get('show_cat_tags', 1) && !empty($this->category->tags->itemTags)) : ?>
<?php $this->category->tagLayout = new FileLayout('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')) : ?>
<?php echo LayoutHelper::render(
'joomla.html.image',
[
'src' => $this->category->getParams()->get('image'),
'alt' => empty($this->category->getParams()->get('image_alt')) && empty($this->category->getParams()->get('image_alt_empty')) ? false : $this->category->getParams()->get('image_alt'),
]
); ?>

<?php endif; ?>
<?php echo $beforeDisplayContent; ?>
<?php if ($this->params->get('show_description') && $this->category->description) : ?>
<?php echo HTMLHelper::_('content.prepare', $this->category->description, '', 'com_content.category'); ?>
<?php endif; ?>
<?php echo $afterDisplayContent; ?>
</div>
<?php endif; ?>

<?php if (empty($this->intro_items)) : ?>
<?php if ($this->params->get('show_no_articles', 1)) : ?>
<div class="alert alert-info">
<span class="icon-info-circle" aria-hidden="true"></span><span class="visually-hidden"><?php echo Text::_('INFO'); ?></span>
<?php echo Text::_('COM_CONTENT_NO_ARTICLES'); ?>
</div>
<?php endif; ?>
<?php endif; ?>

<?php if (!empty($this->intro_items)) : ?>
<?php
$count_items = 0;
$collapse_first_item = $this->params->get('collapse_first_item',0);
?>

<div class="accordion" id="accordionBlog">
<?php foreach ($this->intro_items as $key => &$item) : ?>
<?php
$canEdit = $item->params->get('access-edit');
$count_items++;
?>

<div class="accordion-item">
<<?php echo $htagart; ?> class="accordion-header">
<button id="accordionItem-<?php echo $item->id; ?>" class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-<?php echo $item->id; ?>" aria-expanded="<?php echo ($count_items == 1 && $collapse_first_item == 1) ? 'true' : 'false'; ?>" aria-controls="collapse-<?php echo $item->id; ?>">
<?php echo $this->escape($item->title); ?>
</button>
</<?php echo $htagart; ?>>
<div id="collapse-<?php echo $item->id; ?>" class="accordion-collapse collapse <?php echo ($count_items == 1 && $collapse_first_item == 1) ? 'show' : ''; ?>" data-bs-parent="#accordionBlog" role="region" aria-labelledby="accordionItem-<?php echo $item->id; ?>">
<div class="accordion-body">
<?php if ($canEdit) : ?>
<?php echo LayoutHelper::render('joomla.content.icons', ['params' => $item->params, 'item' => $item]); ?>
<?php endif; ?>

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

<?php // Content is generated by content plugin event "onContentBeforeDisplay" ?>
<?php echo $item->event->beforeDisplayContent; ?>
<div class="item-content">
<?php echo $item->introtext; ?>
<?php echo $item->fulltext; ?>
</div>
<?php // Content is generated by content plugin event "onContentAfterDisplay" ?>
<?php echo $item->event->afterDisplayContent; ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>

<?php // Code to add a link to submit an article. ?>
<?php if ($this->category->getParams()->get('access-create')) : ?>
<?php echo HTMLHelper::_('contenticon.create', $this->category, $this->category->params); ?>
<?php endif; ?>
<?php if (($this->params->def('show_pagination', 1) == 1 || ($this->params->get('show_pagination') == 2)) && ($this->pagination->pagesTotal > 1)) : ?>
<div class="com-content-category-blog__navigation w-100">
<?php if ($this->params->def('show_pagination_results', 1)) : ?>
<p class="com-content-category-blog__counter counter float-md-end pt-3 pe-2">
<?php echo $this->pagination->getPagesCounter(); ?>
</p>
<?php endif; ?>
<div class="com-content-category-blog__pagination">
<?php echo $this->pagination->getPagesLinks(); ?>
</div>
</div>
<?php endif; ?>
</div>;
?>

In meinem Beispiel ist die Darstellung der Fragen/Antworten minimalistisch: der Titel des Beitrags steht für die Frage, der Inhalt ist die Antwort.
Das übergeordnete div-Element, das alle Artikel enthält, erhält die Klasse „accordion“ (das ist die Bootstrap-Klasse, die die Magie bewirkt) und die ID „accordionBlog“. Jeder Artikel ist ein „accordion-item“, ebenfalls eine Bootstrap-Klasse.

Der Titel des Artikels ist in einem Button-Element enthalten, das mehrere Attribute benötigt:

  • id: eindeutiger Bezeichner für das Element (wird später verwendet)
  • class: Akkordeon-Button, Bootstrap-Styling
  • data-bs-umschalten: Bootstrap-Attribut, das vom Javascript verwendet wird
  • data-bs-target: Bootstrap-Attribut, das vom Javascript verwendet wird, der Wert ist die ID des nächsten Elements (siehe unten)
  • aria-expanded: zusätzliche Informationen für unterstützende Technologien (z.B. Screenreader)
  • aria-controls: zusätzliche Informationen für unterstützende Technologien (z.B. Screenreader), der Wert ist die ID des nächsten Elements (siehe unten)

Das Attribut „aria-expanded“ hat den Wert „false“ und die Fragen sind standardmässig geschlossen. Mit einer Einstellung im Menüpunkt wird gesteuert, ob das erste Element geöffnet sein soll oder nicht. Wenn es sich um das erste Element handelt und es als offen definiert ist, dann hat „aria-expanded“ den Wert „true“:

<?php id="collapse-<?php echo $item->id; ?>"; ?>

Der Inhalt des Beitrags (die Antwort) befindet sich in einem anderen div-Element mit mehreren Attributen:

  • id: eindeutiger Bezeichner für das Element, der mit dem „data-bs-target“ der Schaltfläche übereinstimmt. Um die id eindeutig zu machen, verwende ich die id des Artikels:

<?php id="collapse-<?php echo $item->id; ?>"; ?>

  • class: accordion-collapse collapse und zusätzlich anzeigen, ob das erste Element standardmässig geöffnet sein soll, Bootstrap Styling
  • data-bs-parent: verweist auf die ID des übergeordneten Elements (accordionBlog)
  • region: erzeugt eine Landmark-Region, die das aktuell aufgeklappte Akkordeon-Panel enthält
  • aria-labelledby: zusätzliche Information für assistive Technologien (z.B. Screenreader), definiert den zugänglichen Namen für das region-Element (in unserem Fall der Titel des Artikels)

Jetzt brauchen wir eine Kategorie „FAQ“ mit den entsprechenden Beiträgen. Dann erstellen wir den Menüpunkt:

menu

Das Resultat sieht nun so aus:
result
Und hier kann man es „live“ auf der Cassiopeia-Demoseite von Viviana Menzel ansehen.

Du brauchst keine einzige Zeile CSS oder Javascript zu schreiben. Die von Joomla mitgelieferte Bootstrap-Bibliothek übernimmt die ganze Arbeit für dich.
Ein weiterer Vorteil dieser Methode, eine FAQ zu erstellen, ist, dass leicht neue Fragen hinzugefügt/entfert werden können, indem neue Beiträge im Backend erstellt oder nicht veröffentlicht werden Man kann auch die Reihenfolge der Fragen ändern, indem die Einstellungen im Menüpunkt geändert oder die Artikel verschoben werden.
Ich hoffe, ich konnte wieder einmal zeigen, dass Joomla ein sehr vielseitiges System ist und mit kleinen Änderungen im Code kann man fast alles erstellen!

Dieser Artikel enthält zugegebenermassen ein bisschen viel Code, und es wäre eine Zumutung das alles zum Ausprobieren und Nachstellen herauskopieren zu müssen. Deshalb können die beiden erwähnten Dateien accordion.xml und accordion.php hier im ZIP-Format heruntergeladen werden. In der XML sind ein paar Dinge von mir (Chris) eingedeutscht worden.


Über die Autorin dieses Artikels

viviana menzelDieser Beitag wurde im Original von Viviana Menzel verfasst. Seit 2008 erstellt Viviana Webseiten mit Joomla. Seit 2014 engagiert sie sich ehrenamtlich in der Joomla-Community und ist mittlerweile in verschiedenen Teams (Maintainer, Accessibility, Events) aktiv. Viviana schreibt gerne Artikel für das Joomla Magazin und andere Blogs. Seit 2019 ist sie zudem (Mit-)Organisator des JoomlaDay Germany / D-A-CH.