Added preliminary autocompletion support
diff --git a/Changes b/Changes
index 4278db6..7b27252 100755
--- a/Changes
+++ b/Changes
@@ -1,3 +1,6 @@
+0.03 2014-06-20
+        - Added preliminary autocompletion support (freetime project)
+
 0.02 2014-06-17
         - Added morphological tables
 
diff --git a/Makefile.PL b/Makefile.PL
index a9b76e4..b7c1fb1 100644
--- a/Makefile.PL
+++ b/Makefile.PL
@@ -9,7 +9,6 @@
   ABSTRACT     => 'Test Frontend for the KorAP API',
   NAME         => 'Korap',
   AUTHOR       => 'Nils Diewald',
-  VERSION      => '0.01',
   BUILD_REQUIRES => {
     'Test::More' => 0
   },
@@ -20,9 +19,9 @@
     'Mojolicious::Plugin::Notifications' => 0.03,
     'Mojolicious::Plugin::CHI' => 0.09,
     'Cache::FastMmap' => 0,
-#    'Mojolicious::Plugin::Search' => 0.01,
     'Mojolicious::Plugin::Number::Commify' => '0.022',
-    'JSON::XS' => 0.0
+    'JSON::XS' => 0.0,
+#    'Mojolicious::Plugin::Search' => 0.01,
   },
   test => {
     TESTS => 't/*.t'
diff --git a/lib/Korap.pm b/lib/Korap.pm
index bf61bfa..f79ef61 100644
--- a/lib/Korap.pm
+++ b/lib/Korap.pm
@@ -1,7 +1,7 @@
 package Korap;
 use Mojo::Base 'Mojolicious';
 
-our $VERSION = '0.02';
+our $VERSION = '0.03';
 
 # This method will run once at server start
 sub startup {
diff --git a/public/hint.css b/public/hint.css
new file mode 100644
index 0000000..6b1bc50
--- /dev/null
+++ b/public/hint.css
@@ -0,0 +1,51 @@
+#searchMirror {
+  position: absolute;
+  height: 0px;
+  margin-top: 1px;
+}
+
+#searchMirror > span {
+  opacity: 0;
+  white-space:nowrap;
+  overflow: hidden;
+}
+
+#searchMirror > ul {
+  color: white;
+  margin: 0;
+  text-indent: 0;
+  display: inline-block;
+  background-color: #496000;
+  opacity: 0;
+  padding: 0;
+  padding-top: 5px;
+  padding-bottom: 10px;
+  border-bottom-left-radius: 10px;
+  border-bottom-right-radius: 10px;
+}
+
+#searchMirror > ul > li {
+  list-style-type: none;
+  list-style-position: outside;
+  padding: 3pt 10pt;
+  text-shadow: none;
+}
+
+#searchMirror > ul > li > span {
+  float: right;
+  margin-left: 30pt;
+  text-align: right;
+  font-style: italic;
+  color: #7ba400;
+}
+
+/* like sidebar ul li.active */
+#searchMirror > ul > li.active {
+  background-color: #7ba400;
+  text-shadow: 1px 1px rgba(0, 0, 0, 0.4);
+}
+
+#searchMirror > ul > li.active > span {
+  color: #496000;
+  text-shadow: none;
+}
\ No newline at end of file
diff --git a/public/hint.js b/public/hint.js
new file mode 100644
index 0000000..6f20c67
--- /dev/null
+++ b/public/hint.js
@@ -0,0 +1,200 @@
+/*
+TODO:
+  - Limit the size to a certain number of elements
+  - addEventListener("click", ... , false);
+  - addEventListener("paste", ... , false);
+*/
+
+var Hint = function (param) {
+  var foundryRegex = new RegExp("(?:^|[^a-zA-Z0-9])([-a-zA-Z0-9]+?)\/(?:([^=]+?)=)?$");
+
+  var search = document.getElementById(param["ref"]);
+  var mirror = document.createElement("div");
+  var hint = document.createElement("ul");
+  var hintSize = param["hintSize"] ? param["hintSize"] : 10;
+  var hints = param["hints"];
+  var that = this;
+
+  // Build the mirror element
+  // <div id="searchMirror"><span></span><ul></ul></div>
+  mirror.setAttribute("id", "searchMirror");
+  mirror.appendChild(document.createElement("span"));
+  mirror.appendChild(hint);
+  search.parentNode.insertBefore(mirror, search);
+
+  // Default active state
+  this.active = -2;
+
+  // Show hint table
+  this.show = function (topic) {
+    if (!hints[topic])
+      return;
+    this.active = -1;
+    this.list(hints[topic]);
+    var searchRight = search.getBoundingClientRect().right;
+    var infoRight = hint.getBoundingClientRect().right;
+    if (infoRight > searchRight) {
+      hint.style.marginLeft = '-' + (infoRight - searchRight) + 'px';
+    };
+    hint.style.opacity = 1;
+  };
+
+  // Initialize the mirror element
+  function init () {
+    // Copy input style
+    var searchRect = search.getBoundingClientRect();
+    var searchStyle = window.getComputedStyle(search, null);
+
+    with (mirror.style) {
+      left = searchRect.left + "px";
+      top = searchRect.bottom + "px";
+      borderLeftColor = "transparent";
+      paddingLeft     = searchStyle.getPropertyValue("padding-left");
+      marginLeft      = searchStyle.getPropertyValue("margin-left");
+      borderLeftWidth = searchStyle.getPropertyValue("border-left-width");
+      borderLeftStyle = searchStyle.getPropertyValue("border-left-style");
+      fontSize        = searchStyle.getPropertyValue("font-size");
+      fontFamily      = searchStyle.getPropertyValue("font-family");
+    };
+  };
+
+  // Hide hint table
+  this.hide = function () {
+    this.active = -2;
+    hint.style.opacity = 0;
+    hint.style.marginLeft = 0;
+
+    // Remove all children
+    var lis = hint.childNodes;
+    for (var li = lis.length - 1; li >= 0; li--) {
+      hint.removeChild(lis[li])
+    };
+  };
+
+  // List elements in the hint table
+  this.list = function (hintList) {
+    var li, title;
+    var arrayType = hintList instanceof Array;
+    for (var i in hintList) {
+      // Create list items
+      li = document.createElement("li");
+      li.setAttribute("data-action", arrayType ? hintList[i] : hintList[i][0]);
+      title = document.createElement("strong");
+      title.appendChild(document.createTextNode(arrayType ? hintList[i] : i));
+      li.appendChild(title);
+      hint.appendChild(li);
+
+      // Include descriptions
+      if (!arrayType && hintList[i][1]) {
+        var desc = document.createElement("span");
+        desc.appendChild(document.createTextNode(hintList[i][1]));
+        li.appendChild(desc);
+      };
+    };
+  };
+
+  // Choose next item in list
+  this.next = function () {
+    if (this.active === -2)
+      return;
+    var lis = hint.getElementsByTagName("li");
+    if (this.active === -1) {
+      lis[0].setAttribute("class", "active");
+      this.active = 0;
+    }
+    else if (this.active === lis.length - 1) {
+      lis[this.active].removeAttribute("class");
+      lis[0].setAttribute("class", "active");
+      this.active = 0;
+    }
+    else {
+      lis[this.active].removeAttribute("class");
+      lis[++this.active].setAttribute("class", "active");
+    };
+  };
+
+  // Choose previous item in list
+  this.previous = function () {
+    if (this.active === -2)
+      return;
+
+    var lis = hint.getElementsByTagName("li");
+    if (this.active === -1) {
+      this.active = lis.length - 1;
+      lis[this.active].setAttribute("class", "active");
+    }
+    else if (this.active === 0) {
+      lis[0].removeAttribute("class");
+      this.active = lis.length - 1;
+      lis[this.active].setAttribute("class", "active");
+    }
+    else {
+      lis[this.active].removeAttribute("class");
+      lis[--this.active].setAttribute("class", "active");
+    };
+  };
+
+  // Choose item from list
+  this.choose = function () {
+    if (this.active < 0)
+      return;
+
+    var action = hint.getElementsByTagName("li")[this.active].getAttribute("data-action");
+
+    var value = search.value;
+    var start = search.selectionStart;
+    var begin = value.substring(0, start);
+    var end = value.substring(start, value.length);
+    search.value = begin + action + end;
+    search.selectionStart = search.selectionEnd = (begin + action).length;
+
+    this.hide();
+
+    // Check for new changes
+    mirror.firstChild.innerHTML = begin + action;
+    if (foundryRegex.exec(begin + action))
+      this.show(RegExp.$1 + (RegExp.$2 ? '/' + RegExp.$2 : ''));
+  };
+
+  function changed (e) {
+    var el = e.target;
+
+    if (e.key === '/' || e.key === '=') {
+      var start = el.selectionStart;
+      mirror.firstChild.innerHTML = el.value.substring(0, start);
+      var sub = el.value.substring(start - 128 >= 0 ? start - 128 : 0, start);
+
+      if (foundryRegex.exec(sub))
+	that.show(RegExp.$1 + (RegExp.$2 ? '/' + RegExp.$2 : ''));
+    }
+    else if (e.key !== 'Shift' &&
+             e.key !== 'Up'    &&
+             e.key !== 'Down'  &&
+             e.key !== 'Enter') {
+      that.hide();
+    };
+  };
+
+  // Select item from suggestion list
+  function select (e) {
+    if (that.active === -2)
+      return;
+    if (e.key === 'Down') {
+      that.next();
+    }
+    else if (e.key === 'Up') {
+      that.previous();
+    }
+    else if (e.key === 'Enter') {
+      that.choose();
+      e.stopPropagation();
+      e.preventDefault();
+      return false;
+    };
+  };
+
+  // Initialize style
+  init();
+  search.addEventListener("keyup", changed, false);
+  search.addEventListener("keypress", select, false);
+};
diff --git a/public/translateTree.js b/public/translateTree.js
index 728877b..0ade20b 100644
--- a/public/translateTree.js
+++ b/public/translateTree.js
@@ -41,6 +41,40 @@
     };
     return this;
   };
