webForumDet fria alternativet

DHTML Trädmeny

PHPur PHP

23 svar · 1 482 visningar · startad av MickeA.com

Medlem sedan feb. 20034 441 inlägg
Frågan#1

Hej,
Jag har kört den här menyn på ett par sajter: (den som är i webbläsarens övre kant)
http://www.dhtmlgoodies.com/scripts/menu-for-applications/demos/demo-menu-bar.html

Nu ska jag köra den här i ett administrations gränssnitt, där användare med olika behörigheter kommer att jobba. Jag vill alltså en skriva ut de menyitems som aktuell användare har behörighet att jobba med.

Jag vill skriva ut menyn från en MySQL databas.
Har givetvis sökt en del innan jag postat, och hittat bl.a. den här tråden, som _nästan_ tar upp samma ämne:
http://www.webforum.nu/showthread.php?p=1393825#post1393825

Men jag har rört ihop det i huvudet en del, så jag är inte helt hundra på hur jag ska lösa detta. Menyn ska kunna skrivas ut i x antal djup, fast det kommer troligtvis aldrig vara fler än tre.

Koden för menyn jag vill använda kan skrivas ut på lite olika sätt, men jag vill ha outputen såhär:

menuModel.addItem(1,'Page 1','','',false,'','');
menuModel.addSeparator();

menuModel.addItem(2,'Menu 2','','',false);
	menuModel.setSubMenuWidth(2,120);
	menuModel.addItem(21,'Child 2-1','','',2,'','');
	menuModel.addItem(22,'Child 2-2','','',2,'','');

menuModel.addItem(3,'Menu 3','','',false);
	menuModel.setSubMenuWidth(3,175);
	menuModel.addItem(31,'Child 2-1','','',3,'','');
		menuModel.setSubMenuWidth(31,175);
			menuModel.addItem(311,'Child 3-1-1','','',31,'','');
			menuModel.addItem(312,'Child 3-1-2','','',31,'','');

Jag tänker mig att man använder en tabell för hela menyn, men hur gör jag för att skriva ut allt i "rätt" ordning?

Såhär ser tabellen ut:

menu_id  	int(3)
menu_parent 	int(3)
menu_level 	int(1)
menu_min_perm	int(1)
menu_title	varchar(35)

Men nu tar det stopp, jag har ingen aning om hur jag ska lösa resten.

Tacksam för förslag!

Medlem sedan apr. 2008137 inlägg
#2

Om menu_level är tänkt att indikera indenteringsnivån så är det ganska onödigt, då parent gör samma sak.

Det finns egentligen två lösningar. Det mer traditionella sättet för en nästlad trädstruktur är att helt enkelt börja på första nivån, och sedan loopa igenom varje nod på den nivån. Och för varje nod hämta ut alla undernoder, osv osv.
Detta har den stora nackdelen att det blir en himla massa sqlfrågor vilket kostar en hel del prestanda.

Som komplement till detta finns det något som kallas
Modified Preorder Tree Traversal som i princip bygger på att varje nod i trädet har ett vänster och ett högervärde, och man vandrar igenom trädet genom att följa dessa värden i ordning. På så sätt kan man hämta ut hela trädet, ett underträd till en nod, eller vägen från noden till roten.

Har du inte behov av högpresterande kod, eller om antalet användare är väldigt litet, så har du ingen större nytta av MPTT, men det kan vara bra att veta om inför framtiden.

Medlem sedan feb. 20034 441 inlägg
#3

Tack för ditt svar!
Det här är som sagt en meny, där alla värden _alltid_ ska skrivas ut, från rooten till "slutet".
Det kommer inte vara jättemånga användare (ett 10 tal kanske).
Saken är den att menyn kommer skrivas ut varje gång sidan laddas om.

De olika användarna har olika behörighet och olika många meny items kommer att skrivas ut.

Jag har tidigare varit inne på Modified Preorder Tree Traversal, men förstår inte riktigt var jag ska börja.
Är inte helt med på det här med vänster / högervärde.

Tanken är att det ska vara enkelt att sätta in nya items, ta bort, redigera, samt ändra visningsordning.

Vilket sätt är mest lämpligt i mitt fall?

