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;
+ }
+ };
+});