Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: [Symfony] Nested Set jako dynamiczne menu nawigacyjne
Forum PHP.pl > Forum > PHP > Frameworki
kudlatypawelek
Witam !

Udało mi się zaimplementować strukturę drzewiastą w oparciu o "Doctrine Nested Sety". We frontendzie chciałbym wyświetlać menu nawigacyjne oparte o tą właśnie strukturę. Nie wiem jednak jak przygotować system renderujący całą hierarchię. Wyświetlenie całości to nie problem, ale ja chciałbym zagłębiać się tylko w poddrzewo klikniętego aktualnie elementu, a pozostałe "dzieci" utrzymywać schowane, np:


  • test
  • test2
    • subtest2_1
      • subtest2_1_1
    • subtest2_2
  • test3



Zakładam, że będzie trzeba wykorzystać tu rekurencję (a może da radę zrobić to iteracyjnie?). Za wszelkie porady o pomoc z góry dziękuję

Pozdrawiam
mkopytko
Cześć,

żeby zrobić takie drzewko możesz iteracyjne przelecieć po kolekcji wyciągniętej z bazy i wyświetlić tylko te elementy które Cię interesują.

Tu masz opisane jeśli nie czytałeś:
- drzewko NestedSet : http://www.doctrine-project.org/api/orm/1...._nestedset.html
- tutaj pojedynczy węzeł drzewka: http://www.doctrine-project.org/api/orm/1...._nestedset.html

Musisz znać kliknięty węzeł (node) i jego porównujesz odpowiedni do kolejnych węzłów w foreachu.

Poniżej podaję przykład wyświetlenia takiego drzewka w pętli (dla potrzeb przykładu nie jest zrefaktorowane smile.gif ):

  1. foreach($tree as $node){
  2. $display = false;
  3.  
  4. if($element->isDescendantOf($node->getRawValue())) // jeśli node jest parentem elementu
  5. {
  6. $parent_table[$node->getLevel()] = $node;
  7. $display = true;
  8. }
  9. elseif($node->isDescendantOfOrEqualTo($element->getRawValue()) && $node->getLevel()-1 <= $element->getLevel()) // jeśli node jestem samym soba lub bezpośrednim dzieckiem
  10. {
  11. $display = true;
  12. }
  13. elseif(isset($parent_table[$node->getLevel()-1]) && $parent_table[$node->getLevel()-1]->isParent($node->getRawValue())) // jesli jego rodzic znajduje się w tablicy otwartych nodów
  14. {
  15. $display = true;
  16. }
  17.  
  18. if($display){
  19. for($i = 0; $i <= $node->getLevel(); $i++)
  20. {
  21. echo "-";
  22. }
  23.  
  24. echo $node->getName() . '<br/>';
  25. }
  26. }



Element drzewka musi mieć następujące metody:

  1.  
  2. public function isDescendantOf(Element $subj)
  3. {
  4. return (($this->getLft() > $subj->getLft()) &&
  5. ($this->getRgt() < $subj->getRgt()) &&
  6. ($this->getRootId() == $subj->getRootId()));
  7. }
  8.  
  9.  
  10. public function isDescendantOfOrEqualTo(Element $subj)
  11. {
  12. return (($this->getLft() >= $subj->getLft()) &&
  13. ($this->getRgt() <= $subj->getRgt()) &&
  14. ($this->getRootId() == $subj->getRootId()));
  15. }
  16.  
  17.  
  18. public function isParent(Element $subj)
  19. {
  20. return (($this->getLft() < $subj->getLft()) &&
  21. ($this->getRgt() > $subj->getRgt()) &&
  22. ($this->getLevel() == $subj->getLevel()-1));
  23. }
  24.  



P.S. Podaj więcej szczegółów odnośnie tego jak będziesz i gdzie używał drzewka bo może inny sposób jest lepszy (css/js)
kudlatypawelek
Cześć

Bardzo dziękuję za wskazówki. Na początek odniosę się do pytania o zasadność stosowania tej metody (tak mi zadano, więc tak ma być arrowheadsmiley.png ). Druga sprawa dotyczy metod obsługi "klikniętego" node [u Ciebie $element]. O ile te dwie metody isDescendantOf oraz isDescendantOfOrEqualTo są już zaimplementowane w /Doctrine/Node/NestedSet.php o tyle isParent($subj) już nie. W związku z tym pytanie - czy mam je przygotować w modelu "Strony" i tam zaimplementować isParent; znalazłem bowiem metodę isAncestorOf($subj), która robi to samo [/Doctrine/Node/NestedSet.php linia 918].

Pozdrawiam

UPDATE sad.gif