Medlem sedan apr. 2008137 inlägg
#4

Om det inte är för många poster skulle jag nog skippa MPTT och bara köra en rekursiv hämtning. Alternativt hämta ut alla noder användaren har behörighet till och sortera dom i php. T.ex. genom att stoppa in dom i en array indexerat på parent.
Typ så här:
$nodes = array("parentid1" => [node1, node2, node3], "parentid2" => [node4, node5])
Om man förutsätter att alla noder i roten har parentid 1 så börjar man skriva ut där, och för varje nod tittar du om det finns några childnodes för det id:t, osv...

Medlem sedan feb. 20034 441 inlägg
#5

Hej,

Totalt är det kring 40 undersidor i det här systemet och det kommer kansek vara 4-5 stycken som har tillgång till samtliga sidor.

Alla sitter säkerligen inte samtidigt och jobbar heller, så det är nog inga problem att köra rekursivt.

I alla exempel jag sett används aldrig INT som datatyp för "parent", utan allt är text hela tiden.

Visst måste det gå att göra samma sak, fast där parent är ett id på "root" objektet? Sen kommer ju jag ha flera objekt på rootnivå, eftersom denna kommer att vara "level 1" i mitt menyträd.

Kommer det fortfarande att funka att köra t.ex. den här funktionen:

<?php
// $parent is the parent of the children we want to see
// $level is increased when we go deeper into the tree,
//        used to display a nice indented tree
function display_children($parent, $level) {
   // retrieve all children of $parent
   $result = mysql_query('SELECT title FROM tree '.
                          'WHERE parent="'.$parent.'";');

   // display each child
   while ($row = mysql_fetch_array($result)) {
       // indent and display the title of this child
       echo str_repeat('  ',$level).$row['title']."\n";

       // call this function again to display this
       // child's children
       display_children($row['title'], $level+1);
   }
}
?>

(Källa: http://www.sitepoint.com/article/hierarchical-data-database)

Medlem sedan apr. 2008137 inlägg
#6

Det går alldeles utmärkt att använda int för parent, så gör jag alltid. Vet inte varför de flesta försök inte gör så, tycker det är dålig databasdesign att inte använda intbaserade nycklar.
Funkionen du skriver bör funka bra, med modifiering för rättigheter och sortering förstås. :)
Jag brukar göra så att parentid 0 betyder rotnivån.

Medlem sedan feb. 20034 441 inlägg
#7

Precis, exakt så brukar jag också göra, _alltid_ intbaserade nycklar och 0 = root nivå.
Det känns som om jag kommer få ihop det här precis som jag vill nu, tack!

Medlem sedan feb. 20034 441 inlägg
#8

Hej!

Nu har jag dykt på ett problem, som jag inte riktigt vet hur jag ska lösa.
Det meny items som ligger på rootnivå, har alla parentid "0".

Såhär ser min tabell ut:

menu_id  	int(3)
menu_parent 	int(3)
menu_title 	varchar(35)
menu_icon 	varchar(35)
menu_uri 	varchar(50)
menu_desc 	varchar(175)
menu_helptext 	varchar(50)
menu_jsfunc 	varchar(35)
menu_visible 	int(1)
menu_order 	int(3)

Funktionen för att hämta items ser ut såhär:

function display_children($parent) {
   $query = "SELECT ".
			"menu_id, menu_parent, menu_title, menu_icon, menu_uri, menu_helptext, menu_jsfunc ".
			"FROM menu ".
			"WHERE menu_parent = " . $parent . " ".
			"AND menu_visible = 1 ".
			"ORDER BY menu_order ASC";		

	$result = mysql_query($query) or(die((DEBUG ? "<h2>An error occurred</h2>\n<p>" . mysql_error() . "<br />\n" . $query . "</p>\n" : "<p>An error has occurred, please contact site admin.</p>\n")));
	while ($row = mysql_fetch_array($result)) {
		echo "menuModel.addItem(" . $row['menu_id'] . ",'" . $row['menu_title'] . "','" . $row['menu_icon'] . "','" . $row['menu_uri'] . "'," . $row['menu_parent'] . ",'" . $row['menu_helptext'] . "','" . $row['menu_jsfunc'] . "', '', '');\n";
		display_children($row['menu_parent']);
	}
}

