Made Unspecified docs editable and start general Menu class
diff --git a/public/js/runner/menu.html b/public/js/runner/menu.html
new file mode 100644
index 0000000..2b05abc
--- /dev/null
+++ b/public/js/runner/menu.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Spec Runner for Menu Helper</title>
+  <link rel="shortcut icon" type="image/png" href="../lib/jasmine-2.1.1/jasmine_favicon.png">
+  <link rel="stylesheet" href="../lib/jasmine-2.1.1/jasmine.css">
+  <script src="../lib/jasmine-2.1.1/jasmine.js"></script>
+  <script src="../lib/jasmine-2.1.1/jasmine-html.js"></script>
+  <script src="../lib/jasmine-2.1.1/boot.js"></script>
+  <script src="../src/menu.js"></script>
+  <script src="../spec/menuSpec.js"></script>
+</head>
+<body>
+</body>
+</html>
diff --git a/public/js/spec/menuSpec.js b/public/js/spec/menuSpec.js
new file mode 100644
index 0000000..0477442
--- /dev/null
+++ b/public/js/spec/menuSpec.js
@@ -0,0 +1,674 @@
+KorAP.OwnMenuItem = {
+  create : function (params) {
+    return Object.create(KorAP.MenuItem).upgradeTo(KorAP.OwnMenuItem)._init(params);
+  },
+  content : function (content) {
+    if (arguments.length === 1) {
+      this._content = content;
+    };
+    return this._content;
+  },
+  _init : function (params) {
+    if (params[0] === undefined)
+      throw new Error("Missing parameters");
+
+    this._content = document.createTextNode(params[0]);
+    this._lcField = ' ' + this.content().textContent.toLowerCase();
+
+    return this;
+  }
+};
+
+KorAP.ComplexMenuItem = {
+  create : function (params) {
+    return Object.create(KorAP.MenuItem)
+      .upgradeTo(KorAP.ComplexMenuItem)
+      ._init(params);
+  },
+  content : function (content) {
+    if (arguments.length === 1) {
+      this._content = content;
+    };
+    return this._content;
+  },
+  _init : function (params) {
+    if (params[0] === undefined)
+      throw new Error("Missing parameters");
+
+    var r = document.createElement('div');
+    for (var i = 1; i <= params.length; i++) {
+      var h = document.createElement('h' + i);
+      h.appendChild(document.createTextNode(params[i-1]));
+      r.appendChild(h);
+    };
+
+    this._content = r;
+    this._lcField = ' ' + this.content().textContent.toLowerCase();
+
+    return this;
+  }
+};
+
+
+describe('KorAP.MenuItem', function () {
+  it('should be initializable', function () {
+    expect(
+      function() { KorAP.MenuItem.create([]) }
+    ).toThrow(new Error("Missing parameters"));
+
+    expect(
+      function() { KorAP.OwnMenuItem.create([]) }
+    ).toThrow(new Error("Missing parameters"));
+
+    var mi = KorAP.OwnMenuItem.create(["Baum"]);
+    expect(mi.element().firstChild.nodeValue).toEqual('Baum');
+    expect(mi.lcField()).toEqual(' baum');
+  });
+
+  it('should be activatable and deactivateable by class', function () {
+    var menuItem = KorAP.OwnMenuItem.create(['Test']);
+
+    expect(menuItem.active()).toBe(false);
+    expect(menuItem.element().getAttribute("class")).toBe(null);
+    menuItem.active(true);
+    expect(menuItem.active()).toBe(true);
+    expect(menuItem.element().getAttribute("class")).toEqual("active");
+    menuItem.active(false); // Is active
+    expect(menuItem.active()).toBe(false);
+    expect(menuItem.element().getAttribute("class")).toEqual("");
+    menuItem.active(true);
+    expect(menuItem.active()).toBe(true);
+    expect(menuItem.element().getAttribute("class")).toEqual("active");
+
+    menuItem = KorAP.OwnMenuItem.create(['Spiegel']);
+    expect(menuItem.active()).toBe(false);
+    expect(menuItem.element().getAttribute("class")).toBe(null);
+    menuItem.active(false); // Is not active
+    expect(menuItem.active()).toBe(false);
+    expect(menuItem.element().getAttribute("class")).toBe(null);
+  });
+
+  it('should be set to boundary', function () {
+    var menuItem = KorAP.OwnMenuItem.create(['CoreNLP']);
+    expect(menuItem.active()).toBe(false);
+    expect(menuItem.element().getAttribute("class")).toBe(null);
+
+    // Set active
+    menuItem.active(true);
+    expect(menuItem.active()).toBe(true);
+    expect(menuItem.noMore()).toBe(false);
+    expect(menuItem.element().getAttribute("class")).toEqual("active");
+
+    // Set no more
+    menuItem.noMore(true);
+    expect(menuItem.active()).toBe(true);
+    expect(menuItem.noMore()).toBe(true);
+    expect(menuItem.element().getAttribute("class")).toEqual("active no-more");
+
+    // No no more
+    menuItem.noMore(false);
+    expect(menuItem.active()).toBe(true);
+    expect(menuItem.noMore()).toBe(false);
+    expect(menuItem.element().getAttribute("class")).toEqual("active");
+
+    // Set no more, deactivate
+    menuItem.noMore(true);
+    menuItem.active(false);
+    expect(menuItem.active()).toBe(false);
+    expect(menuItem.noMore()).toBe(true);
+    expect(menuItem.element().getAttribute("class")).toEqual("no-more");
+
+    // Set active
+    menuItem.active(true);
+    expect(menuItem.active()).toBe(true);
+    expect(menuItem.noMore()).toBe(true);
+    expect(menuItem.element().getAttribute("class")).toEqual("no-more active");
+  });
+
+
+  it('should be highlightable', function () {
+    // Highlight in the middle
+    var menuItem = KorAP.OwnMenuItem.create(['CoreNLP']);
+    menuItem.highlight("ren");
+    expect(menuItem.element().innerHTML).toEqual("Co<mark>reN</mark>LP");
+
+    menuItem.lowlight();
+    expect(menuItem.element().innerHTML).toEqual("CoreNLP");
+
+    var plain = "<div><h1>CoreNLP</h1><h2>corenlp/</h2></div>";
+
+    // Starting highlight
+    menuItem = KorAP.ComplexMenuItem.create(['CoreNLP', 'corenlp/']);
+    menuItem.highlight("cor");
+    expect(menuItem.element().innerHTML).toEqual("<div><h1><mark>Cor</mark>eNLP</h1><h2><mark>cor</mark>enlp/</h2></div>");
+
+    menuItem.lowlight();
+    expect(menuItem.element().innerHTML).toEqual(plain);
+
+    // Starting highlight - short
+    menuItem = KorAP.ComplexMenuItem.create(['CoreNLP', 'corenlp/']);
+    menuItem.highlight("c");
+    expect(menuItem.element().innerHTML).toEqual("<div><h1><mark>C</mark>oreNLP</h1><h2><mark>c</mark>orenlp/</h2></div>");
+
+    menuItem.lowlight();
+    expect(menuItem.element().innerHTML).toEqual(plain);
+
+    // Highlight at the end
+    menuItem = KorAP.ComplexMenuItem.create(['CoreNLP', 'corenlp/']);
+    menuItem.highlight("nlp");
+    expect(menuItem.element().innerHTML).toEqual("<div><h1>Core<mark>NLP</mark></h1><h2>core<mark>nlp</mark>/</h2></div>");
+
+    menuItem.lowlight();
+    expect(menuItem.element().innerHTML).toEqual(plain);
+
+    // Highlight at the end - short
+    menuItem = KorAP.ComplexMenuItem.create(['CoreNLP', 'corenlp/']);
+    menuItem.highlight("p");
+    expect(menuItem.element().innerHTML).toEqual("<div><h1>CoreNL<mark>P</mark></h1><h2>corenl<mark>p</mark>/</h2></div>");
+
+    menuItem.lowlight();
+    expect(menuItem.element().innerHTML).toEqual(plain);
+
+    // No highlight
+    menuItem = KorAP.ComplexMenuItem.create(['CoreNLP', 'corenlp/']);
+    menuItem.highlight("xp");
+    expect(menuItem.element().innerHTML).toEqual(plain);
+
+    menuItem.lowlight();
+    expect(menuItem.element().innerHTML).toEqual(plain);
+
+    // Highlight in the middle - first
+    menuItem = KorAP.ComplexMenuItem.create(['CoreNLP', 'corenlp/', 'This is my Example']);
+    menuItem.highlight("ren");
+    expect(menuItem.element().innerHTML).toEqual("<div><h1>Co<mark>reN</mark>LP</h1><h2>co<mark>ren</mark>lp/</h2><h3>This is my Example</h3></div>");
+
+    plain = "<div><h1>CoreNLP</h1><h2>corenlp/</h2><h3>This is my Example</h3></div>"
+
+    menuItem.lowlight();
+    expect(menuItem.element().innerHTML).toEqual(plain);
+
+    // Highlight in the middle - second
+    menuItem = KorAP.ComplexMenuItem.create(['CoreNLP', 'corenlp/', 'This is my Example']);
+    menuItem.highlight("ampl");
+    expect(menuItem.element().innerHTML).toEqual("<div><h1>CoreNLP</h1><h2>corenlp/</h2><h3>This is my Ex<mark>ampl</mark>e</h3></div>");
+
+    menuItem.lowlight();
+    expect(menuItem.element().innerHTML).toEqual(plain);
+
+    // Highlight in the middle - both
+    menuItem = KorAP.ComplexMenuItem.create(['CoreNLP', 'corenlp/', 'This is my Example']);
+    menuItem.highlight("e");
+    expect(menuItem.element().innerHTML).toEqual("<div><h1>Cor<mark>e</mark>NLP</h1><h2>cor<mark>e</mark>nlp/</h2><h3>This is my <mark>E</mark>xampl<mark>e</mark></h3></div>");
+
+    menuItem.lowlight();
+    expect(menuItem.element().innerHTML).toEqual(plain);
+
+    // Highlight in the end - second
+    menuItem = KorAP.ComplexMenuItem.create(['CoreNLP', 'corenlp/', 'This is my Example']);
+    menuItem.highlight("le");
+    expect(menuItem.element().innerHTML).toEqual("<div><h1>CoreNLP</h1><h2>corenlp/</h2><h3>This is my Examp<mark>le</mark></h3></div>");
+
+    menuItem.lowlight();
+    expect(menuItem.element().innerHTML).toEqual(plain);
+
+    // Highlight at the beginning - second
+    menuItem = KorAP.ComplexMenuItem.create(['CoreNLP', 'corenlp/', 'This is my Example']);
+    menuItem.highlight("this");
+    expect(menuItem.element().innerHTML).toEqual("<div><h1>CoreNLP</h1><h2>corenlp/</h2><h3><mark>This</mark> is my Example</h3></div>");
+
+    menuItem.lowlight();
+    expect(menuItem.element().innerHTML).toEqual(plain);
+  });
+
+});
+
+
+/*
+
+describe('KorAP.MenuItem', function () {
+
+});
+
+
+describe('KorAP.Menu', function () {
+
+  var list = [
+    ["Constituency", "c=", "Example 1"],
+    ["Lemma", "l="],
+    ["Morphology", "m=", "Example 2"],
+    ["Part-of-Speech", "p="],
+    ["Syntax", "syn="]
+  ];
+
+
+  it('should be initializable', function () {
+
+    var menu = KorAP.Menu.create("cnx/", list);
+    expect(menu.context).toEqual('cnx/');
+    expect(menu.element.nodeName).toEqual('UL');
+    expect(menu.element.style.opacity).toEqual("0");
+
+    KorAP.limit = 8;
+
+    // view
+    menu.show();
+
+    // First element in list
+    expect(menu.item(0).active()).toBe(true);
+    expect(menu.item(0).noMore()).toBe(true);
+    
+    // Middle element in list
+    expect(menu.item(2).active()).toBe(false);
+    expect(menu.item(2).noMore()).toBe(false);
+
+    // Last element in list
+    expect(menu.item(menu.length - 1).active()).toBe(false);
+    expect(menu.item(menu.length - 1).noMore()).toBe(true);
+  });
+
+  it('should be visible', function () {
+    var menu = KorAP.Menu.create("cnx/", list);
+    expect(menu.delete()).toBe(undefined);
+
+    KorAP.limit = 3;
+
+    expect(menu.show()).toBe(undefined);
+    expect(menu.element.firstChild.innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Lemma</strong>");
+    expect(menu.element.childNodes[1].getAttribute("data-action")).toEqual("l=");
+    expect(menu.element.childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+    expect(menu.element.childNodes[3]).toBe(undefined);
+
+    // Check boundaries
+    expect(menu.element.childNodes[0].classList.contains("no-more")).toBe(true);
+    expect(menu.element.childNodes[1].classList.contains("no-more")).toBe(false);
+    expect(menu.element.childNodes[2].classList.contains("no-more")).toBe(false);
+  });
+
+
+  it('should be filterable', function () {
+    var menu = KorAP.Menu.create("cnx/", list);
+
+    KorAP.limit = 3;
+
+    expect(menu.show("o")).toBe(undefined);
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>C<em>o</em>nstituency</strong><span>Example 1</span>");
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>M<em>o</em>rphology</strong><span>Example 2</span>");
+    expect(menu.element.childNodes[2].innerHTML).toEqual("<strong>Part-<em>o</em>f-Speech</strong>");
+    expect(menu.element.childNodes[3]).toBe(undefined);
+
+    // Check boundaries
+    expect(menu.element.childNodes[0].classList.contains("no-more")).toBe(true);
+    expect(menu.element.childNodes[1].classList.contains("no-more")).toBe(false);
+    expect(menu.element.childNodes[2].classList.contains("no-more")).toBe(true);
+
+
+    KorAP.limit = 2;
+
+    expect(menu.show("o")).toBe(undefined);
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>C<em>o</em>nstituency</strong><span>Example 1</span>");
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>M<em>o</em>rphology</strong><span>Example 2</span>");
+    expect(menu.element.childNodes[2]).toBe(undefined);
+
+    // Check boundaries
+    expect(menu.element.childNodes[0].classList.contains("no-more")).toBe(true);
+    expect(menu.element.childNodes[1].classList.contains("no-more")).toBe(false);
+    expect(menu.element.childNodes[2]).toBe(undefined);
+  });
+
+  it('should be nextable', function () {
+    var menu = KorAP.Menu.create("cnx/", list);
+
+    KorAP.limit = 3;
+    expect(menu.show()).toBe(undefined);
+
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+    expect(menu.shownItem(0).active()).toBe(true);
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Lemma</strong>");
+    expect(menu.shownItem(1).active()).toBe(false);
+    expect(menu.element.childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+    expect(menu.shownItem(2).active()).toBe(false);
+    expect(menu.element.childNodes[3]).toBe(undefined);
+
+    // Activate next (1)
+    menu.next();
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+    expect(menu.shownItem(0).active()).toBe(false);
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Lemma</strong>");
+    expect(menu.shownItem(1).active()).toBe(true);
+    expect(menu.element.childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+    expect(menu.shownItem(2).active()).toBe(false);
+    expect(menu.element.childNodes[3]).toBe(undefined);
+
+    // Activate next (2)
+    menu.next();
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+    expect(menu.shownItem(0).active()).toBe(false);
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Lemma</strong>");
+    expect(menu.shownItem(1).active()).toBe(false);
+    expect(menu.element.childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+    expect(menu.shownItem(2).active()).toBe(true);
+    expect(menu.element.childNodes[3]).toBe(undefined);
+
+    // Activate next (3)
+    menu.next();
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>Lemma</strong>");
+    expect(menu.shownItem(0).active()).toBe(false);
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+    expect(menu.shownItem(1).active()).toBe(false);
+    expect(menu.element.childNodes[2].innerHTML).toEqual("<strong>Part-of-Speech</strong>");
+    expect(menu.shownItem(2).active()).toBe(true);
+    expect(menu.element.childNodes[3]).toBe(undefined);
+
+    // Activate next (4)
+    menu.next();
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+    expect(menu.shownItem(0).active()).toBe(false);
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Part-of-Speech</strong>");
+    expect(menu.shownItem(1).active()).toBe(false);
+    expect(menu.element.childNodes[2].innerHTML).toEqual("<strong>Syntax</strong>");
+    expect(menu.shownItem(2).active()).toBe(true);
+    expect(menu.element.childNodes[3]).toBe(undefined);
+
+    // Activate next (5) - ROLL
+    menu.next();
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+    expect(menu.shownItem(0).active()).toBe(true);
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Lemma</strong>");
+    expect(menu.shownItem(1).active()).toBe(false);
+    expect(menu.element.childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+    expect(menu.shownItem(2).active()).toBe(false);
+    expect(menu.element.childNodes[3]).toBe(undefined);
+
+    // Active next (6)
+    menu.next();
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+    expect(menu.shownItem(0).active()).toBe(false);
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Lemma</strong>");
+    expect(menu.shownItem(1).active()).toBe(true);
+    expect(menu.element.childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+    expect(menu.shownItem(2).active()).toBe(false);
+    expect(menu.element.childNodes[3]).toBe(undefined);
+
+  });
+
+
+  it('should be prevable', function () {
+    var menu = KorAP.Menu.create("cnx/", list);
+
+    KorAP.limit = 3;
+    expect(menu.show()).toBe(undefined);
+
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+    expect(menu.shownItem(0).active()).toBe(true);
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Lemma</strong>");
+    expect(menu.shownItem(1).active()).toBe(false);
+    expect(menu.element.childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+    expect(menu.shownItem(2).active()).toBe(false);
+    expect(menu.element.childNodes[3]).toBe(undefined);
+
+    // Activate prev (1) - roll to bottom
+    menu.prev();
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+    expect(menu.shownItem(0).active()).toBe(false);
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Part-of-Speech</strong>");
+    expect(menu.shownItem(1).active()).toBe(false);
+    expect(menu.element.childNodes[2].innerHTML).toEqual("<strong>Syntax</strong>");
+    expect(menu.shownItem(2).active()).toBe(true);
+    expect(menu.element.childNodes[3]).toBe(undefined);
+
+    // Activate prev (2)
+    menu.prev();
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+    expect(menu.shownItem(0).active()).toBe(false);
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Part-of-Speech</strong>");
+    expect(menu.shownItem(1).active()).toBe(true);
+    expect(menu.element.childNodes[2].innerHTML).toEqual("<strong>Syntax</strong>");
+    expect(menu.shownItem(2).active()).toBe(false);
+    expect(menu.element.childNodes[3]).toBe(undefined);
+
+    // Activate prev (3)
+    menu.prev();
+    expect(menu.shownItem(0).name).toEqual("Morphology");
+    expect(menu.shownItem(0).active()).toBe(true);
+    expect(menu.shownItem(1).name).toEqual("Part-of-Speech");
+    expect(menu.shownItem(1).active()).toBe(false);
+    expect(menu.shownItem(2).name).toEqual("Syntax");
+    expect(menu.shownItem(2).active()).toBe(false);
+    expect(menu.element.childNodes[3]).toBe(undefined);
+
+    // Activate prev (4)
+    menu.prev();
+    expect(menu.shownItem(0).name).toEqual("Lemma");
+    expect(menu.shownItem(0).active()).toBe(true);
+    expect(menu.shownItem(1).name).toEqual("Morphology");
+    expect(menu.shownItem(1).active()).toBe(false);
+    expect(menu.shownItem(2).name).toEqual("Part-of-Speech");
+    expect(menu.shownItem(2).active()).toBe(false);
+    expect(menu.element.childNodes[3]).toBe(undefined);
+
+    // Activate prev (5)
+    menu.prev();
+    expect(menu.shownItem(0).name).toEqual("Constituency");
+    expect(menu.shownItem(0).active()).toBe(true);
+    expect(menu.shownItem(1).name).toEqual("Lemma");
+    expect(menu.shownItem(1).active()).toBe(false);
+    expect(menu.shownItem(2).name).toEqual("Morphology");
+    expect(menu.shownItem(2).active()).toBe(false);
+    expect(menu.element.childNodes[3]).toBe(undefined);
+
+    // Activate next (1)
+    menu.next();
+    expect(menu.shownItem(0).name).toEqual("Constituency");
+    expect(menu.shownItem(0).active()).toBe(false);
+    expect(menu.shownItem(1).name).toEqual("Lemma");
+    expect(menu.shownItem(1).active()).toBe(true);
+    expect(menu.shownItem(2).name).toEqual("Morphology");
+    expect(menu.shownItem(2).active()).toBe(false);
+    expect(menu.element.childNodes[3]).toBe(undefined);
+
+    // Activate prev (6)
+    menu.prev();
+
+    // Activate prev (7)
+    menu.prev();
+    expect(menu.shownItem(0).name).toEqual("Morphology");
+    expect(menu.shownItem(0).active()).toBe(false);
+    expect(menu.shownItem(1).name).toEqual("Part-of-Speech");
+    expect(menu.shownItem(1).active()).toBe(false);
+    expect(menu.shownItem(2).name).toEqual("Syntax");
+    expect(menu.shownItem(2).active()).toBe(true);
+    expect(menu.element.childNodes[3]).toBe(undefined);
+  });
+
+  it('should be navigatable and filterable (prefix = "o")', function () {
+    var menu = KorAP.Menu.create("cnx/", list);
+
+    KorAP.limit = 2;
+
+    expect(menu.show("o")).toBe(undefined);
+
+    expect(menu.shownItem(0).name).toEqual("Constituency");
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>C<em>o</em>nstituency</strong><span>Example 1</span>");
+    expect(menu.shownItem(0).active()).toBe(true);
+    expect(menu.shownItem(1).name).toEqual("Morphology");
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>M<em>o</em>rphology</strong><span>Example 2</span>");
+    expect(menu.shownItem(1).active()).toBe(false);
+    expect(menu.shownItem(2)).toBe(undefined);
+
+    // Next (1)
+    menu.next();
+    expect(menu.shownItem(0).name).toEqual("Constituency");
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>C<em>o</em>nstituency</strong><span>Example 1</span>");
+    expect(menu.shownItem(0).active()).toBe(false);
+    expect(menu.shownItem(1).name).toEqual("Morphology");
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>M<em>o</em>rphology</strong><span>Example 2</span>");
+    expect(menu.shownItem(1).active()).toBe(true);
+    expect(menu.shownItem(2)).toBe(undefined);
+
+
+    // Next (2)
+    menu.next();
+    expect(menu.shownItem(0).name).toEqual("Morphology");
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>M<em>o</em>rphology</strong><span>Example 2</span>");
+    expect(menu.shownItem(0).active()).toBe(false);
+    expect(menu.shownItem(1).name).toEqual("Part-of-Speech");
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Part-<em>o</em>f-Speech</strong>");
+    expect(menu.shownItem(1).active()).toBe(true);
+    expect(menu.shownItem(2)).toBe(undefined);
+
+    // Next (3)
+    menu.next();
+    expect(menu.shownItem(0).name).toEqual("Constituency");
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>C<em>o</em>nstituency</strong><span>Example 1</span>");
+    expect(menu.shownItem(0).active()).toBe(true);
+    expect(menu.shownItem(1).name).toEqual("Morphology");
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>M<em>o</em>rphology</strong><span>Example 2</span>");
+    expect(menu.shownItem(1).active()).toBe(false);
+    expect(menu.shownItem(2)).toBe(undefined);
+  });
+
+  it('should be navigatable and filterable (prefix = "ex", "e")', function () {
+    var menu = KorAP.Menu.create("cnx/", list);
+
+    KorAP.limit = 2;
+
+    expect(menu.show("ex")).toBe(undefined);
+
+    expect(menu.shownItem(0).name).toEqual("Constituency");
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>Constituency</strong><span><em>Ex</em>ample 1</span>");
+    expect(menu.shownItem(0).active()).toBe(true);
+    expect(menu.shownItem(1).name).toEqual("Morphology");
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Morphology</strong><span><em>Ex</em>ample 2</span>");
+    expect(menu.shownItem(1).active()).toBe(false);
+    expect(menu.shownItem(2)).toBe(undefined);
+
+    // Next (1)
+    menu.next();
+    expect(menu.shownItem(0).name).toEqual("Constituency");
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>Constituency</strong><span><em>Ex</em>ample 1</span>");
+    expect(menu.shownItem(0).active()).toBe(false);
+    expect(menu.shownItem(1).name).toEqual("Morphology");
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Morphology</strong><span><em>Ex</em>ample 2</span>");
+    expect(menu.shownItem(1).active()).toBe(true);
+    expect(menu.shownItem(2)).toBe(undefined);
+
+    // Next (2)
+    menu.next();
+    expect(menu.shownItem(0).name).toEqual("Constituency");
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>Constituency</strong><span><em>Ex</em>ample 1</span>");
+    expect(menu.shownItem(0).active()).toBe(true);
+    expect(menu.shownItem(1).name).toEqual("Morphology");
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Morphology</strong><span><em>Ex</em>ample 2</span>");
+    expect(menu.shownItem(1).active()).toBe(false);
+    expect(menu.shownItem(2)).toBe(undefined);
+
+    // Reset limit
+    KorAP.limit = 5;
+
+    // Change show
+    expect(menu.show("e")).toBe(undefined);
+
+    expect(menu.shownItem(0).name).toEqual("Constituency");
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>Constitu<em>e</em>ncy</strong><span><em>E</em>xample 1</span>");
+    expect(menu.shownItem(0).active()).toBe(true);
+    expect(menu.shownItem(1).name).toEqual("Morphology");
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Morphology</strong><span><em>E</em>xample 2</span>");
+    expect(menu.shownItem(1).active()).toBe(false);
+    expect(menu.shownItem(2)).toBe(undefined);
+
+    // Next (1)
+    menu.next();
+    expect(menu.shownItem(0).name).toEqual("Constituency");
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>Constitu<em>e</em>ncy</strong><span><em>E</em>xample 1</span>");
+    expect(menu.shownItem(0).active()).toBe(false);
+    expect(menu.shownItem(1).name).toEqual("Morphology");
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Morphology</strong><span><em>E</em>xample 2</span>");
+    expect(menu.shownItem(1).active()).toBe(true);
+    expect(menu.shownItem(2)).toBe(undefined);
+
+    // Next (2)
+    menu.next();
+    expect(menu.shownItem(0).name).toEqual("Constituency");
+    expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>Constitu<em>e</em>ncy</strong><span><em>E</em>xample 1</span>");
+    expect(menu.shownItem(0).active()).toBe(true);
+    expect(menu.shownItem(1).name).toEqual("Morphology");
+    expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Morphology</strong><span><em>E</em>xample 2</span>");
+    expect(menu.shownItem(1).active()).toBe(false);
+    expect(menu.shownItem(2)).toBe(undefined);
+  });
+});
+
+describe('KorAP.ContextAnalyzer', function () {
+
+  it('should be initializable', function () {
+    var analyzer = KorAP.ContextAnalyzer.create(")");
+    expect(analyzer).toBe(undefined);
+
+    analyzer = KorAP.ContextAnalyzer.create(".+?");
+    expect(analyzer).not.toBe(undefined);
+
+  });
+
+  it('should check correctly', function () {
+    analyzer = KorAP.ContextAnalyzer.create(KorAP.context);
+    expect(analyzer.test("cnx/]cnx/c=")).toEqual("cnx/c=");
+    expect(analyzer.test("cnx/c=")).toEqual("cnx/c=");
+    expect(analyzer.test("cnx/c=np mate/m=mood:")).toEqual("mate/m=mood:");
+    expect(analyzer.test("impcnx/")).toEqual("impcnx/");
+    expect(analyzer.test("cnx/c=npcnx/")).toEqual("npcnx/");
+    expect(analyzer.test("mate/m=degree:pos corenlp/ne_dewac_175m_600="))
+      .toEqual("corenlp/ne_dewac_175m_600=");
+  });
+});
+
+describe('KorAP.InputField', function () {
+  var input;
+
+  beforeAll(function () {
+    input = document.createElement("input");
+    input.setAttribute("type", "text");
+    input.setAttribute("value", "abcdefghijklmno");
+    input.style.position = 'absolute';
+    input.style.top  = "20px";
+    input.style.left = "30px";
+    input.focus();
+    input.selectionStart = 5;
+  });
+
+  afterAll(function () {
+    document.getElementsByTagName("body")[0].removeChild(input);
+    document.getElementsByTagName("body")[0].removeChild(
+      document.getElementById("searchMirror")
+    );
+  });
+
+  it('should be initializable', function () {
+    // Supports: context, searchField
+    var inputField = KorAP.InputField.create(input);
+    expect(inputField._element).not.toBe(undefined);
+  });
+
+  it('should have text', function () {
+    var inputField = KorAP.InputField.create(input);
+
+    expect(inputField.value).toEqual("abcdefghijklmno");
+    expect(inputField.element.selectionStart).toEqual(5);
+    expect(inputField.split()[0]).toEqual("abcde");
+    expect(inputField.split()[1]).toEqual("fghijklmno");
+
+    inputField.insert("xyz");
+    expect(inputField.split()[0]).toEqual("abcdexyz");
+    expect(inputField.split()[1]).toEqual("fghijklmno");
+
+  });
+
+  it('should be correctly positioned', function () {
+    var inputField = KorAP.InputField.create(input);
+    document.getElementsByTagName("body")[0].appendChild(input);
+    inputField.reposition();
+    expect(inputField.mirror.style.left).toEqual("30px");
+    expect(inputField.mirror.style.top.match(/^(\d+)px$/)[1]).toBeGreaterThan(20);
+  });
+});
+*/
diff --git a/public/js/spec/vcSpec.js b/public/js/spec/vcSpec.js
index da5ddaa..11ada63 100644
--- a/public/js/spec/vcSpec.js
+++ b/public/js/spec/vcSpec.js
@@ -534,7 +534,7 @@
     expect(docElement.getAttribute('class')).toEqual('doc unspecified');
     expect(docElement.firstChild.firstChild.data).toEqual('⋯');
     expect(docElement.lastChild.lastChild.data).toEqual('⋯');