+
+  // Recursively parse children
+  this.parseChildren2 = function (children) {
+    for (var i in children) {
+      var c = children[i];
+      if (c.nodeType === 1) {
+	if (c.getAttribute("title")) {
+	  var title = this.cleanTitle(c.getAttribute("title"));
+	  var childTree = this.addChild({ type : title });
+	  if (c.hasChildNodes())
+	    childTree.parseChildren2(c.childNodes);
+	}
+	else if (c.hasChildNodes())
+	  this.parseChildren2(c.childNodes);
+      }
+      else if (c.nodeType === 3)
+	if (c.nodeValue.match(/[-a-z0-9]/i)) {
+	  this.addChild({
+	    type : "leaf",
+	    word : c.nodeValue
+	  });
+	};
+    };
+    return this;
+  };
+
+
+  this.toTable = function () {
+    
+  };
+
+  var tree = document.createElement('tree');
+  html.innerHTML = snippet;
+  this.parseChildren2(html.childNodes);
 };
 
 // Make tree from snippet
diff --git a/templates/collections.html.ep b/templates/collections.html.ep
index 3dee20f..3f73e96 100644
--- a/templates/collections.html.ep
+++ b/templates/collections.html.ep
@@ -1,14 +1,14 @@
 <h2>Virtual Collections</h2>
 <ul>
 % foreach my $vc (@{resource_info('collection')}) {
-<li class="active" title="<%= $vc->{description} // '' %>"><h3><%= $vc->{name} %></h3>
+  <li class="active" title="<%= $vc->{description} // '' %>"><h3><%= $vc->{name} %></h3>
 % my $stats = $vc->{stats};
-  <dl class="info">
-    <dt>Documents</dt><dd><%= commify $stats->{documents} %></dd>
-    <dt>Paragraphs</dt><dd><%= commify $stats->{paragraphs} %></dd>
-    <dt>Sentences</dt><dd><%= commify $stats->{sentences} %></dd>
-    <dt>Tokens</dt><dd><%= commify $stats->{tokens} %></dd>
-  </dl>
-</li>
+    <dl class="info">
+      <dt>Documents</dt>  <dd><%= commify $stats->{documents} %></dd>
+      <dt>Paragraphs</dt> <dd><%= commify $stats->{paragraphs} %></dd>
+      <dt>Sentences</dt>  <dd><%= commify $stats->{sentences} %></dd>
+      <dt>Tokens</dt>     <dd><%= commify $stats->{tokens} %></dd>
+    </dl>
+  </li>
 % };
 </ul>
