HTML - PHP - MySQL - SVG

KERNI's SEITE

Das Nested Set Model

Veröffentlicht am


Nested Set

 

Vor einiger Zeit musste ich mir ein Tool zum bearbeiten der Menüleiste überlegen. Es musste relativ einfach zu bedienen sein. Im Netz bin ich auf das Nested Set Modell gestoßen was mich gleich faszinierte. Ich hatte schon etwas Erfahrung mit hierachischen Strukturen, aber einfach zu bedienen sind die nicht. Einem Eintrag muss immer ein Vorgänger zugewiesen werden und das Auslesen gestalltet sich auch etwas schwerer.

 

Ein Beispiel: Test

ID Bezeichnung parent_ID Sortierung
1 Reptilien 0 1
2 Frosch 1 4
3 Krokodil 1 5
4 Primaten 0 2
5 Schimpanse 4 6
6 Orangutan 4 7
7 Bonobo 4 11
8 Vögel 0 3
9 Star 8 13
10 Rabe 8 14
11 Elster 8 15
12 Kanari 8 16
13 Test 01 6 8
14 Test 02 6 9
15 Test 03 6 10
16 Gorilla 4 12

 

An diesem Beispiel sieht man wie eine Hierarchische Struktur aussieht. Jedes Element besitzt eine ID, eine Bezeichnung und eine parrent_ID.

Die parent_ID spiegelt die ID wieder. Reptielien hat die ID 1. Bei Frosch und Krokodil ist diese in der parent_ID. Somit kann der Frosch und das Krokodil den Reptilien zugeordnet werden.

Sortiert soll das Ganze auch noch ausgegeben werden. Dazu wird die Spalte Sortierung benötigt.

Nun kann man mit einer Schleife die Elemente nach parrent_ID auslesen und zusammenbauen. Dazu muss in jeder Schleife auf die Datenbank zugegriffen werden was für die Performanz eher nicht so toll ist.

Da die beiden Felder für parent_ID und Sortierung schon vorhanden sind, warum diese nicht etwas besser nutzen und das Nested Set Modell verwenden?

 

Das Nested Set Model

Arne Klempert hat das Thema Nested Set auf seiner Seite sehr schön beschrieben. Wer mehr dazu wissen will kann sich seinen Artikel gern durchlesen.

Nested Set verwendet die Mengenlehre um Hirarchische Strukturen abzubilden. Datenbanktechnisch ist dieses Model sehr leicht auszulesen und die Geschwindigkeit ist auch nicht zu verachten.

Hr. Klempert zeigt sehr schön wie in das Nested Set Elemente eingefügt oder auch gelöscht werden können. In seinem Artikel fehlte mir nur: Wie rücke ich ein Element ein oder aus?

Ein paar Excel Tests später war die Lösung eigentlich ganz einfach.

 

Aber der Reihe nach

Als erstes benötigen wir eine Tabelle in der Datenbank.

 

		CREATE TABLE tree (
		    id    INT(12)      UNSIGNED NOT NULL AUTO_INCREMENT,
		    name  VARCHAR(50)  NOT NULL,
		    lft   INT(12)      UNSIGNED NOT NULL,
		    rgt   INT(12)      UNSIGNED NOT NULL,
		    PRIMARY KEY (id)
		);
	

 

Nachdem die Tabelle angelegt wurde können wir auch schon ein paar Daten einpflegen.

 

		INSERT INTO `tree` (`id`, `name`, `lft`, `rgt`) VALUES
		(1, 'Reptilien', 1, 6),
		(2, 'Frosch', 2, 3),
		(3, 'Krokodil', 4, 5),
		(4, 'Primaten', 7, 14),
		(5, 'Schimpanse', 8, 9),
		(6, 'Orangutan', 10, 11),
		(7, 'Bonobo', 12, 13),
		(8, 'Vögel', 15, 24),
		(9, 'Star', 16, 17),
		(10, 'Rabe', 18, 19),
		(11, 'Elster', 20, 21),
		(12, 'Kanari', 22, 23);
	

 

Wie einzelne Nodes (Knoten) hinzugefügt werden, dazu kommen wir noch.

Nun schauen wir uns mal an wie der Baum aussehen soll.

Reptilien
ZFrosch
ZKrokodil
Primaten
ZSchimpanse
ZOrangutan
ZBonobo
Vögel
ZStar
ZRabe
ZElster
ZKanari

Dies ist ein einfacher Baum. Für dieses Beispiel aber ausreichend. Nun basteln wir uns ein Formular zum bearbeiten. Jede Zeile bekommt 5 Buttons. Einen zum Löschen, einen für nach oben und unten verschieben, einen zum einrücken und einen zum ausrücken.

Zum füllen des Formulars benötigen wir ersteinmal den Code zum auslesen. (Die Verbindung zur Datenbank via PDO setzte ich voraus)

 

PHP Code: auslesen.php

$sql = "SELECT n.id, n.name, n.lft, n.rgt, COUNT(*)-1 AS ebene
		        FROM tree AS n, tree AS p
		        WHERE n.lft BETWEEN p.lft AND p.rgt GROUP BY n.lft ORDER BY n.lft";
		 
		$rst = $pdo->prepare($sql);
		$rst->execute();
		$arr = array();
		 
		while($row = $rst->fetch(PDO::FETCH_ASSOC)){
		    array_push($arr,$row);
		}
		 
		echo json_encode($arr);
	

 

