Added tutorial and session mechanism
diff --git a/public/js/src/api.js b/public/js/src/api.js
new file mode 100644
index 0000000..ed33cc2
--- /dev/null
+++ b/public/js/src/api.js
@@ -0,0 +1,80 @@
+var KorAP = KorAP || {};
+
+(function (KorAP) {
+  "use strict";
+
+  // Default log message
+  KorAP.log = KorAP.log || function (type, msg) {
+    console.log(type + ": " + msg);
+  };
+
+  KorAP.URL = KorAP.URL || 'http://korap.ids-mannheim.de/kalamar';
+
+  // TODO: https://github.com/honza/140medley/blob/master/140medley.js
+  // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest
+  // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest
+  // r.addEventListener("progress", updateProgress, false);
+  // http://www.javascriptkit.com/javatutors/loadjavascriptcss.shtml
+  // http://stackoverflow.com/questions/6112744/load-javascript-on-demand
+
+  KorAP.API = {
+    getMatchInfo : function (match, param, cb) {
+      // match is a KorAP.Match object
+
+      var url = KorAP.URL;
+      url += '/corpus';
+      url += '/' + match.corpusID;
+      url += '/' + match.docID + '.' + match.textID; // TODO
+      url += '/' + match.matchID;
+
+      // { spans: true, layer:x, foundry : y}
+      if (param['spans'] == true) {
+	url += '?spans=true';
+	if (param['foundry'] !== undefined)
+	  url += '&foundry=' + param['foundry'];
+	if (param['layer'] !== undefined)
+	  url += '&layer=' + param['layer'];
+      }
+
+      // { spans : false, layer: [Array of KorAP.InfoLayer] }
+      else {
+	// TODO
+	url += '?spans=false';
+      }
+
+      this.getJSON(url, cb);
+    },
+    getJSON : function (url, onload) {
+      var req = new XMLHttpRequest();
+
+      console.log('Request url: ' + url);
+
+      req.open("GET", url, true);
+
+
+      req.setRequestHeader("Accept", "application/json");
+      req.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 
+      req.onreadystatechange = function () {
+	/*
+	  States:
+	  0 - unsent (prior to open)
+	  1 - opened (prior to send)
+	  2 - headers received
+	  3 - loading (responseText has partial data)
+	  4 - done
+	 */
+	if (this.readyState == 4) {
+	  if (this.status === 200)
+	    onload(JSON.parse(this.responseText));
+	  else
+	    KorAP.log(this.status, this.statusText);
+	}
+      };
+      req.ontimeout = function () {
+	KorAP.log(0, 'Request Timeout');
+      };
+      req.send();
+    }
+  };
+
+}(this.KorAP));
diff --git a/public/js/src/hint.js b/public/js/src/hint.js
index 8011bee..80631ec 100644
--- a/public/js/src/hint.js
+++ b/public/js/src/hint.js
@@ -52,7 +52,7 @@
 
       // Update position of the mirror
       var that = this;