diff --git a/templates/intro.html.ep b/templates/intro.html.ep
new file mode 100644
index 0000000..bcf4117
--- /dev/null
+++ b/templates/intro.html.ep
@@ -0,0 +1,5 @@
+<div id="intro">
+  <p>This is the alternative KorAP Frontend.</p>
+  <p>The primary goal is to serve as a testbed for the query serialization and for different flavours of user interfaces.</p>
+  <p>Search capabilities are limited to the demo user.</p>
+</div>
diff --git a/templates/layouts/default.html.ep b/templates/layouts/default.html.ep
index 2af32a2..a14b0e0 100644
--- a/templates/layouts/default.html.ep
+++ b/templates/layouts/default.html.ep
@@ -3,11 +3,13 @@
   <head>
     <title><%= title %></title>
 %= stylesheet '/style.css'
+%= stylesheet '/hint.css'
 %= stylesheet '/table.css'
 %= stylesheet '/kwic-4.0.css'
 %= stylesheet '/fontawesome/font-awesome.min.css'
 %= javascript '/jquery-2.0.0.min.js'
 %= javascript '/translateTable.js'
+%= javascript '/hint.js'
 <meta charset="utf-8" />
   </head>
   <body>
@@ -26,7 +28,59 @@
 %= form_for url_for() => begin
 %= select_field ql => [[Poliqarp => 'poliqarp'], ['Cosmas II' => 'cosmas2']], id => 'ql-field'
 <br />
