Finalized tree view / removed d3
diff --git a/public/js/src/matchInfo.js b/public/js/src/matchInfo.js
index 540a605..30bd7b5 100644
--- a/public/js/src/matchInfo.js
+++ b/public/js/src/matchInfo.js
@@ -3,27 +3,35 @@
  *
  * @author Nils Diewald
  */
+// require menu.js
 /*
-  - Scroll with a static left legend.
-  - Highlight (at least mark as bold) the match
-  - Scroll to match vertically per default
+ * - Scroll with a static left legend.
+ * - Highlight (at least mark as bold) the match
+ * - Scroll to match vertically per default
  */
 var KorAP = KorAP || {};
 
 (function (KorAP) {
   "use strict";
 
+  var svgXmlns = "http://www.w3.org/2000/svg";
+
   // Default log message
   KorAP.log = KorAP.log || function (type, msg) {
     console.log(type + ": " + msg);
   };
 
+  // Localization values
+  var loc   = (KorAP.Locale = KorAP.Locale || {} );
+  loc.ADDTREE = loc.ADDTREE || 'Add tree view';
+
   KorAP._AvailableRE = new RegExp("^([^\/]+?)\/([^=]+?)(?:=(spans|rels|tokens))?$");
   KorAP._TermRE = new RegExp("^(?:([^\/]+?)\/)?([^:]+?):(.+?)$");
   KorAP._matchTerms  = ["corpusID", "docID", "textID"];
 
   // API requests
   KorAP.API = KorAP.API || {};
+
   // TODO: Make this async
   KorAP.API.getMatchInfo = KorAP.API.getMatchInfo || function () { return {} };
 
@@ -41,6 +49,19 @@
       return Object.create(KorAP.MatchInfo)._init(match, available);
     },
 
+    /**
+     * Destroy this match information view.
+     */
+    destroy : function () {
+
+      // Remove circular reference
+      if (this._treeMenu !== undefined)
+	delete this._treeMenu["info"];
+
+      this._treeMenu.destroy();
+      this._treeMenu = undefined;
+    },
+
     _init : function (match, available) {
       this._match = KorAP.Match.create(match);
       this._available = {
@@ -153,12 +174,124 @@
 
       // Get snippet from match info
       if (matchResponse["snippet"] !== undefined) {
-	this._tree = KorAP.MatchTree.create(matchResponse["snippet"]);
-	return this._tree;
+	// Todo: This should be cached somehow
+	return KorAP.MatchTree.create(matchResponse["snippet"]);
       };
 
       return null;
- 
+    },
+
+    /**
+     * Add a new tree view to the list
+     */
+    addTree : function (foundry, layer) {
+      var treeObj = this.getTree(foundry, layer);
+
+      // Something went wrong - probably log!!!
+      if (treeObj === null)
+	return;
+
+      var matchtree = document.createElement('div');
+      matchtree.classList.add('matchtree');
+
+      var h6 = matchtree.appendChild(document.createElement('h6'));
+      h6.appendChild(document.createElement('span'))
+	.appendChild(document.createTextNode(foundry));
+      h6.appendChild(document.createElement('span'))
+	.appendChild(document.createTextNode(layer));
+
+      var tree = matchtree.appendChild(
+	document.createElement('div')
+      );
+      tree.appendChild(treeObj.element());
+      this._element.insertBefore(matchtree, this._element.lastChild);
+
+      var close = tree.appendChild(document.createElement('em'));
+      close.addEventListener(
+	'click', function (e) {
+	  matchtree.parentNode.removeChild(matchtree);
+	  e.halt();
+	}
+      );
+
+      // Reposition the view to the center
+      // (This may in a future release be a reposition
+      // to move the root into the center or the actual
+      // match)
+      treeObj.center();
+    },
+
+    /**
+     * Create match information view.
+     */
+    element : function () {
+      if (this._element !== undefined)
+	return this._element;
+
+      // Create info table
+      var info = document.createElement('div');
+      info.classList.add('matchinfo');
+
+      // Append default table
+      var matchtable = document.createElement('div');
+      matchtable.classList.add('matchtable');
+      matchtable.appendChild(this.getTable().element());
+      info.appendChild(matchtable);
+
+      var spanLayers = this.getSpans().sort(
+	function (a, b) {
+	  if (a.foundry < b.foundry) {
+	    return -1;
+	  }
+	  else if (a.foundry > b.foundry) {
+	    return 1;
+	  }
+	  else if (a.layer < b.layer) {
+	    return -1;
+	  }
+	  else if (a.layer > b.layer) {
+	    return 1;
+	  };
+	  return 0;
+	});
+
+      var menuList = [];
+
+      // Show tree views
+      for (var i = 0; i < spanLayers.length; i++) {
+	var span = spanLayers[i];
+	
+	// Add foundry/layer to menu list
+	menuList.push([
+	  span.foundry + '/' + span.layer,
+	  span.foundry,
+	  span.layer
+	]);
+      };
+
+      // Create tree menu
+      var treemenu = this.treeMenu(menuList);
+      var span = info.appendChild(document.createElement('p'));
+      span.appendChild(document.createTextNode(loc.ADDTREE));
+
+      var treeElement = treemenu.element();
+      span.appendChild(treeElement);
+
+      span.addEventListener('click', function (e) {
+	treemenu.show('');
+	treemenu.focus();
+      });
+      
+      this._element = info;
+
+      return info;
+    },
+
+    treeMenu : function (list) {
+      if (this._treeMenu !== undefined)
+	return this._treeMenu;
+
+      return this._treeMenu = KorAP.MatchTreeMenu.create(this, list);
     }
   };
 
@@ -332,6 +465,9 @@
      * Get HTML table view of annotations.
      */
     element : function () {
+      if (this._element !== undefined)
+	return this._element;
+
       // First the legend table
       var d = document;
       var table = d.createElement('table');
@@ -401,14 +537,14 @@
 	};
       };
 