-    expect(doc.toQuery()).toEqual('⋯');
+    expect(doc.toQuery()).toEqual('');
 
     // Only removable
     expect(docElement.lastChild.children.length).toEqual(0);
@@ -566,6 +566,79 @@
     expect(unspec.lastChild.children.length).toEqual(1);
     expect(unspec.lastChild.children[0].getAttribute('class')).toEqual('delete');
   });
+
+  it('should be replaceable by a doc', function () {
+    var doc = KorAP.UnspecifiedDoc.create();
+    expect(doc.ldType()).toEqual("non");
+    // No parent, therefor not updateable
+    expect(doc.key("baum")).toBeNull();
+
+    var docGroup = KorAP.DocGroup.create();
+    docGroup.operation('or');
+    expect(docGroup.operation()).toEqual('or');
+
+    docGroup.append({
+      "@type": 'korap:doc',
+      "key": 'pubDate',
+      "match": 'match:eq',
+      "value": '2014-12-05',
+      "type": 'type:date'      
+    });
+
+    expect(docGroup.toQuery()).toEqual("pubDate in 2014-12-05");
+    docGroup.append();
+
+    expect(docGroup.getOperand(0).ldType()).toEqual("doc");
+    expect(docGroup.getOperand(1).ldType()).toEqual("non");
+
+    var op = docGroup.getOperand(1).element().lastChild;
+    expect(op.getAttribute('class')).toEqual('operators');
+    expect(op.children[0].getAttribute('class')).toEqual('delete');
+    expect(op.children.length).toEqual(1);
+
+    // Replace unspecified doc
+    expect(docGroup.getOperand(1).key("name")).not.toBeNull();
+    expect(docGroup.getOperand(1).ldType()).toEqual("doc");
+    expect(docGroup.getOperand(1).key()).toEqual("name");
+    expect(docGroup.getOperand(1).value()).toEqual("");
+
+    op = docGroup.getOperand(1).element().lastChild;
+    expect(op.getAttribute('class')).toEqual('operators');
+    expect(op.children[0].getAttribute('class')).toEqual('and');
+    expect(op.children[1].getAttribute('class')).toEqual('or');
+    expect(op.children[2].getAttribute('class')).toEqual('delete');
+    expect(op.children.length).toEqual(3);
+
+    docGroup.getOperand(1).value("Pachelbel");
+    expect(docGroup.getOperand(1).value()).toEqual("Pachelbel");
+    expect(docGroup.getOperand(1).type()).toEqual("string");
+    expect(docGroup.getOperand(1).matchop()).toEqual("eq");
+
+    // Specified!
+    expect(docGroup.toQuery()).toEqual('pubDate in 2014-12-05 | name = "Pachelbel"');
+  });
+
+  it('should be replaceable on root', function () {
+    var vc = KorAP.VirtualCollection.render();
+    expect(vc.toQuery()).toEqual("");
+
+    expect(vc.root().ldType()).toEqual("non");
+
+    // No operators on root
+    op = vc.root().element().lastChild;
+    expect(op.lastChild.textContent).toEqual('⋯');
+
+    // Replace
+    expect(vc.root().key("baum")).not.toBeNull();
+    expect(vc.root().ldType()).toEqual("doc");
+
+    op = vc.root().element().lastChild;
+    expect(op.getAttribute('class')).toEqual('operators');
+    expect(op.children[0].getAttribute('class')).toEqual('and');
+    expect(op.children[1].getAttribute('class')).toEqual('or');
+    expect(op.children[2].getAttribute('class')).toEqual('delete');
+    expect(op.children.length).toEqual(3);
+  });
 });
 
 describe('KorAP.Doc element', function () {
@@ -1038,7 +1111,7 @@
 
     // Clean everything
     vc.clean();
-    expect(vc.toQuery()).toEqual('⋯');
+    expect(vc.toQuery()).toEqual('');
   });
 });
 