-%= search_field 'q', id => 'q-field'
+%= search_field 'q', id => 'q-field', autofocus => 'autofocus'
+%= javascript begin
+new Hint({
+  "ref" : "q-field",
+  "hintSize" : 10,
+  "hints" : {
+    "corenlp" : {
+      "ne_dewac_175m_600" : ["ne_dewac_175m_600=", "Named Entity"],
+      "ne_hgc_175m_600" : ["ne_hgc_175m_600=", "Named Entity"]
+    },
+    "corenlp/ne_dewac_175m_600" : ["I-LOC","I-MISC","I-ORG","I-PER"],
+    "corenlp/ne_hgc_175m_600" : ["I-LOC","I-MISC","I-ORG","I-PER"],
+    "cnx" : {
+      "c" : ["c=", "Constituency"],
+      "l" : ["l=", "Lemma"],
+      "m" : ["m=", "Morpho Syntax"],
+      "p" : ["p=", "Part of Speech"],
+      "syn" : ["syn=", "Syntax"]
+    },
+    "cnx/m" : ["Abbr","CMP","IMP","IND","INF","ORD","PAST","PCP","PERF","PL","PRES","PROG","Prop","SUB","SUP"],
+    "cnx/p" : ["A","ADV","CC","CS","DET","INTERJ","N","NUM","PREP","PRON","V"],
+    "cnx/syn" : ["@ADVL","@AUX","@CC","@MAIN","@NH","@POSTMOD","@PREMARK","@PREMOD"],
+    "opennlp" : {
+      "p" : ["p=", "Part of Speech"]
+    },
+    "opennlp/p" : ["$(","$,","$.","ADJA","ADJD","ADV","APPR","APPRART","ART","CARD","FM","KOKOM","KON","KOUI","KOUS","NE","NN","PDAT","PDS","PIAT","PIS","PPER","PPOSAT","PRELS","PRF","PROAV","PTKNEG","PTKVZ","PTKZU","PWAT","PWAV","PWS","TRUNC","VAFIN","VAINF","VAPP","VMFIN","VVFIN","VVIMP","VVINF","VVIZU","VVPP","XY"],
+    "xip" : {
+      "c" : ["c=", "Constituency"],
+      "d" : ["d=", "Dependency"],
+      "l" : ["l=", "Lemma"],
+      "p" : ["p=", "Part of Speech"]
+    },
+    "xip/c" : ["ADJ","ADV","AP","CONJ","DET","INFC","INS","ITJ","MC","NEGAT","NOUN","NP","NPA","NUM","POSTP","PP","PREP","PRON","PTCL","PUNCT","SC","SYMBOL","TOP","TRUNC","VERB"],
+    "xip/d" : ["ADJMOD","AUXIL","CONNECT","COORD","DATE","DETERM","EXPL","LOC","MODAL","NEGAT","NMOD","NMOD2","NUMMOD","OBJ","ORG","PERSON","PLINK","PRED","REFLEX","SUBJ","THEMA","TIME","TRUNC","VMAIN","VMOD","VPREF"],
+    "xip/p" : ["ADJ","ADV","CONJ","DET","ITJ","NEGAT","NOUN","NUM","POSTP","PREP","PRON","PTCL","PUNCT","SYMBOL","TRUNC","VERB"],
+    "tt" : {
+      "l" : ["l=", "Lemma"],
+      "p" : ["p=", "Part of Speech"]
+    },
+    "tt/p" : ["$.","ADJA","ADJD","ADV","APPO","APPR","APPRART","APZR","ART","CARD","FM","ITJ","KOKOM","KON","KOUI","KOUS","NE","NN","PDAT","PDS","PIAT","PIS","PPER","PPOSAT","PRELS","PRF","PROAV","PTKA","PTKNEG","PTKVZ","PTKZU","PWAT","PWAV","PWS","TRUNC","VAFIN","VAINF","VAPP","VMFIN","VMINF","VVFIN","VVIMP","VVINF","VVIZU","VVPP","XY"],
+    "mate" : {
+      "d" : ["d=", "Dependency"],
+      "l" : ["l=", "Lemma"],
+      "m" : ["m=", "Morpho Syntax"],
+      "p" : ["p=", "Part of Speech"]
+    },
+    "mate/d" : ["--","AG","AMS","APP","CC","CD","CJ","CM","CP","CVC","DA","DM","EP","JU","MNR","MO","NG","NK","NMC","OA","OC","OG","OP","PAR","PD","PG","PH","PM","PNC","RC","RE","RS","SB","SBP","SVP","UC"],
+    "mate/m" : ["<no-type>","case:*","case:acc","case:dat","case:gen","case:nom","degree:comp","degree:pos","degree:sup","gender:*","gender:fem","gender:masc","gender:neut","mood:imp","mood:ind","mood:subj","number:*","number:pl","number:sg","person:1","person:2","person:3","tense:past","tense:pres"],
+    "mate/p" : ["$(","$,","$.","<root-POS>","ADJA","ADJD","ADV","APPO","APPR","APPRART","ART","CARD","FM","ITJ","KOKOM","KON","KOUI","KOUS","NE","NN","PDAT","PDS","PIAT","PIS","PPER","PPOSAT","PPOSS","PRELAT","PRELS","PRF","PROAV","PTKA","PTKNEG","PTKVZ","PTKZU","PWAT","PWAV","PWS","TRUNC","VAFIN","VAINF","VAPP","VMFIN","VVFIN","VVIMP","VVINF","VVIZU","VVPP","XY"]
+  }}
+);
+
+% end
 <button type="submit" name="action" value="ok"><i class="fa fa-search"></i></button>
 <button type="submit" name="action" value="inspect"><i class="fa fa-code"></i></button>
 % end