Um die Daten die von dieser Datei zurückgegeben werden auch verarbeiten zu können und anschaulich auszugeben, benötigen wir noch eine HTML Datei. Um uns das Leben etwas einfacher zu machen benötigen wir zusätzlich JQuery.

 

HTML Code: index.php
JavaScript: JQuery, EasyUI
Font: IcoMoon (Download auf der IcoMoon Seite)

		<!DOCTYPE HTML>
		<html>
		<head>
		<title>Titel der in der Tableiste angezeigt wird</title>
		<meta charset="utf-8">
		<script src="js/jquery.min.js"></script>
		<script src="js/jquery.easyui.min.js"></script>
		<link rel="stylesheet" href="js/themes/metro/easyui.css"></link>
		<link rel="stylesheet" href="js/themes/color.css"></link>
		</head>
		<body>
		  <table id="bearbtab">
		  <thead>
		    <th>DELETE</th>
		    <th>UP</th>
		    <th>DOWN</th>
		    <th>LEFT</th>
		    <th>RIGHT</th>
		    <th>name</th>
		    <th>id</th>
		    <th>lft</th>
		    <th>rgt</th>
		    <th>ebene</th>
		  </thead>
		  <tbody></tbody>
		  </table>
		  
		  <label for="mname">Neuer Name:</label>
		  <input type="text" maxlength="100" id="mname" name="mname" class="easyui-textbox">
		  <label for="mid">Einfügen nach:</label>
		  <select id="mid" name="mid" data-options="valueField:'id',textField:'name',url:'php/auslesen.php'" class="easyui-combobox" style="width:100px;"></select>
		  <button onclick="EinfuegenNach()" class="easyui-linkbutton">Speichern</button>
		 
		</body>
		</html>
	

 

Noch ein wenig Styling

 

		#bearbtab td:nth-child(1) button { background-color: #f00; }
		.mup { display: block; transform: rotate(270deg); }
		.mdown{ display: block; transform: rotate(90deg); }
	

 

Das Grundgerüst für die Bearbeitung unsere Struktur steht schon mal. Nun muss noch der Inhalt geladen und eingefügt werden. Dazu benötigen wir dieses Script.

 

		function laden(){
		 
		  $.ajax({
		    url:"php/auslesen.php"
		  })
		  .done(function(data){
		    var obj = $.parseJSON(data);
		    var tstr = "";
		    var placeholder = "-";
		    var tempph = "";
		    var anzahl = Object.keys(obj).length;
		    var i = 1;
		    var tmplinks = 0;
		    var links=0;
		    $.each(obj,function(key,val){
		      links=parseInt(val.ebene);
		      tstr += "<tr>";
		    // Löschen
		      tstr += "<td><button onclick=\"openWindowLoeschen("+val.id+")\">X</button></td>";
		    // UP
		      if(i!=1){tstr += "<td><button onclick=\"nodeUP("+val.id+")\"><span class=\"mup\">></span></button></td>";}else{tstr += "<td></td>";}
		    // DOWN
		      if(i!=anzahl){tstr += "<td><button onclick=\"nodeDOWN("+val.id+")\"><span class=\"mdown\">></span></button></td>";}else{tstr += "<td></td>";}
		    // LEFT
		      if(links<tmplinks){
		        tstr += "<td> </td>";
		      }else{
		        tstr += "<td><button onclick=\"toLeft("+val.id+")\"><</button></td>";
		      }
		    // RIGHT
		      if(links>tmplinks){
		        tstr += "<td> </td>";
		      }else{
		        tstr += "<td><button onclick=\"toRight("+val.id+")\">></button></td>";
		      }
		      tmplinks=links;
		    // NAME + Einzug (-)
		      tempph = placeholder.repeat(val.ebene);
		      tstr += "<td><span class=\"im\">"+tempph+"</span>"+val.name+"</td>";
		    // ID
		      tstr += "<td>"+val.id+"</td>";
		    // LFT
		      tstr += "<td>"+val.lft+"</td>";
		    // RGT
		      tstr += "<td>"+val.rgt+"</td>";
		    // EBENE
		      tstr += "<td>"+val.ebene+"</td>";
		      
		      tstr += "</tr>";
		      i++;
		    });
		    $("tbody").html(tstr);
		  });
		}
	

 

Ergebnis:

So sollte die Seite nun aussehen. Natürlich könnte man das etwas hübscher gestalten, aber hier geht es um das Wesentliche, die Funktion. Gestalten könnt Ihr das nach eurem Geschmacksempfinden.

 

DELETE UP DOWN LEFT RIGHT name id lft rgt ebene
  Reptilien 1 1 6 0
  -Frosch 2 2 3 1
-Krokodil 3 4 5 1
  Primaten 4 7 14 0
  -Schimpanse 5 8 9 1
-Orangutan 6 10 11 1
-Bonobo 7 12 13 1
  Vögel 8 15 24 0
  -Star 9 16 17 1
-Rabe 10 18 19 1
-Elster 11 20 21 1
  -Kanari 12 22 23 1

 

Nachdem dies erledigt ist können wir mit den einzelnen Funktionen der Buttons anfangen.