{"id":137,"date":"2010-04-05T15:56:31","date_gmt":"2010-04-05T15:56:31","guid":{"rendered":"https:\/\/blog.ecotronics.ch\/wordpress\/?p=137"},"modified":"2011-08-29T17:51:03","modified_gmt":"2011-08-29T17:51:03","slug":"synchronisierbarer-tree-mit-jquery","status":"publish","type":"post","link":"https:\/\/blog.ecotronics.ch\/wordpress\/?p=137","title":{"rendered":"Synchronisierbarer Tree mit JQuery"},"content":{"rendered":"<p>Unter einem <strong>synchronisierbaren Tree<\/strong> verstehe ich einen Tree, der sich <strong>nicht nur mit Klicks innerhalb des Baumes <\/strong>\u00f6ffnet und schliesst, sondern bei dem die <strong>\u00c4ste auch von aussen ge\u00f6ffnet und geschlossen <\/strong>werden k\u00f6nnen. Ausl\u00f6ser f\u00fcr die vorliegende Technik waren die Anforderungen eines Webshops: Einerseits sollten die Kategorien eines Artikelkatalogs mit beliebiger Verschachtelungstiefe als Navigationsbaum dargestellt werden. Andererseits sollte auch \u00fcber eine statische Men\u00fcleiste sowie eine Bildnavigation in die Katalognavigation eingesprungen werden k\u00f6nnen. Der vorliegende Beitrag stellt nur den <strong>HTML- und JavaScript-Teil <\/strong>der L\u00f6sung vor, auf die serverseitige Aufbereitung des Baumes aus den Daten einer Oracle-Datenbank verzichte ich hier. F\u00fcr das JavaScript verwende ich das Framework <strong>JQuery <\/strong>in der <strong>Version 1.3.2<\/strong>.<\/p>\n<h1>Einbindung von JQuery<\/h1>\n<p>Im Header der HTML-Seite muss nat\u00fcrlich JQuery eingebunden werden. Die vorliegende L\u00f6sung beruht auf <strong>JQuery allein<\/strong>, es ist nicht n\u00f6tig, weitere JQuery-Pakete wie jquery.treeview einzubinden.<\/p>\n<pre>&lt;script type=\"text\/javascript\" src=\"jquery-1.3.2.min.js\"&gt;&lt;\/script&gt;<\/pre>\n<h1>HTML-Tree mit DIV-Tags<\/h1>\n<p>F\u00fcr den HTML-Teil des Baumes verwende ich nicht die Listen-Tags, sondern DIV-Elemente. Die \u00dcberlegung dahinter war, mir das Leben zu vereinfachen, indem ich den ganzen Baum aus einer einzigen Art von Tags aufbaue. \u00c4ste und Bl\u00e4tter werden dabei danach unterschieden, ob es noch ein verschachteltes Unterelement gibt oder nicht. Mit den Selektoren von JQuery l\u00e4sst sich das sehr bequem realisieren.<\/p>\n<p>Der Baum sieht in HTML folgendermassen aus:<\/p>\n<pre>&lt;div\r\n  style=\"border: 1px solid rgb(204, 204, 204); padding: 10px;\" &gt;\r\n  &lt;div id=\"ds_1\"&gt;Item 1\r\n    &lt;div id=\"ds_1_1\"&gt;Item 1_1&lt;\/div&gt;\r\n    &lt;div id=\"ds_1_2\"&gt;Item 1_2\r\n      &lt;div id=\"ds_1_2_1\"&gt;Item 1_2_1\r\n        &lt;div id=\"ds_1_2_1_1\"&gt;Item 1_2_1_1\r\n          &lt;div id=\"ds_1_2_1_1_1\"&gt;Item 1_2_1_1_1&lt;\/div&gt;\r\n        &lt;\/div&gt;\r\n        &lt;div id=\"ds_1_2_1_2\"&gt;Item 1_2_1_2&lt;\/div&gt;\r\n      &lt;\/div&gt;\r\n      &lt;div id=\"ds_1_2_2\"&gt;Item 1_2_2&lt;\/div&gt;\r\n      &lt;div id=\"ds_1_2_3\"&gt;Item 1_2_3&lt;\/div&gt;\r\n    &lt;\/div&gt;\r\n    &lt;div id=\"ds_1_3\"&gt;Item 1_3&lt;\/div&gt;\r\n  &lt;\/div&gt;\r\n  &lt;div id=\"ds_2\"&gt;Item 2\r\n    &lt;div id=\"ds_2_1\"&gt;Item 2_1&lt;\/div&gt;\r\n    &lt;div id=\"ds_2_2\"&gt;Item 2_2\r\n      &lt;div id=\"ds_2_2_1\"&gt;Item 2_2_1&lt;\/div&gt;\r\n      &lt;div id=\"ds_2_2_2\"&gt;Item 2_2_2&lt;\/div&gt;\r\n      &lt;div id=\"ds_2_2_3\"&gt;Item 2_2_3&lt;\/div&gt;\r\n    &lt;\/div&gt;\r\n    &lt;div id=\"ds_2_3\"&gt;Item 2_3&lt;\/div&gt;\r\n  &lt;\/div&gt;\r\n  &lt;div id=\"ds_3\"&gt;Item 3\r\n    &lt;div id=\"ds_3_1\"&gt;Item 3_1&lt;\/div&gt;\r\n    &lt;div id=\"ds_3_2\"&gt;Item 3_2\r\n      &lt;div id=\"ds_3_2_1\"&gt;Item 3_2_1&lt;\/div&gt;\r\n      &lt;div id=\"ds_3_2_2\"&gt;Item 3_2_2&lt;\/div&gt;\r\n      &lt;div id=\"ds_3_2_3\"&gt;Item 3_2_3&lt;\/div&gt;\r\n    &lt;\/div&gt;\r\n    &lt;div id=\"ds_3_3\"&gt;Item 3_3&lt;\/div&gt;\r\n  &lt;\/div&gt;\r\n  &lt;div id=\"ds_4\"&gt;Item 4\r\n    &lt;div id=\"ds_4_1\"&gt;Item 4_1&lt;\/div&gt;\r\n    &lt;div id=\"ds_4_2\"&gt;Item 4_2&lt;\/div&gt;\r\n  &lt;\/div&gt;\r\n  &lt;div id=\"ds_5\"&gt;Item 5\r\n  &lt;\/div&gt;\r\n&lt;\/div&gt;<\/pre>\n<p>Man ben\u00f6tigt nichts als die folgenden Zutaten:<\/p>\n<ul>\n<li>Ein Root-Element mit einer eindeutigen Klasse, hier\n<pre>&lt;div...&gt;...&lt;\/div&gt;<\/pre>\n<\/li>\n<li>Viele verschachtelte Div-Elemente mit einer eindeutigen id und dem angezeigten Text. Wenn die Inhalte wie bei einem Artikelkatalog aus einer Datenbank stammen, dann nimmt man als id am Besten den Prim\u00e4rschl\u00fcssel.<\/li>\n<\/ul>\n<h1>CSS<\/h1>\n<p>Diesen Div-Tags wird nachher dynamisch eine Klasse zugewiesen. Es gibt drei Klassen:<\/p>\n<ol>\n<li>ds_close: Alle \u00c4ste, die geschlossen sind<\/li>\n<li>ds_open: Alle \u00c4ste, die ge\u00f6ffnet sind<\/li>\n<li>ds_leaf: Alle Bl\u00e4tter (Childs)<\/li>\n<\/ol>\n<p>Ob ein Element angezeigt wird oder nicht, ist im zugeh\u00f6rigen CSS geregelt:<\/p>\n<pre>    &lt;style type=\"text\/css\"&gt;\r\n\r\n      \/* Style f\u00fcr das Root-Element des Baumes *\/\r\n      div.<strong>ds_katgliederung<\/strong> div {\r\n       padding-left:16px;\r\n      }\r\n\r\n      div.ds_katgliederung div.<strong>ds_close<\/strong> {\r\n       cursor:pointer !important;\r\n      }\r\n\r\n      div.ds_katgliederung div.<strong>ds_open<\/strong> {\r\n       cursor:pointer !important;\r\n      }\r\n\r\n      \/* Alle Div-Tags innerhalb eines geschlossenen Astes\r\n         werden nicht angezeigt *\/\r\n      div.ds_katgliederung div.<strong>ds_close div<\/strong> {\r\n       <strong>display:none;<\/strong>\r\n      }\r\n\r\n      div.ds_katgliederung div.<strong>ds_leaf<\/strong> {\r\n       cursor:default;\r\n      }\r\n\r\n    &lt;\/style&gt;<\/pre>\n<h1>JQuery-Code<\/h1>\n<p>Nun fehlt uns nur noch der JavaScript-Code. Er besteht aus zwei Funktionen und einem Event-Handler.\u00a0 Die Idee ist, dass man bei jedem Klick <strong>zuerst einmal bestimmt, welche Div-Tags \u00c4ste und welche Bl\u00e4tter sind, die entsprechenden Klassen anh\u00e4ngt, und anschliessend alle \u00c4ste schliesst<\/strong>. Zust\u00e4ndig daf\u00fcr ist die Funktion <strong>ds_init()<\/strong>.<\/p>\n<pre>function ds_init() {\r\n  \/\/setzt f\u00fcr alle Bl\u00e4tter bzw. Childs die Klasse ds_leaf\r\n  $('div.ds_katgliederung div').not('div:has(div)').attr(\"class\", \"ds_leaf\");\r\n  \/\/setzt alle \u00c4ste auf Klasse ds_close\r\n  \/\/-&gt; im Anfangszustand sind alle \u00c4ste geschlossen\r\n  $('div.ds_katgliederung div:has(div)').attr(\"class\", \"ds_close\");\r\n  return true;\r\n}<\/pre>\n<p>Dieses brachiale Vorgehen, <strong>alle DIV-Tags bei jedem Klick neu zu initialisieren<\/strong>, ist notwendig, weil es sich ja um einen dynamischen Baum handelt, der mit JavaScript und AJAX ver\u00e4ndert werden kann. Da somit aus einem Blatt pl\u00f6tzlich ein Ast werden kann, muss man die Klassen bei jedem Klick neu bestimmen.<\/p>\n<p>Die zweite Funktion namens ds_toggleNavigation \u00f6ffnet und schliesst die \u00c4ste. Als Parameter \u00fcbergibt man ihr die eindeutige id des Elementes, das ge\u00f6ffnet oder geschlossen werden soll. Diese Funktion ruft zuerst ds_init() auf, \u00f6ffnet dann alle Elternelemente des \u00fcbergebenen Tags und \u00f6ffnet schliesslich noch das Element selbst, falls es geschlossen ist. Die Umkehrung, das Schliessen eines ge\u00f6ffneten Elementes, ist nicht n\u00f6tig, das wird bereits in ds_init erledigt.<\/p>\n<pre>function ds_toggleNavigation(id) {\r\n  var tag = $(\"#\" + id);\r\n  var tagclass = tag.attr(\"class\");\r\n  ds_init();\r\n  tag.parents(\"div.ds_close\").attr(\"class\", \"ds_open\");\r\n  if (tagclass == \"ds_close\") {\r\n    tag.attr(\"class\", \"ds_open\");\r\n  }\r\n  return true;\r\n}<\/pre>\n<p>Nun ben\u00f6tigen wir noch den Event-Handler, der auf das OnClick-Ereignis reagiert. Dieser wird ebenso wie die erste Initialisierung\u00a0 innerhalb von $(document).ready platziert. Beim Eventhandler gilt es zu beachten, dass live(..) verwendet wird, damit auch auf dynamisch zum Baum hinzugef\u00fcgte Ereignisse reagiert werden kann.<\/p>\n<pre>$(document).ready(function() {\r\n  \/\/Meine ds_katgliederung\r\n  ds_init();\r\n\r\n  $('div.ds_katgliederung div').live('click', function(evt) {\r\n    var tag = $(this);\r\n\r\n    \/\/alert(tag.attr(\"id\"));\r\n    ds_toggleNavigation(tag.attr(\"id\"));\r\n    evt.stopImmediatePropagation();\r\n    return true;\r\n\r\n  });\r\n});<\/pre>\n<p>Damit haben wir eigentlich alles, was zum Baum geh\u00f6rt, beisammen. Allerdings habe ich zwei Behauptungen aufgestellt, die ich noch kurz beweisen m\u00f6chte:<\/p>\n<ol>\n<li>Der Baum l\u00e4sst sich nicht nur mit Klicks innerhalb, sondern auch von aussen \u00f6ffnen und schliessen. Um das zu zeigen spendieren Sie der Seite die folgenden Links und klicken darauf:\n<pre>&lt;a href=\"#\" onclick=\"<strong>ds_toggleNavigation('ds_1_2_1')<\/strong>\"&gt;Toggle 1_2_1&lt;\/a&gt;&lt;br\/&gt;\r\n&lt;a href=\"#\" onclick=\"ds_toggleNavigation('ds_2_2')\"&gt;Toggle 2_2&lt;\/a&gt;&lt;br\/&gt;\r\n&lt;a href=\"#\" onclick=\"ds_toggleNavigation('ds_2')\"&gt;Toggle 2&lt;\/a&gt;<\/pre>\n<\/li>\n<li>Die Navigation funktioniert auch dann, wenn Elemente dynamisch ver\u00e4ndert, angeh\u00e4ngt oder gel\u00f6scht werden. Der folgende JQuery-Code h\u00e4ngt\u00a0 einem mit der \u00fcbergebenen Id bestimmten Element ein neues Blatt-Element an.\n<pre>function ds_append(id) {\r\n  var tag = $(\"#\" + id);\r\n  var laufnr = 1;\r\n  if (tag.children()) {\r\n    laufnr = tag.children().length + 1;\r\n  }\r\n  var parent = 0;\r\n  \/\/alert(\"id \" +id );\r\n  var position = id.lastIndexOf(\"_\");\r\n  parent = id.substr(position + 1);\r\n\r\n  \/\/alert(\"Laufnr : \" + laufnr + \"\\nParent: \" + parent);\r\n  var childId = parent + \"_\" + laufnr;\r\n  tag.append(\"&lt;div id=\\\"ds_\" + childId +\"\\\"&gt;Item \" + childId + \"&lt;\/div&gt;\");\r\n  ds_init();\r\n}<\/pre>\n<p>Um die Funktion aufzurufen, binden Sie ebenfalls einen Link ein:<\/p>\n<pre>&lt;a href=\"#\" <strong>onclick=\"ds_append('ds_5')<\/strong>\"&gt;Append to 5&lt;\/a&gt;&lt;br\/&gt;<\/pre>\n<p>Jedes Mal, wenn Sie auf diesen Link klicken, wird dem Element mit id &#8220;ds_5&#8221; ein weiteres Blatt angeh\u00e4ngt. Beim ersten Klick wird aus dem Blatt sofort ein Ast, der nun seinerseits auf Klicks reagiert.<\/li>\n<\/ol>\n","protected":false},"excerpt":{"rendered":"<p>Unter einem synchronisierbaren Tree verstehe ich einen Tree, der sich nicht nur mit Klicks innerhalb des Baumes \u00f6ffnet und schliesst, sondern bei dem die \u00c4ste auch von aussen ge\u00f6ffnet und geschlossen werden k\u00f6nnen. Ausl\u00f6ser f\u00fcr die vorliegende Technik waren die Anforderungen eines Webshops: Einerseits sollten die Kategorien eines Artikelkatalogs mit beliebiger Verschachtelungstiefe als Navigationsbaum dargestellt [&#8230;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[35],"tags":[290,28,47,46],"_links":{"self":[{"href":"https:\/\/blog.ecotronics.ch\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/137"}],"collection":[{"href":"https:\/\/blog.ecotronics.ch\/wordpress\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.ecotronics.ch\/wordpress\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.ecotronics.ch\/wordpress\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.ecotronics.ch\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=137"}],"version-history":[{"count":8,"href":"https:\/\/blog.ecotronics.ch\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/137\/revisions"}],"predecessor-version":[{"id":142,"href":"https:\/\/blog.ecotronics.ch\/wordpress\/index.php?rest_route=\/wp\/v2\/posts\/137\/revisions\/142"}],"wp:attachment":[{"href":"https:\/\/blog.ecotronics.ch\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=137"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.ecotronics.ch\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=137"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.ecotronics.ch\/wordpress\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=137"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}