New Suggestion module
diff --git a/public/js/hint.js b/public/js/hint.js
index 80c7d25..eed45f3 100644
--- a/public/js/hint.js
+++ b/public/js/hint.js
@@ -1,248 +1,813 @@
-/*
-TODO:
- - Limit the size to a certain number of elements
- - addEventListener("click", ... , false);
- - addEventListener("paste", ... , false);
- - Make this a general purpose hint-System with left-context-suport
- - Die Funktion, wann was angezeigt werden soll, sollte extern
- definiert sein (der Kontext / changed)
- - Die Werteliste sollte weitere Attribute enthalten, wie title und class
-*/
+"use strict";
-var Hint = function (param) {
- var foundryRegex = new RegExp("(?:^|[^a-zA-Z0-9])([-a-zA-Z0-9]+?)\/(?:([^=]+?)=)?$");
+// Don't let events bubble up
+Event.prototype.halt = function () {
+ this.stopPropagation();
+ this.preventDefault();
+};
- var search = document.getElementById(param["ref"]);
- var qlField = document.getElementById(param["qlRef"]);
- var mirror = document.createElement("div");
- var hint = document.createElement("ul");
- var hintSize = param["hintSize"] ? param["hintSize"] : 10;
- var hints = param["hints"];
- var that = this;
- var ql;
+// http://www.nlpado.de/~sebastian/software/ner_german.shtml
+// http://www.cnts.ua.ac.be/conll2003/ner/
+var namedEntities = [
+ ["I-LOC", "I-LOC", "Location"],
+ ["I-MISC", "I-MISC", "Miscellaneous"],
+ ["I-ORG", "I-ORG", "Organization"],
+ ["I-PER", "I-PER", "Person"]
+];
- // Build the mirror element
- // <div id="searchMirror"><span></span><ul></ul></div>
- mirror.setAttribute("id", "searchMirror");
- mirror.appendChild(document.createElement("span"));
- mirror.appendChild(hint);
- document.getElementsByTagName("body")[0].appendChild(mirror);
- // Default active state
- this.active = -2;
+// http://www.ids-mannheim.de/cosmas2/projekt/referenz/stts/morph.html
+// http://nachhalt.sfb632.uni-potsdam.de/owl-docu/stts.html
+var sttsArray = [
+ // "$.", "$(", "$,"
+ ["ADJA","ADJA", "Attributive Adjective"],
+ ["ADJD","ADJD", "Predicative Adjective"],
+ ["ADV","ADV", "Adverb"],
+ ["APPO","APPO", "Postposition"],
+ ["APPR","APPR", "Preposition"],
+ ["APPRART","APPRART", "Preposition with Determiner"],
+ ["APZR","APZR","Right Circumposition"],
+ ["ART","ART", "Determiner"],
+ ["CARD","CARD", "Cardinal Number"],
+ ["FM","FM", "Foreign Material"],
+ ["ITJ","ITJ", "Interjection"],
+ ["KOKOM","KOKOM", "Comparison Particle"],
+ ["KON","KON", "Coordinating Conjuncion"],
+ ["KOUI","KOUI", "Subordinating Conjunction with 'zu'"],
+ ["KOUS","KOUS", "Subordinating Conjunction with Sentence"],
+ ["NE","NE", "Named Entity"],
+ ["NN","NN", "Normal Nomina"],
+ ["PAV", "PAV", "Pronominal Adverb"],
+ ["PDAT","PDAT","Attributive Demonstrative Pronoun"],
+ ["PDS","PDS", "Substitutive Demonstrative Pronoun"],
+ ["PIAT","PIAT", "Attributive Indefinite Pronoun without Determiner"],
+ ["PIDAT","PIDAT", "Attributive Indefinite Pronoun with Determiner"],
+ ["PIS","PIS", "Substitutive Indefinite Pronoun"],
+ ["PPER","PPER", "Personal Pronoun"],
+ ["PPOSAT","PPOSAT", "Attributive Possessive Pronoun"],
+ ["PPOSS","PPOSS", "Substitutive Possessive Pronoun"],
+ ["PRELAT","PRELAT", "Attributive Relative Pronoun"],
+ ["PRELS","PRELS", "Substitutive Relative Pronoun"],
+ ["PRF","PRF", "Reflexive Pronoun"],
+ ["PROAV","PROAV", "Pronominal Adverb"],
+ ["PTKA","PTKA","Particle with Adjective"],
+ ["PTKANT","PTKANT", "Answering Particle"],
+ ["PTKNEG","PTKNEG", "Negation Particle"],
+ ["PTKVZ","PTKVZ", "Separated Verbal Particle"],
+ ["PTKZU","PTKZU", "'zu' Particle"],
+ ["PWAT","PWAT", "Attributive Interrogative Pronoun"],
+ ["PWAV","PWAV", "Adverbial Interrogative Pronoun"],
+ ["PWS","PWS", "Substitutive Interrogative Pronoun"],
+ ["TRUNC","TRUNC","Truncated"],
+ ["VAFIN","VAFIN", "Auxiliary Finite Verb"],
+ ["VAINF","VAINF", "Auxiliary Infinite Verb"],
+ ["VAIMP","VAIMP", "Auxiliary Finite Imperative Verb"],
+ ["VAPP","VAPP", "Auxiliary Perfect Participle"],
+ ["VMFIN","VMFIN", "Modal Finite Verb"],
+ ["VMINF","VMINF", "Modal Infinite Verb"],
+ ["VMPP","VMPP", "Modal Perfect Participle"],
+ ["VVFIN","VVFIN","Finite Verb"],
+ ["VVIMP","VVIMP", "Finite Imperative Verb"],
+ ["VVINF","VVINF", "Infinite Verb"],
+ ["VVIZU","VVIZU", "Infinite Verb with 'zu'"],
+ ["VVPP","VVPP", "Perfect Participle"],
+ ["XY", "XY", "Non-Word"]
+];
- // Show hint table
- this.show = function (topic) {
- if (!hints[topic])
- return;
- this.hide();
- 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;
- };
+var mateSttsArray = sttsArray.slice(0);
+mateSttsArray.push(
+ ["<root-POS>","<root-POS>","Root Part of Speech"]
+);
- // 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");
+var hintArray = {
+ "-" : [
+ ["Connexor", "cnx/"],
+ ["CoreNLP", "corenlp/"],
+ ["Mate", "mate/"],
+ ["OpenNLP", "opennlp/"],
+ ["TreeTagger", "tt/"],
+ ["Xerox Parser", "xip/"]
+ ],
+ "corenlp/" : [
+ ["Named Entity", "ne=" , "Combined"],
+ ["Named Entity", "ne_dewac_175m_600=" , "ne_dewac_175m_600"],
+ ["Named Entity", "ne_hgc_175m_600=", "ne_hgc_175m_600"]
+ ],
+ "corenlp/ne=" : namedEntities,
+ "corenlp/ne_dewac_175m_600=" : namedEntities,
+ "corenlp/ne_hgc_175m_600=" : namedEntities,
+ "cnx/" : [
+ ["Constituency", "c="],
+ ["Lemma", "l="],
+ ["Morphology", "m="],
+ ["Part-of-Speech", "p="],
+ ["Syntax", "syn="]
+ ],
+ "cnx/c=" : [
+ ["np", "np", "Nominal Phrase"]
+ ],
+ // http://www.ids-mannheim.de/cosmas2/projekt/referenz/connexor/morph.html
+ "cnx/m=" : [
+ ["Abbr","Abbr", "Nouns: Abbreviation"],
+ ["CMP","CMP", "Adjective: Comparative"],
+ ["IMP", "IMP", "Mood: Imperative"],
+ ["IND", "IND", "Mood: Indicative"],
+ ["INF", "INF", "Infinitive"],
+ ["ORD","ORD", "Numeral: Ordinal"],
+ ["PAST", "PAST", "Tense: past"],
+ ["PCP", "PCP", "Participle"],
+ ["PERF", "PERF", "Perfective Participle"],
+ ["PL","PL", "Nouns: Plural"],
+ ["PRES", "PRES", "Tense: present"],
+ ["PROG", "PROG", "Progressive Participle"],
+ ["Prop","Prop", "Nouns: Proper Noun"],
+ ["SUB", "SUB", "Mood: Subjunctive"],
+ ["SUP","SUP", "Adjective: Superlative"]
+ ],
+ // http://www.ids-mannheim.de/cosmas2/projekt/referenz/connexor/morph.html
+ "cnx/p=" : [
+ ["A", "A", "Adjective"],
+ ["ADV", "ADV", "Adverb"],
+ ["CC", "CC", "Coordination Marker"],
+ ["CS", "CS", "Clause Marker"],
+ ["DET", "DET", "Determiner"],
+ ["INTERJ", "INTERJ", "Interjection"],
+ ["N", "N", "Noun"],
+ ["NUM", "NUM", "Numeral"],
+ ["PREP", "PREP", "Preposition"],
+ ["PRON", "PRON", "Pro-Nominal"],
+ ["V", "V", "Verb"]
+ ],
+ // http://www.ids-mannheim.de/cosmas2/projekt/referenz/connexor/syntax.html
+ "cnx/syn=" : [
+ ["@ADVL", "@ADVL", "Adverbial Head"],
+ ["@AUX", "@AUX", "Auxiliary Verb"],
+ ["@CC", "@CC", "Coordination"]
+ ["@MAIN", "@MAIN", "Main Verb"],
+ ["@NH", "@NH", "Nominal Head"],
+ ["@POSTMOD", "@POSTMOD", "Postmodifier"],
+ ["@PREMARK", "@PREMARK", "Preposed Marker"],
+ ["@PREMOD", "@POSTMOD", "Premodifier"]
+ ],
+ "opennlp/" : [
+ ["Part-of-Speech", "p="]
+ ],
+ "opennlp/p=" : sttsArray,
+ "xip/" : [
+ ["Constituency", "c="],
+ // Inactive: ["Dependency", "d="],
+ ["Lemma", "l="],
+ ["Part-of-Speech", "p="],
+ ],
+ // "xip/c=" : [],
+ // Inactive: "xip/d=" : [],
+ // "xip/p=" : [],
+ "tt/" : [
+ ["Lemma", "l="],
+ ["Part-of-Speech", "p="]
+ ],
+ "tt/p=" : sttsArray,
+ "mate/" : [
+ // Inactive: "d" : ["d=", "Dependency"],
+ ["Lemma", "l="],
+ ["Morphology", "m="],
+ ["Part-of-Speech", "p="]
+ ],
+ // Inactive: mate/d=
+ "mate/p=" : mateSttsArray,
+ "mate/m=" : [
+ ["Case", "case:"],
+ ["Degree", "degree:"],
+ ["Gender", "gender:"],
+ ["Mood", "mood:"],
+ ["Number", "number:"],
+ ["Person", "person:"],
+ ["Tense","tense:"],
+ ["No type", "<no-type>"]
+ ],
+ "mate/m=case:" : [
+ ["acc", "acc", "Accusative"],
+ ["dat","dat", "Dative"],
+ ["gen", "gen","Genitive"],
+ ["nom","nom", "Nominative"],
+ ["*","*", "Undefined"]
+ ],
+ "mate/m=degree:" : [
+ ["comp","comp", "Comparative"],
+ ["pos","pos", "Positive"],
+ ["sup","sup", "Superative"]
+ ],
+ "mate/m=gender:" : [
+ ["fem", "fem", "Feminium"],
+ ["masc", "masc", "Masculinum"],
+ ["neut","neut", "Neuter"],
+ ["*","*","Undefined"]
+ ],
+ "mate/m=mood:" : [
+ ["imp","imp", "Imperative"],
+ ["ind","ind", "Indicative"],
+ ["subj","subj", "Subjunctive"]
+ ],
+ "mate/m=number:" : [
+ ["pl","pl","Plural"],
+ ["sg","sg","Singular"],
+ ["*","*","Undefined"]
+ ],
+ "mate/m=person:" : [
+ ["1","1", "First Person"],
+ ["2","2", "Second Person"],
+ ["3","3", "Third Person"]
+ ],
+ "mate/m=tense:" : [
+ ["past","past", "Past"],
+ ["pres","pres", "Present"]
+ ]
+};
+
+
+/**
+ * Analyze strings for prefixes
+ */
+var PrefixAnalyzer = {
+ _regex : new RegExp(
+ "(?:^|[^a-zA-Z0-9])" + // Anchor
+ "((?:[-a-zA-Z0-9]+?)\/" + // Foundry
+ "(?:" +
+ "(?:[^=:]+?)=" + // Layer
+ "(?:(?:[^:]+?):)?" + // Key
+ ")?" +
+ ")$"),
+ analyze : function (text) {
+ if (!this._regex.exec(text))
+ return undefined;
+ return RegExp.$1
+ }
+};
+
+/**
+ * Event handling after a key pressed
+ */
+function updateKey (that, e) {
+ var menu = that.menu();
+ switch (e.key) {
+ case 'Esc':
+ // Hide menu
+ menu.hide();
+ break;
+ case 'Down':
+ e.halt(); // No event propagation
+
+ // Menu is not active
+ if (!menu.active) {
+ that.popUp()
+ }
+ // Menu is active
+ else {
+ that.removePrefix();
+ menu.next();
};
- qlSelect();
- };
+ break;
+ case "Up":
+ if (!menu.active)
+ break;
+ e.halt(); // No event propagation
+ that.removePrefix();
+ menu.prev();
+ break;
+ case "Enter":
+ if (!menu.active)
+ break;
+ e.halt(); // No event propagation
+ that.insertText(menu.getActiveItem().getAction());
+ that.removePrefix();
- // Hide hint table
- this.hide = function () {
- if (this.active === -2)
- return;
- this.active = -2;
- hint.style.opacity = 0;
- hint.style.marginLeft = 0;
+ // Remove menu
+ menu.hide();
- // Remove all children
- var lis = hint.childNodes;
- for (var li = lis.length - 1; li >= 0; li--) {
- hint.removeChild(lis[li])
+ // 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
+ );
};
- };
- // 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);
+ break;
+ default:
+ if (!menu.active)
+ break;
- // Include descriptions
- if (!arrayType && hintList[i][1]) {
- var desc = document.createElement("span");
- desc.appendChild(document.createTextNode(hintList[i][1]));
- li.appendChild(desc);
+ // key stroke is not a character
+ if (e.key.length != 1) {
+
+ // Key stroke is not a text modifying key
+ if (e.key !== 'Shift' &&
+ e.key !== 'Up' &&
+ e.key !== 'Down' &&
+ e.key !== 'Enter' &&
+ e.key !== 'Alt' &&
+ e.key !== 'AltGraph' &&
+ e.key !== 'CapsLock') {
+ that.insertPrefix();
+ menu.hide();
};
- };
- };
-
- // Choose next item in list
- this.next = function () {
- if (this.active === -2)
- return false;
-
- 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");
+ break;
};
- return true;
- };
+ e.halt(); // No event propagation
+
+ // Try to identify prefix
+ if (menu.skipToPrefix(e.key))
+ break;
- // Choose previous item in list
- this.previous = function () {
- if (this.active === -2)
+ // Prefix not found
+ that.insertPrefix();
+ menu.hide();
+ };
+};
+
+// new hint object
+var Hint = {
+ _search : undefined, // Return the search element
+ _mirror : undefined, // Return the mirror element
+ _menu : undefined,
+ _analyzer : undefined,
+ firstTry : true,
+ menu : function () {
+ // In case this wasn't defined yet
+ if (this._menu === undefined) {
+ this._menu = Object.create(Menu).init();
+ this._mirror.appendChild(this._menu.getElement());
+ };
+ return this._menu;
+ },
+
+ // Initialize the object
+ init : function () {
+ this._search = document.getElementById("q-field");
+ this._mirror = document.createElement("div");
+ this._mirror.setAttribute("id", "searchMirror");
+ this._mirror.appendChild(document.createElement("span"));
+ document.getElementsByTagName("body")[0].appendChild(this._mirror);
+
+ this._analyzer = Object.create(PrefixAnalyzer);
+
+ // Update positional information, in case the windows size changes
+ var that = this;
+ window.onresize = function () { that.reposition() };
+
+ // Add event listener for key pressed down
+ this._search.addEventListener(
+ "keydown",
+ function (e) {
+ updateKey(that, e)
+ },
+ false
+ );
+
+ // Reposition the mirror
+ this.reposition();
+
+ // Return object for chaining
+ return this;
+ },
+
+ // Popup method
+ popUp : function () {
+ if (this.active)
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");
- };
- };
+ // Reposition hint list on first try
+ if (this.firstTry)
+ this.reposition().firstTry = false;
- // Choose item from list
- this.choose = function () {
- if (this.active < 0)
- return;
+ // Update view
+ this.update();
- var action = hint.getElementsByTagName("li")[this.active].getAttribute("data-action");
+ // Fill this with the correct value
+ if (this.menu().show(this.analyzeContext()))
+ this.update(
+ this._search.getBoundingClientRect().right
+ );
+ else
+ this.hide();
- var value = search.value;
- var start = search.selectionStart;
+ this._search.focus();
+ },
+
+ // Reposition the mirror object
+ reposition : function () {
+
+ // Update style properties
+ var searchRect = this._search.getBoundingClientRect();
+ var searchStyle = window.getComputedStyle(this._search, null);
+ var mStyle = this._mirror.style;
+ mStyle.left = searchRect.left + "px";
+ mStyle.top = searchRect.bottom + "px";
+ mStyle.borderLeftColor = "transparent";
+ mStyle.height = "1px";
+ mStyle.paddingLeft = searchStyle.getPropertyValue("padding-left");
+ mStyle.marginLeft = searchStyle.getPropertyValue("margin-left");
+ mStyle.borderLeftWidth = searchStyle.getPropertyValue("border-left-width");
+ mStyle.borderLeftStyle = searchStyle.getPropertyValue("border-left-style");
+ mStyle.fontSize = searchStyle.getPropertyValue("font-size");
+ mStyle.fontFamily = searchStyle.getPropertyValue("font-family");
+ return this;
+ },
+
+ // Reposition the menu object
+ update : function () {
+ var s = this._search;
+ var start = s.selectionStart;
+ this._mirror.firstChild.textContent = s.value.substring(0, start);
+ },
+
+ analyzeContext : function () {
+ var context = this._splitInputText()[0];
+ if (context === undefined || context.length === 0)
+ return "-";
+ context = this._analyzer.analyze(context);
+ if (context === undefined || context.length === 0)
+ return "-";
+
+ return context;
+ },
+
+ _splitInputText : function () {
+ var s = this._search;
+ var value = s.value;
+ var start = s.selectionStart;
var begin = value.substring(0, start);
- var end = value.substring(start, value.length);
- search.value = begin + action + end;
- search.selectionStart = (begin + action).length
- search.selectionEnd = search.selectionStart;
+ var end = value.substring(start, value.length);
+ return new Array(begin, end);
+ },
- this.hide();
+ // Insert text at the current cursor position
+ insertText : function (text) {
+ var s = this._search;
+ var splitText = this._splitInputText();
+ s.value = splitText[0] + text + splitText[1];
+ s.selectionStart = (splitText[0] + text).length
+ s.selectionEnd = s.selectionStart;
+ this._mirror.firstChild.textContent = splitText[0] + text;
+ },
- // Check for new changes
- mirror.firstChild.textContent = begin + action;
+ // Remove stored prefix
+ removePrefix : function () {
+ this.menu()._prefix = undefined;
+ },
- if (foundryRegex.exec(begin + action))
- this.show(RegExp.$1 + (RegExp.$2 ? '/' + RegExp.$2 : ''));
-
- return true;
- };
-
- function changed (e) {
- var el = e.target;
- if (e.key === '/' || e.key === '=') {
- var start = el.selectionStart;
- mirror.firstChild.textContent = 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 === '>') {
- that.hide();
- }
- else if (ql === 'poliqarp' && (e.key === '[' || e.key === '<')) {
- mirror.firstChild.textContent = el.value.substring(0, el.selectionStart);
- that.show("-foundries");
- }
- else if (e.key !== 'Shift' &&
- e.key !== 'Up' &&
- e.key !== 'Down' &&
- e.key !== 'Enter' &&
- e.key !== 'Alt' &&
- e.key !== 'AltGraph' &&
- e.key !== 'CapsLock') {
- that.hide();
- };
- };
-
- // Select item from suggestion list
- function select (e) {
- if (that.active === -2)
+ // Insert stored prefix at current cursor position
+ insertPrefix : function () {
+ if (this.menu()._prefix === undefined)
return;
- if (e.key === 'Down') {
- e.stopPropagation();
- e.preventDefault();
- return that.next();
- }
- else if (e.key === 'Up') {
- e.stopPropagation();
- e.preventDefault();
- return that.previous();
- }
- else if (e.key === 'Enter' && that.choose()) {
- e.stopPropagation();
- e.preventDefault();
- return false;
- }
- else if (e.key !== 'Shift' &&
- e.key !== 'Alt' &&
- e.key !== 'AltGraph' &&
- e.key !== 'CapsLock') {
- that.hide();
- };
- return true;
- };
+ this.insertText(this.menu()._prefix);
+ }
+};
- function qlSelect () {
- var nodes = qlField.childNodes;
- for (var i = 0; i < nodes.length; i++) {
- if (nodes[i].selected) {
- ql = nodes[i].value;
- break;
+
+/**
+* Menu list
+*/
+var Menu = {
+ active : false,
+ _element : undefined,
+ _position : 0, // Position of menu item
+ _offset : 0, // Visual offset for chosen highlight
+ _size : 8, // Number of items to be shown
+ _items : [], // Items for menu
+ _name : undefined,
+ _prefix : undefined,
+ getElement : function () {
+ return this._element;
+ },
+ init : function () {
+ this._element = document.createElement("ul");
+
+ // Add onclick event
+ this._element.addEventListener("click", chooseHint, false);
+
+ this._element.style.opacity = 0;
+ this.active = false;
+ this._setDefaults();
+ return this;
+ },
+ update : function (searchRightPosition) {
+ var infoRightPosition = this._element.getBoundingClientRect().right;
+ if (infoRightPosition > searchRightPosition) {
+ this._element.style.marginLeft = '-' + (infoRightPosition - searchRightPosition) + 'px';
+ };
+ return this;
+ },
+ next : function () {
+ if (!this.active)
+ return;
+ this._clearView();
+ this._position++;
+
+ // In case the list is bigger than the view
+ if (this._items.length > this._size) {
+ if (this._position >= this._items.length) {
+ // Roll to top
+ this._offset = 0;
+ this._position = 0;
+ this._showItems(0);
+ }
+ else if (this._position >= (this._size + this._offset)) {
+ // Roll down
+ this._element.removeChild(this._element.firstChild);
+ this._offset++;
+ this._element.appendChild(this.getItem(this._position).getElement());
+ };
+ }
+ else if (this._position >= this._items.length) {
+ this._position = 0;
+ };
+ this._updateView();
+ },
+ prev : function () {
+ if (!this.active)
+ return;
+ this._clearView();
+ this._position--;
+
+ // In case the list is bigger than the view
+ if (this._items.length > this._size) {
+ if (this._position < 0) {
+ // roll to bottom
+ this._setToLast();
+ this._offset = (this._position - this._size) + 1;
+ this._showLastItems();
+ }
+ else if (this._position < this._offset) {
+ // roll up
+ this._element.removeChild(this._element.lastChild);
+ this._offset--;
+ this._element.insertBefore(
+ this.getItem(this._position).getElement(),
+ this._element.firstChild
+ );
+ };
+ }
+ else if (this._position < 0) {
+ this._setToLast();
+ };
+ this._updateView();
+ },
+ skipToPrefix : function (prefix) {
+ if (this._prefix === undefined)
+ this._prefix = prefix.toLocaleLowerCase();
+ else
+ this._prefix += prefix.toLocaleLowerCase();
+
+ var pos = 0;
+ var found = false;
+ var good = -1;
+ var test;
+ for (; pos < this._items.length; pos++) {
+ if ((test = this.getItem(pos).getLCName().indexOf(this._prefix)) !== -1) {
+ if (test === 0) {
+ found = true;
+ break;
+ };
+ good = pos;
};
};
+
+ // Perfect prefix
+ if (found)
+ return this.skipToPos(pos);
+ // At least infix
+ else if (good !== -1)
+ return this.skipToPos(good);
+ // No
+ return false;
+ },
+ skipToPos : function (index) {
+ if (!this.active)
+ return false;
+ if (index < 0 || index >= this._items.length)
+ return false;
+
+ this._clearView();
+ this._position = index;
+
+ if (index < this._offset || index >= (this._offset + this._size)) {
+
+ // Index is in the final frame
+ if (index >= (this._items.length - this._size)) {
+ this._offset = this._items.length - this._size;
+ this._showLastItems();
+ }
+
+ // Index is in the final frame
+ else {
+ this._offset = index;
+ this._showItems(index);
+ };
+ };
+
+ // Activate new position
+ this._updateView();
+ return true;
+ },
+ show : function (name) {
+ // The element is already given
+ if (this._name !== name) {
+
+ // Todo: store hints in hash
+
+ // Delete items
+ this._items.length = 0;
+
+ var items = hintArray[name];
+
+ // Hints not found
+ if (items === undefined)
+ return false;
+
+ var i;
+ for (i in items) {
+ var item = Object.create(MenuItem).init(items[i]);
+ this._items.push(item);
+ };
+
+ // Add classes for rolling menus
+ this.getItem(0).getElement().classList.add("no-more");
+ this.getItem(i).getElement().classList.add("no-more");
+
+ this._name = name;
+ };
+ this._showItems(0);
+ this._element.style.opacity = 1;
+ this._setDefaults();
+ this.active = true;
+ this._updateView();
+ return true;
+ },
+ hide : function () {
+ this._element.style.opacity = 0;
+ if (this.active)
+ this.getActiveItem().deactivate();
+ this._setDefaults();
+ this.active = false;
+ },
+ getActiveItem : function () {
+ return this._items[this._position];
+ },
+ getItem : function (index) {
+ return this._items[index];
+ },
+ getPrefix : function () {
+ return this._prefix;
+ },
+ _setDefaults : function () {
+ this._offset = 0;
+ this._position = 0;
+ this._prefix = undefined;
+ },
+ // Remove all visible list items
+ _deleteMenu : function () {
+ var child;
+ while (child = this._element.firstChild)
+ this._element.removeChild(child);
+ },
+ _clearView : function () {
+ var active = this.getActiveItem();
+ if (active !== undefined)
+ active.deactivate();
+ },
+ _updateView : function () {
+ var active = this.getActiveItem();
+ if (active !== undefined)
+ active.activate();
+ },
+
+ // Make all list items visible starting at a certain offset
+ _showItems : function (offset) {
+ this._deleteMenu();
+ for (var i = offset; i < this._size + offset; i++) {
+ if (i >= this._items.length)
+ break;
+ this._element.appendChild(
+ this._items[i].getElement()
+ )
+ };
+ },
+
+ // Make all final list items visible
+ _showLastItems : function () {
+ this._deleteMenu();
+ for (var i = (this._items.length - 1); i >= (this._items.length - this._size); i--) {
+ if (i < 0)
+ break;
+ if (!this._element.firstChild)
+ this._element.appendChild(this._items[i].getElement());
+ else
+ this._element.insertBefore(
+ this._items[i].getElement(),
+ this._element.firstChild
+ );
+ };
+ },
+ _setToLast : function () {
+ this._position = this._items.length - 1;
+ }
+};
+
+function chooseHint (e) {
+ var element = e.target;
+ while (element.nodeName == "STRONG" || element.nodeName == "SPAN") {
+ element = element.parentNode;
};
- // Initialize style
- init();
- window.onresize = init;
- search.addEventListener("keyup", changed, false);
- search.addEventListener("keydown", select, false);
- qlField.addEventListener("change", qlSelect, false);
+ 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
+ );
+ };
+
+ hint._search.focus();
+};
+
+var MenuItem = {
+ _name : undefined,
+ _lcname : undefined,
+ _desc : undefined,
+ _element : undefined,
+ _action : "",
+ activate : function () {
+ this._element.classList.add("active");
+ },
+ deactivate : function () {
+ this._element.classList.remove("active");
+ },
+ // Initialize this item
+ init : function (param) {
+ this._name = param[0];
+ this._action = param[1];
+ this._lcname = this._name.toLocaleLowerCase();
+
+ if (param.length > 2) {
+ this._desc = param[2];
+ this._lcname += " " + this._desc.toLocaleLowerCase();
+ };
+
+ return this;
+ },
+
+ // Created element of this item
+ getElement : function () {
+ if (this._element !== undefined)
+ return this._element;
+
+ var li = document.createElement("li");
+
+ li.setAttribute("data-action", this._action);
+
+ var name = document.createElement("strong");
+
+ name.appendChild(document.createTextNode(this._name));
+ li.appendChild(name);
+ if (this._desc !== undefined) {
+ var desc = document.createElement("span");
+ desc.appendChild(document.createTextNode(this._desc));
+ li.appendChild(desc);
+ };
+ this._element = li;
+ return this._element;
+ },
+
+ // Name of this item
+ getName : function () {
+ return this._name;
+ },
+
+ getLCName : function () {
+ return this._lcname;
+ },
+
+ // Description of this item
+ getDesc : function () {
+ return this._desc;
+ },
+
+
+ getAction : function () {
+ return this._action;
+ }
};