2009-06-18 3 views
0

Я пытаюсь создать динамическое меню в своей PHP CMS; страницы/категории организованы с использованием модели вложенных множеств.Построение динамического меню с использованием вложенных наборов

Полное дерево:

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

Я хочу, чтобы преобразовать этот результат установлен в unordererd список, который отображает только часть дерева. Например: Если я нажимаю на 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

Мне удалось создать полный список без рекурсии (используя столбец глубины), но я не могу отфильтровать меню, как показано выше; Мне кажется, мне нужно получить значение lft и rgt родительского элемента для каждого узла и отфильтровать элементы с помощью PHP. Но как я могу получить эти значения в одном запросе?

Есть ли другие предложения о том, как достичь этого?

Заранее благодарен!

ответ

-1

Будет ли это соответствовать объему вашего проекта, чтобы просто скрыть нежелательные элементы? например (CSS):

  • .menu литий> уль {дисплей: нет;}
  • .menu li.clicked> уль {дисплей: блок;}

Затем с помощью JavaScript, чтобы добавить класс «щелкнул» на любой элемент < li>, который был нажат. Обратите внимание, что этот CSS не будет работать в IE6.

+0

Я уже пробовал что-то подобное, и он работает. НО: Если в меню содержится много элементов, пользователь загружает большой неупорядоченный список, из которого он видит только очень мало элементов; вот чего я пытаюсь избежать. Я также не хочу, чтобы функциональность меню зависела от javascript. – 2009-06-21 08:23:54

1

Следующий запрос позволит вам открыть любой путь (или набор путей), воспользовавшись предложением SQL и функцией group_concat MySQL.

Ниже данные определения таблицы и примеры я использовал:

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

Следующий запрос дает полное дерево (за исключением головы):

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) 

, имеющее положение применяет фильтры к результатам группы по запросу.Часть «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 | 
+------+-----+-----+------+-----------+-------+ 

Вот об этом. вы просто продолжаете дублировать это или раздел для каждого пути, который вам нужно открыть.

См. http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/, если вам нужна общая информация о работе с вложенными наборами в MySQL.

Дайте мне знать, если возникнут какие-либо вопросы.

НТН,

-Dipin

0

Я понимаю, что это может быть старый вопрос, но, как я наткнулся на такой же проблемой, я решил дать некоторые материалы, так что другие могут воспользоваться также.

-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% действительно работал, но он только дал верхний уровень и не дал никаких других узлов на одном уровне. Эта версия делает.

WE конкатенация path_of_node с помощью подстановочного знака (%), что означает, что после него все может прийти. Подстрока УДАЛИТЬ имя узла и тире, создавая путь path_of_node на самом деле, путь его parent's узел. Таким образом, A/B/B1 становится «A/B%», который соответствует нашему запросу, если мы нажмем ссылку, чтобы открыть новое поддерево.

Причина, по которой у меня есть глубина = 1, заключается в том, что у меня может быть несколько меню в одном и том же дереве, и я не хочу, чтобы люди видели что-то вроде «МЕНЮ-ДЛЯ-БОЛЬШОЙ ЛЮДЕЙ», «МЕНЮ-ДЛЯ-ЛЮДЕЙ ", или как бы там ни назывались имена. Узлы верхнего уровня моего набора являются дочерними узлами, я исключаю их из фактического результата.

Надеюсь, это окажется полезным для кого-то, по крайней мере, я искал решение в течение нескольких часов, а затем придумал его.

я думаю, я через несколько дней вы можете подтвердить, что это работает, посмотрев на www.race.fi

EDIT/Примечание:

Я проверил некоторые и кажется, что порядок был неисправен. Вот быстрая копия моего запроса с правильным порядком. Есть некоторые ненужные вещи, такие как локали, контент и content_localised, но ключевые моменты должны быть ясными.

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

Хороший пост о том, как построить вложенный набор с нуля, который написал друг, здесь; Nested Set in MySQL

Возможно, это полезно для вас.