簡體   English   中英

使用嵌套集構建動態菜單

[英]Build dynamic menu using Nested Sets

我正在嘗試在PHP CMS中建立一個動態菜單; 頁面/類別使用嵌套集模型進行組織。

全樹:

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

我想將此結果集轉換為一個無序列表,該列表僅顯示樹的一部分。 例如:如果單擊B,則只顯示列表的以下部分:

A
B
 B1
 B2
C
D

接下來,如果我單擊B1,我希望顯示此列表:

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

等等

我使用以下SQL查詢從(mysql)數據庫獲取所有節點:

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

我設法創建了沒有遞歸的完整列表(使用depth列),但是我不能像上面顯示的那樣過濾菜單。 我想我需要獲取每個節點的父級的lft和rgt值,並使用PHP過濾掉元素。 但是,如何在同一查詢中獲得這些值?

關於如何實現這一目標還有其他建議嗎?

提前致謝!

以下查詢將允許您利用SQL的Have子句和MySQL的group_concat函數來打開任何路徑(或路徑集)。

以下是我使用的表定義和示例數據:

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);

以下查詢為您提供整棵樹(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

對示例數據運行時,輸出以下內容:

+------+-----+-----+------+-----------+-------+
| 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 |
+------+-----+-----+------+-----------+-------+

上述查詢的以下新增內容將為您提供打開各個部分所需的控件:

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)

hading子句將過濾器應用於按查詢分組的結果。 “ depth = 0”部分是為了確保我們始終有基本菜單節點(A,B,C和D)。 下一部分是控制哪些節點處於打開狀態的部分。 它將節點的路徑與您要打開的設置路徑('')進行比較,以查看是否匹配,並且還確保僅對路徑打開級別。 可以根據需要復制和添加帶有''邏輯的整個或部分,以根據需要打開多個路徑。 確保''不以斜杠(/)結尾。

以下是一些輸出示例,向您展示如何構造查詢以獲取所需的輸出:

=========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 |
+------+-----+-----+------+-----------+-------+

就是這樣 您只需要為您需要打開的每個路徑重復復制該部分。

如果您需要有關在MySQL中使用嵌套集的常規信息,請參見http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/

如果您有任何疑問,請告訴我。

HTH,

-蘸

我意識到這可能是一個古老的問題,但是當我偶然發現相同的問題時,我決定提供一些意見,以便其他人也能從中受益。

-Dipins的答案是我根據自己的進度得出的答案,現在我認為我可以解決所有“ OR”問題。

只需將一部分替換為:

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.

它所做的是將請求的路徑進行比較,比如說A / B / B1與節點的路徑。 節點的路徑需要一些艱苦的工作。 LIKE節點路徑%確實起作用,但是它僅給出了較高級別,而沒有給出同一級別上的任何其他節點。 這個版本可以。

我們將通配符(%)連接到path_of_node,這意味着后面可以包含任何內容。 子字符串會刪除節點自己的名稱和破折號,從而使path_of_node實際上是其父節點的路徑。 因此,如果我們單擊鏈接打開新的子樹,則A / B / B1變為與我們的請求匹配的“ A / B%”。

我的depth = 1的原因是我可能在同一棵樹中擁有多個菜單,並且我不希望人們看到“ MENU-FOR-RICH-PEOPLE”,“ MENU-FOR-POOR-PEOPLE”之類的內容無論如何,名字都是。 我的集合的頂層節點是一個持有節點,我將它們從實際結果中排除。

我希望這對某人有用,至少我尋找了幾個小時的解決方案,然后提出了解決方案。

我認為,幾天之內您可以通過訪問www.race.fi確認此方法有效

編輯/注意:

我測試了一些,看來訂購有誤。 這是正確排序的查詢的快速復制粘貼。 有一些不必要的內容,例如語言環境,內容和content_localized,但要點很明確。

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";

一位朋友寫的一篇有關如何從頭開始構建嵌套集的好文章。 MySQL中的嵌套集

也許對您有幫助。

僅隱藏不需要的元素是否適合您的項目范圍? 例如(css):

  • .menu li> ul {顯示:無;}
  • .menu li.clicked> ul {display:block;}

然后使用javascript將“已單擊”類添加到已單擊的任何<li>元素中。 請注意,此CSS在IE6中不起作用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM