Merge morphTable and morphTree to matchInfo module
diff --git a/public/js/src/matchInfo.js b/public/js/src/matchInfo.js
new file mode 100644
index 0000000..515a78f
--- /dev/null
+++ b/public/js/src/matchInfo.js
@@ -0,0 +1,319 @@
+/**
+ * Make annotations visible.
+ *
+ * @author Nils Diewald
+ */
+/*
+  - 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";
+
+  KorAP._AvailableRE = new RegExp("^([^\/]+?)\/([^=]+?)(?:=(spans|rels|tokens))?$");
+  KorAP._TermRE = new RegExp("^([^\/]+?)(?:\/([^:]+?))?:(.+?)$");
+  KorAP._matchTerms  = ["corpusID", "docID", "textID"];
+
+  // API requests
+  KorAP.API = KorAP.API || {};
+  KorAP.API.getMatchInfo = KorAP.API.getMatchInfo || function () { return {} };
+
+  KorAP.MatchInfo = {
+
+    /**
+     * Create a new annotation object.
+     * Expects an array of available foundry/layer=type terms.
+     * Supported types are 'spans', 'tokens' and 'rels'.
+     */
+    create : function (match, available) {
+      if (arguments.length < 2)
+	throw new Error("Missing parameters");
+
+      return Object.create(KorAP.MatchInfo)._init(match, available);
+    },
+
+    _init : function (match, available) {
+      this._match = KorAP.Match.create(match);
+      this._available = {
+	tokens : [],
+	spans : [],
+	rels : []
+      };
+      for (var i = 0; i < available.length; i++) {
+	var term = available[i];
+	// Create info layer objects
+	try {
+	  var layer = KorAP.InfoLayer.create(term);
+	  this._available[layer.type].push(layer);
+	}
+	catch (e) {
+	  continue;
+	};
+      };
+      return this;
+    },
+
+
+    /**
+     * Return a list of parseable tree annotations.
+     */
+    getSpans : function () {
+      return this._available.spans;
+    },
+
+
+    /**
+     * Return a list of parseable token annotations.
+     */
+    getTokens : function () {
+      return this._available.tokens;
+    },
+
+
+    /**
+     * Return a list of parseable relation annotations.
+     */
+    getRels : function () {
+      return this._available.rels;
+    },
+
+
+    getTable : function (tokens) {
+      var focus = [];
+
+      // Get all tokens
+      if (tokens === undefined) {
+	focus = this.getTokens();
+      } 
+
+      // Get only some tokens
+      else {
+
+	// Push newly to focus array
+	for (var i = 0; i < tokens.length; i++) {
+	  var term = tokens[i];
+	  try {
+	    // Create info layer objects
+	    var layer = KorAP.InfoLayer.create(term);
+	    layer.type = "tokens";
+	    focus.push(layer);
+	  }
+	  catch (e) {
+	    continue;
+	  };
+	};
+      };
+
+      // No tokens chosen
+      if (focus.length == 0)
+	return;
+
+      // Get info (may be cached)
+      var matchResponse = KorAP.API.getMatchInfo(
+	this._match,
+	{ 'spans' : true, 'layer' : focus }
+      );
+
+      // Get snippet from match info
+      if (matchResponse["snippet"] !== undefined) {
+	this._table = KorAP.InfoTable.create(matchResponse["snippet"]);
+	return this._table;
+      };
+
+      return null;
+    }
+
+    /*
+    // Parse snippet for table visualization
+    getTree : function (foundry, layer) {
+    },
+    */
+  };
+
+  KorAP.Match = {
+    create : function (match) {
+      return Object.create(KorAP.Match)._init(match);
+    },
+    _init : function (match) {
+      for (var i in KorAP._matchTerms) {
+	var term = KorAP._matchTerms[i];
+	if (match[term] !== undefined) {
+	  this[term] = match[term];
+	}
+	else {
+	  this[term] = undefined;
+	}
+      };
+      return this;
+    },
+  };
+
+  /**
+   *
+   * Alternatively pass a string as <tt>base/s=span</tt>
+   *
+   * @param foundry
+   */
+  KorAP.InfoLayer = {
+    create : function (foundry, layer, type) {
+      return Object.create(KorAP.InfoLayer)._init(foundry, layer, type);
+    },
+    _init : function (foundry, layer, type) {
+      if (foundry === undefined)
+	throw new Error("Missing parameters");
+
+      if (layer === undefined) {
+	if (KorAP._AvailableRE.exec(foundry)) {
+	  this.foundry = RegExp.$1;
+	  this.layer = RegExp.$2;
+	  this.type = RegExp.$3;
+	}
+	else {
+	  throw new Error("Missing parameters");
+	};
+      }
+      else {
+	this.foundry = foundry;
+	this.layer = layer;
+	this.type = type;
+      };
+
+      if (this.type === undefined)
+	this.type = 'tokens';
+
+      return this;
+    }
+  };
+
+
+  KorAP.InfoTable = {
+    create : function (snippet) {
+      return Object.create(KorAP.InfoTable)._init(snippet);
+    },
+    _init : function (snippet) {
+      // Create html for traversal
+      var html = document.createElement("div");
+      html.innerHTML = snippet;
+
+      this._pos = 0;
+      this._token = [];
+      this._info = [];
+      this._foundry = [];
+      this._layer = [];
+
+      // Parse the snippet
+      this._parse(html.childNodes);      
+
+      this._layer = undefined;
+      this._foundry = undefined;
+
+      html.innerHTML = '';
+      return this;
+    },
+
+    length : function () {
+      return this._pos;
+    },
+
+    getToken : function (pos) {
+      if (pos === undefined)
+	return this._token;
+      return this._token[pos];
+    },
+
+    getValue : function (pos, foundry, layer) {
+      return this._info[pos][foundry + '/' + layer]
+    },
+
+    getLayerPerFoundry : function (foundry) {
+      return this._foundry[foundry]
+    },
+
+    getFoundryPerLayer : function (layer) {
+      return this._layer[layer];
+    },
+
+    // Parse the snippet
+    _parse : function (children) {
+
+      // Get all children
+      for (var i in children) {
+	var c = children[i];
+
+	// Create object on position unless it exists
+	if (this._info[this._pos] === undefined)
+	  this._info[this._pos] = {};
+
+	// Store at position in foundry/layer as array
+	var found = this._info[this._pos];
+
+	// Element with title
+	if (c.nodeType === 1) {
+	  if (c.getAttribute("title") &&
+	      KorAP._TermRE.exec(c.getAttribute("title"))) {
+
+	    // Fill position with info
+	    var foundry, layer, value;
+	    if (RegExp.$2) {
+	      foundry = RegExp.$1;
+	      layer   = RegExp.$2;
+	    }
+	    else {
+	      foundry = "base";
+	      layer   = RegExp.$1
+	    };
+
+	    value = RegExp.$3;
+
+	    if (found[foundry + "/" + layer] === undefined)
+	      found[foundry + "/" + layer] = [];
+
+	    // Push value to foundry/layer at correct position
+	    found[foundry + "/" + layer].push(RegExp.$3);
+
+	    // Set foundry
+	    if (!this._foundry[foundry])
+	      this._foundry[foundry] = {};
+	    this._foundry[foundry][layer] = 1;
+
+	    // Set layer
+	    if (!this._layer[layer])
+	      this._layer[layer] = {};
+	    this._layer[layer][foundry] = 1;
+	  };
+
+	  // depth search
+	  if (c.hasChildNodes())
+	    this._parse(c.childNodes);
+	}
+
+	// Leaf node - store string on position and go to next string
+	else if (c.nodeType === 3) {
+	  if (c.nodeValue.match(/[a-z0-9]/i))
+	    this._token[this._pos++] = c.nodeValue;
+	};
+      };
+
+      delete this._info[this._pos];
+    },
+    element : function () {
+      var ce = document.createElement;
+      // First the legend table
+      /*
+      var table = ce('table');
+      var row = ce('tr');
+      table.appendChild(tr);
+      */      
+    }
+  };
+
+  
+  /*
+    KorAP.InfoFoundryLayer = {};
+    KorAP.InfoTree = {};
+    KorAP.InfoTable = {};
+  */
+}(this.KorAP));
diff --git a/public/js/src/menu.js b/public/js/src/menu.js
index 3d0017d..8ccf740 100644
--- a/public/js/src/menu.js
+++ b/public/js/src/menu.js
@@ -83,6 +83,9 @@
 	this.next();
 	break;
       case 39: // 'Right'
