2009-06-18 10 views
0

Ich versuche ein dynamisches Menü in meinem PHP CMS zu erstellen; Die Seiten/Kategorien werden mithilfe des Modells der verschachtelten Sätze organisiert.Dynamisches Menü mit verschachtelten Sets erstellen

Vollbaum:

 
root 
A 
B 
    B1 
    B1.1 
    B1.2 
    B2 
    B2.1 
    B2.1 
C 
    C1 
    C2 
    C3 
D 

Ich möchte dieses Ergebnis auf eine unordererd Liste gesetzt konvertieren, die nur einen Teil des Baumes zeigt. Zum Beispiel: Wenn ich auf B klicken, möchte ich nur den folgenden Teil der Liste anzuzeigen:

 
A 
B 
B1 
B2 
C 
D 

Als nächstes, wenn ich auf B1 klicken Ich möchte die diese Liste anzuzeigen:

 
A 
B 
B1 
    B1.1 
    B1.2 
B2 
C 
D 

usw.

ich verwende die Abfrage folgende SQL alle Knoten aus der (mySQL) Datenbank zu erhalten:

SELECT node.id, node.lft, node.rgt, node.name, 
GROUP_CONCAT(parent.name ORDER BY parent.lft SEPARATOR "/") AS path, 
(COUNT(parent.lft) - 1) AS depth 
FROM pages AS node, pages AS parent 
WHERE node.lft BETWEEN parent.lft AND parent.rgt 
AND (parent.hidden = "no" AND node.hidden = "no") AND parent.lft > 1 
GROUP BY node.id ORDER BY node.lft

Ich habe es geschafft, die vollständige Liste ohne Rekursion zu erstellen (mit der Tiefe Spalte), aber ich kann das Menü nicht filtern, wie ich oben gezeigt habe; Ich denke, ich muss den Lft und Rgt-Wert der Eltern für jeden Knoten erhalten und die Elemente mit PHP herausfiltern. Aber wie kann ich diese Werte in derselben Abfrage erhalten?

Gibt es weitere Vorschläge, wie dies zu erreichen ist?

Vielen Dank im Voraus!

Antwort

-1

Würde es in den Rahmen Ihres Projekts passen, nur die unerwünschten Elemente zu verstecken? eg (CSS):

  • .menu li> ul {display: none;}
  • .menu li.clicked> ul {display: block;}

Dann verwenden Javascript die hinzuzufügen Klasse "klickte" auf ein beliebiges < li> Element, auf das geklickt wurde. Beachten Sie, dass dieses CSS in IE6 nicht funktioniert.

+0

Ich habe schon so etwas probiert, und es funktioniert. ABER: Wenn das Menü viele Elemente enthält, lädt der Benutzer eine große ungeordnete Liste von denen er nur sehr wenige Elemente sieht; das versuche ich zu vermeiden. Ich möchte auch nicht, dass die Funktionalität des Menüs von Javascript abhängt. –

1

Die folgende Abfrage ermöglicht es Ihnen, einen beliebigen Pfad (oder mehrere Pfade) zu öffnen, indem Sie die having-Klausel von SQL und die group_concat-Funktion von MySQL nutzen.

Hier finden Sie die Tabellendefinition und Beispieldaten I verwendet:

drop table nested_set; 

CREATE TABLE nested_set (
id INT, 
name VARCHAR(20) NOT NULL, 
lft INT NOT NULL, 
rgt INT NOT NULL 
); 

INSERT INTO nested_set (id, name, lft, rgt) VALUES (1,'HEAD',1,28); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (2,'A',2,3); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (3,'B',4,17); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (4,'B1',5,10); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (5,'B1.1',6,7); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (6,'B1.2',8,9); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (7,'B2',11,16); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (8,'B2.1',12,13); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (9,'B2.2',14,15); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (10,'C',18,25); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (11,'C1',19,20); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (12,'C2',21,22); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (13,'C3',23,24); 
INSERT INTO nested_set (id, name, lft, rgt) VALUES (14,'D',26,27); 

Die folgende Abfrage gibt Ihnen den gesamten Baum (mit Ausnahme des HEAD):

SELECT 
    node.id 
