Für grafische Bedienoberflächen von Apps wünschen Benutzer häufig ein Baum-Element zum Navigieren über hierarchische Strukturen. Dieser Beitrag beschäftigt sich mit der Frage, wie ein solches Baum-Element mit Web-Mitteln hergestellt werden kann.

Baumstruktur

Eine Baumstruktur kann mit einfachen Einrückungen dargestellt werden. Je tiefer ein Element sich in der Hierarchie befindet, desto weiter ist es vom linken Rand nach rechts eingerückt. Elemente einer Baum-Struktur können Knoten oder Blätter sein, die beiden Elementtypen sind in der Baum-Struktur unterschiedlich gekennzeichnet.

Knoten enthalten weitere Elemente, Blätter nicht. Für einen geöffneten Knoten sind die Elemente, die der Knoten enthält, eine Ebene tiefer eingerückt sichtbar. Geschlossene Knoten zeigen die in ihnen enthaltenen Elemente nicht.

Jedes Element des Baums besteht aus einem Piktogramm und einem Bezeichnungstext.

200

Darstellung mit Web-Mitteln

Damit eine Baumstruktur im Web dargestellt werden kann, müssen Mittel verwendet werden, die in einem Webbrowser funktionieren: HTML, CSS und JavaScript. Zudem ist eine effiziente Form der Verwendung von Piktogrammen erforderlich. Nachfolgend die eingesetzten Mittel in der Übersicht.

HTML

Die Baumstruktur wird als Hierarchie von <div>-Elementen beschrieben. Ein <div> kann beliebige weitere <div> enthalten und so beliebige Hierarchien 1:1 abbilden.

CSS

Alle Gestaltungsvorgaben können in Form eines Cascading Style Sheets (CSS) deklariert werden. Hier werden bspw. die Einrückung von Elementen und die Schriftart für Piktogramme deklariert.

JavaScript

JavaScript-Funktionen reagieren auf Aktionen des Benutzers. Sie sorgen dafür, dass Baumelemente beim Klick geöffnet und geschlossen, dass Piktogramme an den Zustand von Elemente angepasst und ggf. Inhalte geladen werden.

Piktogramme

Für Piktogramme im Web ist die Nutzung von Schriftarten besonders gut geeignet. Die Einzelheiten der Vorgehenseweise für Piktogramm beschreibt der Artikel Piktogramme im Web nutzen. Für eine Baumstruktur werden drei Symbole benötigt:

  1. Blatt,

  2. aufgeklappter Knoten und

  3. geschlossener Knoten

Diese Symbole werden mit Hilfe von Fontello als Schriftart erzeugt und eingebunden.

Herstellung der Baum-Anzeige

Die Baum-Darstellung wird aus einer Kombination aus HTML und CSS gebaut. In der nachfolgend beschriebenen statischen Variante sind Struktur und Inhalt im HTML vermischt.

Struktur und Inhalt

Eine HTML-Seite für unsere Baumstruktur sieht zum Beispiel wie folgt aus.

Beispiel für eine HTML-Seite mit einer Baumstruktur
<!DOCTYPE html>
<html>
  <head>
    <title>Baum-Demo</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalabe=no">
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <link rel="stylesheet" type="text/css" href="stile.css">
  </head>
  <body>
    <div>
      <h4>Baum-Demo</h4>
    </div>

    <div class="baum">
      <div class="knoten">
        <i class="icon-folder-open"></i>
        1: Knoten
        <div class="knoten">
          <i class="icon-folder-open"></i>
          1.1: Knoten
          <div class="blatt">
            <i class="icon-doc-inv"></i>
            1.1.1: Blatt
          </div>
        </div>
        <div class="blatt">
          <i class="icon-doc-inv"></i>
          1.2: Blatt
        </div>
        <div class="blatt">
          <i class="icon-doc-inv"></i>
          1.3: Blatt
        </div>
        <div class="knoten" data1="irgendwas" data2="nochwas">
          <i class="icon-folder"></i>
          1.4: Knoten
        </div>
      </div>
      <div class="blatt">
        <i class="icon-doc-inv"></i>
        2: Blatt
      </div>
    </div>
  </body>
</html>

Im obigen Beispiel ist der Baum als ineinander verschachtelte Struktur aus <div>-Elementen beschrieben. Wie bei HTML üblich sind Struktur und Inhalt miteinander vermischt. Dieser Umstand wird im weiteren Verlauf des Artikels weiter verfolgt. Zunächst aber soll der Inhalt gestaltet werden.

Gestaltung

Für die Darstellung von Piktogrammen wird die Datei pikto.ttf eingebunden, die mit Hilfe von Fontello erzeugt wurde (vgl. Piktogramme im Web nutzen).