+	if (this._prefix.active())
+	  break;
+
 	var item = this.liveItem(this._position);
 	if (item["further"] !== undefined) {
 	  item["further"].bind(item).apply();
@@ -223,6 +226,7 @@
      * @param {string} Prefix for filtering the list
      */
     show : function () {
+
       // Initialize the list
       if (!this._initList())
 	return false;
@@ -233,6 +237,7 @@
       // Set the first element to active
       // Todo: Or the last element chosen
       this.liveItem(0).active(true);
+      this._prefix.active(false);
 
       this._active = this._list[0];
       this._position = 0;
@@ -466,8 +471,13 @@
       var newItem;
 
       // Set new live item
-      var oldItem = this.liveItem(this._position++);
-      oldItem.active(false);
+      if (!this._prefix.active()) {
+	var oldItem = this.liveItem(this._position);
+	oldItem.active(false);
+      };
+
+      this._position++;
+
       newItem = this.liveItem(this._position);
 
       // The next element is undefined - roll to top or to prefix
@@ -501,6 +511,42 @@
       newItem.active(true);
     },
 
+    /*
+     * Page down to the first item on the next page
+     */
+    /*
+    nextPage : function () {
+
+      // Prefix is active
+      if (this._prefix.active()) {
+	this._prefix.active(false);
+      }
+
+      // Last item is chosen
+      else if (this._position >= this.limit() + this._offset) {
+
+	this._position = this.limit() + this._offset - 1;
+	newItem = this.liveItem(this._position);
+	var oldItem = this.liveItem(this._position--);
+	oldItem.active(false);
+      }
+
+      // Last item of page is chosen
+      else if (0) {
+
+      // Jump to last item
+      else {
+	var oldItem = this.liveItem(this._position);
+	oldItem.active(false);
+
+	this._position = this.limit() + this._offset - 1;
+	newItem = this.liveItem(this._position);
+      };
+
+      newItem.active(true);
+    },
+	*/
+
 
     /*
      * Make the previous item in the menu active
@@ -508,8 +554,10 @@
     prev : function () {
 
       // No active element set
-      if (this._position == -1)
+      if (this._position === -1) {
 	return;
+	// TODO: Choose last item
+      };
 
       var newItem;
 
@@ -534,7 +582,6 @@
 	this._position = this.liveLength() - 1;
 
 	if (prefix.isSet() && !prefix.active()) {
-
 	  this._position++;
 	  prefix.active(true);
 	  return;
@@ -792,6 +839,10 @@
       // Add prefix span
       this._element = document.createElement('span');
       this._element.classList.add('pref');
+      // Connect action
+      if (this.onclick !== undefined)
+	this._element["onclick"] = this.onclick.bind(this);
+
       return this;
     },
     _update : function () {