Seite 1 von 1

Nested set model als unordered list (<ul><li>) d

Verfasst: 17.07.2009, 07:23
von nerd
Hallo,

habe hier ein problem mit dem Nested Set Model (). daten gehen zwar rein in meine datenbank, aber wie bekomme ich sie wieder sinnvoll formatiert raus? ich hab hier schonmal mal was vorbereitet :D

Code: Alles auswählen

CREATE TABLE `hl_places` &#40;
  `id` int&#40;4&#41; unsigned NOT NULL auto_increment,
  `name` varchar&#40;100&#41; collate latin1_general_ci NOT NULL,
  `lft` int&#40;4&#41; unsigned NOT NULL,
  `rgt` int&#40;4&#41; unsigned NOT NULL,
  PRIMARY KEY  &#40;`id`&#41;
&#41; ENGINE=MyISAM AUTO_INCREMENT=20 DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;

INSERT INTO `hl_places` VALUES &#40;'1', 'Home', '1', '24'&#41;;
INSERT INTO `hl_places` VALUES &#40;'2', 'USA', '2', '23'&#41;;
INSERT INTO `hl_places` VALUES &#40;'3', 'CA', '3', '14'&#41;;
INSERT INTO `hl_places` VALUES &#40;'4', 'San Francisco', '4', '5'&#41;;
INSERT INTO `hl_places` VALUES &#40;'12', 'Los Angeles', '6', '9'&#41;;
INSERT INTO `hl_places` VALUES &#40;'13', 'Sunset Blvd', '7', '8'&#41;;
INSERT INTO `hl_places` VALUES &#40;'14', 'Redding', '10', '13'&#41;;
INSERT INTO `hl_places` VALUES &#40;'15', 'Dana Dr', '11', '12'&#41;;
INSERT INTO `hl_places` VALUES &#40;'16', 'CO', '15', '22'&#41;;
INSERT INTO `hl_places` VALUES &#40;'17', 'Denver', '16', '19'&#41;;
INSERT INTO `hl_places` VALUES &#40;'18', 'Champa St', '17', '18'&#41;;
INSERT INTO `hl_places` VALUES &#40;'19', 'Boulder', '20', '21'&#41;;
raus bekommt man die daten dann so (schoen eingerueckt, je nach zone):

Code: Alles auswählen

SELECT 
CONCAT&#40;REPEAT&#40;_latin1'   ', COUNT&#40;parent.name&#41; - 1&#41;, node.name&#41; AS name
FROM 
hl_places AS node,
hl_places AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.id
ORDER BY node.lft;
oder einfach:

Code: Alles auswählen

SELECT 
node.name, 
&#40;COUNT&#40;parent.name&#41; - 1&#41; AS depth
FROM 
hl_places AS node,
hl_places AS parent
WHERE node.lft BETWEEN parent.lft AND parent.rgt
GROUP BY node.id
ORDER BY node.lft;
wie bekomme ich diese formatierung aber jetzt mit PHP hin? ich brauche das ergebniss als verschachtetlte liste, also etwa so:

Code: Alles auswählen

<ul>
	<li>USA
		<ul>
			<li>CA
				<ul>
					<li>San Francisco</li>
					<li>Los Angeles
						<ul>
							<li>Sunset Blvd</li>
							...
						</ul>
					</li>					
				</ul>
			</li>
		</ul>
	</li>
</ul>
irgendwer ne idee, wie man es optimal hinbekommt - also ohne zuviel schleifendurchlaeufe...?

Verfasst:
von
SEO Consulting bei ABAKUS Internet Marketing
Erfahrung seit 2002
  • persönliche Betreuung
  • individuelle Beratung
  • kompetente Umsetzung

Jetzt anfragen: 0511 / 300325-0.


Verfasst: 17.07.2009, 08:14
von SloMo
Mit "depth" ist das wirklich einfach. Immer, wenn ein Eintrag weiter eingerückt ist, als sein Vorgänger, wird eine <ul> aufgemacht. Immer, wenn ein Eintrag weniger eingerückt ist, wird die Liste wieder geschlossen. Du musst Dir nur merken, was die letzte Tiefe war.

Also ungefähr so:

Code: Alles auswählen

$prev_depth = 0;

while&#40; $obj = mysql_fetch_object&#40;$res&#41; &#41;&#123;
 
 $curr_depth = $obj->depth;

 if&#40; $curr_depth>$prev_depth &#41;&#123;
  echo "<ul>";
 &#125;elseif&#40; $curr_depth<$prev_depth &#41;&#123;
  echo "</ul>";
 &#125;
 
 echo "<li>&#123;$obj->name&#125;</li>";

 $prev_depth = $curr_depth;
&#125;
Allerdings gibt es da noch den Sonderfall, wenn die Diffenrenz zwischen $prev_depth und $curr_depth größer als 1 ist. Dann müssen ja mehrere Listen geschlossen werden. Aber das entwickelst Du am besten beim Debuggen. Da sind bestimmt auch noch andere kleine Macken im Code, aber das Prinzip sollte klar sein.

Verfasst: 17.07.2009, 10:20
von nerd
so hatte ichs ja schon versucht, dann wirds aber spagetticode. dachte eher an ne rekursive funktion...?