, node.lft 
, node.rgt 
, node.name 
, GROUP_CONCAT(parent.name ORDER BY parent.lft SEPARATOR "/") AS path 
, (COUNT(parent.lft) - 1) AS depth 
FROM nested_set AS node 
inner join nested_set AS parent 
on node.lft BETWEEN parent.lft AND parent.rgt 
where parent.lft > 1 
GROUP BY node.id 

Mit einer Leistung von der folgende, wenn gegen die Probendaten ausgeführt:

+------+-----+-----+------+-----------+-------+ 
| id | lft | rgt | name | path  | depth | 
+------+-----+-----+------+-----------+-------+ 
| 2 | 2 | 3 | A | A   |  0 | 
| 3 | 4 | 17 | B | B   |  0 | 
| 4 | 5 | 10 | B1 | B/B1  |  1 | 
| 5 | 6 | 7 | B1.1 | B/B1/B1.1 |  2 | 
| 6 | 8 | 9 | B1.2 | B/B1/B1.2 |  2 | 
| 7 | 11 | 16 | B2 | B/B2  |  1 | 
| 8 | 12 | 13 | B2.1 | B/B2/B2.1 |  2 | 
| 9 | 14 | 15 | B2.2 | B/B2/B2.2 |  2 | 
| 10 | 18 | 25 | C | C   |  0 | 
| 11 | 19 | 20 | C1 | C/C1  |  1 | 
| 12 | 21 | 22 | C2 | C/C2  |  1 | 
| 13 | 23 | 24 | C3 | C/C3  |  1 | 
| 14 | 26 | 27 | D | D   |  0 | 
+------+-----+-----+------+-----------+-------+ 

Die folgenden Ergänzungen zu die obige Abfrage gibt Ihnen die Kontrolle müssen Sie die verschiedenen Abschnitte öffnen:

having 
depth = 0 
or ('<PATH_TO_OPEN>' = left(path, length('<PATH_TO_OPEN>')) 
    and depth = length('<PATH_TO_OPEN>') - length(replace('<PATH_TO_OPEN>', '/', '')) + 1) 

Die having-Klausel gilt Filter auf die Ergebnisse der Gruppe von Abfrage.Der "depth = 0" -Teil soll sicherstellen, dass wir immer Base-Menü-Knoten (A, B, C und D) haben. Der nächste Teil ist der Teil, der steuert, welche Knoten offen sind. Er vergleicht den Pfad der Knoten mit einem festgelegten Pfad, den Sie öffnen möchten (''), um zu sehen, ob er übereinstimmt, und stellt sicher, dass er nur die Ebene im Pfad öffnet. Der gesamte Abschnitt mit der Logik kann nach Bedarf dupliziert und hinzugefügt werden, um mehrere Pfade nach Bedarf zu öffnen. Stellen Sie sicher, dass "" nicht in einem Schrägstrich endet (/).

Im Folgenden sind einige Beispiele Ausgangs Ihnen zu zeigen, wie Sie Abfragen konstruieren würden die Ausgänge Sie wollten zu bekommen:

=========Open B========== 

SELECT 
    node.id 
, node.lft 
, node.rgt 
, node.name 
, GROUP_CONCAT(parent.name ORDER BY parent.lft SEPARATOR "/") AS path 
, (COUNT(parent.lft) - 1) AS depth 
FROM nested_set AS node 
inner join nested_set AS parent 
on node.lft BETWEEN parent.lft AND parent.rgt 
where parent.lft > 1 
GROUP BY node.id 
having 
depth = 0 
or ('B' = left(path, length('B')) 
    and depth = length('B') - length(replace('B', '/', '')) + 1) 

+------+-----+-----+------+------+-------+ 
| id | lft | rgt | name | path | depth | 
+------+-----+-----+------+------+-------+ 
| 2 | 2 | 3 | A | A |  0 | 
| 3 | 4 | 17 | B | B |  0 | 
| 4 | 5 | 10 | B1 | B/B1 |  1 | 
| 7 | 11 | 16 | B2 | B/B2 |  1 | 
| 10 | 18 | 25 | C | C |  0 | 
| 14 | 26 | 27 | D | D |  0 | 
+------+-----+-----+------+------+-------+ 

=========Open B and B/B1========== 

SELECT 
    node.id 
