Problem mit mehrstufiger Navigation mit php

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • h3ll
    replied
    Eine weitere Variante von mir:
    PHP Code:
    class NavigationNode {
        public 
    $title;
        public 
    $url;
        public 
    $children;
        
        public function 
    __construct($title ''$url '', array $children = []) {
            
    $this->title $title;
            
    $this->url $url;
            
    $this->children $children;
        }

    PHP Code:
    class NavigationHtmlListRenderer {
        private 
    $rootNode;
        private 
    $charset;
        
        public function 
    __construct(NavigationNode $rootNode$charset 'utf-8') {
            
    $this->rootNode $rootNode;
            
    $this->charset $charset;
        }
        
        public function 
    render($activeUrl ''$formatOutput false) {
            
    $dom = new DOMDocument('1.0'$this->charset);
            
            
    $rootElement $this->renderNode($dom$this->rootNode);
            
            
    $listElement $dom->createElement('ul');
            
    $listElement->appendChild($rootElement);
            
    $rootElement->appendChild($this->renderChildren($dom$this->rootNode));
            
            
    $dom->appendChild($listElement);
            
            foreach (
    $dom->getElementsByTagName('a') as $linkElement) {
                if (
    $linkElement->getAttribute('href') === $activeUrl) {
                    
    $linkElement->setAttribute('class''active');
                }
            }
            
            if (
    $formatOutput) {
                
    $dom->formatOutput true;
            }
            return 
    $dom->saveXML($listElement);
        }
        
        private function 
    renderNode(DOMDocument $domNavigationNode $node) {
            
    $linkElement $dom->createElement('a'$node->title);
            
    $linkElement->setAttribute('href'$node->url);
            
            
    $listItemElement $dom->createElement('li');
            
    $listItemElement->appendChild($linkElement);
            
            return 
    $listItemElement;
        }
        
        private function 
    renderChildren(DOMDocument $domNavigationNode $parentNode) {
            
    $listElement $dom->createElement('ul');
            
            foreach (
    $parentNode->children as $node) {
                
    $listItemElement $this->renderNode($dom$node);
                if (!empty(
    $node->children)) {
                    
    $listItemElement->appendChild($this->renderChildren($dom$node));
                }
                
                
    $listElement->appendChild($listItemElement);
            }
            
            return 
    $listElement;
        }

    PHP Code:
    $navigation = new NavigationNode('Home''/', [
        new 
    NavigationNode(
            
    'Main''/main', [
                new 
    NavigationNode(
                    
    'Main Page 1''/main/1'
                
    ),
                new 
    NavigationNode(
                    
    'Main Page 2''/main/2'
                
    ),
                new 
    NavigationNode(
                    
    'Main Page 3''/main/3', [
                        new 
    NavigationNode(
                            
    'Sub Page 1''/main/3/1'
                        
    ),
                        new 
    NavigationNode(
                            
    'Sub Page 2''/main/3/2'
                        
    )
                    ]
                )
            ]
        ),
        new 
    NavigationNode(
            
    'Contact''/contact'
        
    )
    ]);

    $renderer = new NavigationHtmlListRenderer($navigation);
    echo 
    $renderer->render('/main/3/1'true); 
    Ausgabe:
    HTML Code:
    <ul>
      <li>
        <a href="/">Home</a>
        <ul>
          <li>
            <a href="/main">Main</a>
            <ul>
              <li>
                <a href="/main/1">Main Page 1</a>
              </li>
              <li>
                <a href="/main/2">Main Page 2</a>
              </li>
              <li>
                <a href="/main/3">Main Page 3</a>
                <ul>
                  <li>
                    <a href="/main/3/1" class="active">Sub Page 1</a>
                  </li>
                  <li>
                    <a href="/main/3/2">Sub Page 2</a>
                  </li>
                </ul>
              </li>
            </ul>
          </li>
          <li>
            <a href="/contact">Contact</a>
          </li>
        </ul>
      </li>
    </ul>
    Last edited by h3ll; 12-01-2016, 19:47.

    Leave a comment:


  • fireweasel
    replied
    Verschachtelte Menüs, Nested Menus, Bäume und Trees, Walk, Traverse, ...

    Originally posted by Theryl View Post
    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.
    Last edited by fireweasel; 18-01-2016, 20:50. Reason: korrigierte li-ul-Verschachtelung + ::parentName im Fließtext wurde zum :-P-Smiley ...

    Leave a comment:


  • mermshaus
    replied
    Willkommen im Forum.

    Ich fürchte, das ist nicht so leicht, weil der Code ziemlich „speziell“ für die Aufgabe geschrieben ist, die er aktuell erfüllt.

    Etwa so was…

    Code:
    if(array_search($url,$value))
    …, das vermutlich bedeuten soll, dass… Ehrlich gesagt: Ich habe keine Ahnung, was das genau bedeuten soll. Untermenüs klappen sich wieder ein, sobald ein Element daraus ausgewählt wird, weil $url dann nicht mehr in $value liegt?

    Wie auch immer: Man kann veruschen, dir da irgendwie was reinzubasteln, aber das wird voraussichtlich auf Anhieb nicht so werden, wie du es haben willst. Und bevor das dann ein ewiges Hin und Her gibt, könnte man besser gleich ne ordentliche Lösung wählen, die etwa auch mit einer beliebigen Anzahl an Ebenen umgehen kann und die ordentlichen HTML-Code erzeugt (mit Listen).

    Leave a comment:


  • Theryl
    started a topic Problem mit mehrstufiger Navigation mit php

    Problem mit mehrstufiger Navigation mit php

    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.
    Also so sieht es nun aus:
    Code:
    1
    1.1
    1.2
    2
    2.1
    2.2
    2.3
    3
    4.1
    schön wäre es wenn sie so aussehen könnte
    Code:
    1.
    1.1
    1.1.1
    1.1.2
    2
    3
    4.1.1
    Ich hoffe mir kann einer bei diesem Problem helfen und Danke jetzt schon mal im voraus.

    Hier ist die .php Datei

    PHP Code:
    <?php
    $siteRoot
    ="index.php?inhalt_mitte=";
    $menuStruct = array("Vorwort"=>array("root"=>$siteRoot."content/vorwort.inc.php""CSS Dateien strukturieren"=>$siteRoot."content/strukturieren.inc.php"),
                        
    "Eigenschaften"=>array("root"=>$siteRoot."content/eigenschaften.inc.php"),
                        
    "Aufgaben"=>array("root"=>$siteRoot."content/aufgaben.inc.php","Formularfelder"=>$siteRoot."content/formularfelder.inc.php","Listen"=>$siteRoot."content/listen.inc.php","Buttons"=>$siteRoot."content/buttons.inc.php")
                       );
    $aktuell $_GET["inhalt_mitte"];
    $url $siteRoot.$aktuell;
    foreach(
    $menuStruct as $key=>$value)
    {
      if(
    $url == $value['root'])
      {
        echo 
    "<a  class=\"fstLevelActive\" href=".$value['root'].">$key</a>\n";
      }
      else
      {
        echo 
    "<a class=\"fstLevel\" href=".$value['root'].">$key</a>\n";
      }
      foreach(
    $value as $key2=>$value2)
      {
        if(
    array_search($url,$value))
        {
          if(
    $key2 != "root")
          {
            if(
    $url == $value2)
            {
              echo 
    "<a class=\"secLevelActive\" href=".$value2.">$key2</a>\n";
            }
            else
            {
              echo 
    "<a class=\"secLevel\" href=".$value2.">$key2</a>\n";
            }
          }
        }
      }
    }
    ?>
Working...
X