@@ -1125,7 +1198,7 @@
     // Clean with delete from root
     expect(vc.root().element().lastChild.lastChild.getAttribute('class')).toEqual('delete');
     _delOn(vc.root());
-    expect(vc.root().toQuery()).toEqual('⋯');
+    expect(vc.root().toQuery()).toEqual('');
     expect(vc.root().element().lastChild.lastChild.data).toEqual('⋯');
   });
 
@@ -1184,7 +1257,7 @@
     // Cleanwith direct element access
     expect(vc.toQuery()).toEqual('pubDate in 2014-12-05 & foo = "bar"');
     _delOn(vc.root());
-    expect(vc.toQuery()).toEqual('⋯');
+    expect(vc.toQuery()).toEqual('');
     expect(vc.root().ldType()).toEqual('non');
   });
 
@@ -1696,14 +1769,18 @@
 	]
       }
     );
-
-    expect(vc.toQuery()).toEqual('pubDate in 2014-12-05');
-    expect(vc.root().key()).toEqual('pubDate');
-    expect(vc.root().value()).toEqual('2014-12-05');
-
-    // Wrap on root
-    _orOn(vc.root());
-    expect(vc.root().ldType()).toEqual('docGroup');
+    expect(vc.toQuery()).toEqual(
+      '(title = "t1" & title = "t2") | (title = "t3" & title = "t4")'
+    );
     expect(vc.root().operation()).toEqual('or');
