Akkordeons verschachteln

Aus Contao Community Documentation


betrifft
Contao Version ab 2.9


Einleitung

Ein verschachteltes Akkorden

Gelegentlich wäre es schön, wenn man in einem äußeren Akkordeon als Inhaltselemente auch innere Akkordeons haben könnte (siehe Forumsthread Akkordeons im Akkordeon). Auf diese Weise könnte man z.B. FAQ's mit Akkordeons realisieren:

Der im Forum gezeigte MooTools JavaScript Code hat unter bestimmten Umständen nicht funktioniert; auch war es unbefriedigend, die inneren Akkordeon Elemente via HTML Inhalts-Elementen eingeben zu müssen. Der Diskussionsthread ist jedoch alt; MooTools und TypoLight/Contao haben sich seither weiter entwickelt. Dennoch Dank an Max und Sebastian für ihre Hinweise/Tips.

Hinweis: für eine FAQ als verschachteltes Akkordeon gibt es inzwischen die Erweiterung faqaccordion.

Editieren im Backend

Eingabe der Klassen 1. Ebene im BE

In Contao 2.9.5 sind verschachtelte Akkordeons (2 Ebenen) recht einfach, wenn man so vorgeht:

  • die erste Akkordeon-Ebene wird (unbedingt/selbstverständlich) als "Klammer" angelegt (Inhaltselement Akkordeon in Betriebsart Umschlag Anfang [### ACCORDION WRAPPER START ###] bzw. Betriebsart Umschlag Ende [### ACCORDION WRAPPER END ###])
  • in beiden Klammer-Elementen werden die Akkordeon-Klassen "toggler_outer" und "accordion_outer" eingetragen
  • die zweite Akkordeon-Ebene (zwischen den äußeren Umschlag-Elementen) wird ganz normal mit weiteren Akkordeons eingegeben, die die Standard-Klassen "toggler" und "accordion" bekommen (Klassenfelder leer lassen). Das dürfen normale Akkordeons sein (Betriebsart Einzelnes Element) oder ebenfalls Umschlag Anfang/Ende, wenn man dazwischen mehrere Inhaltselemente benötigt.

Der Zusatzaufwand der speziellen Klassen ist also nur bei der ersten Ebene erforderlich, deren Anzahl ja typisch viel geringer ist als die Anzahl der Akkordeons in der zweiten Ebene. Der Hauptteil der Arbeit ist also ganz normal zu bewerkstelligen.

Code/Template

Damit das verschachtelte Akkordeon richtig funktioniert, muss man MooTools nun noch die beiden neuen Akkordeon-Klassen beibringen. Dazu erzeugt man im Template-Verzeichnis eine Datei "moo_accordion_nested.tpl" und wählt im Seitenlayout dies neue Template anstatt des Core "moo_accordion.tpl". Der Code des neuen Templates:

<script type="text/javascript">
<!--//--><![CDATA[//><!--
window.addEvent('domready', function()
{
  var heightValue = window.ie6 ? '100%' : '';
 
  this.accordion_outer = new Accordion($$('div.toggler_outer'), $$('div.accordion_outer'),
  {
    display: -1,
    alwaysHide: true,
    opacity: false,
 
    onActive: function(toggler, element)
    {
      toggler.addClass('active');
      toggler.removeClass('inactive');
      if (toggler.getFirst()) {
        toggler.getFirst().addClass('active');
        toggler.getFirst().removeClass('inactive');
      }
    },
 
    onBackground: function(toggler, element)
    {
      toggler.removeClass('active');
      toggler.addClass('inactive');
      if (toggler.getFirst()) {
        toggler.getFirst().removeClass('active');
        toggler.getFirst().addClass('inactive')
      }
    },
 
    onComplete: function()
    {
      var element = $(this.elements[this.previous]);
      if (element && element.offsetHeight > 0)
      {
        element.setStyle('height', heightValue);
      }
    }
  });
 
  this.accordion = new Accordion($$('div.toggler'), $$('div.accordion'),
  {
    display: -1,
    alwaysHide: true,
    opacity: false,
 
    onActive: function(toggler, element)
    {
      toggler.addClass('active');
      toggler.removeClass('inactive');
      if (toggler.getFirst()) {
        toggler.getFirst().addClass('active');
        toggler.getFirst().removeClass('inactive');
      }
    },
 
    onBackground: function(toggler, element)
    {
      toggler.removeClass('active');
      toggler.addClass('inactive');
      if (toggler.getFirst()) {
        toggler.getFirst().removeClass('active');
        toggler.getFirst().addClass('inactive')
      }
    },
 
    onComplete: function()
    {
      var element = $(this.elements[this.previous]);
      if (element && element.offsetHeight > 0)
      {
        element.setStyle('height', heightValue);
      }
    }
  });
});
//--><!]]>
</script>

Erläuterungen zum Code

Die beiden Hauptabteilungen sind weitgehend identisch, nur die Resultate und Parameter für new Accordion(...) unterscheiden sich. Der Code für die Events onActive und onBackground ist im Prinzip bekannt von "moo_accordion_active.tpl" (von xchs im Forum bzw. Peter Müllers Buch Kapitel 10.2.6).

Allerdings wurde eine Unschönheit beseitigt: die div.toggler bekommen jetzt immer die Klassen "in/active". Gibt es ein HTML Element innerhalb des div, dann bekommt es diese Klassen ebenfalls/zusätzlich. Im Original Code war das ein entweder/oder ... das führte aber in manchen Fällen (siehe Bild) dazu, dass ein schlichtes <b> die Klassen bekam und das +/- Symbol des div.toggler nicht umschaltete. Der ursprüngliche Code ging offenbar davon aus, dass der Text innerhalb des div.toggler komplett von einem Tag (z.B. H1-7) umschlossen ist.

Der Event onComplete ist nötig, damit die Akkordeons zuverlässig in korrekter Höhe angezeigt werden, besonders im Fall, dass beide Ebenen völlig zugeklappt und dann wieder geöffnet werden. Gefunden in diesem Blog.

Der JS Code oben klappt initial beide Akkordeon-Ebenen zu und erlaubt auch aktives Zuklappen durch den Besucher. Will man das anders haben, dann kann man die beiden Optionen "display:" bzw. "alwaysHide:" verändern:

  • display: Vorgabe ist -1 => alles zugeklappt. Ändern auf 0, 1, 2 ... um das 1., 2., 3. ... Akkordeon von vornherein aufzuklappen. Sinnvoll wäre etwa display: 0 oben (toggler_outer/accordion_outer) um das erste Element der äußeren Ebene zu öffnen und die zweite Ebene geschlossen zu halten.
  • alwaysHide: Vorgabe ist true => erlaubt das aktive Zuklappen. Ändern auf false, um das aktive Zuklappen zu verhindern.

Getestet wurde das in allen gängigen Browsern:

  • Opera 11.11 (XP, W7)
  • FireFox 3.6.17 (XP)
  • FireFox 4.0.1 (W7)
  • Safari 5.0.5 (XP, W7)
  • Chrome 11.0.696.77 (XP, W7)
  • IE 8.0.6001.18702 (XP)
  • IE 9.0.8112.16421 (W7)
  • Mac (diverse Browser, Liste folgt)

Auch einfache, unverschachtelte Akkordions sind weiterhin möglich, der zusätzliche onComplete Event richtet keinen Schaden an. Man benötigt also kein spezielles Seitenlayout für die verschachtelten Akkordeons.

Verbesserung: Akkordeons geöffnet halten

Ein generelles Problem mit Akkordeons ist, dass sie immmer in der Voreinstellung (z.B. alles zugeklappt) erscheinen, wenn man die Seite kurz verläßt, etwa um einem Link im Akkordeontext zu folgen, und wieder zurückkehrt (auch mit Browser Back Button). Bei verschachtelten Akkoredeons ist das besonders nervig, weil die Gefahr besteht, dass der Besucher nicht mehr genau weiss, wo er war, als er dem Link folgte. Dann muss er mühsam mit mehreren Klicks seinen Kontext wieder finden ... sehr frustrierend.

Ein weiteres Scenario, in dem das Zuklappen höchst unfreundlich zum Besucher ist: hat man ein Formular in einem Akkordeon-Element und ist die Eingabe des Besuchers nicht korrekt, dann wird die Seite erneut aufgerufen. Eigentlich, um die Fehler anzuzeigen; da aber das Akkordeon automatisch zuklappt bzw. irgend etwas anderes aufklappt, kann der Besucher die Meldungen nicht sehen und das andere Auf/Zuklappen signalisiert eher: "ist alles gut gegangen". Erst ein aktives wieder Aufklappen des Formulars durch den Besucher zeigt ihm, dass das Formular nicht erfolgreich gesendet werden konnte.

Um die Akkordeons einer Seite wieder so zu öffnen, wie der Besucher sie verlassen hat, ist es nötig, den Zustand irgenwo zu speichern. Dazu bieten sich, wie oft, Cookies an.

Code der Verbesserung

Gezeigt wird hier nur das diff (leider erlaubt das Wiki weder *.zip noch *.txt als Anhang).

--- moo_accordion_act_nested.tpl        2011-06-14 18:55:54.735250000 +0200
+++ moo_accordion_act_nested_cookie.tpl 2011-06-25 18:02:21.339125000 +0200
@@ -3,10 +3,12 @@
 window.addEvent('domready', function()
 {
   var heightValue = window.ie6 ? '100%' : '';
+  var cookieBase = $$('div.toggler')[0] ?
+      'acc_' + $$('div.toggler')[0].getParent('div.mod_article').getProperty('id') : 'acc_x';
+  var maybeScroll = Cookie.read(cookieBase + '_inner') ? 'inner' : 
+                    Cookie.read(cookieBase + '_outer') ? 'outer' : false;
 
   this.accordion_outer = new Accordion($$('div.toggler_outer'), $$('div.accordion_outer'),
   {
-    display: -1,
+    display: Cookie.read(cookieBase + '_outer') ? parseInt(Cookie.read(cookieBase + '_outer')) : -1,
     alwaysHide: true,
     opacity: false,
 
@@ -37,12 +39,39 @@
       {
         element.setStyle('height', heightValue);
       }
+      Cookie.dispose(cookieBase + '_outer');
+      var toggler = this.togglers[this.previous];
+      if (element && toggler && toggler.hasClass('active'))
+      {
+        Cookie.write(cookieBase + '_outer', this.previous);
+        if (maybeScroll == 'outer')
+        {
+          maybeScroll = false;
+
+          var wc = {top: window.getScroll().y, height: window.getSize().y};
+          var tc = toggler.getCoordinates();
+          var ec = element.getCoordinates();
+          var sc =
+          {
+            top:    Math.max(0, tc.top - 20),
+            height: Math.min(tc.height + ec.height + 40, wc.height)
+          };
+          if (sc.top < wc.top)
+          {
+            new Fx.Scroll($(document.body)).start(0, sc.top);
+          }
+          else if (sc.top + sc.height > wc.top + wc.height)
+          {
+            new Fx.Scroll($(document.body)).start(0, sc.top + sc.height - wc.height);
+          }
+        }
+      }
     }
   });
 
   this.accordion = new Accordion($$('div.toggler'), $$('div.accordion'),
   {
-    display: -1,
+    display: Cookie.read(cookieBase + '_inner') ? parseInt(Cookie.read(cookieBase + '_inner')) : -1,
     alwaysHide: true,
     opacity: false,
 
@@ -73,6 +102,33 @@
       {
         element.setStyle('height', heightValue);
       }
+      Cookie.dispose(cookieBase + '_inner');
+      var toggler = this.togglers[this.previous];
+      if (element && toggler && toggler.hasClass('active'))
+      {
+        Cookie.write(cookieBase + '_inner', this.previous);
+        if (maybeScroll == 'inner')
+        {
+          maybeScroll = false;
+
+          var wc = {top: window.getScroll().y, height: window.getSize().y};
+          var tc = toggler.getCoordinates();
+          var ec = element.getCoordinates();
+          var sc =
+          {
+            top:    Math.max(0, tc.top - 20),
+            height: Math.min(tc.height + ec.height + 40, wc.height)
+          };
+          if (sc.top < wc.top)
+          {
+            new Fx.Scroll($(document.body)).start(0, sc.top);
+          }
+          else if (sc.top + sc.height > wc.top + wc.height)
+          {
+            new Fx.Scroll($(document.body)).start(0, sc.top + sc.height - wc.height);
+          }
+        }
+      }
     }
   });
 });