CSS-Deklaration der Schriftart für Piktogramme
@font-face {
  font-family: 'pikto';
  src: url('font/pikto.ttf?10878602') format('truetype');
  font-weight: normal;
  font-style: normal;
}

Zur Anzeige der Piktogramme sind die folgenden zusätzlichen CSS-Angaben nötig. Diese werden von Fontello mit erzeugt und können ins eigene Stylesheet einfach per Cut and Paste übernommen werden.

CSS-Angaben zur Darstellung von Piktogrammen
[class^="icon-"]:before, [class*=" icon-"]:before {
  font-family: "pikto";
  font-style: normal;
  font-weight: normal;
  speak: never;
  display: inline-block;
  text-decoration: inherit;
  width: 1em;
  margin-right: .2em;
  text-align: center;
  font-variant: normal;
  text-transform: none;
  line-height: 1em;
  margin-left: .2em;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.icon-doc:before { content: '\e800'; }
.icon-folder-open:before { content: '\e801'; }
.icon-folder:before { content: '\e802'; }
.icon-folder-empty:before { content: '\f114'; }
.icon-folder-open-empty:before { content: '\f115'; }
.icon-doc-inv:before { content: '\f15b'; }

Nun fehlen noch die Einstellungen zur Einrückung der Baumelemente sowie deren Farbgebung. Diese sind recht überschaubar und lauten wie folgt.

CSS-Deklaration der Einrückung und Farbgebung von Baumelementen
.knoten, .blatt {
  padding: 0 0 0 0.5em;
}

.icon-folder, .icon-folder-open {
  color: navajowhite;
}

.icon-doc-inv {
  color: lightgray;
}

Für die pure Darstellung einer Baumstruktur haben wir nun alles beisammen. Die fertige Baumstruktur im Zusammenspiel von HTML und CSS sieht dann aus wie im hier dargestellten Bildschirmfoto.

200

Die obige Darstellung kann mit der Demoseite für eine statische Baumdarstellung auch direkt im Browser aufgerufen werden.

Steuerung

Um die zuvor beschriebene statische Baumdarstellung mit Benutzeraktionen steuern zu können kommt JavaScript hinzu. Zur Steuerung des Baums zählen Funktionen wie beispielsweise das Ermitteln des angeklickten Elements, das Öffnen und Schließen von Baum-Knoten oder das Laden und Darstellen von Inhalten. Für deren Umsetzung kommt JavaScript zum Einsatz und an dieser Stelle sollen einige Funktionen beispielhaft betrachtet werden.

Als Behälter der dynamisch per Code gesteuerten Baumvariante dient eine HTML-Seite, die ohne statische Deklaration einer Baumstruktur auskommt.

eine HTML-Seite zum dynamischen Einbau einer Baumstruktur
<!DOCTYPE html>
<html>
  <head>
    <title>Baum-Demo</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalabe=no">
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <link rel="stylesheet" type="text/css" href="stile.css">
  </head>
  <body>
    <div>
      <h4>Baum-Demo</h4>
    </div>
    <div class="baum"></div>
    <script src="mustache.min.js"></script>
    <script src="baum-beispiel.js"></script>
    <script>
      document.addEventListener('DOMContentLoaded', function () {
          baum_init();
      });
    </script>
  </body>
</html>

Wie im obigen Beispiel zu sehen, sind anstelle der statischen Baumstruktur Verweise zu Skripten hinzugekommen, auf deren Funktion nachfolgend näher eingegangen wird.

Daten als Baum darstellen

Ein häufiger Fall beim Bestücken einer Baumstruktur ist der Abruf von Daten vom Server wie am folgenden Beispiel der eingebundenen Skriptdatei baum-beispiel.js zu sehen ist.

Abruf von Daten und Darstellung als Baum
function baum_init() {
  http_get('liste.json', function(antwort) {
      html_erzeugen(vorlage, JSON.parse(antwort), function(html) {
          var baumdiv = document.querySelector('.baum');
          baumdiv.appendChild(html_to_element(html));
          addEvtListener('.baum-elem', 'click', baum_klick);
      });
  });
}

function http_get(callurl, cb) {
  //http_call('GET', u, null, cb);
  var xhr = new XMLHttpRequest();
  xhr.onreadystatechange = function () {
    if (this.readyState === 4 && this.status === 200) {
      cb(this.responseText);
    }
  };
  xhr.open('GET', callurl);
  xhr.setRequestHeader('Content-type', 'application/text');
  xhr.send();
}

Die Funktion baum_init simuliert die Anfrage bei einer REST API mit dem Abruf einer JSON-Struktur liste.json. Deren Inhalt ist wie folgt beschaffen.

Inhalt von liste.json
{
    "pfad":"/meine-app/cms/",
    "dateien":[
        {
            "name":"Ein Ordner",
            "typ":"ordner",
            "typKlasse":"icon-folder"
        },{
            "name":"index.html",
            "typ":"datei",
            "typKlasse":"icon-doc-inv"
        },{
            "name":"Noch ein Ordner",
            "typ":"ordner",
            "typKlasse":"icon-folder"
        },{
            "name":"Ordner Nr. 3",
            "typ":"ordner",
            "typKlasse":"icon-folder"
        }
    ]
}

Die Antwort dieses Aufrufes wird einer weiteren Funktion html_erzeugen übergeben, die aus der JSON Struktur unsere Baumstruktur erzeugt. Hierzu wird mit Hilfe der JavaScript-Bibliothek Mustache aus der Vorlage baum-inhalt.mustache der HTML-Code generiert.

Die Vorlage baum-inhalt.mustache
<div class="baum-zweig" nodepath="{{pfad}}">
{{#dateien}}
  <div class="baum-elem">
    <i class="baum-pikto {{typKlasse}}"></i>
    <span class="baum-text">{{name}}</span>
  </div>
{{/dateien}}
</div>

Die Funktion html_erzeugen ist im Artikel Mit Mustache zur dynamischen Single-Page App auf heise Developer näher erläutert. Mit ihrer Hilfe entsteht der obige HTML-Code für jedes Element der JSON-Datenstruktur. Der fertige HTML-Code wird schließlich dem Element mit der Klassenbezeichnung baum angefügt.

Daten dem Baum hinzufügen
var baumdiv = document.querySelector('.baum');
baumdiv.appendChild(html_to_element(html));
addEvtListener('.baum-elem', 'click', baum_klick);

Auf diese Weise wird dynamisch per JavaScript ein erster Inhalt der HTML-Seite hinzugefügt. Der Vorgang wird mit folgendem Aufruf aus der HTML-Seite heraus ausgelöst.

Aktivieren des Skripts von der HTML-Seite aus
document.addEventListener('DOMContentLoaded', function () {
  baum_init();
});

Er bewirkt, dass gleich zu Beginn der Inhalt vom Server als Baum auf der Seite erscheint.

Klicks verarbeiten

Eine zentrale Funktion zur Steuerung der Baum-Darstellung ist das Verarbeiten von Klicks des Benutzers auf Elemente des Baums. Hierfür lassen sich viele Funktionen denken, die das Verhalten eines Baums abbilden können, von denen für diesen Beitrag nur ein Teil beispielhaft betrachtet wird. Zuvor aber eine allgemeine Beschreibung der normalerweise üblichen Fälle im 'Verhalten' einer Baumstruktur bei Klicks des Benutzers.

  1. Klick auf Piktogramm:

    1. Ordner-Element geklickt:

      1. Ordner ist geschlossen: Ordner öffnen und Inhalt darstellen

      2. Ordner ist geöffnet: Ordner schließen und Inhalt verbergen

    2. Datei-Element geklickt: Keine Aktion notwendig

  2. Klick auf Text:

    1. Element ist noch nicht ausgewählt: Geklicktes Element als ausgewählt markieren, Markierung des bisher gewählten Elements entfernen

    2. Element ist als ausgewählt markiert:

      1. Ausgewählte Datei geklickt: Aktion für Datei ausführen, z.B. Datei öffnen

      2. Ausgewählter Ordner geklickt: Keine Aktion notwendig

Element ermitteln

Für alle zuvor erwähnten Fälle muss zunächst das Element ermittelt werden, das geklickt wurde. Folgende Funktion kann dazu dienen.

Ermitteln des geklickten Baumelements
function baum_klick(event) {
    var t = event.target;
    var bereich = t.nodeName;
    if(bereich.toLowerCase() === 'i') {
        baum_pikto_klick(t);
    } else if(bereich.toLowerCase() === 'span') {
        baum_text_klick();
    } else {
        // tue nichts
    }
}

Die Funktion orientiert sich einfach an den Namen der HTML-Elemente. Ein i-Tag markiert ein Piktogramm, ein span-Tag einen Element-Text. Die Funktion baum_pikto_klick verarbeitet daraufhin den Klick, wenn er auf ein Piktogramm erfolgte.

Klick auf Piktogramm verarbeiten
function baum_pikto_klick(iElem) {
    var typ = baum_element_typ(iElem);
    switch(typ) {
        case 1:
            // Klick auf geschlossenen Ordner
            baum_pikto_wechseln(iElem, 'icon-folder', 'icon-folder-open');
            var c = iElem.parentNode.querySelector('.baum-zweig');
            if(c === undefined || c === null) {
                var neue = baum_elemente();
                html_erzeugen(vorlage, neue, function(html) {
                  iElem.parentNode.appendChild(html_to_element(html));
                });
            } else {
                baum_toggle_children(iElem.parentNode, 'block');
            }
            break;
        case 2:
            // Klick auf geoeffneten Ordner
            baum_pikto_wechseln(iElem, 'icon-folder-open', 'icon-folder');
            baum_toggle_children(iElem.parentNode, 'none');
            break;
        case 3:
            // Klick auf Datei
            break;
    }
}

Es wird zunächst mit der Hilfsfunktion baum_element_typ ermittelt, um welches Piktogramm es sich handelt. Anschließend wird die zum jeweiligen Fall passende Funktion ausgeführt: Ordner öffnen und Inhalt darstellen oder Ordner schließen und Inhalt verbergen.

Baum-Elemente hinzufügen

Wenn ein Ordner zum ersten Mal geöffnet wird, würde normalerweise eine Funktion zunächst dessen Inhalt vom Server abholen, wie es weiter oben bereits beispielhaft beschrieben wurde. Zur Vereinfachung werden in diesem Beispiel mit der folgenden Funktion einfach Elemente auf der Client-Seite erzeugt.

Beispiel für das Erzeugen von Baum-Elementen aus Daten, die auf dem Client erzeugt werden
function baum_elemente() {
  var dateien = new Array();
  dateien.push(new Datei('datei.txt', 'datei', 'icon-doc-inv'));
  dateien.push(new Datei('Ordner', 'ordner', 'icon-folder'));
  dateien.push(new Datei('index.html', 'datei', 'icon-doc-inv'));
  dateien.push(new Datei('stile.css', 'datei', 'icon-doc-inv'));
  var elem = new BaumElem('/meine-app/cms/test', dateien);
  return elem;
}

Die obige Funktion verwendet dazu Objekte, die so strukturiert sind wie die Daten, die normalerweise vom Server kommen. Sie sind wie folgt angelegt.

Objekte, die die Server-Datenstruktur nachbilden
function BaumElem(p, d) {
    this.pfad = p;
    this.dateien = d;
}

function Datei(n, t, k) {
    this.name = n;
    this.typ = t;
    this.typKlasse = k;
}

Auf diese Weise kann die schon erwähnte Vorlage zum Erzeugen von HTML unverändert zum Einsatz kommen wie in folgendem Code.

Daten mit Hilfe von Mustache dem geklickten Element hinzufügen
var neue = baum_elemente();
html_erzeugen(vorlage, neue, function(html) {
  iElem.parentNode.appendChild(html_to_element(html));
});

Demo und Code der Lösung

Es lassen sich viele Arten der Umsetzung von Baumfunktionen denken, auch abhängig vom jeweiligen Anwendungsfall empfehlen sich hier unterschiedliche Implementierungen. Die Code-Beispiele in diesem Beitrag greifen repräsentativ für diesen Bereich einige Standardfälle auf und zeigen, dass selbst die Programmierung der Baum-Komponente in JavaScript nur jeweils wenige Zeilen Code umfasst. Im Grunde kann jeder in die Tasten greifen und hat die gewünschten Funktionen schnell geschrieben.

Die hier vorgestellte Variante einer Baum-Komponente kann als Ganzes auf der Demoseite ausprobiert werden. Der Code dazu ist im Repository verfügbar.

Fazit

Werden die elementaren Bestandteile einer Webseite in HTML und CSS um eine Steuerung aus JavaScript-Funktionen ergänzt, ist Mustache der fehlende Puzzlestein, der ein dynamisches Steuerelement ergibt. Mit Piktogrammen, die mit Hilfe von Fontello zu einer Schriftart verpackt werden, lässt sich zudem die gewünschte Gestaltung erzielen. Der aufeinander abgestimmte Einsatz der Bestandteile

  • HTML

  • CSS

  • JavaScript

  • Mustache

  • Piktogramme mit Fontello

ergibt eine ganzheitliche und unviversell verwendbare Lösung. Die Herstellung einer Baum-Komponente zur Verwendung im Web kann quasi mit Bordmitteln entstehen, die frei zur Verfügung stehen und keine weiteren Abhängigkeiten erfordern.

Um nicht Inhalt und Strukturelemente in HTML zu vermischen ist die Nutzung einer Mechanik zur dynamischen Erzeugung von HTML aus Vorlagen wie mit Mustache hilfreich. Allerdings muss auf eine sinnvolle Kombination der vielen Einzelteile geachtet werden.

Am Praxisbeispiel einer Baumstruktur ist ersichtlich, wie die Einzelteile der Webtechnologie im Zusammenspiel funktionieren und dass es keine umfangreichen Frameworks oder besondere Hilfsmittel zu deren Nutzung benötigt. Ein Editor sowie das Wissen um die relevanten Zusammenhänge genügen.