+    expect(vc.root().getOperand(0).toQuery()).toEqual('title = "t1" & title = "t2"');
+    expect(vc.root().getOperand(1).toQuery()).toEqual('title = "t3" & title = "t4"');
+
+    _andOn(vc.root());
+
+    expect(vc.root().operation()).toEqual('and');
+    expect(vc.root().getOperand(0).ldType()).toEqual('docGroup');
+    expect(vc.root().getOperand(1).ldType()).toEqual('non');
   });
 });
+
diff --git a/public/js/src/menu.js b/public/js/src/menu.js
new file mode 100644
index 0000000..3b79bdd
--- /dev/null
+++ b/public/js/src/menu.js
@@ -0,0 +1,202 @@
+var KorAP = KorAP || {};
+
+(function (KorAP) {
+  "use strict";
+
+  /**
+   * Item in the Dropdown menu
+   */
+  KorAP.MenuItem = {
+
+    /**
+     * Create a new MenuItem object.
+     *
+     * @constructor
+     * @this {MenuItem}
+     * @param {Array.<string>} An array object of name, action and
+     *   optionally a description
+     */
+    create : function (params) {
+      return Object.create(KorAP.MenuItem)._init(params);
+    },
+
+    /**
+     * Upgrade this object to another object,
+     * while private data stays intact.
+     *
+     * @param {Object] An object with properties.
+     */
+    upgradeTo : function (props) {
+      for (var prop in props) {
+	this[prop] = props[prop];
+      };
+      return this;
+    },
+
+    content : function (content) {
+      if (arguments.length === 1)
+	this._content = document.createTextNode(content);
+      return this._content;
+    },
+
+    lcField : function () {
+      return this._lcField;
+    },
+
+    action : function (action) {
+      if (arguments.length === 1)
+	this._action = action;
+      return this._action;
+    },
+
+    /**
+     * Check or set if the item is active
+     *
+     * @param {boolean|null} State of activity
+     */
+    active : function (bool) {
+      var cl = this.element().classList;
+      if (bool === undefined)
+	return cl.contains("active");
+      else if (bool)
+	cl.add("active");
+      else
+	cl.remove("active");
+    },
+
+    /**
+     * Check or set if the item is
+     * at the boundary of the menu
+     * list
+     *
+     * @param {boolean|null} State of activity
+     */
+    noMore : function (bool) {
+      var cl = this.element().classList;
+      if (bool === undefined)
+	return cl.contains("no-more");
+      else if (bool)
+	cl.add("no-more");
+      else
+	cl.remove("no-more");
+    },
+
+    /**
+     * Get the document element of the menu item
+     */
+    element : function () {
+      // already defined
+      if (this._element !== undefined)
+	return this._element;
+
+      // Create list item
+      var li = document.createElement("li");
+
+      // Connect action
+      li["action"] = this._action;
+
+      // Append template
+      li.appendChild(this.content());
+
+      return this._element = li;
+    },
+
+    /**
+     * Highlight parts of the item
+     *
+     * @param {string} Prefix string for highlights
+     */
+    highlight : function (prefix) {
+      this._highlight(this.element().firstChild, prefix);
+    },
+
+    // Highlight a certain substring of the menu item
+    _highlight : function (elem, prefix) {
+
+      if (elem.nodeType === 3) {
+
+	var text   = elem.nodeValue;
+	var textlc = text.toLowerCase();
+	var pos    = textlc.indexOf(prefix);
+	if (pos >= 0) {
+
+	  // First element
+	  if (pos > 0) {
+	    elem.parentNode.insertBefore(
+	      document.createTextNode(text.substr(0, pos)),
+	      elem
+	    );
+	  };
+
+	  // Second element
+	  var hl = document.createElement("mark");
+	  hl.appendChild(
+	    document.createTextNode(text.substr(pos, prefix.length))
+	  );
+	  elem.parentNode.insertBefore(hl, elem);
+
+	  // Third element
+	  var third = text.substr(pos + prefix.length);
+	  if (third.length > 0) {
+	    var thirdE = document.createTextNode(third);
+	    elem.parentNode.insertBefore(
+	      thirdE,
+	      elem
+	    );
+	    this._highlight(thirdE, prefix);
+	  };
+
+	  var p = elem.parentNode;
+	  p.removeChild(elem);
+	};
+      }
+      else {
+	var children = elem.childNodes;
+	for (var i = children.length -1; i >= 0; i--) {
+	  this._highlight(children[i], prefix);
+	};
+      };
+    },
+
+
+    /**
+     * Remove highlight of the menu item
+     */
+    lowlight : function () {
+      var e = this.element();
+
+      var marks = e.getElementsByTagName("mark");
+      for (var i = marks.length - 1; i >= 0; i--) {
+	// Create text node clone
+	var x = document.createTextNode(
+	  marks[i].firstChild.nodeValue
+	);
+
+	// Replace with content
+	marks[i].parentNode.replaceChild(
+	  x,
+	  marks[i]
+	);
+      };
+
+      // Remove consecutive textnodes
+      e.normalize();
+    },
+
+    // Initialize menu item
+    _init : function (params) {
+      if (params[0] === undefined)
+	throw new Error("Missing parameters");
+
+      this.content(params[0]);
+
+      if (params.length === 2)
+	this._action = params[1];
+
+      this._lcField = ' ' + this.content().textContent.toLowerCase();
+
+      return this;
+    },
+  };
+
+}(this.KorAP));
diff --git a/public/js/src/vc.js b/public/js/src/vc.js
index 42e51c3..b68f0aa 100644
--- a/public/js/src/vc.js
+++ b/public/js/src/vc.js
@@ -60,6 +60,7 @@
   // Add new unspecified document
   KorAP._add = function (obj, type) {
     var ref = obj.parentNode.refTo;
+    console.log("DEBUG: " + type + " on " + ref.toQuery());
     var parent = ref.parent();
 
     if (ref.ldType() === 'docGroup') {
@@ -106,6 +107,7 @@
   // Remove doc or docGroup
   KorAP._delete = function () {
     var ref = this.parentNode.refTo;
+console.log("DEBUG: delete " + ref.toQuery());
     if (ref.parent().ldType() !== null) {
       return ref.parent().delOperand(ref).update();
     }
@@ -326,6 +328,34 @@
       return obj;
     },
 
+    // Set key - replace
+    key : function (v) {
+
+      // Not replaceable
+      if (this._parent === undefined)
+	return null;
+
+      // Set JSON-LD type
+      var newDoc = KorAP.Doc.create(this._parent, {
+	"@type" : "korap:doc",
+	"value" : "",
+	"key"   : v
+      });
+
+      // Unspecified document on root
+      if (this._parent.ldType() === null) {
+	this._parent.root(newDoc);
+	this.destroy();
+      }
+
+      // Unspecified document in group
+      else {
+	this._parent.replaceOperand(this, newDoc);
+      };
+      this._parent.update();
+      return newDoc;
+    },
+
     update : function () {
 
       if (this._element === undefined)
@@ -361,7 +391,9 @@
       this._element.setAttribute('class', 'doc unspecified');
       this.update();
       return this._element;
-    }
+    },
+
+    
   };
 
 
@@ -370,7 +402,7 @@
    */
   KorAP.Doc = {
     _ldType : "doc",
-    _obj : function () { return KorAP.Doc; },
+    _obj : function () { return KorAP.Doc },
 
     create : function (parent, json) {
       var obj = Object(KorAP.JsonLD).
@@ -572,6 +604,7 @@
       if (arguments.length === 1) {
 	this._key = value;
 	this._changed = true;
+	return this;
       };
       return this._key;
     },
@@ -580,6 +613,7 @@
       if (arguments.length === 1) {
 	this._matchop = match.replace(/^match:/, '');
 	this._changed = true;
+	return this;
       };
       return this._matchop || "eq";
     },
@@ -588,6 +622,7 @@
       if (arguments.length === 1) {
 	this._type = type;
 	this._changed = true;
+	return this;
       };
       return this._type || "string";
     },
@@ -596,6 +631,7 @@
       if (arguments.length === 1) {
 	this._value = value;
 	this._changed = true;
+	return this;
       };
       return this._value;
     },
@@ -1042,7 +1078,7 @@
     },
 
     toQuery : function () {
-      return loc.EMPTY;
+      return '';
     }
   };