, node.lft 
, node.rgt 
, node.name 
, GROUP_CONCAT(parent.name ORDER BY parent.lft SEPARATOR "/") AS path 
, (COUNT(parent.lft) - 1) AS depth 
FROM nested_set AS node 
inner join nested_set AS parent 
on node.lft BETWEEN parent.lft AND parent.rgt 
where parent.lft > 1 
GROUP BY node.id 
having 
depth = 0 
or ('B' = left(path, length('B')) 
    and depth = length('B') - length(replace('B', '/', '')) + 1) 
or ('B/B1' = left(path, length('B/B1')) 
    and depth = length('B/B1') - length(replace('B/B1', '/', '')) + 1) 

+------+-----+-----+------+-----------+-------+ 
| id | lft | rgt | name | path  | depth | 
+------+-----+-----+------+-----------+-------+ 
| 2 | 2 | 3 | A | A   |  0 | 
| 3 | 4 | 17 | B | B   |  0 | 
| 4 | 5 | 10 | B1 | B/B1  |  1 | 
| 5 | 6 | 7 | B1.1 | B/B1/B1.1 |  2 | 
| 6 | 8 | 9 | B1.2 | B/B1/B1.2 |  2 | 
| 7 | 11 | 16 | B2 | B/B2  |  1 | 
| 10 | 18 | 25 | C | C   |  0 | 
| 14 | 26 | 27 | D | D   |  0 | 
+------+-----+-----+------+-----------+-------+ 

=========Open B and B/B1 and C========== 

SELECT 
    node.id 
, node.lft 
, node.rgt 
, node.name 
, GROUP_CONCAT(parent.name ORDER BY parent.lft SEPARATOR "/") AS path 
, (COUNT(parent.lft) - 1) AS depth 
FROM nested_set AS node 
inner join nested_set AS parent 
on node.lft BETWEEN parent.lft AND parent.rgt 
where parent.lft > 1 
GROUP BY node.id 
having 
depth = 0 
or ('B' = left(path, length('B')) 
    and depth = length('B') - length(replace('B', '/', '')) + 1) 
or ('B/B1' = left(path, length('B/B1')) 
    and depth = length('B/B1') - length(replace('B/B1', '/', '')) + 1) 
or ('C' = left(path, length('C')) 
    and depth = length('C') - length(replace('C', '/', '')) + 1) 

+------+-----+-----+------+-----------+-------+ 
| id | lft | rgt | name | path  | depth | 
+------+-----+-----+------+-----------+-------+ 
| 2 | 2 | 3 | A | A   |  0 | 
| 3 | 4 | 17 | B | B   |  0 | 
| 4 | 5 | 10 | B1 | B/B1  |  1 | 
| 5 | 6 | 7 | B1.1 | B/B1/B1.1 |  2 | 
| 6 | 8 | 9 | B1.2 | B/B1/B1.2 |  2 | 
| 7 | 11 | 16 | B2 | B/B2  |  1 | 
| 10 | 18 | 25 | C | C   |  0 | 
| 11 | 19 | 20 | C1 | C/C1  |  1 | 
| 12 | 21 | 22 | C2 | C/C2  |  1 | 
| 13 | 23 | 24 | C3 | C/C3  |  1 | 
| 14 | 26 | 27 | D | D   |  0 | 
+------+-----+-----+------+-----------+-------+ 

, was darüber ist. Sie kopieren einfach diesen oder einen Abschnitt für jeden Pfad, den Sie öffnen müssen.

Siehe http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/ für den Fall, dass Sie allgemeine Informationen zum Arbeiten mit verschachtelten Sets in MySQL benötigen.

Lassen Sie mich wissen, wenn Sie irgendwelche Fragen haben.

HTH,

-Dipin

0

Ich weiß, dies könnte eine alte Frage, aber als ich auf das gleiche Problem gestolpert, habe ich beschlossen, eine Eingabe zu geben, damit auch andere profitieren können.

-Dipins Antwort war die, auf der ich meinen Fortschritt basierte, und jetzt denke ich, ich habe eine Lösung ohne alle 'ODER'.

einfach die mit Teil ersetzen mit:

