Use requirejs for clientside scripting
diff --git a/dev/js/src/match.js b/dev/js/src/match.js
index 4e3786b..beb4bca 100644
--- a/dev/js/src/match.js
+++ b/dev/js/src/match.js
@@ -4,46 +4,31 @@
  *
  * @author Nils Diewald
  */
-// require menu.js, dagre
 /*
  * - 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);
-  };
+define([
+  'match/infolayer',
+  'match/info',
+  'util'
+], function (infoLayerClass,
+	     infoClass) {
 
   // Localization values
-  var loc   = (KorAP.Locale = KorAP.Locale || {} );
+  var loc   = KorAP.Locale;
   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', 'matchID', 'available'];
-
-  // API requests
-  KorAP.API = KorAP.API || {};
-
-  // TODO: Make this async
-  KorAP.API.getMatchInfo = KorAP.API.getMatchInfo || function () {
-    KorAP.log(0, 'KorAP.API.getMatchInfo() not implemented')
-    return {};
-  };
+  
+  // KorAP._AvailableRE = new RegExp("^([^\/]+?)\/([^=]+?)(?:=(spans|rels|tokens))?$");
+  // KorAP._TermRE      = new RegExp("^(?:([^\/]+?)\/)?([^:]+?):(.+?)$");
+  var _matchTerms  = ['corpusID', 'docID', 'textID', 'matchID', 'available'];
 
   /**
    * Match object
    */