-      window.resize = function () {
+      window.onresize = function () {
 	that.reposition();
       };
 
@@ -311,6 +311,7 @@
 
       var that = this;
 
+
       // Add event listener for key pressed down
       inputFieldElement.addEventListener(
 	"keypress", function (e) {
diff --git a/public/js/src/match.js b/public/js/src/match.js
index b974031..4e3786b 100644
--- a/public/js/src/match.js
+++ b/public/js/src/match.js
@@ -35,8 +35,10 @@
   KorAP.API = KorAP.API || {};
 
   // TODO: Make this async
-  KorAP.API.getMatchInfo = KorAP.API.getMatchInfo || function () { return {} };
-
+  KorAP.API.getMatchInfo = KorAP.API.getMatchInfo || function () {
+    KorAP.log(0, 'KorAP.API.getMatchInfo() not implemented')
+    return {};
+  };
 
   /**
    * Match object
@@ -302,7 +304,7 @@
     /**
      * Retrieve and parse snippet for table representation
      */
-    getTable : function (tokens) {
+    getTable : function (tokens, cb) {
       var focus = [];
 
       // Get all tokens
@@ -330,51 +332,56 @@
 
       // No tokens chosen
       if (focus.length == 0)
-	return;
+	cb(null);
 
       // Get info (may be cached)
       // TODO: Async
-      var matchResponse = KorAP.API.getMatchInfo(
+      KorAP.API.getMatchInfo(
 	this._match,
-	{ 'spans' : false, 'layer' : focus }
+	{ '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)
       );
 
-      // Get snippet from match info
-      if (matchResponse["snippet"] !== undefined) {
-	this._table = KorAP.MatchTable.create(matchResponse["snippet"]);
-	return this._table;
-      };
-
+/*
       // Todo: Store the table as a hash of the focus
-
       return null;
+*/
     },
 
 
     /**
      * Retrieve and parse snippet for tree representation
      */
-    getTree : function (foundry, layer) {
+    getTree : function (foundry, layer, cb) {
       var focus = [];
 
-      // TODO: Async
-      var matchResponse = KorAP.API.getMatchInfo(
+      // 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)
       );
-
-      // TODO: Support and cache multiple trees
-
-      // Get snippet from match info
-      if (matchResponse["snippet"] !== undefined) {
-	// Todo: This should be cached somehow
-	return KorAP.MatchTree.create(matchResponse["snippet"]);
-      };
-
-      return null;
     },
 
     /**
@@ -397,13 +404,7 @@
     /**
      * 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;
-
+    addTree : function (foundry, layer, cb) {
       var matchtree = document.createElement('div');
       matchtree.classList.add('matchtree');
 
@@ -416,7 +417,7 @@
       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'));
@@ -427,11 +428,24 @@
 	}
       );
 
-      // 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();
+      // 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);
+      });
     },
 
     /**
@@ -449,9 +463,15 @@
       // Append default table
       var matchtable = document.createElement('div');
       matchtable.classList.add('matchtable');
-      matchtable.appendChild(this.getTable().element());
       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) {
@@ -917,23 +937,33 @@
 	  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);
+	  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
-	  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);
+	  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);
 	}
       );
diff --git a/public/js/src/menu.js b/public/js/src/menu.js
index 3267b69..1cf3455 100644
--- a/public/js/src/menu.js
+++ b/public/js/src/menu.js
@@ -154,6 +154,7 @@
       e.style.outline = 0;
       e.setAttribute('tabindex', 0);
       e.classList.add('menu');
+      e.classList.add('roll');
       e.appendChild(this._prefix.element());
 
       // This has to be cleaned up later on
diff --git a/public/js/src/session.js b/public/js/src/session.js
new file mode 100644
index 0000000..a10ce81
--- /dev/null
+++ b/public/js/src/session.js
@@ -0,0 +1,80 @@
+/**
+ * Simple cookie based session library that stores
+ * values in JSON encoded cookies.
+ *
+ * @author Nils Diewald
+ */
+var KorAP = KorAP || {};
+
+(function (KorAP) {
+  "use strict";
+
+  
+  KorAP.Session = {
+
+    /**
+     * Create a new session.
+     * Expects a name or defaults to 'korap'
+     */
+    create : function (name) {
+      var obj = Object.create(KorAP.Session);
+      if (name === undefined)
+	name = 'korap';
+      obj._name = name.toLowerCase();
+      obj._hash = {};
+      obj._parse();
+      return obj;
+    },
+
+    /**
+     * Get a value based on a key.
+     * The value can be complex, as the value is stored as JSON.
+     */
+    get : function (key) {
+      return this._hash[key.toLowerCase()];
+    },
+
+    /**
+     * Set a value based on a key.
+     * The value can be complex, as the value is stored as JSON.
+     */
+    set : function (key, value) {
+      this._hash[key] = value;
+      this._store();
+    },
+
+    /**
+     * Clears the session by removing the cookie
+     */
+    clear : function () {
+      document.cookie = this._name + '=; expires=-1';
+    },
+
+    /* Store cookie */
+    _store : function () {
+      /*
+	var date = new Date();
+	date.setYear(date.getFullYear() + 1);
+      */
+      document.cookie =
+	this._name + '=' + encodeURIComponent(JSON.stringify(this._hash)) + ';';
+    },
+
+    /* Parse cookie */
+    _parse : function () {
+      var c = document.cookie;
+      var part = document.cookie.split(';');
+      for(var i = 0; i < part.length; i++) {
+        var pair = part[i].split('=');
+        var name = pair[0].trim().toLowerCase();
+	if (name === this._name) {
+	  if (pair.length === 1 || pair[1].length === 0)
+	    return;
+          this._hash = JSON.parse(decodeURIComponent(pair[1]));
+	  return;
+	};
+      };
+    }
+  }
+
+}(this.KorAP));
diff --git a/public/js/src/tutorial.js b/public/js/src/tutorial.js
new file mode 100644
index 0000000..03cf903
--- /dev/null
+++ b/public/js/src/tutorial.js
@@ -0,0 +1,133 @@
+/**
+ * Open and close a tutorial page.
+ * The current page is stored and retrieved in a session cookie.
+ */
+// Requires session.js
+var KorAP = KorAP || {};
+
+// Todo: add query mechanism!
+
+(function (KorAP) {
+  "use strict";
+
+  // Localization values
+  var loc   = (KorAP.Locale = KorAP.Locale || {} );
+  loc.CLOSE = loc.CLOSE || 'Close';
+
+  KorAP.Tutorial = {
+
+    /**
+     * Create new tutorial object.
+     * Accepts an element to bind the tutorial window to.
+     */
+    create : function (obj) {
+      return Object.create(KorAP.Tutorial)._init(obj);
+    },
+
+    // Initialize Tutorial object
+    _init : function (obj) {
+      this._session = KorAP.Session.create();
+      this._show = obj;
+      this.start = obj.getAttribute('href');
+      obj.removeAttribute('href');
+      var that = this;
+      obj.onclick = function () {
+	that.show();
+      };
+
+      // Injects a tutorial div to the body
+      var div = document.createElement('div');
+      div.setAttribute('id', 'tutorial');
+      div.style.display = 'none';
+      document.getElementsByTagName('body')[0].appendChild(div);
+      this._iframe = null;
+
+      this._element = div;
+      return this;
+    },
+
+    show : function () {
+      var element = this._element;
+      if (element.style.display === 'block')
+	return;
+
+      if (this._iframe === null) {
+	this._iframe = document.createElement('iframe');
+	this._iframe.setAttribute('src', this.getPage() || this.start);
+
+	var ul = document.createElement('ul');
+	ul.classList.add('action', 'right');
+
+	// 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);
+	close.onclick = function () {
+	  element.style.display = 'none';
+	};
+
+	// Add open in new window button
+	// Add scroll to top 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);
+	*/
+
+	ul.appendChild(close);
+
+	element.appendChild(ul);
+	element.appendChild(this._iframe);
+      };
+
+      element.style.display = 'block';
+    },
+
+    /**
+     * Close tutorial window.
+     */
+    hide : function () {
+      this._element.display.style = 'none';
+    },
+
+    /**
+     * Set a page to be the current tutorial page.
+     * Expects either a string or an element.
+     */
+    setPage : function (obj) {
+      var page = obj;
+      if (typeof page != 'string') {
+	page = window.location.pathname + window.location.search;
+	for (i = 1; i < 5; i++) {
+	  if (obj.nodeName === 'SECTION') {
+	    if (obj.hasAttribute('id'))
+	      page += '#' + obj.getAttribute('id');
+	    break;
+	  }
+	  else if (obj.nodeName === 'PRE' && obj.hasAttribute('id')) {
+	    page += '#' + obj.getAttribute('id');
+	    break;
+	  }
+	  else {
+	    obj = obj.parentNode;
+	  };
+	};
+      };
+      this._session.set('tutpage', page);
+    },
+
+    /**
+     * Get the current tutorial URL
+     */
+    getPage : function () {
+      this._session.get('tutpage');
+    },
+  }
+}(this.KorAP));
diff --git a/public/js/src/util.js b/public/js/src/util.js
index 3adf5a0..ecaad6f 100644
--- a/public/js/src/util.js
+++ b/public/js/src/util.js
@@ -36,6 +36,7 @@
    * Initialize user interface elements
    */
   KorAP.init = function () {
+    var obj = Object.create(KorAP.init);
 
     /**
      * Add actions to match entries
@@ -81,9 +82,43 @@
     };
 
     /**
-     * Init hint helper
+     * Init vc
      */
-    KorAP.Hint.create();
+    var input = document.getElementById('vc');
+    if (input) {
+      input.style.display = 'none';
+      var vcname = document.createElement('span');
+      vcname.setAttribute('id', 'vc-choose');
+      vcname.appendChild(
+	document.createTextNode(
+	  document.getElementById('vc-name').value
+	)
+      );
+      input.parentNode.insertBefore(vcname, input);
+      
+      vcname.onclick = function () {
+	var vc = KorAP.VirtualCollection.render(vcExample);
+	var view = document.getElementById('vc-view');
+	view.appendChild(vc.element());
+      };
+    };
+
+    /**
+     * Init Tutorial view
+     */
+    obj.tutorial = KorAP.Tutorial.create(
+      document.getElementById('view-tutorial')
+    );
+
+    /**
+     * Init hint helper
+     * has to be final because of
+     * reposition
+     */
+// Todo: Pass an element, so this works with
+// tutorial pages as well!
+    obj.hint = KorAP.Hint.create();
+    return obj;
   };
 
 }(this.KorAP));