Major redesign of JS and Sass assets
diff --git a/public/js/src/matchInfo.js b/public/js/src/match.js
similarity index 77%
rename from public/js/src/matchInfo.js
rename to public/js/src/match.js
index 30bd7b5..f8fdd91 100644
--- a/public/js/src/matchInfo.js
+++ b/public/js/src/match.js
@@ -1,11 +1,11 @@
 /**
- * Visualize annotations.
+ * Get information on matches,
+ * generate annotation tables and trees.
  *
  * @author Nils Diewald
  */
-// require menu.js
+// require menu.js, dagre
 /*
- * - Scroll with a static left legend.
  * - Highlight (at least mark as bold) the match
  * - Scroll to match vertically per default
  */
@@ -23,11 +23,13 @@
 
   // Localization values
   var loc   = (KorAP.Locale = KorAP.Locale || {} );
-  loc.ADDTREE = loc.ADDTREE || 'Add tree view';
+  loc.ADDTREE  = loc.ADDTREE  || 'Add tree view';
+  loc.SHOWINFO = loc.SHOWINFO || 'Show information';
+  loc.CLOSE    = loc.CLOSE    || 'Close';
 
   KorAP._AvailableRE = new RegExp("^([^\/]+?)\/([^=]+?)(?:=(spans|rels|tokens))?$");
-  KorAP._TermRE = new RegExp("^(?:([^\/]+?)\/)?([^:]+?):(.+?)$");
-  KorAP._matchTerms  = ["corpusID", "docID", "textID"];
+  KorAP._TermRE      = new RegExp("^(?:([^\/]+?)\/)?([^:]+?):(.+?)$");
+  KorAP._matchTerms  = ['corpusID', 'docID', 'textID', 'matchID', 'available'];
 
   // API requests
   KorAP.API = KorAP.API || {};
@@ -35,42 +37,75 @@
   // TODO: Make this async
   KorAP.API.getMatchInfo = KorAP.API.getMatchInfo || function () { return {} };
 