HAVING 
    depth = 1 
    OR 
    '".$path."' LIKE CONCAT(SUBSTRING(path, 1, (LENGTH(path) - LENGTH(menu_node_name) -1)), '%') 

$path = requested path. parent node's path that the user clicked, "A/B" for example 

path = the path of the current node including the nodes name "A/B/B1" for example, which is a child for the node the user clicked. 

menu-node-name = the name of the node in progress, "B1" for example. 

Was sie tut, vergleicht ist der Pfad angefordert wird, lässt A sagen/B/B1 mit dem Pfad des Knotens. Der Pfad des Knotens benötigte einige harte Arbeit. LIKE Pfad-von-Knoten% hat funktioniert, aber es gab nur die obere Ebene und gab keine anderen Knoten auf der gleichen Ebene. Diese Version tut es.

WE verketten den path_of_node mit einem Platzhalter (%) was bedeutet, dass alles danach kommen kann. Der Teilstring ENTFERNT den eigenen Namen des Knotens und den Bindestrich, was den path_of_node tatsächlich den Pfad seines Parent Knotens macht. So wird A/B/B1 zu "A/B%", was unserer Anfrage entspricht, wenn wir auf einen Link klicken, um einen neuen Teilbaum zu öffnen.

Der Grund, warum ich depth = 1 habe, ist, dass ich mehrere Menüs im selben Baum haben könnte, und ich möchte nicht, dass Leute etwas wie "MENÜ-FÜR-RICH-MENSCHEN", "MENÜ-FÜR-SCHLECHT-MENSCHEN" sehen "oder was auch immer die Namen sind. Die Knoten auf der obersten Ebene meines Sets halten Knoten, ich schließe sie vom eigentlichen Ergebnis aus.

Ich hoffe, dass dies für jemanden nützlich ist, zumindest suchte ich nach einer Lösung für ein paar Stunden und kam dann mit ihm.

ich denke, ich ein paar Tage können Sie bestätigen, dass dies bei www.race.fi indem Sie arbeitete

EDIT/HINWEIS:

ich einige mehr getestet und es scheint, dass die Bestellung fehlerhaft war. Hier ist eine kurze Kopie meiner Anfrage mit der richtigen Reihenfolge. Es gibt einige unnötige Dinge wie Locales, Inhalt und content_localise, aber die wichtigsten Punkte sollten klar sein.

SELECT 
    REPEAT('-',(COUNT(MENU.par_name) - 2)) as indt, 
    GROUP_CONCAT(MENU.par_name ORDER BY MENU.par_lft SEPARATOR '/') AS path, 
    (COUNT(MENU.par_lft) - 1) AS depth, 
    MENU.*, 
    MENU.content 
FROM 
    (SELECT 
     parent.menu_node_name AS par_name, 
     parent.lft AS par_lft, 
     node.menu_node_id, 
     node.menu_node_name, 
     node.content_id, 
     node.node_types, 
     node.node_iprop, 
     node.node_aprop, 
     node.node_brands, 
     node.rgt, 
     node.lft, 
     [TPF]content_localised.content 

    FROM [TPF]" . $this->nestedset_table . " AS node 
    JOIN [TPF]" . $this->nestedset_table . " AS parent 
      ON node.lft BETWEEN parent.lft AND parent.rgt 
    JOIN [TPF]content 
     ON node.content_id = [TPF]content.content_id 
    JOIN [TPF]content_localised 
     ON [TPF]content.content_id = [TPF]content_localised.content_id 
    JOIN [TPF]locales 
     ON [TPF]content_localised.locale_id = [TPF]locales.locale_id 

    ORDER BY node.rgt, FIELD(locale, '" . implode("' , '", $locales) . "', locale) ASC 
    ) AS MENU 

GROUP BY MENU.menu_node_id 
HAVING depth = 1 
    OR '".$path."' LIKE CONCAT(SUBSTRING(path, 1, (LENGTH(path) - LENGTH(MENU.menu_node_name) -1)), '%') 
    AND depth > 0 
ORDER BY MENU.lft"; 
0

Ein guter Beitrag, wie man einen verschachtelten Satz von Grund auf baut, den ein Freund schrieb, ist hier; Nested Set in MySQL

Vielleicht ist es hilfreich für Sie.

Verwandte Themen