Erläuterungen zur Verbesserung

Da man auf verschiedenen Seiten des Auftritts Akkordeons haben kann, muss dafür gesorgt werden, dass jede dieser Seiten eigene Cookies erhält. Dazu wird ganz am Anfang die CSS ID des Artikel-DIVs herangezogen, in dem sich das Akkordeon befindet (var cookieBase). Diese ID ist (soweit ich sehen kann) immer gesetzt: es ist das Artikel-Alias.

Direkt danach wird geprüft, ob/welche geöffnete Akkordeon-Ebene (durch Scroll) "in Sicht" sein soll. Der Scroll-Hinweis wird später aber nur beachtet, wenn die Seite neu angezeigt wird, nicht bei jedem Klick auf einen Toggler (var maybeScroll). Vorrang hat jedenfalls ein geöffnetes Akkordeon der inneren Ebene.

Die Option display: wird ergänzt, so dass zunächst geprüft wird, ob ein Cookie für das betreffende Akkordeon gesetzt ist. Der Wert des Cookies ist eine Integerzahl 0 bis Anzahl Elemente - 1 und das entsprechende Element wird geöffnet. Nur wenn das Cookie nicht gesetzt ist, wird die Vorgabe verwendet (hier -1 für zugeklappt). Das Cookie ist nicht gesetzt beim ersten Besuch oder wenn das Akkordeon aktiv zugeklappt wurde.