-      return table;
+      return this._element = table;
     }
   };
 
+
   /**
-   * Visualize span annotations as a tree.
+   * Visualize span annotations as a tree using Dagre.
    */
-  // http://java-hackers.com/p/paralin/meteor-dagre-d3
   KorAP.MatchTree = {
 
     create : function (snippet) {
@@ -419,44 +555,47 @@
       return this._next;
     },
 
+    _addNode : function (id, obj) {
+      obj["width"] = 55;
+      obj["height"] = 20;
+      this._graph.setNode(id, obj)
+    },
+
+    _addEdge : function (src, target) {
+      this._graph.setEdge(src, target);
+    },
+
     _init : function (snippet) {
       this._next = new Number(0);
 
       // Create html for traversal
       var html = document.createElement("div");
       html.innerHTML = snippet;
-      this._graph = new dagreD3.Digraph();
+      var g = new dagre.graphlib.Graph({
+	"directed" : true	
+      });
+      g.setGraph({
+	"nodesep" : 35,
+	"ranksep" : 15,
+	"marginx" : 40,
+	"marginy" : 10
+      });
+      g.setDefaultEdgeLabel({});
+
+      this._graph = g;
 
       // This is a new root
-      this._graph.addNode(
+      this._addNode(
 	this._next++,
-	{ "nodeclass" : "root" }
+	{ "class" : "root" }
       );
 
       // Parse nodes from root
       this._parse(0, html.childNodes);
 
       // Root node has only one child - remove
-      if (Object.keys(this._graph._outEdges[0]).length === 1)
-	  this._graph.delNode(0);
-
-      // Initialize d3 renderer for dagre
-      this._renderer = new dagreD3.Renderer();
-      /*
-      var oldDrawNodes = this._renderer.drawNodes();
-      this._renderer.drawNodes(
-	function (graph, root) {
-	  var svgNodes = oldDrawNodes(graph, root);
-	  svgNodes.each(
-	    function (u) {
-	      d3.select(this).classed(graph.node(u).nodeClass, true);
-	    }
-	  );
-	}
-      );
-*/
-      // Disable pan and zoom
-      this._renderer.zoom(false);
+      if (g.outEdges(0).length === 1)
+	g.removeNode(0);
 
       html = undefined;
       return this;
@@ -464,7 +603,7 @@
 
     // Remove foundry and layer for labels
     _clean : function (title) {
-      return title.replace(KorAP._TermRE, RegExp.$1);
+      return title.replace(KorAP._TermRE, "$3");
     },
 
     // Parse the snippet
@@ -482,11 +621,11 @@
 	    // Add child node
 	    var id = this._next++;
 
-	    this._graph.addNode(id, {
-	      "nodeclass" : "middle",
+	    this._addNode(id, {
+	      "class" : "middle",
 	      "label" : title
 	    });
-	    this._graph.addEdge(null, parent, id);
+	    this._addEdge(parent, id);
 
 	    // Check for next level
 	    if (c.hasChildNodes())
@@ -505,36 +644,180 @@
 
 	    // Add child node
 	    var id = this._next++;
-	    this._graph.addNode(id, {
-	      "nodeclass" : "leaf",
+	    this._addNode(id, {
+	      "class" : "leaf",
 	      "label" : c.nodeValue
 	    });
 
-	    this._graph.addEdge(null, parent, id);
+	    this._addEdge(parent, id);
 	  };
       };
       return this;
     },
 
+    /**
+     * Center the viewport of the canvas
+     */
+    center : function () {
+      if (this._element === undefined)
+	return;
+
+      var treeDiv = this._element.parentNode;
+
+      var cWidth = parseFloat(window.getComputedStyle(this._element).width);
+      var treeWidth = parseFloat(window.getComputedStyle(treeDiv).width);
+      // Reposition:
+      if (cWidth > treeWidth) {
+	var scrollValue = (cWidth - treeWidth) / 2;
+	treeDiv.scrollLeft = scrollValue;
+      };
+    },
+
+    // Get element
     element : function () {
-      this._element = document.createElement('div');
-      var svg = document.createElement('svg');
-      this._element.appendChild(svg);
-      var svgGroup = svg.appendChild(document.createElement('svg:g'));
+      if (this._element !== undefined)
+	return this._element;
 
-      svgGroup = d3.select(svgGroup);
+      var g = this._graph;
 
-      console.log(svgGroup);
-      var layout = this._renderer.run(this._graph, svgGroup);
-/*
-      var w = layout.graph().width;
-      var h = layout.graph().height;
-      this._element.setAttribute("width", w + 10);
-      this._element.setAttribute("height", h + 10);
-      svgGroup.attr("transform", "translate(5, 5)");
-*/
+      dagre.layout(g);
+
+      var canvas = document.createElementNS(svgXmlns, 'svg');
+      this._element = canvas;
+
+      canvas.setAttribute('height', g.graph().height);
+      canvas.setAttribute('width', g.graph().width);
+
+      // Create edges
+      g.edges().forEach(
+	function (e) {
+	  var src = g.node(e.v);
+	  var target = g.node(e.w);
+	  var p = document.createElementNS(svgXmlns, 'path');
+	  p.setAttributeNS(null, "d", _line(src, target));
+	  p.classList.add('edge');
+	  canvas.appendChild(p);
+	});
+
+      // Create nodes
+      g.nodes().forEach(
+	function (v) {
+	  v = g.node(v);
+	  var group = document.createElementNS(svgXmlns, 'g');
+	  group.classList.add(v.class);
+
+	  // Add node box
+	  var rect = group.appendChild(document.createElementNS(svgXmlns, 'rect'));
+	  rect.setAttributeNS(null, 'x', v.x - v.width / 2);
+	  rect.setAttributeNS(null, 'y', v.y - v.height / 2);
+	  rect.setAttributeNS(null, 'width', v.width);
+	  rect.setAttributeNS(null, 'height', v.height);
+	  rect.setAttributeNS(null, 'rx', 5);
+	  rect.setAttributeNS(null, 'ry', 5);
+
+	  // Add label
+	  var text = group.appendChild(document.createElementNS(svgXmlns, 'text'));
+	  text.setAttributeNS(null, 'x', v.x - v.width / 2);
+	  text.setAttributeNS(null, 'y', v.y - v.height / 2);
+	  text.setAttributeNS(
+	    null,
+	    'transform',
+	    'translate(' + v.width/2 + ',' + ((v.height / 2) + 5) + ')'
+	  );
+	  var tspan = document.createElementNS(svgXmlns, 'tspan');
+	  tspan.appendChild(document.createTextNode(v.label));
+	  text.appendChild(tspan);
+	  canvas.appendChild(group);
+	}
+      );
+
       return this._element;
     }
   };
 
+  /**
+   * Menu item for tree view choice.
+   */
+  KorAP.MatchTreeItem = {
+    create : function (params) {
+      return Object.create(KorAP.MenuItem)
+	.upgradeTo(KorAP.MatchTreeItem)._init(params);
+    },
+    content : function (content) {
+      if (arguments.length === 1) {
+	this._content = content;
+      };
+      return this._content;
+    },
+
+    // The foundry attribute
+    foundry : function () {
+      return this._foundry;
+    },
+
+    // The layer attribute
+    layer : function () {
+      return this._layer;
+    },
+
+    // enter or click
+    onclick : function (e) {
+      var menu = this.menu();
+      menu.hide();
+      e.halt();
+      menu.info().addTree(this._foundry, this._layer);
+    },
+    
+    _init : function (params) {
+      if (params[0] === undefined)
+	throw new Error("Missing parameters");
+
+      this._name    = params[0];
+      this._foundry = params[1];
+      this._layer   = params[2];
+      this._content = document.createTextNode(this._name);
+      this._lcField = ' ' + this.content().textContent.toLowerCase();
+      return this;
+    }
+  };
+
+
+  /**
+   * Menu to choose from for tree views.
+   */
+  KorAP.MatchTreeMenu = {
+    create : function (info, params) {
+      var obj = Object.create(KorAP.Menu)
+	.upgradeTo(KorAP.MatchTreeMenu)
+	._init(KorAP.MatchTreeItem, undefined, params);
+      obj.limit(6);
+      obj._info = info;
+
+      // This is only domspecific
+      obj.element().addEventListener('blur', function (e) {
+	this.menu.hide();
+      });
+
+      return obj;
+    },
+    info :function () {
+      return this._info;
+    }
+  };
+
+
+  // Create path for node connections 
+  function _line (src, target) {
+    var x1 = src.x,
+        y1 = src.y,
+        x2 = target.x,
+        y2 = target.y - target.height / 2;
+
+    // c 0,0 -10,0
+    return 'M ' + x1 + ',' + y1 + ' ' + 
+      'C ' + x1 + ',' + y1 + ' ' + 
+      x2 + ',' + (y2 - (y2 - y1) / 2)  + ' ' + 
+      x2 + ',' + y2;
+  };
+
 }(this.KorAP));
diff --git a/public/js/src/menu.js b/public/js/src/menu.js
index 8ccf740..a7271c5 100644
--- a/public/js/src/menu.js
+++ b/public/js/src/menu.js
@@ -40,6 +40,21 @@
       return Object.create(KorAP.Menu)._init(params);
     },
 