Verfasst: 17.07.2009, 11:04
von SloMo
Du stellst Ansprüche... ;)

Rekursiv ist es dasselbe. Ungefähr so müsste das aussehen:

Code: Alles auswählen

function UL&#40; $res, $depth &#41;&#123;
 echo&#40; '<ul>' &#41;;
 while&#40; $obj = mysql_fetch_object&#40;$res&#41; && $obj->depth>=$depth &#41;&#123;
  if&#40; $obj->depth > $depth &#41;&#123;
   UL&#40; $res, $obj->depth &#41;;
  &#125;else&#123;
   echo&#40; "<li>&#123;$obj->name&#125;</li>" &#41;;
  &#125;
 &#125;
 echo&#40; '</ul>' &#41;;
&#125;

UL&#40; $res, 0 &#41;;

Verfasst: 19.07.2009, 10:53
von nerd
sorry, ich bekomms einfach nicht gebacken.
das result set ($res) kommt als array aus der datenbank, also in der form

Code: Alles auswählen

$res&#91;0&#93;&#91;'name'&#93; = 'Home';
$res&#91;0&#93;&#91;'depth'&#93; = 0;
$res&#91;1&#93;&#91;'name'&#93; = 'USA';
$res&#91;1&#93;&#91;'depth'&#93; = '1';
$res&#91;2&#93;&#91;'name'&#93; = 'CA';
$res&#91;2&#93;&#91;'depth'&#93; = '2';
$res&#91;3&#93;&#91;'name'&#93; = 'San Francisco';
$res&#91;3&#93;&#91;'depth'&#93; = '3';
$res&#91;4&#93;&#91;'name'&#93; = 'Los Angeles';
$res&#91;4&#93;&#91;'depth'&#93; = '3';
$res&#91;5&#93;&#91;'name'&#93; = 'Sunset Blvd';
$res&#91;5&#93;&#91;'depth'&#93; = '4';
...
hier mein code, rapameter 1 ist mein result set, p2 die tiefe welche verglichen werden soll und parameter 3 die zeile mit der begonnen werden soll.
aufruf dann mit echo treeView($res);

Code: Alles auswählen

	function treeView&#40;$res, $depth=0, $start=0 &#41;
	&#123;
		$buf = '<ul>'."\r\n";
		for&#40;$i=$start;$i<=count&#40;$res&#41;-1;++$i&#41;
		&#123;
			//echo $i.' >> '.$res&#91;$i&#93;&#91;'name'&#93;.'&#58;&#58;'.$res&#91;$i&#93;&#91;'depth'&#93;.'&#58;&#58;'.$depth.'<br>';
			if&#40;$res&#91;$i&#93;&#91;'depth'&#93;>$depth&#41;
			&#123;
				$buf .= treeView&#40;$res, $res&#91;$i&#93;&#91;'depth'&#93;, $i&#41;;
			&#125;
			elseif&#40;$res&#91;$i&#93;&#91;'depth'&#93;<$depth&#41;
			&#123;
				break;
			&#125;
			elseif&#40;$res&#91;$i&#93;&#91;'depth'&#93;==$depth&#41; &#123;
				$buf .= '<li>'.$i.' >> '.$res&#91;$i&#93;&#91;'name'&#93;.'&#58;&#58;'.$res&#91;$i&#93;&#91;'depth'&#93;.'&#58;&#58;'.$depth.'</li>';			
			&#125;
		&#125;
		$buf .= '</ul>'."\r\n";
		return $buf;
	&#125;
irgendwie stimmt der zaehler aber nicht, hier das was rauskommt:

Code: Alles auswählen

<ul>
<li>0 >> CA&#58;&#58;0&#58;&#58;0</li>
<ul>
<li>1 >> San Francisco&#58;&#58;1&#58;&#58;1</li>
<li>2 >> Los Angeles&#58;&#58;1&#58;&#58;1</li>
<ul>
<li>3 >> Sunset Blvd&#58;&#58;2&#58;&#58;2</li>
</ul>
<li>4 >> Redding&#58;&#58;1&#58;&#58;1</li>
<ul>
<li>5 >> Dana Dr&#58;&#58;2&#58;&#58;2</li>

</ul>
</ul>
<ul>
<li>2 >> Los Angeles&#58;&#58;1&#58;&#58;1</li>
<ul>
<li>3 >> Sunset Blvd&#58;&#58;2&#58;&#58;2</li>
</ul>
<li>4 >> Redding&#58;&#58;1&#58;&#58;1</li>
<ul>
<li>5 >> Dana Dr&#58;&#58;2&#58;&#58;2</li>
</ul>
</ul>
<ul>

<li>3 >> Sunset Blvd&#58;&#58;2&#58;&#58;2</li>
</ul>
<ul>
<li>4 >> Redding&#58;&#58;1&#58;&#58;1</li>
<ul>
<li>5 >> Dana Dr&#58;&#58;2&#58;&#58;2</li>
</ul>
</ul>
<ul>
<li>5 >> Dana Dr&#58;&#58;2&#58;&#58;2</li>
</ul>
<li>6 >> CO&#58;&#58;0&#58;&#58;0</li>

