Home  PHP Projekte  PHPDoc  Forms  IT[X] Template  Userland Cache  Gtext  Menu 3  Tutorial  Browser  XML/XSLT Menü  Columbo  PHP Schulung  Technik der Site  Büchertipps  Fotografie  Airbrush  Kontakt  Stuff 
|
Statische Navigationsstrukturen dynamisch dargestellt
Die Erstellung von Navigationselementen und ihre Einbindung in ein HTML Dokument
kann ein zeitraubender Prozeß sein, der besonders dann zur Herausforderung wird,
wenn die Navigationsstruktur einer umfangreichen Präsentation geändert wird.
In dem Maße in dem Template Klassen die Arbeit vereinfachen können, gelingt dies
auch den Menü Klassen, sofern sie überhaupt eingesetzt werden können. Aufwendige
Effekte lassen sich kaum erzielen sehr wohl aber einfache Darstellungen, die
nicht minder zur Benutzbarkeit einer Präsentation beitragen.
Der Plan
Schritt für Schritt sollen die Features von Menu 3 an einem praktischem Beispiel dargestellt werden.
Ziel ist es, eine einfache Menüstruktur für eine
fiktive PEAR Website zu erstellen und
das Layout anzupassen. Anschließend wird versucht, die Klasse so zu erweitern, daß auch
mehrsprachige Navigationselemente möglich werden.
Vorbereitungen
Eine aktuelle Version von Menu 3 befindet sich im
PEAR CVS
im Verzeichnis Experimental/Html/. Vom Verzeichnisnamen sollte man sich nicht abschrecken
lassen, mir sind keine Bugs mehr bekannt. Einzig aufgrund der fehlenden englischen
Dokumentation liegt die Klasse noch im Experimental Verzeichnis.
Die Navigationsstruktur für die geplante Seite soll wie folgt aussehen.
- Home
- About PEAR
- Download
- Installation
- Inside PEAR
- Packages
Basierend auf dieser (Baum-)Struktur sollen auch alle
anderen Darstellungsformen erzeugt werden.
Das Layout wird vom Mäuseschupser vorgegeben, im folgenden wird gezeigt,
wie Anpassungen zu machen sind, die Anpassung selbst wird jedoch dem Leser als
Übung überlassen.
Exkurs: Baumstrukturen in PHP
Baumstrukturen lassen sich in PHP nicht wie C/C++ oder Java implementieren,
da PHP keine Zeiger oder gar Adreßoperationen kennt. Wer versucht,
die klassischen Algorithmen in PHP zu formulieren, wird schnell aufgeben.
Eine einfach verlinkte Liste, Basisbaustein aller komplexeren Formen
darunter auch Bäume, bedient sich in C folgender Datenstruktur.
typedef struct node *Ref;
typedef struct node {
int value;
Ref next;
} NODE;
Grafisch dargestellt, entspricht die resultierende Liste folgendem Bild.
Jeder Zeiger in C verweist auf eine bestimmte Adresse (Speicherstelle) im
Hauptspeicher des Rechners, im obigem Beispiel ist "next" ein Zeiger,
sein Wert, ist die Speicheradresse auf die er verweist.
Da PHP keine Zeiger kennt, beginnt man damit den Hauptspeicher unter
Verwendung einer Liste (Array) zu simulieren. Jeder
Eintrag in der Liste entspricht einer Speicherzelle im Hauptspeicher.
Die dargestellten Lücken (1, 2 -> 7) ergeben sich nach wenigen Lösch-
und Einfügeoperationen.
| C - Speicherorganisation |
|
PHP - Simulation mittels Liste |
|
Maximale Speicheradresse |
|
|
count($node_list) |
| ... |
... |
|
... |
... |
| Adresse: 7 |
| Node 3 |
| value: 3 |
next: keiner/NULL |
|
|
$node_list[7] |
| Node 3 |
| value: 3 |
next: keiner/NULL |
|
| ... |
... |
|
... |
... |
| Adresse: 2 |
| Node 2 |
| value: 2 |
next: steht in 7 |
|
|
$node_list[2] |
| Node 2 |
| value: 2 |
next: steht in 7 |
|
| Adresse 1 |
| Node 1 |
| value: 1 |
next: steht in 2 |
|
|
$node_list[1] |
| Node 1 |
| value: 1 |
next: steht in 2 |
|
Erfahrene PHP Anwender ahnen bereits, welcher Verwaltungsaufwand entsteht,
wenn ein Knoten nicht nur einfach, sondern zwei oder gar dreifach verlinkt
ist. Wer es sich nicht vorstellen kann, dem würde ich danken,
wenn er zu Demonstrationszwecken
Menu ADT komplettiert.
Auch wenn es "grausam" aussieht und PHPDoc den Ruf des größten
Hashtest Skripts für PHP eingebracht hat, plädiere ich für
mehrdimensionale Hashes als Datenstruktur für Bäume in PHP.
Hashes können leicht aufgebaut, ausgelesen und modifiziert werden.
Die zahlreichen Arrayfunktionen, die besonders mit PHP 4 ausgebaut wurden,
lassen keinen Wunsch offen.
Menüstrukturen definieren
Die Definition der Menüstruktur ist praktisch selbsterklärend.
Es gilt lediglich zu beachten, daß jeder Eintrag im Hash einen eindeutigen
Key verwendet, da diese zum Aufbau einer internen Lookup Table verwendet werden.
Ob numerische Keys benutzt werden, die die Baumstruktur
wiederspiegeln oder Strings ist nicht von Bedeutung.
| Menüstruktur |
Top |
|
<?php
var $menu = array(
0 => array( "url" => "/", "title" => "Home" ), 1 => array( "url" => "about/", "title" => "About PEAR" ),
2 => array( "url" => "download/", "title" => "Download" ),
3 => array( "url" => "installation/", "title" => "Installation" ),
4 => array( "url" => "inside/", "title" => "Inside PEAR", "sub" => array( 41 => array( "url" => "inside/cvs/", "title" => "CVS" ), 42 => array( "url" => "inside/guide/", "title" => "Coding Guidelines" ) ) ), 5 => array( "url" => "packages/", "title" => "Packages" )
); ?>
|
Die Daten werden in der Klassenvariable $menu abgelegt, um sie nicht bei
jedem Skriptlauf dem Objekt neu zuweisen zu müssen. Wer mit dynamischen
Strukturen arbeitet und auf eine Zuweisung zur Laufzeit angewiesen ist,
der kann die Methode void setMenu(array $menu) verwenden.
Für die weitere Konfiguration von Menu 3 ist es notwendig, einige Methoden
zu überschreiben, weshalb eine Ableitung von der Basisklasse erstellt wird.
| Ableitung zur Anpassung |
Top |
|
<?php // Basisklasse ggf. einbinden require_once($DOCUMENT_ROOT . "/php/Menu.php"); class pear_menu extends menu { var $menu = ... } ?>
|
Der erste Kontakt
Nach diesen Vorbereitungen ist es möglich, Menu 3 in Aktion zu sehen.
Ein kleines Skript bindet die eigene Ableitung ein, erzeugt ein Objekt
und gibt die Navigation in allen möglichen Darstellungsformen aus.
| Testskript |
Top |
|
<?php require_once($DOCUMENT_ROOT . "/php/pear_menu.php"); // Objekt erzeugen $m = new pear_menu; // Alle Darstellungsmodi ausgeben $modes = array("sitemap", "tree", "rows", "urhere", "prevnext"); foreach ($modes as $k => $mode) printf("mode: %s\n%s\n\n", $mode, $m->get($mode)); ?>
|
|
Ausgabe
|
Top |
mode: sitemap
mode: tree
mode: rows
mode: urhere
mode: prevnext
|
|
|
Anpassung des Aussehens
Menu 3 durchläuft den Hash mit der Menüstruktur rekursiv und
ruft drei Methoden auf, die das Rendering übernehmen: string getStart(),
string getEnd(), string getEntry(array $node, integer $level, integer $node_type).
getStart() wird immer vor und getEnd() nach der Traversierung des Hashes aufgerufen.
Meist werden Sie benutzt, um eine HTML Tabelle zu beginnen oder zu schließen.
Eine Ausnahme bildet die Darstellungsform "rows", hier werden die Methoden während der
Traversierung aufgerufen, dies ändert jedoch nichts an der Bedeutung der Methoden.
Einträge im Menü werden durch die Methode getEntry() dargestellt.
Es werden alle Daten des Eintrags (url, title, ...),
die Tiefe im Navigationsbaum und eine Typangabe (aktiv, inaktiv, ...) übergeben.
Zur Anpassung werden die beschriebenen Methoden in der Klasse
pear_menu überschrieben, zunächst also dorthin kopiert.
Die Anpassung selbst besteht in der Veränderung
des öffnenden HTML <table>-Tags. Da PHPDoc Inline Dokumentation vererbt,
braucht diese nicht erneut niedergeschrieben werden.
| Ableitung zur Anpassung |
Top |
|
<?php // Basisklasse ggf. einbinden require_once($DOCUMENT_ROOT . "/php/Menu.php"); class pear_menu extends menu { var $menu = ... function getStart() { $html = ""; switch ($this->menu_type) { case "rows": case "prevnext": $html .= '<table cellpadding="2" cellspacing="2" border><tr>'; break; case "tree": case "urhere": case "sitemap": $html .= '<table cellpadding="2" cellspacing="2" border>'; break; } return $html; } // end func getStart
function getEnd() {
$html = ""; switch ($this->menu_type) { case "rows": case "prevnext": $html .= '</tr></table>'; break; case "tree": case "urhere": case "sitemap": $html .= '</table>'; break; } return $html; } // end func getEnd function getEntry(&$node, $level, $item_type) {
$html = ""; if ("tree" == $this->menu_type) { // tree menu $html .= '<tr>'; $indent = ""; if ($level) for ($i = 0; $i < $level; $i++) $indent .= ' '; } // draw the <td></td> cell depending on the type of the menu item switch ($item_type) { case 0: // plain menu item $html .= sprintf('<td>%s<a href="%s">%s</a></td>', $indent, $node["url"], $node["title"] ); break; case 1: // selected (active) menu item $html .= sprintf('<td>%s<b>%s</b></td>', $indent, $node["title"] ); break; case 2: // part of the path to the selected (active) menu item $html .= sprintf('<td>%s<b><a href="%s">%s</a></b>%s</td>', $indent, $node["url"], $node["title"], ("urhere" == $this->menu_type) ? " >> " : "" ); break; case 3: // << previous url $html .= sprintf('<td>%s<a href="%s"><< %s</a></td>', $indent, $node["url"], $node["title"] ); break;
case 4: // next url >> $html .= sprintf('<td>%s<a href="%s">%s >></a></td>', $indent, $node["url"], $node["title"] ); break;
case 5: // up url ^^ $html .= sprintf('<td>%s<a href="%s">^ %s ^</a></td>', $indent, $node["url"], $node["title"] ); break; } if ("tree" == $this->menu_type) $html .= '</tr>';
return $html; } // end func getEnty } ?>
|
Ein etwas längeres switch() { ... } bildet das Herzstück der Methode getEntry().
Je nach Eintragstyp wird eine leicht andere Darstellungsform gewählt. Wer sehr
viele Anpassungen vornehmen, muß tut gut daran, je nach Darstellungsform,
($this->menu_type) in andere Methoden zu verzweigen, um die Übersichtlichkeit zu wahren.
Für den Parameter $item_type können fünf Werte auftreten.
| getEntry() - $item_type |
| 0 |
"Normaler", nicht angewählter Eintrag. |
alle Darstellungsformen |
| 1 |
"Aktiver", angewählter Eintrag. Der Menüpunkt beschreibt die angezeigte Seite. |
alle Darstellungsformen |
| 2 |
Der Eintrag gehört zum Pfad des aktuellen Eintrags.
1 und 1.1 gehören zum Pfad zum Eintrag 1.1.1
|
alle Darstellungsformen |
| 3 |
"Vorheriger" Eintrag des aktuellen Menüpunkts. 1 liegt vor 2.
|
ausschließlich "prevnext" |
| 4 |
"Nächster" Eintrag des aktuellen Menüpunkts. 3 liegt nach 2.
|
ausschließlich "prevnext" |
| 5 |
Eintrag "über" dem aktuellen Menü. 1 liegt über 1.2.
|
ausschließlich "prevnext" |
Wie findet Menu 3 den aktuellen Menüpunkt?
Der aktuelle Menüpunkt wird durch einen Vergleich der "url" Angabe im Menühash mit
dem Rückgabewert von string getCurrentURL() ermittelt. Durch Überschreibung der Methode
kann statt des voreingestellten Vergleichs mit $PHP_SELF z.B. ein Vergleich mit $REQUEST_URI
durchgeführt werden, was die Verwendung von URLs in der Form http://www.example.com/?index
ermöglicht.
Existiert kein Eintrag für den Rückgabewert von getCurrentURL(), wird der
zurückgelieferte String so lange gekürzt, bis ein Eintrag gefunden wird.
Dies erlaubt es, Seiten anzulegen, die nicht explizit im Menü aufgeführt werden.
Wird die Seite angezeigt, aktiviert Menu 3 den nächsten "Hauptpunkt".
Ein Beispiel verdeutlicht das Gesagte. Im Menühash sei ein Eintrag "url" => "/example/" enthalten.
Im Verzeichnis /example/ liegen die Seiten index.php, foo.php und bar.php, wobei die
letzen beiden Dateien nicht im Menü eingetragen sind. Bei Aufruf der Seite foo.php liefert
getCurrentURL() den Wert "/example/foo.php". Menu 3 findet keinen Eintrag und sucht den
nach einer Alternative, dem "Hauptpunkt".
| Suche nach Alternativen |
| /example/foo.php |
!= |
/example/ |
| /example/foo.ph |
!= |
/example/ |
| /example/foo.p |
!= |
/example/ |
| ... |
!= |
/example/ |
| /example/f |
!= |
/example/ |
| /example/ |
== |
/example/ |
Das Verfahren der Verkürzung ist PHPLib Nutzern wohlbekannt, menu.inc bietet einen ähnlichen
Mechanismus. Sollte die Verzeichnisstruktur nicht der Menüstruktur entsprechen, sind unliebsame
Überraschungen leider vorprogrammiert.
Frames und Mehrsprachigkeit
Unterstützung für Frames, Mehrsprachigkeit und vieles mehr kann leicht hinzugefügt werden.
Links in Framesets benötigen oft ein target Attribut, mehrsprachige Navigationen verlangen,
daß mehrere Beschritungen (title) für einen Eintrag gespeichert werden. Diese Daten
sind dem Menühash hinzuzufügen.
Menu 3 benötigt zur Funktion die Elemente "url" und (optional) "sub" in einem Eintrag (Node) im
Hash (Baum). Neben diesen Pflichtelementen können beliebig viele weitere Angaben enthalten sein,
selbst "title" ist eine solche, optionale Angabe.
Bei Bedarf können "target" und weitere Daten im Node abgelegt werden,
um sie durch getEntry() auszulesen. Vor diesem Hintergrund wird
klar, das Frames und Mehrsprachigkeit nur wenige Erweiterungen benötigen.
Für diese Homepage wurde in der verwendeten Klasse
redsys_menu.php, eine "target" Angabe hinzugefügt.
Leistungsgrenze
Die einfache Menüklasse ist gut geeignet für Homepages und Präsentationen mit
einer tendenziell statischen Struktur. Große Systeme, deren Navigation
dynamisch aufgebaut wird, überfordern Menu 3, obwohl
die Struktur auch dynamisch zugewiesen und manipuliert werden kann.
Sourcen:
Lizenz: PHP Licence (darf in kommerziellen Projekten benutzt werden)
< ^ >
|