-  KorAP.Match = {
+  return {
 
     /**
      * Create a new annotation object.
@@ -51,7 +36,7 @@
      * Supported types are 'spans', 'tokens' and 'rels'.
      */
     create : function (match) {
-      return Object.create(KorAP.Match)._init(match);
+      return Object.create(this)._init(match);
     },
 
     /**
@@ -87,8 +72,8 @@
       else {
 
 	// Iterate over allowed match terms
-	for (var i in KorAP._matchTerms) {
-	  var term = KorAP._matchTerms[i];
+	for (var i in _matchTerms) {
+	  var term = _matchTerms[i];
 	  if (match[term] !== undefined) {
 	    this[term] = match[term];
 	  }
@@ -97,7 +82,7 @@
 	  }
 	};
       };
-
+      
       this._available = {
 	tokens : [],
 	spans  : [],
@@ -110,7 +95,7 @@
 
 	// Create info layer objects
 	try {
-	  var layer = KorAP.InfoLayer.create(term);
+	  var layer = infoLayerClass.create(term);
 	  this._available[layer.type].push(layer);
 	}
 	catch (e) {
@@ -178,7 +163,7 @@
 	.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'))
@@ -227,7 +212,7 @@
 
       // Create match info
       if (this._info === undefined)
-	this._info = KorAP.MatchInfo.create(this);
+	this._info = infoClass.create(this);
 
       // There is an element to append
       if (this._element === undefined ||
@@ -251,812 +236,4 @@
       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;
-      this.opened = false;
-      return this;
-    },
-
-    /**
-     * Get match object
-     */
-    match : function () {
-      return this._match;
-    },
-
-    toggle : function () {
-      if (this.opened == true) {
-	this._match.element().children[0].removeChild(
-	  this.element()
-	);
-	this.opened = false;
-      }
-      else {
-	// Append element to match
-	this._match.element().children[0].appendChild(
-	  this.element()
-	);
-	this.opened = true;
-      };
-
-      return this.opened;
-    },
-
-
-    /**
-     * Retrieve and parse snippet for table representation
-     */
-    getTable : function (tokens, cb) {
-      var focus = [];
-
-      // Get all tokens
-      if (tokens === undefined) {
-	focus = this._match.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)
-	cb(null);
-
-      // Get info (may be cached)
-      // TODO: Async
-      KorAP.API.getMatchInfo(
-	this._match,
-	{ 'spans' : false, 'layer' : focus },
-
-	// Callback for retrieval
-	function (matchResponse) {
-	  // Get snippet from match info
-	  if (matchResponse["snippet"] !== undefined) {
-	    this._table = KorAP.MatchTable.create(matchResponse["snippet"]);
-	    cb(this._table);
-	  };
-	}.bind(this)
-      );
-
-/*
-      // Todo: Store the table as a hash of the focus
-      return null;
-*/
-    },
-
-
-    /**
-     * Retrieve and parse snippet for tree representation
-     */
-    getTree : function (foundry, layer, cb) {
-      var focus = [];
-
-      // TODO: Support and cache multiple trees
-
-      KorAP.API.getMatchInfo(
-	this._match, {
-	  'spans' : true,
-	  'foundry' : foundry,
-	  'layer' : layer
-	},
-	function (matchResponse) {
-	  // Get snippet from match info
-	  if (matchResponse["snippet"] !== undefined) {
-	    // Todo: This should be cached somehow
-	    cb(KorAP.MatchTree.create(matchResponse["snippet"]));
-	  }
-	  else {
-	    cb(null);
-	  };
-	}.bind(this)
-      );
-    },
-
-    /**
-     * 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, cb) {
-      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')
-      );
-
-      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();
-	}
-      );
-
-      // Get tree data async
-      this.getTree(foundry, layer, function (treeObj) {
-	// Something went wrong - probably log!!!
-	if (treeObj === null) {
-	  tree.appendChild(document.createTextNode('No data available.'));
-	}
-	else {
-	  tree.appendChild(treeObj.element());
-	  // 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();
-	}
-
-	if (cb !== undefined)
-	  cb(treeObj);
-      });
-    },
-
-    /**
-     * 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');
-      info.appendChild(matchtable);
-
-      // Create the table asynchronous
-      this.getTable(undefined, function (table) {
-	if (table !== null) {
-	  matchtable.appendChild(table.element());
-	};
-      });
-
-      // Get spans
-      var spanLayers = this._match.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.classList.add('addtree');
-      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;
-
-    },
-
-
-    /**
-     * 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;
-
-      return this._treeMenu = KorAP.MatchTreeMenu.create(this, list);
-    }
-  };
-
-
-
-  /**
-   *
-   * 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.MatchTable = {
-    create : function (snippet) {
-      return Object.create(KorAP.MatchTable)._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);      
-
-      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] === undefined)
-	      this._foundry[foundry] = {};
-	    this._foundry[foundry][layer] = 1;
-
-	    // Set layer
-	    if (this._layer[layer] === undefined)
-	      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];
-    },
-
-
-    /**
-     * 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');
-
-      // Single row in head
-      var tr = table.appendChild(d.createElement('thead'))
-	.appendChild(d.createElement('tr'));
-
-      // Add cell to row
-      var addCell = function (type, name) {
-	var c = this.appendChild(d.createElement(type))
-	if (name === undefined)
-	  return c;
-
-	if (name instanceof Array) {
-	  for (var n = 0; n < name.length; n++) {
-	    c.appendChild(d.createTextNode(name[n]));
-	    if (n !== name.length - 1) {
-	      c.appendChild(d.createElement('br'));
-	    };
-	  };
-	}
-	else {
-	  c.appendChild(d.createTextNode(name));
-	};
-      };
-
-      tr.addCell = addCell;
-
-      // Add header information
-      tr.addCell('th', 'Foundry');
-      tr.addCell('th', 'Layer');
-
-      // Add tokens
-      for (var i in this._token) {
-	tr.addCell('th', this.getToken(i));
-      };
-
-      var tbody = table.appendChild(
-	d.createElement('tbody')
-      );
-
-      var foundryList = Object.keys(this._foundry).sort();
-
-      for (var f = 0; f < foundryList.length; f++) {
-	var foundry = foundryList[f];
-	var layerList =
-	  Object.keys(this._foundry[foundry]).sort();
-
-	for (var l = 0; l < layerList.length; l++) {
-	  var layer = layerList[l];
-	  tr = tbody.appendChild(
-	    d.createElement('tr')
-	  );
-	  tr.setAttribute('tabindex', 0);
-	  tr.addCell = addCell;
-
-	  tr.addCell('th', foundry);
-	  tr.addCell('th', layer);
-
-	  for (var v = 0; v < this.length(); v++) {
-	    tr.addCell(
-	      'td',
-	      this.getValue(v, foundry, layer) 
-	    );
-	  };
-	};
-      };
-
-      return this._element = table;
-    }
-  };
-
-
-  /**
-   * Visualize span annotations as a tree using Dagre.
-   */
-  KorAP.MatchTree = {
-
-    create : function (snippet) {
-      return Object.create(KorAP.MatchTree)._init(snippet);
-    },
-
-    nodes : function () {
-      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;
-      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._addNode(
-	this._next++,
-	{ "class" : "root" }
-      );
-
-      // Parse nodes from root
-      this._parse(0, html.childNodes);
-
-      // Root node has only one child - remove
-      if (g.outEdges(0).length === 1)
-	g.removeNode(0);
-
-      html = undefined;
-      return this;
-    },
-
-    // Remove foundry and layer for labels
-    _clean : function (title) {
-      return title.replace(KorAP._TermRE, "$3");
-    },
-
-    // Parse the snippet
-    _parse : function (parent, children) {
-      for (var i in children) {
-	var c = children[i];
-
-	// Element node
-	if (c.nodeType == 1) {
-
-	  // Get title from html
-	  if (c.getAttribute("title")) {
-	    var title = this._clean(c.getAttribute("title"));
-
-	    // Add child node
-	    var id = this._next++;
-
-	    this._addNode(id, {
-	      "class" : "middle",
-	      "label" : title
-	    });
-	    this._addEdge(parent, id);
-
-	    // Check for next level
-	    if (c.hasChildNodes())
-	      this._parse(id, c.childNodes);
-	  }
-
-	  // Step further
-	  else if (c.hasChildNodes())
-	    this._parse(parent, c.childNodes);
-	}
-
-	// Text node
-	else if (c.nodeType == 3)
-
-	  if (c.nodeValue.match(/[-a-z0-9]/i)) {
-
-	    // Add child node
-	    var id = this._next++;
-	    this._addNode(id, {
-	      "class" : "leaf",
-	      "label" : c.nodeValue
-	    });
-
-	    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 () {
-      if (this._element !== undefined)
-	return this._element;
-
-      var g = this._graph;
-
-      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, 'rx', 5);
-	  rect.setAttributeNS(null, 'ry', 5);
-	  rect.setAttributeNS(null, 'width', v.width);
-	  rect.setAttributeNS(null, 'height', v.height);
-
-	  if (v.class === 'root' && v.label === undefined) {
-	    rect.setAttributeNS(null, 'width', v.height);
-	    rect.setAttributeNS(null, 'x', v.x - v.height / 2);
-	    rect.setAttributeNS(null, 'class', 'empty');
-	  };
-
-	  // Add label
-	  if (v.label !== undefined) {
-	    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();
-      if (menu.info() !== undefined)
-	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));
+});