diff --git a/templates/query.html.ep b/templates/query.html.ep
index 6ef646f..3485296 100644
--- a/templates/query.html.ep
+++ b/templates/query.html.ep
@@ -1,8 +1,11 @@
 % use JSON::XS;
 
 % if (stash('search.query')) {
-<code class="query serial<% if (param('action') eq 'inspect') { %> active<% } %>"><span>JSON-LD Query for <%= param 'q' %> (<%= param 'ql' %>)</span><pre>
-%  my $json = JSON::XS->new->allow_blessed->pretty;
-%= $json->encode(stash('search.query'))
-</pre></span></code>
-% }
+%   state $json = JSON::XS->new->allow_blessed->pretty;
+<code class="query serial<% if (param('action') eq 'inspect') { %> active<% } %>">
+  <span>JSON-LD Serialization for <%= param 'q' %> (<%= param 'ql' %>)</span>
+  <pre>
+<%= $json->encode(stash('search.query')) =%>
+  </pre>
+</code>
+% };
diff --git a/templates/search.html.ep b/templates/search.html.ep
index a409a40..c393c72 100644
--- a/templates/search.html.ep
+++ b/templates/search.html.ep
@@ -3,49 +3,37 @@
 
 % unless (param 'snippet') {
 <div style="clear: both">
-  <p class="found">
-    <span class="pagination">
 % my $url = url_with->query(['p' => '{page}']);
 % my $pages = (stash('search.totalResults') / (stash('search.itemsPerPage') || 1));
 % $pages = $pages < 0 ? 0 : $pages;
-    </span>
-
-    <div id="pagination">
-%= pagination(stash('search.startPage'), $pages, $url)
-    </div>
-
-Found <span id="total-results"><%= commify(stash('search.totalResults')) %> matches</span>
-% if (stash 'search.bm.hit') {
- in <%= stash 'search.bm.hit' %> (<%= stash 'search.bm.result' %>)
-% }
+  <div id="pagination"><%= pagination(stash('search.startPage'), $pages, $url) =%></div>
+  <p class="found">Found
+    <span id="total-results"><%= commify(stash('search.totalResults')) %> matches</span>
+    <% if (stash 'search.bm.hit') { %> in <%= stash 'search.bm.hit' %> (<%= stash 'search.bm.result' %>)<% } %>
   </p>
 </div>
 
 %= include 'query'
 % };
 
