2009-06-18 5 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 

など

私は(MySQLの)データベースからすべてのノードを取得するには、次のSQLクエリを使用します。

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のLI> UL {表示:なし;}
  • .menu li.clicked> UL {表示:ブロック;}

そして追加するJavaScriptを使用クリックされた任意の< li>要素に「クリック」されたクラス。このCSSはIE6では動作しません。

+0

私はすでにそれを試してみました。 しかし、メニューに多数の要素が含まれている場合、ユーザは非常に少数の要素しか見ない大きな順序付けられていないリストをロードします。それが私が避けようとしているものです。 私はまた、メニューの機能がjavascriptに依存しないようにしたいと思います。 –

1

次のクエリでは、SQLのhaving節と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) 

having節はクエリによってグループの結果にフィルタを適用します。"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、

-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のパスを実際にはの親のノードのパスにします。だからA/B/B1は "A/B%"になります。新しいサブツリーを開くためにリンクをクリックすると、私たちの要求に合っています。

深度= 1の理由は、同じツリーに複数のメニューが表示されている可能性があります。「MENU-FOR-RICH-PEOPLE」、「MENU-FOR-POOR-PEOPLEとにかく名前が何であっても。私のセットのトップレベルのノードは種類が多いノードで、実際の結果から除外します。

私はこれが誰かにとって有用であることを証明したいと思っています。少なくとも、私は数時間の解決策を探してからそれを思いつきました。

私はあなたがこれはwww.race.fi

EDIT/NOTEを見て、働いていたことを確認することができます私は数日間、と思う:

私はいくつかのより多くをテストし、順序が故障したようです。ここでは、正確な注文と私のクエリの迅速なcopypasteです。ロケール、コンテンツ、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

多分あなたにとって役に立ちます。

関連する問題