Use requirejs for clientside scripting
diff --git a/dev/js/src/hint/contextanalyzer.js b/dev/js/src/hint/contextanalyzer.js
new file mode 100644
index 0000000..bdbf885
--- /dev/null
+++ b/dev/js/src/hint/contextanalyzer.js
@@ -0,0 +1,23 @@
+/**
+ * Regex object for checking the context of the hint
+ */
+define({
+  create : function (regex) {
+    return Object.create(this)._init(regex);
+  },
+  _init : function (regex) {
+    try {
+      this._regex = new RegExp(regex);
+    }
+    catch (e) {
+      KorAP.log(0, e);
+      return;
+    };
+    return this;
+  },
+  test : function (text) {
+    if (!this._regex.exec(text))
+      return;
+    return RegExp.$1;
+  }
+});
diff --git a/dev/js/src/hint/input.js b/dev/js/src/hint/input.js
new file mode 100644
index 0000000..b1c6632
--- /dev/null
+++ b/dev/js/src/hint/input.js
@@ -0,0 +1,103 @@
+// Input field for queries
+define({
+  create : function (element) {
+    return Object.create(this)._init(element);
+  },
+    
+  _init : function (element) {
+    this._element = element;
+
+    // Create mirror for searchField
+    if ((this._mirror = document.getElementById("searchMirror")) === null) {
+      this._mirror = document.createElement("div");
+      this._mirror.setAttribute("id", "searchMirror");
+      this._mirror.appendChild(document.createElement("span"));
+      this._container = this._mirror.appendChild(document.createElement("div"));
+      this._mirror.style.height = "0px";
+      document.getElementsByTagName("body")[0].appendChild(this._mirror);
+    };
+
+    // Update position of the mirror
+    var that = this;
+    var repos = function () {
+      that.reposition();
+    };
+    window.addEventListener('resize', repos);
+    this._element.addEventListener('onfocus', repos);
+    that.reposition();
+
+    return this;
+  },
+
+  rightPos : function () {
+    var box = this._mirror.firstChild.getBoundingClientRect();
+    return box.right - box.left;
+  },
+
+  mirror : function () {
+    return this._mirror;
+  },
+
+  container : function () {
+    return this._container;
+  },
+
+  element : function () {
+    return this._element;
+  },
+
+  value : function () {
+    return this._element.value;
+  },
+
+  update : function () {
+    this._mirror.firstChild.textContent = this.split()[0];
+    this._container.style.left = this.rightPos() + 'px';
+  },
+
+  insert : function (text) {
+    var splittedText = this.split();
+    var s = this._element;
+    s.value = splittedText[0] + text + splittedText[1];
+    s.selectionStart = (splittedText[0] + text).length;
+    s.selectionEnd = s.selectionStart;
+    this._mirror.firstChild.textContent = splittedText[0] + text;
+  },
+  
+  // Return two substrings, splitted at current cursor position
+  split : function () {
+    var s = this._element;
+    var value = s.value;
+    var start = s.selectionStart;
+    return new Array(
+      value.substring(0, start),
+      value.substring(start, value.length)
+    );
+  },
+
+  // Position the input mirror directly below the input box
+  reposition : function () {
+    var inputClientRect = this._element.getBoundingClientRect();
+    var inputStyle = window.getComputedStyle(this._element, null);
+
+    var bodyClientRect = 
+      document.getElementsByTagName('body')[0].getBoundingClientRect();
+
+    // Reset position
+    var mirrorStyle = this._mirror.style;
+    mirrorStyle.left = inputClientRect.left + "px";
+    mirrorStyle.top  = (inputClientRect.bottom - bodyClientRect.top) + "px";
+    mirrorStyle.width = inputStyle.getPropertyValue("width");
+    
+    // These may be relevant in case of media depending css
+    mirrorStyle.paddingLeft     = inputStyle.getPropertyValue("padding-left");
+    mirrorStyle.marginLeft      = inputStyle.getPropertyValue("margin-left");
+    mirrorStyle.borderLeftWidth = inputStyle.getPropertyValue("border-left-width");
+    mirrorStyle.borderLeftStyle = inputStyle.getPropertyValue("border-left-style");
+    mirrorStyle.fontSize        = inputStyle.getPropertyValue("font-size");
+    mirrorStyle.fontFamily      = inputStyle.getPropertyValue("font-family");
+  },
+  context : function () {
+    return this.split()[0];
+  }
+});
diff --git a/dev/js/src/hint/item.js b/dev/js/src/hint/item.js
new file mode 100644
index 0000000..f23cabc
--- /dev/null
+++ b/dev/js/src/hint/item.js
@@ -0,0 +1,85 @@
+/**
+ * Hint menu item based on MenuItem
+ */
+define(['menu/item'], function (itemClass) {
+  return {
+    create : function (params) {
+      return Object.create(itemClass)
+	.upgradeTo(this)
+	._init(params);
+    },
+    _init : function (params) {
+      if (params[0] === undefined ||
+	  params[1] === undefined)
+	throw new Error("Missing parameters");
+      
+      this._name   = params[0];
+      this._action = params[1];
+      this._lcField = ' ' + this._name.toLowerCase();
+      
+      if (params.length > 2) {
+	this._desc = params[2];
+	this._lcField += " " + this._desc.toLowerCase();
+      };
+
+      return this;
+    },
+
+    content : function (content) {
+      if (arguments.length === 1) {
+	this._content = content;
+      };
+      return this._content;
+    },
+    
+    onclick : function () {
+      var m = this.menu();
+      var h = m.hint();
+      m.hide();
+
+      // Update input field
+      var input = h.inputField();
+      input.insert(this._action);
+      input.update();
+
+      h.active = false;
+      h.show(true);
+    },
+    name : function () {
+      return this._name;
+    },
+    action : function () {
+      return this._action;
+    },
+    desc : function () {
+      return this._desc;
+    },
+    element : function () {
+      // already defined
+      if (this._element !== undefined)
+	return this._element;
+
+      // Create list item
+      var li = document.createElement("li");
+
+      if (this.onclick !== undefined) {
+	li["onclick"] = this.onclick.bind(this);
+      };
+
+      // Create title
+      var name =  document.createElement("span");
+      name.appendChild(document.createTextNode(this._name));
+      
+      li.appendChild(name);
+
+      // Create description
+      if (this._desc !== undefined) {
+	var desc = document.createElement("span");
+	desc.classList.add('desc');
+	desc.appendChild(document.createTextNode(this._desc));
+	li.appendChild(desc);
+      };
+      return this._element = li;
+    }
+  };
+});
diff --git a/dev/js/src/hint/menu.js b/dev/js/src/hint/menu.js
new file mode 100644
index 0000000..c579945
--- /dev/null
+++ b/dev/js/src/hint/menu.js
@@ -0,0 +1,36 @@
+/**
+ * Hint menu
+ */
+define(['menu', 'hint/item', 'hint/prefix'], function (menuClass, itemClass, prefixClass) {
+  return {
+    create : function (hint, context, params) {
+      var obj = Object.create(menuClass)
+	.upgradeTo(this)
+	._init(itemClass, prefixClass, params);
+      obj._context = context;
+      obj._element.classList.add('hint');
+      obj._hint = hint;
+
+      // This is only domspecific
+      obj.element().addEventListener('blur', function (e) {
+	this.menu.hide();
+      });
+
+      // Focus on input field on hide
+      obj.onHide = function () {
+	var input = this._hint.inputField();
+	input.container().classList.remove('active');
+	input.element().focus();
+      };
+
+      return obj;
+    },
+    // Todo: Is this necessary?
+    context : function () {
+      return this._context;
+    },
+    hint : function () {
+      return this._hint;
+    }
+  };
+});
diff --git a/dev/js/src/hint/prefix.js b/dev/js/src/hint/prefix.js
new file mode 100644
index 0000000..84f785a
--- /dev/null
+++ b/dev/js/src/hint/prefix.js
@@ -0,0 +1,15 @@
+define(['menu/prefix'], function (prefixClass) {
+  return {
+    create : function (params) {
+      return Object.create(prefixClass).upgradeTo(this)._init(params);
+    },
+    onclick : function () {
+      var m = this.menu();
+      var h = m.hint();
+      m.hide();
+
+      h.inputField().insert(this.value());
+      h.active = false;
+    }
+  };
+});