Już mnie oświeciło - getRawValue() używamy w szablonach, a ja testy robię póki co w kontrolerze. Rzeczywiście metody należało overridować w klasie Strony.
mkopytko
Tak isDescendantOfOrEqualTo jest zaimplementowana w NestedSet ale żeby jej użyć musisz użyć ->getNode() a przynajmniej u mnie powoduje to wykonanie bezsensownego zapytania do bazy czyli każdy wezeł to +1 zapytań do bazy exclamation.gif!!

Na razie nie mam czasu by to sprawdzić dokładnie ale postaram się na dniach zbadać sprawę i przedstawić najlepsze rozwiązanie smile.gif


P.S. Przy okazji jeszcze są inne opcje na zabawy z drzewem:

  1. $elements = Doctrine_Core::getTable('Element')->createQuery('c')->execute(array(), Doctrine_Core::HYDRATE_RECORD_HIERARCHY);


Dostajesz faktycznie drzewiastą kolekcje i wtedy po niej działasz rekurencyjnie (chyba będzie to lepsze niż iteracyjny sposób z poprzedniego przykładu)

Więcej na temat wyciągania drzew: http://www.doctrine-project.org/projects/o...ecord-hierarchy
kudlatypawelek
Poniżej podaję to co udało mi się stworzyć z pomocą kolegi mkopytko:

  1. $treeObject = Doctrine_Core::getTable('Strony')->getTree();
  2. $element = Doctrine_Core::getTable('Strony')->getPageWithTranslationBySlug($currentSlug);
  3.  
  4. $lastLevel = 1;
  5. $start = true;
  6.  
  7. $html = '<ul>'."\n";
  8.  
  9. foreach($treeObject->fetchTree() as $node)
  10. {
  11. $display = false;
  12.  
  13. if($element->isDescendantOf($node)) // jeśli node jest parentem elementu
  14. {
  15. $parent_table[$node->getLevel()] = $node;
  16. $display = true;
  17. }
  18. elseif($node->isDescendantOfOrEqualTo($element) && $node->getLevel()-1 <= $element->getLevel()) // jeśli node jestem samym soba lub bezpośrednim dzieckiem
  19. {
  20. $display = true;
  21. }
  22. elseif(isset($parent_table[$node->getLevel()-1]) && $parent_table[$node->getLevel()-1]->isAncestorOf($node)) // jesli jego rodzic znajduje się w tablicy otwartych nodów
  23. {
  24. $display = true;
  25. }
  26.  
  27. if($display && $node->getLevel() != 0)
  28. {
  29.  
  30. if (($node->getLevel() == $lastLevel) && ($lastLevel >= 1) && !$start) {
  31. $html .= '</li>'."\n";
  32. }
  33. if ($node->getLevel() > $lastLevel) {
  34. $html .= '<ul>'."\n";
  35. }
  36. if ($node->getLevel() < $lastLevel) {
  37. $html .= str_repeat("</li></ul>"."\n", $lastLevel - $node->getLevel()).'</li>'."\n";
  38. }
  39. $html .= '<li '.($node->getSlug() == $currentSlug ? 'class="selected"' : '').' id="CatalogCategory'.$node['id'].'">'.link_to($node->getNazwa(), '@browse_page?slug='.$node['slug'].'&id='.$node['id']);
  40.  
  41. $start = false;
  42. $lastLevel = $node->getLevel();
  43. }
  44. }
  45.  
  46. $html .= '</li></ul>';
  47.  
  48. echo $html;


Wersji rekurencyjnej jeszcze nie próbowałem, ale wydaje mi się, że iteracja jest lepsza [pozostaje kwestia ilości zapytań].
mkopytko
Cytat(kudlatypawelek @ 1.07.2011, 14:16:10 ) *
Wersji rekurencyjnej jeszcze nie próbowałem, ale wydaje mi się, że iteracja jest lepsza [pozostaje kwestia ilości zapytań].


Zapytania możesz w najprostszy sposób zlikwidować używając metod zdefiniowanych w modelu (tych które podałem) a rekurencja zmniejszy ilość iteracji i to w niektórych przypadkach znacznie bo nie będą one wykonywane dla elementów które nie są otwarte.


P.S. Jeśli u Ciebie z każdym nodem rośnie ilość zapytań to takie wykonanie drzewa jest nie do przyjęcia i broń Boże nie pokazuj takiego drzewa nikomu smile.gif Użycie NestedSet służy głównie zmniejszeniu ilości zapytań a nie zwiększeniu smile.gif
kudlatypawelek
Drzewo oparte jest na "behaviorze" nestedSet Doctrine, więc nie powinno być problemu z przyrostem zapytań.
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.