Added hint and vc to collected kalamar assets
diff --git a/public/js/src/hint.js b/public/js/src/hint.js
index b5c2043..8011bee 100644
--- a/public/js/src/hint.js
+++ b/public/js/src/hint.js
@@ -1,64 +1,21 @@
/**
- * Context aware drop down menu
- * for annotation related query hints.
+ * Hint menu for Kalamar.
*
* @author Nils Diewald
*/
-// - http://www.cryer.co.uk/resources/javascript/script20_respond_to_keypress.htm
-// - https://developers.google.com/closure/compiler/docs/js-for-compiler
-// TODO:
-// - Add help option that opens the tutorial, e.g. to the foundry
-// - http://en.wikipedia.org/wiki/JSDoc
+// requires menu.js
-// The last entry types (foundry, foundry/layer) is remembered and chosen by default
-
-// Show the context at the top as breadcrumbs
-// Highlight the context in the query (probably)
-// Support backspace for removing the last prefix
-
-/*
-Alternative: Use right arrow for temporary context switch and arrow back
-for temporary context removal
-*/
-
-/**
- * The KorAP namespace for project related scripts
- * @namespace
- */
var KorAP = KorAP || {};
-/*
- this._search.addEventListener(
- "keyup",
- function () {
- that.update();
- },
- false
- );
-*/
-
(function (KorAP) {
"use strict";
- // Don't let events bubble up
- if (Event.halt === undefined) {
- Event.prototype.halt = function () {
- this.stopPropagation();
- this.preventDefault();
- };
- };
-
// Default log message
KorAP.log = KorAP.log || function (type, msg) {
console.log(type + ": " + msg);
};
- /* TODO: Add event listener on windows resize to change that! */
-
- /** @define {number} Limited view of menu items */
- KorAP.limit = 8;
-
/**
* @define {regex} Regular expression for context
*/
@@ -74,14 +31,12 @@
// Initialize hint array
KorAP.hintArray = KorAP.hintArray || {};
-
- KorAP.updateKeyDown = function (event) {
- };
-
+ // Input field for queries
KorAP.InputField = {
create : function (element) {
return Object.create(KorAP.InputField)._init(element);
},
+
_init : function (element) {
this._element = element;
@@ -90,6 +45,7 @@
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 = "1px";
document.getElementsByTagName("body")[0].appendChild(this._mirror);
};
@@ -99,39 +55,47 @@
window.resize = function () {
that.reposition();
};
-/*
- // Add event listener for key down
- element.addEventListener(
- "keydown",
- function (e) {
-// KorAP.updateKeyDown(e).bind(that)
- },
- false
- );
-*/
+
+ that.reposition();
+
return this;
},
- get mirror () {
+
+ rightPos : function () {
+ var box = this._mirror.getBoundingClientRect();
+ return box.right - box.left;
+ },
+
+ mirror : function () {
return this._mirror;
},
- get element () {
+
+ container : function () {
+ return this._container;
+ },
+
+ element : function () {
return this._element;
},
- get value () {
+
+ value : function () {
return this._element.value;
},
+
update : function () {
this._mirror.firstChild.textContent = this.split()[0];
},
+
insert : function (text) {
var splittedText = this.split();
- var s = this.element;
+ 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 position
+
+ // Return two substrings, splitted at current cursor position
split : function () {
var s = this._element;
var value = s.value;
@@ -141,13 +105,16 @@
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);
+
+ // Reset position
var mirrorStyle = this._mirror.style;
mirrorStyle.left = inputClientRect.left + "px";
- mirrorStyle.top = inputClientRect.bottom + "px";
+ mirrorStyle.top = inputClientRect.bottom + "px";
// These may be relevant in case of media depending css
mirrorStyle.paddingLeft = inputStyle.getPropertyValue("padding-left");
@@ -157,182 +124,14 @@
mirrorStyle.fontSize = inputStyle.getPropertyValue("font-size");
mirrorStyle.fontFamily = inputStyle.getPropertyValue("font-family");
},
- get context () {
+ context : function () {
return this.split()[0];
}
};
- KorAP.Hint = {
- _firstTry : true,
- create : function (param) {
- return Object.create(KorAP.Hint)._init(param);
- },
- _init : function (param) {
- param = param || {};
- this._menu = {};
-
- // Get input field
- this._inputField = KorAP.InputField.create(
- param["inputField"] || document.getElementById("q-field")
- );
-
- var that = this;
- var inputFieldElement = this._inputField.element;
-
- // Add event listener for key pressed down
- inputFieldElement.addEventListener(
- "keypress", function (e) {that.updateKeyPress(e)}, false
- );
-
- // Set Analyzer for context
- this._analyzer = KorAP.ContextAnalyzer.create(
- param["context"]|| KorAP.context
- );
-
- return this;
- },
- _codeFromEvent : function (e) {
- if ((e.charCode) && (e.keyCode==0))
- return e.charCode
- return e.keyCode;
- },
- updateKeyPress : function (e) {
- if (!this._active)
- return;
-
- var character = String.fromCharCode(
- this._codeFromEvent(e)
- );
-
- e.halt(); // No event propagation
-
- console.log("TODO: filter view");
- },
- updateKeyDown : function (e) {
- var code = this._codeFromEvent(e)
-
- /*
- * keyCodes:
- * - Down = 40
- * - Esc = 27
- * - Up = 38
- * - Enter = 13
- * - shift = 16
- * for characters use e.key
- */
- switch (code) {
- case 27: // 'Esc'
- // TODO: menu.hide();
- break;
- case 40: // 'Down'
- e.halt(); // No event propagation
-
- // Menu is not active
- if (!this._active)
- this.popUp();
- // Menu is active
- else {
- // TODO: that.removePrefix();
- // TODO: menu.next();
- };
-
- break;
- case 38: // "Up"
- if (!this._active)
- break;
- e.halt(); // No event propagation
- // TODO: that.removePrefix();
- // TODO: menu.prev();
- break;
- case 13: // "Enter"
- if (!this._active)
- break;
- e.halt(); // No event propagation
- // TODO: that.insertText(menu.getActiveItem().getAction());
- // TODO: that.removePrefix();
-
- // Remove menu
- // TODO: menu.hide();
-
- // Fill this with the correct value
- // Todo: This is redundant with click function
- /*
- var show;
- if ((show = that.analyzeContext()) != "-") {
- menu.show(show);
- menu.update(
- e.target.getBoundingClientRect().right
- );
- };
- */
-
- break;
- default:
- if (!this._active)
- return;
-
- // Surpress propagation in firefox
- /*
- if (e.key !== undefined && e.key.length != 1) {
- menu.hide();
- };
- */
- };
- },
-
- getMenuByContext : function () {
- var context = this._inputField.context;
- if (context === undefined || context.length == 0)
- return this.menu("-");
-
- context = this._analyzer.analyze(context);
- if (context === undefined || context.length == 0)
- return this.menu("-");
-
- return this.menu(context);
- },
- // Return and probably init a menu based on an action
- menu : function (action) {
- if (this._menu[action] === undefined) {
- if (KorAP.hintArray[action] === undefined)
- return;
- this._menu[action] = KorAP.menu.create(action, KorAP.hintArray[action]);
- };
- return this._menu[action];
- },
- get inputField () {
- return this._inputField;
- },
- get active () {
- return this._active;
- },
- popUp : function () {
- if (this.active)
- return;
-
- if (this._firstTry) {
- this._inputField.reposition();
- this._firstTry = false;
- };
-
- // update
-
- var menu;
- if (menu = this.getMenuByContext()) {
- menu.show();
-// Update bounding box
- }
- else {
-// this.hide();
- };
-
- // Focus on input field
- this.inputField.element.focus();
- }
- };
/**
- Regex object for checking the context of the hint
+ * Regex object for checking the context of the hint
*/
KorAP.ContextAnalyzer = {
create : function (regex) {
@@ -357,498 +156,295 @@
/**
- * List of items for drop down menu (complete).
- * Only a sublist of the menu is filtered (live).
- * Only a sublist of the filtered menu is visible (shown).
+ * Hint menu item based on MenuItem
*/
- KorAP.Menu = {
- _position : 0, // position in the active list
- _active : -1, // active item in the item list
-
- /**
- * Create new Menu based on the action prefix
- * and a list of menu items.
- *
- * @this {Menu}
- * @constructor
- * @param {string} Context prefix
- * @param {Array.<Array.<string>>} List of menu items
- */
- create : function (context, items) {
- return Object.create(KorAP.Menu)._init(context, items);
+ KorAP.HintMenuItem = {
+ create : function (params) {
+ return Object.create(KorAP.MenuItem)
+ .upgradeTo(KorAP.HintMenuItem)
+ ._init(params);
},
-
- /*
- * Make the previous item in the menu active
- */
- prev : function () {
- if (this._position == -1)
- return;
-
- // Set new live item
- var oldItem = this.liveItem(this._position--);
- oldItem.active(false);
- var newItem = this.liveItem(this._position);
-
- // The previous element is undefined - roll to bottom
- if (newItem === undefined) {
- this._position = this.liveLength - 1;
- newItem = this.liveItem(this._position);
- this._offset = this.liveLength - this.limit;
- this._showItems(this._offset);
- }
-
- // The previous element is outside the view - roll up
- else if (this._position < this._offset) {
- this._removeLast();
- this._offset--;
- this._prepend(this._list[this._position]);
+ content : function (content) {
+ if (arguments.length === 1) {
+ this._content = content;
};
- newItem.active(true);
+ return this._content;
},
-
- /*
- * Make the next item in the menu active
- */
- next : function () {
- // No active element set
- if (this._position == -1)
- return;
-
- // Set new live item
- var oldItem = this.liveItem(this._position++);
- oldItem.active(false);
- var newItem = this.liveItem(this._position);
-
- // The next element is undefined - roll to top
- if (newItem === undefined) {
- this._offset = 0;
- this._position = 0;
- newItem = this.liveItem(0);
- this._showItems(0);
- }
-
- // The next element is outside the view - roll down
- else if (this._position >= (this.limit + this._offset)) {
- this._removeFirst();
- this._offset++;
- this._append(this._list[this._position]);
- };
- newItem.active(true);
- },
-
- /**
- * Delete all visible items from the menu element
- */
- delete : function () {
- var child;
- for (var i = 0; i <= this.limit; i++)
- if (child = this.shownItem(i))
- child.lowlight();
- while (child = this._element.firstChild)
- this._element.removeChild(child);
- },
-
- /**
- * Filter the list and make it visible
- *
- * @param {string} Prefix for filtering the list
- */
- show : function (prefix) {
- this._prefix = prefix;
-
- // Initialize the list
- if (!this._initList())
- return;
-
- // show based on offset
- this._showItems(0);
-
- // Set the first element to active
- this.liveItem(0).active(true);
- this._position = 0;
- this._active = this._list[0];
-
- // Add classes for rolling menus
- this._boundary(true);
- },
-
- /**
- * Get the prefix for filtering,
- * e.g. "ve"" for "verb"
- */
- get prefix () {
- return this._prefix || '';
- },
-
- /**
- * Get the numerical value for limit
- */
- get limit () {
- return KorAP.limit;
- },
-
- /**
- * Get the context of the menue,
- * e.g. "tt/" for the tree tagger menu
- */
- get context () {
- return this._context;
- },
-
- /**
- * Get a specific item from the complete list
- *
- * @param {number} index of the list item
- */
- item : function (index) {
- return this._items[index]
- },
-
- /**
- * Get a specific item from the filtered list
- *
- * @param {number} index of the list item
- */
- liveItem : function (index) {
- if (this._list === undefined)
- if (!this._initList())
- return;
-
- return this._items[this._list[index]];
- },
- /*
- * Get a specific item from the visible list
- *
- * @param {number} index of the list item
- */
- shownItem : function (index) {
- if (index >= this.limit)
- return;
- return this.liveItem(this._offset + index);
- },
- get element () {
- return this._element;
- },
- get length () {
- return this._items.length;
- },
- get liveLength () {
- if (this._list === undefined)
- this._initList();
- return this._list.length;
- },
- chooseHint : function (e) {
-/*
- var element = e.target;
- while (element.nodeName == "STRONG" || element.nodeName == "SPAN")
- element = element.parentNode;
-
- if (element === undefined || element.nodeName != "LI")
- return;
-
- var action = element.getAttribute('data-action');
- hint.insertText(action);
- var menu = hint.menu();
- menu.hide();
-
- // Fill this with the correct value
- var show;
- if ((show = hint.analyzeContext()) != "-") {
- menu.show(show);
- menu.update(
- hint._search.getBoundingClientRect().right
- );
+ _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();
};
- hint._search.focus();
-*/
- },
-
- _reset : function () {
- this._offset = 0;
- this._pos = 0;
- this._prefix = undefined;
- },
- _boundary : function (bool) {
- this.item(this._list[0]).noMore(bool);
- this.item(this._list[this._list.length - 1]).noMore(bool);
- },
- _initList : function () {
-
-console.log("..." + this._items.length);
-
- if (this._list === undefined)
- this._list = [];
- else if (this._list.length != 0) {
- this._boundary(false);
- this._list.length = 0;
- };
-
- this._offset = 0;
-
- if (this.prefix().length <= 0) {
- for (var i = 0; i < this._items.length; i++)
- this._list.push(i);
- return true;
- };
-
- var pos;
- var paddedPrefix = " " + this.prefix;
- for (pos = 0; pos < this._items.length; pos++) {
- if ((this.item(pos).lcfield.indexOf(paddedPrefix)) >= 0)
- this._list.push(pos);
- };
- if (this._list.length == 0) {
- for (pos = 0; pos < this._items.length; pos++) {
- if ((this.item(pos).lcfield.indexOf(this.prefix)) >= 0)
- this._list.push(pos);
- };
- };
-
- // Filter was successful
- return this._list.length > 0 ? true : false;
- },
-
- _removeFirst : function () {
- this.item(this._list[this._offset]).lowlight();
- this._element.removeChild(this._element.firstChild);
- },
-
- _removeLast : function () {
- this.item(this._list[this._offset + this.limit - 1]).lowlight();
- this._element.removeChild(this._element.lastChild);
- },
-
- // Append item to the shown list based on index
- _append : function (i) {
- var item = this.item(i);
-
- // Highlight based on prefix
- if (this.prefix.length > 0)
- item.highlight(this.prefix);
-
- // Append element
- this.element.appendChild(item.element);
- },
-
- // Prepend item to the shown list based on index
- _prepend : function (i) {
- var item = this.item(i);
-
- // Highlight based on prefix
- if (this.prefix.length > 0)
- item.highlight(this.prefix);
-
- // Append element
- this.element.insertBefore(
- item.element,
- this.element.firstChild
- );
- },
- _init : function (context, items) {
- this._context = context;
- this._element = document.createElement("ul");
- this._element.style.opacity = 0;
- this.active = false;
-/*
- Todo:
- this._element.addEventListener("click", chooseHint, false);
-*/
- this._items = new Array();
- var i;
- for (i in items)
- this._items.push(KorAP.MenuItem.create(items[i]));
-
- this._reset();
return this;
},
+ onclick : function () {
+ var m = this.menu();
+ var h = m.hint();
+ m.hide();
- _showItems : function (offset) {
- this.delete();
+ h.inputField().insert(this._action);
+ h.active = false;
- // Use list
- var shown = 0;
- var i;
- for (i in this._list) {
-
- // Don't show - it's before offset
- if (shown++ < offset)
- continue;
-
- this._append(this._list[i]);
-
- if (shown >= (this.limit + this._offset))
- break;
- };
- }
- };
-
-
- /**
- * 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);
+ h.show(true);
},
-
- /**
- * Get the name of the item
- */
- get name () {
+ name : function () {
return this._name;
},
-
- /**
- * Get the action string
- */
- get action () {
+ action : function () {
return this._action;
},
-
- /**
- * Get the description of the item
- */
- get desc () {
+ desc : function () {
return this._desc;
},
-
- /**
- * Get the lower case field
- */
- get lcfield () {
- return this._lcfield;
- },
-
- /**
- * 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
- */
- get element () {
+ element : function () {
// already defined
if (this._element !== undefined)
return this._element;
// Create list item
var li = document.createElement("li");
- li.setAttribute("data-action", this._action);
+
+ if (this.onclick !== undefined) {
+ li["onclick"] = this.onclick.bind(this);
+ };
// Create title
- var name = document.createElement("strong");
+ 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;
+ }
+ };
+
+ KorAP.HintMenuPrefix = {
+ create : function (params) {
+ return Object.create(KorAP.MenuPrefix).upgradeTo(KorAP.HintMenuPrefix)._init(params);
},
+ onclick : function () {
+ var m = this.menu();
+ var h = m.hint();
+ m.hide();
- /**
- * Highlight parts of the item
- *
- * @param {string} Prefix string for highlights
- */
- highlight : function (prefix) {
- var e = this.element;
- this._highlight(e.firstChild, prefix);
- if (this._desc !== undefined)
- this._highlight(e.lastChild, prefix);
- },
+ h.inputField().insert(this.value());
+ h.active = false;
+ }
+ };
- /**
- * Remove highlight of the menu item
- */
- lowlight : function () {
- var e = this.element;
- e.firstChild.innerHTML = this._name;
- if (this._desc !== undefined)
- e.lastChild.innerHTML = this._desc;
- },
+ KorAP.HintMenu = {
+ create : function (hint, context, params) {
+ var obj = Object.create(KorAP.Menu)
+ .upgradeTo(KorAP.HintMenu)
+ ._init(KorAP.HintMenuItem, KorAP.HintMenuPrefix, params);
+ obj._context = context;
+ obj._element.classList.add('hint');
+ obj._hint = hint;
- // Initialize menu item
- _init : function (params) {
- if (params[0] === undefined || params[1] === undefined)
- throw new Error("Missing parameters");
+ // This is only domspecific
+ obj.element().addEventListener('blur', function (e) {
+ this.menu.hide();
+ });
- 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();
+ // Focus on input field on hide
+ obj.onHide = function () {
+ var input = this._hint.inputField();
+ input.element().focus();
};
+
+ return obj;
+ },
+ // Todo: Is this necessary?
+ context : function () {
+ return this._context;
+ },
+ hint : function () {
+ return this._hint;
+ }
+ };
+
+
+ /**
+ * KorAP.Hint.create({
+ * inputField : node,
+ * context : context regex
+ * });
+ */
+ KorAP.Hint = {
+
+ // Some variables
+ // _firstTry : true,
+ active : false,
+
+ create : function (param) {
+ return Object.create(KorAP.Hint)._init(param);
+ },
+
+ _init : function (param) {
+ param = param || {};
+
+ // Holds all menus per prefix context
+ this._menu = {};
+
+ // Get input field
+ this._inputField = KorAP.InputField.create(
+ param["inputField"] || document.getElementById("q-field")
+ );
+
+ var inputFieldElement = this._inputField.element();
+
+ var that = this;
+
+ // Add event listener for key pressed down
+ inputFieldElement.addEventListener(
+ "keypress", function (e) {
+ var code = _codeFromEvent(e);
+ if (code === 40) {
+ that.show(false);
+ e.halt();
+ };
+ }, false
+ );
+
+ // Move infobox
+ inputFieldElement.addEventListener(
+ "keyup", function (e) {
+ var input = that._inputField;
+ input.update();
+ input.container().style.left = input.rightPos() + 'px';
+ }
+ );
+
+ // Set Analyzer for context
+ this._analyzer = KorAP.ContextAnalyzer.create(
+ param["context"] || KorAP.context
+ );
return this;
},
- // Highlight a certain element of the menu item
- _highlight : function (elem, prefix) {
- var text = elem.firstChild.nodeValue;
- var textlc = text.toLowerCase();
- var pos = textlc.indexOf(prefix);
- if (pos >= 0) {
+ inputField : function () {
+ return this._inputField;
+ },
- // First element
- elem.firstChild.nodeValue = pos > 0 ? text.substr(0, pos) : "";
+ /**
+ * A new update by keypress
+ */
+ /*
+updateKeyPress : function (e) {
+ if (!this._active)
+ return;
- // Second element
- var hl = document.createElement("em");
- hl.appendChild(
- document.createTextNode(text.substr(pos, prefix.length))
+ var character = String.fromCharCode(_codeFromEvent(e));
+
+ e.halt(); // No event propagation
+
+ // Only relevant for key down
+ console.log("TODO: filter view");
+ },
+ */
+
+ // updateKeyDown : function (e) {},
+
+ /**
+ * Return hint menu and probably init based on an action
+ */
+ menu : function (action) {
+
+ if (this._menu[action] === undefined) {
+
+ // No matching hint menu
+ if (KorAP.hintArray[action] === undefined)
+ return;
+
+ // Create matching hint menu
+ this._menu[action] = KorAP.HintMenu.create(
+ this, action, KorAP.hintArray[action]
);
- elem.appendChild(hl);
- // Third element
- elem.appendChild(
- document.createTextNode(text.substr(pos + prefix.length))
- );
+ };
+
+ // Return matching hint menu
+ return this._menu[action];
+ },
+
+ /**
+ * Get the correct menu based on the context
+ */
+ contextMenu : function (ifContext) {
+ var context = this._inputField.context();
+ if (context === undefined || context.length == 0)
+ return ifContext ? undefined : this.menu("-");
+
+ context = this._analyzer.test(context);
+ if (context === undefined || context.length == 0)
+ return ifContext ? undefined : this.menu("-");
+
+ return this.menu(context);
+ },
+
+
+ /**
+ * Show the menu
+ */
+ show : function (ifContext) {
+
+ // Menu is already active
+ if (this.active)
+ return;
+
+ // Initialize the menus position
+ /*
+ if (this._firstTry) {
+ this._inputField.reposition();
+ this._firstTry = false;
+ };
+ */
+
+ // update
+
+ // Get the menu
+ var menu;
+ if (menu = this.contextMenu(ifContext)) {
+ this._inputField.container().appendChild(menu.element());
+ menu.show('');
+ menu.focus();
+// Update bounding box
+/*
+ }
+ else if (!ifContext) {
+ // this.hide();
+ };
+*/
+ // Focus on input field
+ // this.inputField.element.focus();
};
}
};
+
+
+ /**
+ * Return keycode based on event
+ */
+ function _codeFromEvent (e) {
+ if ((e.charCode) && (e.keyCode==0))
+ return e.charCode
+ return e.keyCode;
+ };
+
}(this.KorAP));