Einzelnen Beitrag anzeigen
  #3 (permalink)  
Alt 12-01-2016, 13:49
Benutzerbild von fireweasel fireweasel
 Registrierter Benutzer
Links : Onlinestatus : fireweasel ist offline
Registriert seit: Sep 2008
Ort: At home
Beiträge: 851
fireweasel wird schon bald berühmt werdenfireweasel wird schon bald berühmt werden
fireweasel eine Nachricht über AIM schicken fireweasel eine Nachricht über Yahoo! schicken
Standard Verschachtelte Menüs, Nested Menus, Bäume und Trees, Walk, Traverse, ...

Zitat:
Zitat von Theryl Beitrag anzeigen
Hallo zusammen ich habe da ein hoffentlich nur kleines Problem. Und zwar geht es um eine mehrstufige Navigation. Ich habe das die Datei im Netz gefunden und bin schwer begeistert davon.
Ich hätte nun lieber anstatt der zwei level, drei level.
Prinzipiell könntest du einfach eine weitere foreach()-Ebene hinzufügen. Aber man kann die Aufgabenstellung auch generalisieren und beliebige Verschachtelungstiefen zulassen. Dazu benötigt man zwar Rekursion[0], dafür kommt man mit einer foreach()-Schleife aus.

Hinzu kommt, dass hier (möglicherweise) vom Benutzer eingegebene Daten (Zeichenketten) in einen HTML-Zusammenhang gebracht werden. Das schreit nach HTML-Escaping. Leider unterstützt uns PHP dabei nicht konsequent, wir müssen also selbst darauf achten, keine einzige Variable zu vergessen. Und über ein ganzes Script verstreute HTML-Fragmente führen auch ganz schnell zu invalidem Markup.

Ich hab da mal schnell was gebastelt. Die Formatierung ist getrennt vom Durchlaufen des Menü-Baums. Damit ist das HTML-Escaping an einer Stelle konzentriert, und die HTML-Fragmente ebenfalls.

PHP-Code:
interface MenuFormatter {
    function 
formatList($children$level 0);
    function 
formatItem(
        
$children ''// rendered text
        
$level 0// indentation level
        
$href ''// url, path
        
$desc null// description
        
$selected false // whether this item is the current one or not
    
);
}

class 
HtmlListFormatter implements MenuFormatter {
    static function 
esc($cdata) {
        
// or whatever suits your needs
        
return htmlspecialchars($cdataENT_QUOTES'UTF-8');
    }

    private 
$styleClassMenu;
    private 
$styleClassSelected;

    function 
__construct() {
        
// initialize vars
        
$this->styleClassMenu();
        
$this->styleClassSelected();
    }

    
// template for html class attribute
    
const cssClass ' class="%s"';

    
// set class for the surrounding ul-element
    
function styleClassMenu($class 'menu') {
        
$this->styleClassMenu sprintf($this::cssClass$this::esc($class));
        return 
$this;
    }

    
// set class for selected item
    
function styleClassSelected($class 'selected') {
        
$this->styleClassSelected sprintf($this::cssClass$this::esc($class));
        return 
$this;
    }

    const 
nl "\n";
    const 
indent '    ';

    const 
listTemplate '%1$s<ul%2$s>%3$s%1$s</ul>';

    function 
formatList(
        
$children,
        
$level 0
    
) {
        
$indent $this::nl str_repeat($this::indent$level);
        return 
sprintf(
            
$this::listTemplate,
            
$indent,
            
$level $this->styleClassMenu '',
            
$children
        
);
    }

    const 
itemTemplate '%s<li%s><a href="%s">%s</a>%s%s</li>';

    function 
formatItem(
        
$children '',
        
$level 0,
        
$href '',
        
$desc null,
        
$selected false
    
) {
        
$indent $this::nl str_repeat($this::indent$level);
        return 
sprintf(
            
$this::itemTemplate,
            
$indent,
            
$selected $this->styleClassSelected '',
            
$this::esc($href),
            
$this::esc($desc),
            (string) 
$children,
            empty (
$children) ? '' $indent
        
);
    }
}