<ul>
<li>7 >> Denver&#58;&#58;1&#58;&#58;1</li>
<ul>
<li>8 >> Champa St&#58;&#58;2&#58;&#58;2</li>
</ul>
<li>9 >> Boulder&#58;&#58;1&#58;&#58;1</li>
</ul>
<ul>
<li>8 >> Champa St&#58;&#58;2&#58;&#58;2</li>
</ul>
<ul>
<li>9 >> Boulder&#58;&#58;1&#58;&#58;1</li>
</ul>
</ul>

Verfasst: 19.07.2009, 12:20
von SloMo
Deshalb hatte ich Dir oben den leichten "Spaghetticode" gegeben. Damit wärest Du bei ähnlicher Zeilenzahl wohl schon am Ziel.

Ich denke Dein Problem ist, dass $i lokal ist. Wenn Du treeView(,,$i) aufrufst, arbeitet die Funktion sich zwar ab $i weiter durch das Array, aber der Stand geht beim Rücksprung verloren.

Du musst deshalb $i als Referenz übergeben. Ich bin mir nicht ganz sicher, ob der folgende Code stimmt, weil in PHP Referenzen etwas seltsam gehandhabt werden. Aber ich denke, da kommen im Fehlerfall aussagekräftige Meldungen.

Versuche mal die folgenden Änderungen (statt "$start" wird "&$i" in der Funktionsdefinition verwendet und in der Schleife wird $i nicht initialisiert:

Code: Alles auswählen

function treeView&#40;$res, $depth=0, &$i &#41;
&#123;
  $buf = '<ul>'."\r\n"; 
  for&#40;;$i<count&#40;$res&#41;;$i++&#41;
  &#123;
Beim ersten Aufruf musst Du so vorgehen:

Code: Alles auswählen

$i = 0; // init des Iterators
$output = treeView&#40; $res, 0, $i &#41;;
Der Rest des Codes müsste weitestgehend so bleiben können. Allerdings ist die HTML-Listenstruktur nicht richtig. Listen werden so verschachtelt:

Code: Alles auswählen

  <ul>
    <li>
      Parent Item
      <ul>
        <li>Child Item</li>
      </ul>
    </li>
  </ul>

Verfasst: 20.07.2009, 00:31
von nerd
yay - so gehts:

Code: Alles auswählen

	function treeView&#40;$res, $depth=0, &$i=0 &#41;
	&#123;
		$buf = '<ul>'."\r\n";
		for&#40;;$i<=count&#40;$res&#41;-1;$i++&#41;
		&#123;
			if&#40;$res&#91;$i&#93;&#91;'depth'&#93;>$depth&#41;
			&#123;
				$buf .= $this->treeView&#40;$res, $res&#91;$i&#93;&#91;'depth'&#93;, $i&#41;;
			&#125;
			elseif&#40;$res&#91;$i&#93;&#91;'depth'&#93;<$depth&#41;
			&#123;
				$i--; //hier muss der zaehler ums ein zurueckgesetzt werden, sonst werden elemente uebersprungen!
				break;
			&#125;
			elseif&#40;$res&#91;$i&#93;&#91;'depth'&#93;==$depth&#41; &#123;
				$buf .= '<li>'.$res&#91;$i&#93;&#91;'name'&#93;.'</li>'."\r\n";			
			&#125;
		&#125;
		$buf .= '</ul>'."\r\n";
		return $buf;
	&#125;
die ul/li's sind zwar nocht ganz korrekt, aber so akzeptiert es auch jeder browser. habe diese anordnung jedenfalls bisher immer so benutzt...

Verfasst: 20.07.2009, 08:24
von SloMo
nerd hat geschrieben:$i--; //hier muss der zaehler ums ein zurueckgesetzt werden, sonst werden elemente uebersprungen!
Weißte noch, als Du oben keinen Spaghetti-Code gefordert hast?

Man sollte eine Schleife nicht betreten, wenn man sie bei Abbruchbedingung ($res[$i]['depth']<$depth) ohnehin tatenlos verlässt. Im Endeffekt musst Du in der Abbruchbedingung nun sogar noch das aufräumen, was Du unordentlich zurückgelassen hast: $i--. Es ist also im Prinzip ein Bug mit Work-Around. So geht's sauber:

for(;$i<count($res) && $res[$i]['depth']>=$depth;$i++)

Verfasst: 20.07.2009, 13:05
von nerd
Danke fuer dein feedback; ich habe es ausprobiert (deine schleife rein und mein $i-- raus), aber dann stimmt das ergebniss nichtmehr:

Code: Alles auswählen

<li>CA
	<ul>
	<li><a href="city.php?id=123">San Francisco</a>
	<li>Los Angeles
		<ul>

		<li><a href="city.php?id=123">Sunset Blvd</a>
		</ul>
		<ul>
		<li><a href="city.php?id=123">Dana Dr</a>
		</ul>
	<li>Denver
		<ul>
		<li><a href="city.php?id=123">Champa St</a>

		</ul>
	</ul>
</ul>
hier fehlen ein paar elemente dazwischen...