Das Cookie wird im Event onComplete gelöscht bzw. neu gesetzt, abhängig davon, ob das Element geöffnet ist (Toggler hat die CSS-Klasse active) oder nicht. Dabei werden für äußere und innere Akkordeon-Elemente unterschiedliche (Session-)Cookies gesetzt.

Schließlich wird im Block if (maybeScroll == 'xxx') geprüft, ob das Element bereits sichtbar ist und, falls nicht, wird das Element und sein Toggler (die Bereichsüberschrift im BE) weich/animiert in Sicht gerollt. Die länglichen Berechnungen sorgen dafür, dass das Element nur gerade eben sichtbar wird (+20px oben/unten). War es ganz oder teilweise unterhalb des sichtbaren Bereichs, dann kommt es von unten herein, bis seine Unterkante sichtbar wird. War es ganz oder teilweise oberhalb, kommt es von oben, bis seine Oberkante sichtbar wird. Ist das Element höher als die Höhe des sichtbaren Bereichs, dann wird jedenfalls die Oberkante sichtbar.

Auch die Verbesserung funktioniert sowohl mit einfachen, wie auch mit verschachtelten Akkordeons.

--Deerwood 19:51, 23. Jun. 2011 (CEST)

Ansichten
Meine Werkzeuge

Contao Community Documentation

Atari Teenage Riot ist eine Mischung aus singen, schreien und sich übergeben.

Leo Unglaub
Navigation
Verstehen
Verwenden
Entwickeln
Verschiedenes
Werkzeuge