class 
NestedMenu {
    static function 
hasValidStructure(Array $hmenu) {
        
// insert your menu structure validation code here
        // ...
        
return true;
    }

    private 
$selectedShortPath '';
    private 
$fmtr// MenuFormatter::
    
private $menu// array() menu structure

    
function __construct(
        Array 
$menu,
        
MenuFormatter $fmtr null
    
) {
        if (!
$this::hasValidStructure($menu)) {
            throw new 
Exception('invalid menu structure given');
        }
        
$this->fmtr $fmtr ?? new HtmlListFormatter();
        
$this->menu $menu;
    }

    function 
__toString() {
        return (string) 
$this->render($this->menu);
    }

    
// set the class names in the HtmlListFormatter:: class (if available)
    
function styleClass($what$name) {
        if (!
method_exists($this->fmtr$func "styleClass$what")) {
            throw new 
Exception('invalid style class given');
        }
        return 
$this->fmtr->$func($name);
    }

    
// set the current short path, so the MenuFormatter:: can highlight it
    
function selectedShortPath($spath) {
        
$this->selectedShortPath = (string) $spath;
        return 
$this;
    }

    
// get valid URL or localpath from given short path
    
function buildPath($short_path) {
        return 
$short_path;
    }

    const 
parentName '.';

    
/// return something which is convertible to string
    
function render(
        Array 
$nodes// nested Array; for structure see usage example below
        
$level /// int(0...)
    
) {
        
$out '';
        foreach (
$nodes as $name => $node) {
            if (
$this::parentName === $name) {
                continue;
            }
            if (empty (
$node) && is_array($node)) {
                continue; 
// empty lists make no sense, so we do not render them
            
}
            
$short_path is_array($node)
                ? 
$node[$this::parentName] ?? ''
                
$node;
            
$out .= $this->fmtr->formatItem(
                
is_array($node) ? $this->render($node$level 2) : '',
                
$level 1,
                
$this->buildPath($short_path),
                
$name,
                
$short_path === $this->selectedShortPath
            
);
        }
        if (!isset (
$out[0])) {
            return 
null// skip empty lists
        
}
        return 
$this->fmtr->formatList($out$level);
    }

Die Anwendung ist simpel. Ein Beispiel:
PHP-Code:
$from = array (
    
'Vorwort' => array (
        
'.' => 'vorwort.inc.php',
        
'CSS Dateien strukturieren' => 'strukturieren.inc.php',
    ),
    
'Eigenschaften' => array (
        
'.' => 'eigenschaften.inc.php',
    ),
    
'Aufgaben' => array (
        
'.' => 'aufgaben.inc.php',
        
'Formularfelder' => array (
            
'.' => 'formularfelder.inc.php',
            
'Texteingabefelder' => 'textinput.inc.php',
            
'Optionslisten' => 'optionen.inc.php',
        ),
        
'Listen' => 'listen.inc.php',
        
'Buttons' => 'buttons.inc.php',
    ),
);
$menu = new NestedMenu($from);
$menu->selectedShortPath('formularfelder.inc.php');
$menu->styleClass('Selected''active'); 
echo 
$menu;

// dump html source
echo '<pre>'HtmlListFormatter::esc($menu), '</pre>'
Damit die Links auch "funktionieren", musst du noch die Funktion ->buildPath() anpassen (oder besser in einer abgeleiteten Klasse überschreiben). Wenn du einmal dabei bist, kannst du auch gleich die "short-paths" im Menü-Array um die wiederholten ".inc.php" kürzen.
... und darüber nachdenken, ob es sinnvoll ist, Inhalte als PHP-Dateien einzubinden, statt nur als HTML.

Aus deinem "root"-Key hab ich "." gemacht. Das erschien mir sinnvoller: Root ist das Wurzelverzeichnis, nicht das übergeordnete oder aktuelle. Außerdem tippt sich das schneller. Wenn dir das nicht gefällt, musst du die Konstante :: parentName ändern.

Spezielle visuelle Effekte (wie Hervorhebung oder Einklappen benachbarter Untermenüs) lassen sich über CSS realisieren. Dazu enthält das erzeugte HTML (meiner Meinung nach) ausreichend Kennzeichnungen durch class-Attribute.

Das Implementieren einer Überprüfung der Menüstruktur auf formale Korrektheit in ::hasValidStructure() ist als Übungsaufgabe gedacht.

--
[0] Ich wollte da eigentlich einen Wikipedia-Artikel verlinken, aber zumindest die beiden deutschsprachigen Artikel (Rekursion, Rekursive_Programmierung) sind vollkommen nutzlos, um das rekursive "Durchforsten" von Baum-Strukturen (oder verschachtelten Listen) zu verstehen.

Geändert von fireweasel (18-01-2016 um 21:50 Uhr) Grund: korrigierte li-ul-Verschachtelung + ::parentName im Fließtext wurde zum :-P-Smiley ...
Mit Zitat antworten