+    /**
+     * Destroy this menu
+     * (in case you don't trust the
+     * mark and sweep GC)!
+     */
+    destroy : function () {
+      if (this._element != undefined)
+	delete this._element["menu"]; 
+
+      for (var i = 0; i < this._items.length; i++) {
+	delete this._items[i]["_menu"];
+      };
+      
+    },
+
     focus : function () {
       this._element.focus();
     },
@@ -96,11 +111,11 @@
 
 	// Click on prefix
 	if (this._prefix.active())
-	  this._prefix.onclick();
+	  this._prefix.onclick(e);
 
 	// Click on item
 	else
-	  this.liveItem(this._position).onclick();
+	  this.liveItem(this._position).onclick(e);
 	e.halt();
 	break;
       case 8: // 'Backspace'
@@ -139,6 +154,9 @@
       e.setAttribute('class', 'menu');
       e.appendChild(this._prefix.element());
 
+      // This has to be cleaned up later on
+      e["menu"] = this;
+
       // Arrow keys
       e.addEventListener(
 	'keypress',
@@ -165,6 +183,10 @@
       // Initialize item list based on parameters
       for (i in params) {
 	var obj = itemClass.create(params[i]);
+
+	// This may become circular
+	obj["_menu"] = this;
+
 	this._items.push(obj);
       };
       this._limit    = KorAP.menuLimit;
@@ -252,10 +274,7 @@
       this.active = false;
       this.delete();
       this._element.style.opacity = 0;
-
-/*
-      this._element.blur();
-*/
+      /* this._element.blur(); */
     },
 
     // Initialize the list
@@ -827,6 +846,13 @@
 
       return this;
     },
+
+    /**
+     * Return menu list.
+     */
+    menu : function () {
+      return this._menu;
+    }
   };
 
   KorAP.MenuPrefix = {
@@ -890,7 +916,7 @@
       this._string += string;
       this._update();
     },
-    onclick : function () {},
+    onclick : function (e) {},
     backspace : function () {
       if (this._string.length > 1) {
 	this._string = this._string.substring(
diff --git a/public/js/src/vc.js b/public/js/src/vc.js
index fa63457..2c8ad01 100644
--- a/public/js/src/vc.js
+++ b/public/js/src/vc.js
@@ -14,6 +14,8 @@
   TODO: Implement a working localization solution!
   TODO: Disable "and" or "or" in case it's followed
         by an unspecified document
+  TODO: Implement "persistance"-Option,
+        injecting the current creation date stamp
 
   Error codes:
   701: "JSON-LD group has no @type attribute"