När funktionen kallar på sig själv, blir det en oändlig loop av det hela, som resulterar i att samma objekt hämtas om och om igen, tills sidan time'ar ut.

Detta eftersom man från början hämtar alla items med parentid 0.

Någon som har förslag på hur jag löser detta?

Tack!

Medlem sedan jan. 2003296 inlägg
#9

Du vill nog skicka in menu_id istället för menu_parent till display_children i while-loopen...

Medlem sedan feb. 20034 441 inlägg
#10

jawbreaker skrev:

Du vill nog skicka in menu_id istället för menu_parent till display_children i while-loopen...

Stämmer, upptäckte det strax efter att jag postat här, upptäckte även fler andra fel i min kod.

Nu har jag fått ordning på allt, följande script skriver ut hela menyn med obegränsat antal nivåer.

Är koden optimerad i den utsträckning det går nu?

function display_children($parent = 0) {
	$menuitem = "";
	$query = "SELECT "
			."menu_id, menu_parent, menu_title, menu_icon, menu_uri, menu_helptext, menu_jsfunc, "
			."(SELECT COUNT(*) FROM menu WHERE menu_parent = " . $parent . " AND menu_parent <> 0) "
			."FROM menu "
			."WHERE menu_parent = " . $parent . " "
			."AND menu_visible = 1 "
			."ORDER BY menu_order ASC";		

	$result = mysql_query($query) or(die((DEBUG ? "<h2>An error occurred</h2>\n<p>" . mysql_error() . "<br />\n" . $query . "</p>\n" : "<p>An error has occurred, please contact site admin.</p>\n")));
	while ($row = mysql_fetch_array($result)) {
		$menuitem .= "menuModel.addItem(" . $row['menu_id'] . ",'" . $row['menu_title'] . "','" . $row['menu_icon'] . "','" . $row['menu_uri'] . "'," . $row['menu_parent'] . ",'" . $row['menu_helptext'] . "','" . $row['menu_jsfunc'] . "', '', '200');\n";
		$menuitem .= display_children($row['menu_id']);
	}
	return $menuitem;
}
Medlem sedan apr. 2008137 inlägg
#11

Vad är meningen med subqueryn "(SELECT COUNT(*) FROM menu WHERE menu_parent = " . $parent . " AND menu_parent <> 0)"? Ser inte den datan användas någonstans?

Medlem sedan feb. 20034 441 inlägg
#12

effata skrev:

Vad är meningen med subqueryn "(SELECT COUNT(*) FROM menu WHERE menu_parent = " . $parent . " AND menu_parent <> 0)"? Ser inte den datan användas någonstans?

Precis, tanken *var* att funktionen endast skulle kalla på sig själv om antalet i den frågan var mer än 0.

Men jag löste det på annat sätt.
Det frågan är borttagen ut min riktiga kod.

Medlem sedan feb. 20034 441 inlägg
#13

Nu har jag ett till litet problem.
Håller på med administrationen för menyn, där man ska kunna lägga till / ta bort objekt, ändra visnings ordning o.s.v.

Koden är nästan en copy / paste på den ovan:

function menu_overview($parent = 0) {
	$i = 0;
	$menuoverview = "";
	$query = "SELECT "
	        ."menu_id, menu_parent, menu_title, menu_icon, menu_uri, menu_helptext, menu_jsfunc "
		."FROM menu "
		."WHERE menu_parent = " . $parent . " "
		."AND menu_visible = 1 "
		."ORDER BY menu_order ASC";		
	
	$result = mysql_query($query) or(die((DEBUG ? "<h2>An error occurred</h2>\n<p>" . mysql_error() . "<br />\n" . $query . "</p>\n" : "<p>An error has occurred, please contact site admin.</p>\n")));
	while ($row = mysql_fetch_array($result)) {
		$row_class = ($i%2 == 0 ? "odd" : "even");
		$menuoverview .= "<tr class=\"" . $row_class . "\">\n"
				."<td style=\"padding-left: " . ($i * 10) . "px;\">" . $row['menu_title'] . "</td>\n"
				."<td>" . "!knapp!" . "</td>\n"
				."</tr>\n";
		$menuoverview .= menu_overview($row['menu_id']);
		$i++;
	}
	mysql_free_result($result);
	return $menuoverview;
}