+
+
 <ol class="left-aligned">
 %=  search_hits begin
-
+%# ID, title, corpusID, author, pubDate, textClass
   <li data-corpus-id="<%= $_->{corpusID} %>"
       data-doc-id="<%= korap_doc_id($_) %>"
       data-match-id="<%= korap_match_id($_) %>">
-%# ID, title, corpusID, author, pubDate, textClass
     <div>
-      <div class="snippet">
-        <%== $_->{snippet} %>
-      </div>
-
-%#    as <%= $_->{ID} %>
-%#  textClass docID
-%# foreach (grep { m!/morpho$! } split(/\s+/, $_->{foundries})) {
-%#  <%= $_ %>
-%# };
-
+      <div class="snippet"><%== $_->{snippet} %></div>
       <div class="tokenInfo"></div>
     </div>
-    <p><strong><%= $_->{title} %></strong><%= $_->{author} ? ' by ' . $_->{author}  : '' %>; published on <%= date_format $_->{pubDate} %> as <%= $_->{docID} %> (<%= $_->{corpusID} %>)</p>
-
+    <p>
+      <strong><%= $_->{title} %></strong>
+      <%= $_->{author} ? ' by ' . $_->{author}  : '' %>;
+      published on <%= date_format $_->{pubDate} %>
+      as <%= $_->{docID} %> (<%= $_->{corpusID} %>)
+    </p>
     <ul class="action right">
       <li onmouseup="closeSnippet(this)" title="close"><i class="fa fa-toggle-up"></i></li>
       <li onclick="showTable(this)" title="Annotations"><i class="fa fa-info-circle"></i></li>
@@ -54,16 +42,13 @@
       <li title="Remember"><i class="fa fa-star-o"></i></li>
 -->
     </ul>
-
-
   </li>
 %   end
 </ol>
+
 % end
-% } else {
-<div id="intro">
-  <p>This is the alternative KorAP Frontend.</p>
-  <p>The primary goal is to serve as a testbed for the query serialization and for different flavours of user interfaces.</p>
-  <p>Search capabilities are limited to the demo user.</p>
-</div>
+
+% }
+% else {
+%= include 'intro'
 % };