-  KorAP.MatchInfo = {
+
+  /**
+   * Match object
+   */
+  KorAP.Match = {
 
     /**
      * 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);
+    create : function (match) {
+      return Object.create(KorAP.Match)._init(match);
     },
 
     /**
-     * Destroy this match information view.
+     * Initialize match.
      */
-    destroy : function () {
+    _init : function (match) {
+      this._element = null;
 
-      // Remove circular reference
-      if (this._treeMenu !== undefined)
-	delete this._treeMenu["info"];
+      // No match defined
+      if (arguments.length < 1 ||
+	  match === null ||
+	  match === undefined) {
+	throw new Error('Missing parameters');
+      }
 
-      this._treeMenu.destroy();
-      this._treeMenu = undefined;
-    },
+      // Match defined as a node
+      else if (match instanceof Node) {
+	this._element  = match;
 
-    _init : function (match, available) {
-      this._match = KorAP.Match.create(match);
+	// Circular reference !!
+	match["_match"] = this;
+
+	this.corpusID  = match.getAttribute('data-corpus-id'),
+	this.docID     = match.getAttribute('data-doc-id'),
+	this.textID    = match.getAttribute('data-text-id'),
+	this.matchID   = match.getAttribute('data-match-id')
+
+	// List of available annotations
+	this.available = match.getAttribute('data-available-info').split(' ');
+      }
+
+      // Match as an object
+      else {
+
+	// Iterate over allowed match terms
+	for (var i in KorAP._matchTerms) {
+	  var term = KorAP._matchTerms[i];
+	  if (match[term] !== undefined) {
+	    this[term] = match[term];
+	  }
+	  else {
+	    this[term] = undefined;
+	  }
+	};
+      };
+
       this._available = {
 	tokens : [],
-	spans : [],
-	rels : []
+	spans  : [],
+	rels   : []
       };
-      for (var i = 0; i < available.length; i++) {
-	var term = available[i];
+
+      // Iterate over info layers
+      for (var i = 0; i < this.available.length; i++) {
+	var term = this.available[i];
+
 	// Create info layer objects
 	try {
 	  var layer = KorAP.InfoLayer.create(term);
@@ -80,6 +115,7 @@
 	  continue;
 	};
       };
+
       return this;
     },
 
@@ -107,16 +143,160 @@
       return this._available.rels;
     },
 
+    /**
+     * Open match
+     */
+    open : function () {
+
+      // Add actions unless it's already activated
+      var element = this._element;
+
+      // There is an element to open
+      if (this._element === undefined || this._element === null)
+	return false;
+
+      // The element is already opened
+      if (element.classList.contains('active'))
+	return false;
+      
+      // Add active class to element
+      element.classList.add('active');
+
+      // Create action buttons
+      var ul = document.createElement('ul');
+      ul.classList.add('action', 'right');
+      element.appendChild(ul);
+
+      // Use localization
+      var loc = KorAP.Locale;
+
+      // Add close button
+      var close = document.createElement('li');
+      close.appendChild(document.createElement('span'))
+	.appendChild(document.createTextNode(loc.CLOSE));
+      close.classList.add('close');
+      close.setAttribute('title', loc.CLOSE);
+
+      // Add info button
+      var info = document.createElement('li');
+      info.appendChild(document.createElement('span'))
+	.appendChild(document.createTextNode(loc.SHOWINFO));
+      info.classList.add('info');
+      info.setAttribute('title', loc.SHOWINFO);
+
+      var that = this;
+
+      // Close match
+      close.addEventListener('click', function (e) {
+	e.halt();
+	that.close()
+      });
+
+      // Add information, unless it already exists
+      info.addEventListener('click', function (e) {
+	e.halt();
+	that.info();
+      });
+
+      ul.appendChild(close);
+      ul.appendChild(info);
+
+      return true;
+    },
+
 
     /**
-     * Get table object.
+     * Close info view
+     */
+    close : function () {
+      this._element.classList.remove('active');
+
+/*
+      if (this._info !== undefined) {
+	this._info.destroy();
+      };
+*/
+    },
+
+
+
+    /**
+     * Get and open associated match info.
+     */
+    info : function () {
+
+      // Create match info
+      if (this._info === undefined)
+	this._info = KorAP.MatchInfo.create(this);
+
+      // There is an element to append
+      if (this._element === undefined ||
+	  this._element === null)
+	return this._info;
+
+      // Info is already activated
+      if (this._info._elemet !== undefined)
+	return this._info;
+
+      // Append element to match
+      this._element.children[0].appendChild(
+	this._info.element()
+      );
+
+      return this._info;
+    },
+
+
+    /**
+     * Get match element.
+     */
+    element : function () {
+
+      // May be null
+      return this._element;
+    }
+  };
+
+
+
+  /**
+   * Information about a match.
+   */
+  KorAP.MatchInfo = {
+
+    /**
+     * Create new object
+     */
+    create : function (match) {
+      return Object.create(KorAP.MatchInfo)._init(match);
+    },
+
+    /**
+     * Initialize object
+     */
+    _init : function (match) {
+      this._match = match;
+      return this;
+    },
+
+
+    /**
+     * Get match object
+     */
+    match : function () {
+      return this._match;
+    },
+
+
+    /**
+     * Retrieve and parse snippet for table representation
      */
     getTable : function (tokens) {
       var focus = [];
 
       // Get all tokens
       if (tokens === undefined) {
-	focus = this.getTokens();
+	focus = this._match.getTokens();
       } 
 
       // Get only some tokens
@@ -154,10 +334,15 @@
 	return this._table;
       };
 
+      // Todo: Store the table as a hash of the focus
+
       return null;
     },
 
-    // Parse snippet for table visualization
+
+    /**
+     * Retrieve and parse snippet for tree representation
+     */
     getTree : function (foundry, layer) {
       var focus = [];
 
@@ -182,6 +367,23 @@
     },
 
     /**
+     * Destroy this match information view.
+     */
+    destroy : function () {
+
+      // Remove circular reference
+      if (this._treeMenu !== undefined)
+	delete this._treeMenu["info"];
+
+      this._treeMenu.destroy();
+      this._treeMenu = undefined;
+      this._match = undefined;
+
+      // Element destroy
+    },
+
+
+    /**
      * Add a new tree view to the list
      */
     addTree : function (foundry, layer) {
@@ -225,6 +427,7 @@
      * Create match information view.
      */
     element : function () {
+
       if (this._element !== undefined)
 	return this._element;
 
@@ -238,7 +441,8 @@
       matchtable.appendChild(this.getTable().element());
       info.appendChild(matchtable);
 
-      var spanLayers = this.getSpans().sort(
+      // Get spans
+      var spanLayers = this._match.getSpans().sort(
 	function (a, b) {
 	  if (a.foundry < b.foundry) {
 	    return -1;
@@ -272,6 +476,7 @@
       // Create tree menu
       var treemenu = this.treeMenu(menuList);
       var span = info.appendChild(document.createElement('p'));
+      span.classList.add('addtree');
       span.appendChild(document.createTextNode(loc.ADDTREE));
 
       var treeElement = treemenu.element();
@@ -285,8 +490,15 @@
       this._element = info;
 
       return info;
+
     },
 
+
+    /**
+     * Get tree menu.
+     * There is only one menu rendered
+     * - no matter how many trees exist
+     */
     treeMenu : function (list) {
       if (this._treeMenu !== undefined)
 	return this._treeMenu;
@@ -295,23 +507,7 @@
     }
   };
 
-  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;
-    },
-  };
+
 
   /**
    *
@@ -765,7 +961,8 @@
       var menu = this.menu();
       menu.hide();
       e.halt();
-      menu.info().addTree(this._foundry, this._layer);
+      if (menu.info() !== undefined)
+	menu.info().addTree(this._foundry, this._layer);
     },
     
     _init : function (params) {
@@ -791,6 +988,7 @@
 	.upgradeTo(KorAP.MatchTreeMenu)
 	._init(KorAP.MatchTreeItem, undefined, params);
       obj.limit(6);
+
       obj._info = info;
 
       // This is only domspecific
diff --git a/public/js/src/util.js b/public/js/src/util.js
new file mode 100644
index 0000000..0f0f114
--- /dev/null
+++ b/public/js/src/util.js
@@ -0,0 +1,134 @@
+/**
+ * These are utility functions for the frontend
+ */
+
+// Add toggleClass method similar to jquery
+HTMLElement.prototype.toggleClass = function (c1, c2) {
+  var cl = this.classList;
+  if (cl.contains(c1)) {
+    cl.add(c2);
+    cl.remove(c1);
+  }
+  else {
+    cl.remove(c2);
+    cl.add(c1);
+  };
+};
+
+
+// Don't let events bubble up
+if (Event.halt === undefined) {
+  // Don't let events bubble up
+  Event.prototype.halt = function () {
+    this.stopPropagation();
+    this.preventDefault();
+  };
+};
+
+var KorAP = KorAP || {};
+
+
+(function (KorAP) {
+  "use strict";
+
+  KorAP.init = function () {
+
+    /**
+     * Add actions to match entries
+     */
+    var inactiveLi = document.querySelectorAll('#search > ol > li:not(.active)');
+    var i = 0;
+    for (i = 0; i < inactiveLi.length; i++) {
+      inactiveLi[i].addEventListener('click', function () {
+
+	if (this._match !== undefined) {
+	  this._match.open();
+	  console.log('already open');
+	}
+	else {
+	  KorAP.Match.create(this).open();
+	  console.log('newly open');
+	}
+
+	
+      });
+    };
+
+    /**
+     * Toggle the alignment (left <=> right)
+     */
+    if (i > 0) {
+      var br = document.getElementById('button-right');
+      if (br !== undefined) {
+	var toggle = document.createElement('a');
+	toggle.setAttribute('title', 'toggle Alignment');
+	// Todo: Reuse old alignment from cookie!
+	var cl = toggle.classList;
+	cl.add('align');
+	cl.add('right');
+	toggle.addEventListener(
+	  'click',
+	  function (e) {
+	    var ol = document.querySelector('#search > ol');
+	    ol.toggleClass("align-left", "align-right");
+	    this.toggleClass("left", "right");
+	  });
+	toggle.appendChild(document.createElement('span'))
+	  .appendChild(document.createTextNode('Alignment'));
+	br.appendChild(toggle);
+      };
+    };
+  };
+
+  /*
+  function _openMatch (e) {
+    e.halt();
+    this.classList.add("active");
+    var matchElement = this;
+
+    // Todo: Add object to element
+    var ul = document.createElement('ul');
+    ul.classList.add('action', 'right');
+    matchElement.appendChild(ul);
+
+    // Todo:: Localize!
+    var close = document.createElement('li');
+    close.appendChild(document.createElement('span'))
+      .appendChild(document.createTextNode('Close'));
+    close.classList.add('close');
+    close.setAttribute('title', 'Close');
+
+    close.addEventListener('click', function (ie) {
+      ie.halt();
+      var match = matchElement['_match'];
+      match.destroy();
+      matchElement.classList.remove('active');
+      matchElement.removeChild(ul);
+    });
+
+    // Todo:: Localize!
+    var info = document.createElement('li');
+    info.appendChild(document.createElement('span'))
+      .appendChild(document.createTextNode('Info'));
+    info.classList.add('info');
+    info.setAttribute('title', 'Information');
+
+    // Add information, unless it already exists
+    info.addEventListener('click', function (ie) {
+      ie.halt();
+      KorAP.Match.create(matchElement).addInfo();
+    });
+
+    ul.appendChild(close);
+    ul.appendChild(info);
+  };
+*/
+
+  /**
+  function _closeMatch (e) {
+    e.halt();
+    this.parentNode.parentNode.classList.remove("active");
+  };
+  */
+
+}(this.KorAP));