Ovanstående skriver ut alla meny items under varann, såhär:

Start
Nyheter
Interna Nyheter
Lägg till
Översikt
Publika Nyheter
Lägg till
Översikt
Kalender
Lägg till
Översikt

Jag vill ha det här resultatet:

Start
Nyheter
	Interna Nyheter
		Lägg till
		Översikt
	
	Publika Nyheter
		Lägg till
		Översikt

Kalender
	Lägg till
	Översikt

Jag vill sätta "padding-left + 10px" för varje nivå som skrivs ut, så att det blir som exemplet ovan.

Problemet med min kod ovan att är att paddingen blir helt fel.

Några idéer?

Medlem sedan apr. 2008137 inlägg
#14

Du behöver skicka med ett värde in i menu_overview som talar om vilken nivå det är på. Så ökar du det värdet med ett när du gör det rekursiva anropet inuti loopen.

Medlem sedan feb. 20034 441 inlägg
#15

Aha, detta bör jag kunna ta reda på genom en subquery, right?
Typ som den jag körde med förut, som är borttagen.

- Är "parent_id" 0, då är det ett root obj.
- Är "parent_id" <> 0, så är det en child, men hur tar jag reda på vilken nivå denna child ligger på?

Medlem sedan apr. 2008137 inlägg
#16

Med den typen av struktur du har så är det svårt att lösa nivåbiten direkt i sql. Den absolut enklaste lösningen är att köra med en extra variabel som jag skrev, kostar i princip ingenting extra och sparar en massa trubbel.

Medlem sedan feb. 20034 441 inlägg
#17

effata skrev:

Med den typen av struktur du har så är det svårt att lösa nivåbiten direkt i sql. Den absolut enklaste lösningen är att köra med en extra variabel som jag skrev, kostar i princip ingenting extra och sparar en massa trubbel.

Okej, men hur anger jag det värdet?
Jag måste väl på något sätt räkna ut i whileloopen vilken nivå nuvarande item ligger på?

Medlem sedan apr. 2008137 inlägg
#18
function menu_overview($parent = 0, $level = 0) { 
    $i = 0; 
    $menuoverview = ""; 
    $query = "SELECT " 
            ."menu_id, menu_parent, menu_title, menu_icon, menu_uri, menu_helptext, menu_jsfunc " 
        ."FROM menu " 
        ."WHERE menu_parent = " . $parent . " " 
        ."AND menu_visible = 1 " 
        ."ORDER BY menu_order ASC";         
     
    $result = mysql_query($query) or(die((DEBUG ? "<h2>An error occurred</h2>\n<p>" . mysql_error() . "<br />\n" . $query . "</p>\n" : "<p>An error has occurred, please contact site admin.</p>\n"))); 
    while ($row = mysql_fetch_array($result)) { 
        $row_class = ($i%2 == 0 ? "odd" : "even"); 
        $menuoverview .= "<tr class=\"" . $row_class . "\">\n" 
                ."<td style=\"padding-left: " . ($level * 10) . "px;\">" . $row['menu_title'] . "</td>\n" 
                ."<td>" . "!knapp!" . "</td>\n" 
                ."</tr>\n"; 
        $menuoverview .= menu_overview($row['menu_id'], $level+1); 
        $i++; 
    } 
    mysql_free_result($result); 
    return $menuoverview; 
}

Notera variabeln $level. För varje rekursivt anrop kommer $level att öka med ett.

Medlem sedan feb. 20034 441 inlägg
#19

Ahh, givetvis var det så enkelt, tack för hjälpen!

Medlem sedan feb. 20034 441 inlägg
#20

Ett litet tillägg, för att det skulle funka klockrent måste man nollställa $level efter varje loop gått klart.

132 ms totalt · 3 externa anrop · v20260731065814-full.25f56b17
0 ms — hämta forumlista (cache)
0 ms — hämta statistik (cache)
129 ms — hämta tråd, inlägg och bilagor (db)