ES6: port menu
Change-Id: I8740d81b928e26215276f3ce92c87d83dbc3a7b2
diff --git a/.gitignore b/.gitignore
index 5c2a411..4ede8f7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,7 +7,6 @@
/sandbox
/Sandbox
node_modules
-/local/
/cache/data
/public
/kalamar.*.conf
diff --git a/Changes b/Changes
index 6bbc0b6..1d00ec9 100644
--- a/Changes
+++ b/Changes
@@ -1,3 +1,4 @@
+ - Migration of menu to ES6 (diewald)
- Migration of util and datepicker to ES6 (hebasta)
0.66 2026-07-15
diff --git a/dev/js/src-lib/menu/demo/index.html b/dev/js/src-lib/menu/demo/index.html
new file mode 100644
index 0000000..5407b55
--- /dev/null
+++ b/dev/js/src-lib/menu/demo/index.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Menu demo</title>
+ <meta charset="utf-8" />
+ </head>
+ <body>
+ <div id="menu" style="padding-top: 80px; padding-left: 80px;"></div>
+
+ <script type="module">
+ import '../../../../scss/kalamar.scss';
+ import KalamarMenu from '../menu.js';
+
+ const menu = new KalamarMenu([
+ ["Constituency", "c=", "Example 1"],
+ ["Lemma", "l="],
+ ["Morphology", "m=", "Example 2"],
+ ["Part-of-Speech", "p="],
+ ["Syntax", "syn="]
+ ]);
+
+ document.getElementById('menu').appendChild(menu.element());
+ menu.limit(3).show(3);
+ menu.focus();
+ </script>
+ </body>
+</html>
diff --git a/dev/js/src-lib/menu/menu.js b/dev/js/src-lib/menu/menu.js
new file mode 100644
index 0000000..d9b2e0f
--- /dev/null
+++ b/dev/js/src-lib/menu/menu.js
@@ -0,0 +1,964 @@
+/**
+ * Scrollable drop-down menus with view filter.
+ *
+ * @author Nils Diewald
+ */
+/*
+ * TODO: Show the slider briefly on move (whenever screen is called).
+ * TODO: Ignore alt+ and strg+ key strokes.
+ * TODO: Should scroll to a chosen value after prefixing, if the chosen value is live
+ * TODO: Add a "title" to a menu that is not scrollable.
+ * TODO: Make the menu responsive by showing less items on smaller screens
+ * or anytime items would be outside the screen.
+ * TODO: Add a .match() method to items for scrolling and probably for prefixing.
+ * TODO: Add static header (for title, sortation fields, but also for menu points like "fragments" and "history".
+ * TODO: Support space separated list of prefixes so "co no" will highlight "common noun"
+ */
+
+import defaultItemClass from './menu/item';
+import defaultPrefixClass from './menu/prefix';
+import defaultLengthFieldClass from './menu/lengthField';
+import sliderClass from './menu/slider';
+
+const menuLimit = 8;
+
+export default class KalamarMenu {
+
+ /**
+ * 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).
+ */
+
+ /**
+ * Create new Menu based on the action prefix
+ * and a list of menu items.
+ *
+ *
+ * Accepts an associative array containg the elements
+ * itemClass, prefixClass, lengthFieldClass
+ *
+ * @this {Menu}
+ * @constructor
+ * @param {string} Context prefix
+ * @param {Array.<Array.<string>>} List of menu items
+ */
+ constructor (list, params) {
+ return this._init(list, params);
+ }
+
+ // Initialize list
+ _init (list, params) {
+ if (params === undefined)
+ params = {};
+
+ const t = this;
+ t._notItemElements=3;
+
+ t._itemClass = params["itemClass"] || defaultItemClass;
+
+ // Add prefix object
+ if (params["prefixClass"] !== undefined) {
+ t._prefix = new params["prefixClass"]();
+ }
+ else {
+ t._prefix = new defaultPrefixClass();
+ };
+ t._prefix._menu = t;
+
+ // Add lengthField object
+ if (params["lengthFieldClass"] !== undefined) {
+ t._lengthField = new params["lengthFieldClass"]();
+ }
+ else {
+ t._lengthField = new defaultLengthFieldClass();
+ };
+ t._lengthField._menu = t;
+
+ // Initialize slider
+ t._slider = new sliderClass(t);
+
+ // Create the element
+ var el = document.createElement("ul");
+ el.style.outline = 0;
+ el.setAttribute('tabindex', 0);
+ el.classList.add('menu', 'roll');
+ el.appendChild(t._prefix.element());
+ el.appendChild(t._lengthField.element());
+ el.appendChild(t._slider.element());
+
+ // This has to be cleaned up later on
+ el["menu"] = t;
+
+ // Arrow keys
+ el.addEventListener(
+ 'keydown',
+ t._keydown.bind(t),
+ false
+ );
+
+ // Strings
+ el.addEventListener(
+ 'keypress',
+ t._keypress.bind(t),
+ false
+ );
+
+ // Mousewheel
+ el.addEventListener(
+ 'wheel',
+ t._mousewheel.bind(t),
+ false
+ );
+
+ // Touch events
+ ['touchstart', 'touchend', 'touchmove'].forEach(
+ e => el.addEventListener(e, t._touch.bind(t), false)
+ );
+
+
+ t._el = el;
+
+ t._limit = menuLimit;
+
+ t._items = new Array(); //all childNodes, i.e. ItemClass, prefixClass
+
+ t.readItems(list);
+
+ t.dontHide = false;
+
+ return t;
+ }
+
+ // Read items to add to list
+ readItems (list) {
+ const t = this;
+
+ t._list = undefined; //filtered List containing all itemClass items
+
+ // Remove circular reference to "this" in items
+ for (let i = 0; i < t._items.length; i++) {
+ delete t._items[i]["_menu"];
+ delete t._items[i];
+ };
+
+ t._items = new Array();
+ t.removeItems();
+
+
+ // Initialize items
+ t._lengthField.reset();
+
+ // Initialize item list based on parameters
+ list.forEach(function(i){
+ const obj = new this._itemClass(i);
+
+ // This may become circular
+ obj["_menu"] = this;
+ this._lengthField.add(i);
+ this._items.push(obj);
+ }, t);
+
+ t._slider.length(t.liveLength())
+ .limit(t._limit)
+ .reInit();
+
+ t._firstActive = false;
+ // Show the first item active always?
+ t.offset = 0;
+ t.position = 0;
+ }
+
+ // Append item to list
+ append (item) {
+ const t = this;
+ // This is cyclic!
+ item["_menu"] = t;
+ t._list = undefined;
+ t.removeItems();
+ t._items.push(item);
+ t._lengthField.add([item.content().data]);
+ t._slider.length(t.liveLength()).reInit();
+ t._firstActive = false;
+ t.offset = 0;
+ t.position = 0;
+ }
+
+ // Initialize the item list
+ // returns true if the length of the resulting list is at least 1
+ // and there was a prefix value. Returns true if there was no prefix value set.
+ _initList () {
+ // Upon change also update alwaysmenu.js please
+ const t = this;
+
+ // Create a new list
+ if (t._list === undefined) {
+ t._list = [];
+ }
+ else if (t._list.length !== 0) {
+ t._boundary(false);
+ t._list.length = 0;
+ };
+
+ // Offset is initially zero
+ t.offset = 0;
+
+ // There is no prefix set
+ if (t.prefix().length <= 0) {
+
+ // add all items to the list and lowlight
+ let i = 0;
+ for (; i < t._items.length; i++) {
+ t._list.push(i);
+ t._items[i].lowlight();
+ };
+
+ t._slider.length(i).reInit();
+
+ return true;
+ };
+
+ /*
+ * There is a prefix set, so filter the list!
+ */
+ let pos;
+ const prefixList = t.prefix().toLowerCase().split(" ");
+
+ const items = [];
+ let maxPoints = 1; // minimum 1
+
+ // Iterate over all items and choose preferred matching items
+ // i.e. the matching happens at the word start
+ t._items.forEach(function(it, pos){
+
+ let points = 0;
+
+ prefixList.forEach(function(p) {
+
+ // Check if it matches at the beginning
+ if ((it.lcField().includes(" " + p))) {
+ points += 5;
+ }
+
+ // Check if it matches anywhere
+ else if (it.lcField().includes(p)) {
+ points += 1;
+ };
+ });
+
+ if (points > maxPoints) {
+ this._list = [pos];
+ maxPoints = points;
+ }
+ else if (points == maxPoints) {
+ this._list.push(pos);
+ }
+ }, t);
+
+ t._slider.length(t._list.length).reInit();
+
+ // Filter was successful - yeah!
+ return t._list.length > 0 ? true : false;
+ }
+
+
+ /**
+ * Destroy this menu
+ * (in case you don't trust the
+ * mark and sweep GC)!
+ */
+ destroy () {
+ // Upon change also update alwaysmenu.js please
+ const t = this;
+
+ // Remove circular reference to "this" in menu
+ if (t._el != undefined)
+ delete t._el["menu"];
+
+ // Remove circular reference to "this" in items
+ t._items.forEach(function(i) {
+ delete i["_menu"];
+ });
+
+ // Remove circular reference to "this" in prefix
+ delete t._prefix['_menu'];
+ delete t._lengthField['_menu'];
+ delete t._slider['_menu'];
+ }
+
+
+ /**
+ * Focus on this menu.
+ */
+ focus () {
+ this._el.focus();
+ }
+
+
+ // mouse wheel treatment
+ _mousewheel (e) {
+ const delta = e.deltaY / 120;
+ if (delta > 0)
+ this.next();
+ else if (delta < 0)
+ this.prev();
+ e.halt();
+ }
+
+
+ // touchmove treatment
+ _touch (e) {
+ const s = this.slider();
+
+ if (e.type === "touchstart") {
+ this._lastTouch = e.touches[0].clientY;
+ }
+ else if (e.type === "touchend") {
+ this._lastTouch = undefined;
+ }
+ else if (e.type === "touchmove") {
+ const to = e.touches[0];
+
+ // TODO:
+ // Instead of using 26px, choose the item height
+ // or use the menu height // shownItems
+
+ // s.movetoRel(t.clientY - this._initTouch);
+ if ((this._lastTouch + 26) < to.clientY) {
+ this.viewDown();
+ this._lastTouch = to.clientY;
+ }
+ else if ((this._lastTouch - 26) > to.clientY) {
+ this.viewUp();
+ this._lastTouch = to.clientY;
+ }
+ e.halt();
+ };
+ }
+
+ // Arrow key and prefix treatment
+ _keydown (e) {
+ //Upon change also update alwaysmenu.js please
+ const t = this;
+
+ switch (_codeFromEvent(e)) {
+
+ case 27: // 'Esc'
+ e.halt();
+ t.hide();
+ break;
+
+ case 38: // 'Up'
+ e.halt();
+ t.prev();
+ break;
+
+ case 33: // 'Page up'
+ e.halt();
+ t.pageUp();
+ break;
+
+ case 40: // 'Down'
+ e.halt();
+ t.next();
+ break;
+
+ case 34: // 'Page down'
+ e.halt();
+ t.pageDown();
+ break;
+
+ case 39: // 'Right'
+ if (t._prefix.active())
+ break;
+
+ const item = t.liveItem(t.position);
+
+ if (item["further"] !== undefined) {
+ item["further"].bind(item).apply();
+ };
+
+ e.halt();
+ break;
+
+ case 13: // 'Enter'
+ // Click on prefix
+ if (t._prefix.active())
+ t._prefix.onclick(e);
+
+ // Click on item
+ else
+ t.liveItem(t.position).onclick(e);
+ e.halt();
+ break;
+
+ case 8: // 'Backspace'
+ t._prefix.chop();
+ t.show();
+ e.halt();
+ break;
+ };
+ }
+
+
+ // Add characters to prefix
+ _keypress (e) {
+ if (e.charCode !== 0) {
+ e.halt();
+
+ // Add prefix
+ this._prefix.add(
+ String.fromCharCode(_codeFromEvent(e))
+ );
+
+ this.show();
+ };
+ }
+
+
+ /**
+ * Show a screen with a given offset
+ * in the viewport.
+ */
+ screen (nr) {
+ const t = this;
+
+ // Normalize negative values
+ if (nr < 0) {
+ nr = 0
+ }
+
+ // The shown list already shows everything
+ else if (t.liveLength() < t.limit()) {
+ return false;
+ }
+
+ // Move relatively to the next screen
+ else if (nr > (t.liveLength() - t.limit())) {
+ nr = (t.liveLength() - t.limit());
+ };
+
+ // no change
+ if (t.offset === nr)
+ return false;
+
+ t._showItems(nr);
+
+ return true;
+ }
+
+
+ /**
+ * Get the associated dom element.
+ */
+ element () {
+ return this._el;
+ }
+
+
+ /**
+ * Get the creator class for items
+ */
+ itemClass () {
+ return this._itemClass;
+ }
+
+
+ /**
+ * Get and set the numerical value
+ * for the maximum number of items visible.
+ */
+ limit (limit) {
+ if (arguments.length === 1) {
+ if (this._limit !== limit) {
+ this._limit = limit;
+ this._slider.limit(limit).reInit();
+ };
+ return this;
+ };
+ return this._limit;
+ }
+
+
+ /**
+ * Filter the list and make it visible.
+ * This is always called once the prefix changes.
+ *
+ * @param {string} Prefix for filtering the list
+ */
+ show (active) {
+ //Upon change please also update alwaysmenu.js and containermenu.js (only two lines new there)
+ const t = this;
+
+ // show menu based on initial offset
+ t._unmark(); // Unmark everything that was marked before
+ t.removeItems();
+
+ // Initialize the list
+ if (!t._initList()) {
+
+ // The prefix is not active
+ t._prefix.active(true);
+
+ // finally show the element
+ t._el.classList.add('visible');
+
+ return true;
+ };
+
+ let offset = 0;
+
+ // Set a chosen value to active and move the viewport
+ if (arguments.length === 1) {
+
+ // Normalize active value
+ if (active < 0) {
+ active = 0;
+ }
+ else if (active >= t.liveLength()) {
+ active = t.liveLength() - 1;
+ };
+
+ // Item is outside the first viewport
+ if (active >= t._limit) {
+ offset = active;
+ const newOffset = t.liveLength() - t._limit;
+ if (offset > newOffset) {
+ offset = newOffset;
+ };
+ };
+
+ t.position = active;
+ }
+
+ // Choose the first item
+ else if (t._firstActive) {
+ t.position = 0;
+ }
+
+ // Choose no item
+ else {
+ t.position = -1;
+ };
+
+ t.offset = offset;
+ t._showItems(offset); // Show new item list
+
+ // Make chosen value active
+ if (t.position !== -1) {
+ t.liveItem(t.position).active(true);
+ };
+
+ // The prefix is not active
+ t._prefix.active(false);
+
+ // finally show the element
+ t._el.classList.add('visible');
+
+ // Add classes for rolling menus
+ t._boundary(true);
+
+ return true;
+ }
+
+
+ /**
+ * Hide the menu and call the onHide callback.
+ */
+ hide () {
+ if (!this.dontHide) {
+ this.removeItems();
+ this._prefix.clear();
+ this.onHide();
+ this._el.classList.remove('visible');
+ }
+ // this._el.blur();
+ }
+
+
+ /**
+ * Function released when the menu hides.
+ * This method is expected to be overridden.
+ */
+ onHide () {}
+
+
+ /**
+ * Get the prefix for filtering,
+ * e.g. "ve" for "verb"
+ */
+ prefix (pref) {
+ if (arguments.length === 1) {
+ this._prefix.value(pref);
+ return this;
+ };
+ return this._prefix.value();
+ }
+
+
+ /**
+ * Get the lengthField object.
+ */
+ lengthField () {
+ return this._lengthField;
+ }
+
+
+ /**
+ * Get the associated slider object.
+ */
+ slider () {
+ return this._slider;
+ }
+
+
+ /**
+ * Delete all visible items from the menu element
+ */
+
+ removeItems () {
+ const liElements=this._el.getElementsByTagName("LI");
+ var ignoredCount = 0; //counts how many LI tag elements are not actually direct children
+ while (liElements.length>ignoredCount){
+ if (liElements[ignoredCount].parentNode === this._el){
+ this._el.removeChild(liElements[ignoredCount]);
+ } else {
+ ignoredCount++;
+ }
+ };
+ }
+
+
+
+ /**
+ * Get a specific item from the complete list
+ *
+ * @param {number} index of the list item
+ */
+ item (index) {
+ return this._items[index]
+ }
+
+
+ /**
+ * Get a specific item from the filtered list
+ *
+ * @param {number} index of the list item
+ * in the filtered list
+ */
+ liveItem (index) {
+ if (this._list === undefined)
+ if (!this._initList())
+ return;
+
+ return this._items[this._list[index]];
+ }
+
+
+ /**
+ * Get a specific item from the viewport list
+ *
+ * @param {number} index of the list item
+ * in the visible list
+ */
+ shownItem (index) {
+ if (index >= this.limit())
+ return;
+
+ return this.liveItem(this.offset + index);
+ }
+
+
+ /**
+ * Get the length of the full item list
+ */
+ length () {
+ return this._items.length;
+ }
+
+
+ /**
+ * Length of the filtered item list.
+ */
+ liveLength () {
+ if (this._list === undefined)
+ this._initList();
+ return this._list.length;
+ }
+
+
+ /**
+ * Make the next item in the filtered menu active
+ */
+ next () {
+ //Upon change please update alwaysmenu.js next
+ const t = this;
+
+ // No list
+ if (t.liveLength() === 0)
+ return;
+
+ // Deactivate old item
+ if (t.position !== -1 && !t._prefix.active()) {
+ t.liveItem(t.position).active(false);
+ };
+
+ // Get new active item
+ t.position++;
+ let newItem = t.liveItem(t.position);
+
+ // The next element is undefined - roll to top or to prefix
+ if (newItem === undefined) {
+
+ // Activate prefix
+ const prefix = this._prefix;
+
+ // Prefix is set and not active - choose!
+ if (prefix.isSet() && !prefix.active()) {
+ t.position--;
+ prefix.active(true);
+ return;
+ }
+
+ // Choose first item
+ else {
+ newItem = t.liveItem(0);
+ // choose first item
+ t.position = 0;
+ t._showItems(0);
+ };
+ }
+
+ // The next element is after the viewport - roll down
+ else if (t.position >= (t.limit() + t.offset)) {
+ t.screen(t.position - t.limit() + 1);
+ }
+
+ // The next element is before the viewport - roll up
+ else if (t.position <= t.offset) {
+ t.screen(t.position);
+ };
+
+ t._prefix.active(false);
+ newItem.active(true);
+ }
+
+
+ /*
+ * Make the previous item in the menu active
+ */
+ prev () {
+ //Upon Change please update alwaysmenu.js prev
+ const t = this;
+
+ // No list
+ if (t.liveLength() === 0)
+ return;
+
+ // Deactivate old item
+ if (!t._prefix.active()) {
+
+ // No active element set
+ if (t.position === -1) {
+ t.position = t.liveLength();
+ }
+
+ // No active element set
+ else {
+ t.liveItem(t.position--).active(false);
+ };
+ };
+
+ // Get new active item
+ let newItem = t.liveItem(t.position);
+
+ // The previous element is undefined - roll to bottom
+ if (newItem === undefined) {
+
+ // Activate prefix
+ const prefix = t._prefix;
+ let offset = t.liveLength() - t.limit();
+
+ // Normalize offset
+ offset = offset < 0 ? 0 : offset;
+
+ // Choose the last item
+ t.position = t.liveLength() - 1;
+
+ // Prefix is set and not active - choose!
+ if (prefix.isSet() && !prefix.active()) {
+ t.position++;
+ prefix.active(true);
+ t.offset = offset;
+ return;
+ }
+
+ // Choose last item
+ else {
+ newItem = t.liveItem(t.position);
+ t._showItems(offset);
+ };
+ }
+
+ // The previous element is before the view - roll up
+ else if (t.position < t.offset) {
+ t.screen(t.position);
+ }
+
+ // The previous element is after the view - roll down
+ else if (t.position >= (t.limit() + t.offset)) {
+ t.screen(t.position - t.limit() + 2);
+ };
+
+ t._prefix.active(false);
+ newItem.active(true);
+ }
+
+
+ /**
+ * Move the page up by limit!
+ */
+ pageUp () {
+ this.screen(this.offset - this.limit());
+ }
+
+
+ /**
+ * Move the page down by limit!
+ */
+ pageDown () {
+ this.screen(this.offset + this.limit());
+ }
+
+
+ /**
+ * Move the view one item up
+ */
+ viewUp () {
+ this.screen(this.offset - 1);
+ }
+
+
+ /**
+ * Move the view one item down
+ */
+ viewDown () {
+ this.screen(this.offset + 1);
+ }
+
+ /**
+ * Reset the prefix. Currently not used in regular menu.
+ */
+ reset () {
+ this.prefix("");
+ }
+
+ // Unmark all items
+ _unmark () {
+ this._list.forEach(function(it){
+ const item = this._items[it];
+ item.lowlight();
+ item.active(false);
+ }, this);
+ }
+
+
+ // Set boundary for viewport
+ _boundary (bool) {
+ if (this._list.length === 0)
+ return;
+
+ this.item(this._list[0]).noMore(bool);
+ this.item(this._list[this._list.length - 1]).noMore(bool);
+ }
+
+
+ // Append Items that should be shown
+ _showItems (off) {
+ const t = this;
+
+ // optimization: scroll down one step
+ if (t.offset === (off - 1)) {
+ t.offset = off;
+
+ // Remove the HTML node from the first item
+ // leave lengthField/prefix/slider
+ //console.log("_showItems, at _notItemElements is: ",t._el.children[this._notItemElements]);
+ t._el.removeChild(t._el.children[this._notItemElements]);
+
+ t._append(
+ t._list[t.offset + t.limit() - 1]
+ );
+ }
+
+ // optimization: scroll up one step
+ else if (t.offset === (off + 1)) {
+ t.offset = off;
+
+ // Remove the HTML node from the last item
+ //console.log("_showItems, at lastChild is: ",t._el.lastChild);
+ t._el.removeChild(t._el.lastChild);
+
+ t._prepend(t._list[t.offset]);
+ }
+
+ else {
+ t.offset = off;
+
+ // Remove all items
+ t.removeItems();
+
+ // Use list
+ let shown = 0;
+
+ for (let i = 0; i < t._list.length; i++) {
+
+ // Don't show - it's before offset
+ shown++;
+ if (shown <= off)
+ continue;
+
+ t._append(t._list[i]);
+
+ if (shown >= (t.limit() + off))
+ break;
+ };
+ };
+
+ // set the slider to the new offset
+ t._slider.offset(t.offset);
+ }
+
+
+ // Append item to the shown list based on index
+ _append (i) {
+ const item = this.item(i);
+
+ // Highlight based on prefix
+ if (this.prefix().length > 0) {
+ item.highlight(this.prefix().toLowerCase());
+ };
+
+ // Append element
+ this.element().appendChild(item.element());
+ }
+
+
+ // Prepend item to the shown list based on index
+ _prepend (i) {
+ const item = this.item(i);
+
+ // Highlight based on prefix
+ if (this.prefix().length > 0) {
+ item.highlight(this.prefix().toLowerCase());
+ };
+
+ const e = this.element();
+
+ // Append element after lengthField/prefix/slider
+ e.insertBefore(
+ item.element(),
+ e.children[this._notItemElements]
+ );
+ }
+};
diff --git a/dev/js/src-lib/menu/menu/item.js b/dev/js/src-lib/menu/menu/item.js
new file mode 100644
index 0000000..7ad4366
--- /dev/null
+++ b/dev/js/src-lib/menu/menu/item.js
@@ -0,0 +1,292 @@
+/*
+ * MenuItems may define:
+ *
+ * onclick: action happen on click and enter.
+ * further: action happen on right arrow
+ */
+
+/**
+ * Item in the Dropdown menu
+ */
+export default class KalamarMenuItem {
+ /**
+ * Create a new MenuItem object.
+ *
+ * @constructor
+ * @this {MenuItem}
+ * @param {Array.<string>} An array object of name, action and
+ * optionally a description
+ */
+ constructor (params) {
+ return this._init(params);
+ }
+
+
+ /**
+ * Get or set the content of the meun item.
+ */
+ content (content) {
+ if (arguments.length === 1)
+ this._content = document.createTextNode(content);
+ return this._content;
+ }
+
+
+ /**
+ * Get or set the information for action of this item.
+ */
+ action (action) {
+ if (arguments.length === 1)
+ this._action = action;
+ return this._action;
+ }
+
+
+ /**
+ * Get the lower cased field of the item
+ * (used for analyses).
+ */
+ lcField () {
+ return this._lcField;
+ }
+
+
+ /**
+ * Check or set if the item is active
+ *
+ * @param {boolean|null} State of activity
+ */
+ active (bool) {
+ const 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 (bool) {
+ const 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 () {
+ // already defined
+ if (this._el !== undefined)
+ return this._el;
+
+ // Create list item
+ const li = document.createElement("li");
+
+ // Connect action
+ if (this["onclick"] !== undefined) {
+ li["onclick"] = this.onclick.bind(this);
+ };
+
+ // Append template
+ li.appendChild(this.content());
+
+ return this._el = li;
+ }
+
+ /**
+ * Highlight parts of the item
+ *
+ * @param {string} Prefix string for highlights
+ */
+ highlight (prefix) {
+
+ // The prefix already matches
+ if (this._prefix === prefix)
+ return;
+
+ // There is a prefix but it doesn't match
+ if (this._prefix !== null) {
+ this.lowlight();
+ }
+
+ const children = this.element().childNodes;
+ for (let i = children.length -1; i >= 0; i--) {
+ this._highlight(children[i], prefix);
+ };
+
+ this._prefix = prefix;
+ }
+
+
+ /**
+ * Remove highlight of the menu item
+ */
+ lowlight () {
+ if (this._prefix === null)
+ return;
+
+ const e = this.element();
+
+ const marks = e.getElementsByTagName("mark");
+
+ for (let i = marks.length - 1; i >= 0; i--) {
+
+ // Replace with content
+ marks[i].parentNode.replaceChild(
+ // Create text node clone
+ document.createTextNode(
+ marks[i].firstChild.nodeValue
+ ),
+ marks[i]
+ );
+ };
+
+ // Remove consecutive textnodes
+ e.normalize();
+ this._prefix = null;
+ }
+
+
+ // Highlight a certain substring of the menu item
+ _highlight (elem, prefixString) {
+ if (elem.nodeType === 3) {
+
+ const text = elem.nodeValue;
+ const textlc = text.toLowerCase();
+
+ // Split prefixes
+ if (prefixString) {
+
+ // ND:
+ // Doing this in a single line can trigger
+ // a deep-recursion in Firefox 57.01, though I don't know why.
+ prefixString = prefixString.trim();
+
+ const prefixes = prefixString.split(" ");
+
+ let testPos,
+ pos = -1,
+ len = 0;
+
+ // Iterate over all prefixes and get the best one
+ // for (var i = 0; i < prefixes.length; i++) {
+ prefixes.forEach(function(i) {
+
+ // Get first pos of a matching prefix
+ testPos = textlc.indexOf(i);
+ if (testPos < 0)
+ return;
+
+ if (pos === -1 || testPos < pos) {
+ pos = testPos;
+ len = i.length;
+ }
+ else if (testPos === pos && i.length > len) {
+ len = i.length;
+ };
+ });
+
+ // Matches!
+ if (pos >= 0) {
+
+ // First element
+ if (pos > 0) {
+ elem.parentNode.insertBefore(
+ document.createTextNode(text.substr(0, pos)),
+ elem
+ );
+ };
+
+ // Second element
+ const hl = document.createElement("mark");
+ hl.appendChild(
+ document.createTextNode(text.substr(pos, len))
+ );
+ elem.parentNode.insertBefore(hl, elem);
+
+ // Third element
+ const third = text.substr(pos + len);
+
+ if (third.length > 0) {
+ const thirdE = document.createTextNode(third);
+ elem.parentNode.insertBefore(
+ thirdE,
+ elem
+ );
+ this._highlight(thirdE, prefixString);
+ };
+
+ elem.parentNode.removeChild(elem);
+ };
+ };
+ }
+
+ else {
+ const children = elem.childNodes;
+ for (let i = children.length -1; i >= 0; i--) {
+ this._highlight(children[i], prefixString);
+ };
+ };
+ }
+
+ // Initialize menu item
+ _init (params) {
+
+ if (params[0] === undefined) {
+ throw new Error("Missing parameters");
+ };
+
+ const t = this;
+
+ t.content(params[0]);
+
+ if (params.length > 1) {
+ t._action = params[1];
+
+ if (params.length > 2)
+ t._onclick = params[2];
+ };
+
+ t._lcField = ' ' + t.content().textContent.toLowerCase();
+ t._prefix = null;
+
+ return this;
+ }
+
+
+ /**
+ * The click action of the menu item.
+ */
+ onclick (e) {
+ const m = this.menu();
+
+ // Reset prefix
+ m.prefix("");
+
+ if (this._onclick)
+ this._onclick.apply(this, e);
+
+ m.hide();
+ }
+
+
+ /**
+ * Return menu list.
+ */
+ menu () {
+ return this._menu;
+ }
+};
diff --git a/dev/js/src-lib/menu/menu/lengthField.js b/dev/js/src-lib/menu/menu/lengthField.js
new file mode 100644
index 0000000..3612b22
--- /dev/null
+++ b/dev/js/src-lib/menu/menu/lengthField.js
@@ -0,0 +1,44 @@
+export default class KalamarMenuLengthField {
+
+ /**
+ * Create new lengthField object.
+ */
+ constructor () {
+ return this._init();
+ }
+
+
+ // Initialize lengthField object
+ _init () {
+ this._el = document.createElement('div');
+ this._el.classList.add('lengthField');
+ return this;
+ }
+
+
+ /**
+ * Get the associated dom element.
+ */
+ element () {
+ return this._el;
+ }
+
+
+ /**
+ * Add string to lengthField.
+ */
+ add (param) {
+ this._el.appendChild(document.createElement('span'))
+ .appendChild(document.createTextNode(param[0] + '--'));
+ }
+
+
+ /**
+ * Remove all initialized values
+ */
+ reset () {
+ while (this._el.firstChild) {
+ this._el.firstChild.remove();
+ };
+ }
+};
diff --git a/dev/js/src-lib/menu/menu/prefix.js b/dev/js/src-lib/menu/menu/prefix.js
new file mode 100644
index 0000000..2c15d03
--- /dev/null
+++ b/dev/js/src-lib/menu/menu/prefix.js
@@ -0,0 +1,130 @@
+export default class KalamarMenuPrefix {
+
+ /**
+ * Create new prefix object.
+ */
+ constructor () {
+ return this._init();
+ }
+
+
+ // Initialize prefix object
+ _init () {
+ const t = this;
+
+ t._string = '';
+
+ // Add prefix span
+ t._el = document.createElement('span');
+ t._el.classList.add('non-item');
+ t._el.classList.add('pref');
+ // Connect action
+
+ if (t["onclick"] !== undefined) {
+ t._el["onclick"] = t.onclick.bind(t);
+ }
+ return t;
+ }
+
+
+ _update () {
+ return this._el.innerHTML
+ = this._string;
+ }
+
+
+ /**
+ * Get or set the activity status of the prefix.
+ */
+ active (bool) {
+ const cl = this.element().classList;
+ if (bool === undefined)
+ return cl.contains("active");
+ else if (bool)
+ cl.add("active");
+ else
+ cl.remove("active");
+ }
+
+
+ /**
+ * Check, if a prefix is given or not.
+ */
+ isSet () {
+ return this._string.length > 0 ?
+ true : false;
+ }
+
+
+ /**
+ * Get or set the prefix string.
+ */
+ value (string) {
+ if (arguments.length === 1) {
+ this._string = string;
+ return this._update();
+ };
+ return this._string;
+ }
+
+
+ /**
+ * Add string to prefix.
+ */
+ add (string) {
+ this._string += string;
+ return this._update();
+ }
+
+
+ /**
+ * Clear prefix
+ */
+ clear () {
+ this._string = '';
+ return this._update();
+ }
+
+
+ /**
+ * Action method.
+ * Expected to be overridden.
+ */
+ onclick () {}
+
+
+ /**
+ * Remove the last character of the string
+ */
+ chop () {
+ const t = this;
+
+ // Prefix is long enough for backspace
+ if (t._string.length > 1) {
+ t._string = t._string.substring(
+ 0, t._string.length - 1
+ );
+ }
+ else {
+ t._string = '';
+ };
+
+ return t._update();
+ }
+
+
+ /**
+ * Get the associated dom element.
+ */
+ element () {
+ return this._el;
+ }
+
+
+ /**
+ * Return menu list.
+ */
+ menu () {
+ return this._menu;
+ }
+};
diff --git a/dev/js/src-lib/menu/menu/slider.js b/dev/js/src-lib/menu/menu/slider.js
new file mode 100644
index 0000000..70a96c9
--- /dev/null
+++ b/dev/js/src-lib/menu/menu/slider.js
@@ -0,0 +1,260 @@
+/**
+ * Create slider for menus.
+ * The slider will only be used by mouse - touch support
+ * shouldn't be necessary, as the menu can be scrolled using touch.
+ *
+ * @author Nils Diewald
+ */
+export default class KalamarMenuSlider {
+
+ /**
+ * Create new slider for Menu.
+ * @this {Slider}
+ * @constructor
+ * @param {Menu} menu object
+ */
+ constructor (menu) {
+ return this._init(menu);
+ }
+
+
+ /**
+ * Length attribute of the slider
+ * (as number of items).
+ *
+ * @param {number} Number of items (optional)
+ */
+ length (i) {
+ if (arguments.length === 0)
+ return this._length;
+ if (i == this._length)
+ return this;
+ this._length = i;
+ return this;
+ }
+
+
+ /**
+ * Limit of items per screen.
+ *
+ * @param {number} Number of items per screen (optional)
+ */
+ limit (i) {
+ if (arguments.length === 0)
+ return this._limit;
+ if (i == this._limit)
+ return this;
+ this._limit = i;
+ return this;
+ }
+
+
+ /**
+ * Is the slider active or not.
+ *
+ * @param {bool} true or false (optional)
+ */
+ active (bool) {
+ if (arguments.length === 1) {
+ if (bool) {
+ if (!this._active) {
+ this._el.classList.add('active');
+ this._active = true;
+ };
+ }
+ else if (this._active) {
+ this._el.classList.remove('active');
+ this._active = false;
+ }
+ };
+ return this._active;
+ }
+
+
+ /**
+ * Move the slider to a relative position
+ *
+ * @param {number} relative position
+ */
+ movetoRel (relativePos) {
+
+ // This is important to find the correct percentage!
+ const diffHeight = (this._rulerHeight - this._sliderHeight);
+ const relativeOffset = (relativePos / diffHeight);
+
+ // Offset is a value 0 to this._screens
+ const off = this.offset(
+ parseInt(relativeOffset * this._screens) + this._event.initOffset
+ );
+
+ if (off !== undefined) {
+ this._menu.screen(off);
+ };
+ }
+
+
+ /**
+ * Move the slider to an absolute position
+ *
+ * @param {number} absolute position
+ */
+ movetoAbs (absPos) {
+ const absOffset = (absPos / this._rulerHeight);
+ const off = this.offset(parseInt(absOffset * (this._screens + 1)));
+
+ if (off !== undefined) {
+ this._menu.screen(off);
+ };
+ }
+
+
+ /**
+ * Screen offset of the slider
+ *
+ * @param {number} Offset position of the slider (optional)
+ */
+ offset (off) {
+ if (arguments.length === 0)
+ return this._offset;
+
+ // Normalize offset
+ if (off > this._screens)
+ off = this._screens;
+ else if (off < 0)
+ off = 0;
+
+ // Identical with old value
+ if (off === this._offset)
+ return undefined;
+
+ // Set offset and move
+ this._offset = off;
+ this._slider.style.top = (this._step * off) + '%';
+ return off;
+ }
+
+
+ /**
+ * Get the associated dom element.
+ */
+ element () {
+ return this._el;
+ }
+
+
+ /**
+ * Reinitialize the size of the slider.
+ * Necessary to call after each adjustment of the list.
+ */
+ reInit () {
+
+ const t = this;
+ const s = t._el.style;
+
+ // Do not show the slider, in case there is nothing to scroll
+ if (t._length <= t._limit) {
+ s.display = 'none';
+ return;
+ }
+ else {
+ s.display = 'block';
+ };
+
+ t._height = ((t._limit / t._length) * 100);
+ t._screens = t._length - t._limit;
+ t._step = (100 - t._height) / t._screens;
+ t._slider.style.height = t._height + '%';
+ }
+
+
+ // Initialize prefix object
+ _init (menu) {
+ const t = this;
+
+ t._menu = menu;
+
+ t._offset = 0;
+ t._event = {};
+ t._active = false;
+
+ const el = t._el = document.createElement('div');
+ el.setAttribute('class', 'ruler');
+
+ t._slider = el.appendChild(
+ document.createElement('span')
+ );
+
+ t._ruler = el.appendChild(document.createElement('div'));
+
+ // Do not mark the menu on mousedown
+ t._ruler.addEventListener('mousedown', function (e) {
+ e.halt()
+ }, false);
+
+ // Move the slider to the click position
+ t._ruler.addEventListener('click', t._mouseclick.bind(t), false);
+
+ t._slider.addEventListener('mousedown', t._mousedown.bind(t), false);
+
+ return t;
+ }
+
+
+ // Reinit height based on dom position
+ _initClientHeight () {
+ this._rulerHeight = this._el.clientHeight;
+ this._sliderHeight = this._slider.clientHeight;
+ }
+
+
+ // Release mousemove event
+ _mousemove (e) {
+ this.movetoRel(e.clientY - this._event.init);
+ e.halt();
+ }
+
+
+ // Release mouseup event
+ _mouseup (e) {
+ this.active(false);
+ window.removeEventListener('mousemove', this._event.mov);
+ window.removeEventListener('mouseup', this._event.up);
+ this._menu.focus();
+ }
+
+
+ // Release mousedown event
+ _mousedown (e) {
+ // Bind drag handler
+ const ev = this._event;
+
+ // _step * _offset is the distance of the ruler to the top
+ ev.init = e.clientY;
+ ev.initOffset = this._offset;
+ // By substracting that, it is like initializing to the first point
+
+ ev.mov = this._mousemove.bind(this);
+ ev.up = this._mouseup.bind(this);
+
+ // TODO: This may not be necessary all the time
+ this._initClientHeight();
+
+ this.active(true);
+
+ window.addEventListener('mousemove', ev.mov);
+ window.addEventListener('mouseup', ev.up);
+
+ e.halt();
+ }
+
+
+ // Release event to reposition slider on ruler
+ _mouseclick (e) {
+ this._initClientHeight();
+
+ this.movetoAbs(
+ e.clientY - this._ruler.getClientRects()[0].top
+ );
+ e.halt();
+ }
+};
diff --git a/dev/js/src-lib/menu/package.json b/dev/js/src-lib/menu/package.json
new file mode 100644
index 0000000..75fcb36
--- /dev/null
+++ b/dev/js/src-lib/menu/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "kalamar-menu",
+ "version": "0.0.1",
+ "description": "Scrollable drop-down menu with view filters",
+ "main": "menu.js",
+ "scripts": {
+ "test": "vitest",
+ "dev": "vite --open /demo/index.html"
+ },
+ "author": "",
+ "license": "ISC"
+}
diff --git a/dev/js/src-lib/menu/test/menuSpec.js b/dev/js/src-lib/menu/test/menuSpec.js
new file mode 100644
index 0000000..1fe5c56
--- /dev/null
+++ b/dev/js/src-lib/menu/test/menuSpec.js
@@ -0,0 +1,1933 @@
+import { describe, it, expect } from 'vitest';
+import menuClass from '../menu.js';
+import menuItemClass from '../menu/item.js';
+import prefixClass from '../menu/prefix.js';
+import lengthFieldClass from '../menu/lengthField.js';
+
+function directElementChildrenByTagName(element, tagName) {
+ const result = [];
+ const children = element.childNodes;
+ const upperTag = tagName.toUpperCase();
+ for (let i = 0; i < children.length; i++) {
+ if (children[i].nodeName === upperTag && children[i].parentNode === element) {
+ result.push(children[i]);
+ }
+ }
+ return result;
+}
+
+ // The OwnMenu item
+ class OwnMenuItem extends menuItemClass {
+ content (content) {
+ if (arguments.length === 1) {
+ this._content = content;
+ };
+ return this._content;
+ }
+ _init (params) {
+ if (params[0] === undefined)
+ throw new Error("Missing parameters");
+
+ this._content = document.createTextNode(params[0]);
+ this._lcField = ' ' + this.content().textContent.toLowerCase();
+
+ return this;
+ }
+ };
+
+ // The OwnMenu
+ class OwnMenu extends menuClass {
+ constructor (list) {
+ super(list, { itemClass : OwnMenuItem });
+ }
+ };
+
+
+ // HintMenuItem
+ class HintMenuItem extends menuItemClass {
+ content (content) {
+ if (arguments.length === 1) {
+ this._content = content;
+ };
+ return this._content;
+ }
+ _init (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;
+ }
+
+ name () {
+ return this._name;
+ }
+ action () {
+ return this._action;
+ }
+ desc () {
+ return this._desc;
+ }
+ element () {
+ // already defined
+ if (this._el !== undefined)
+ return this._el;
+
+ // Create list item
+ var li = document.createElement("li");
+ li.setAttribute("data-action", this._action);
+
+ // Create title
+ var name = document.createElement("strong");
+ name.appendChild(document.createTextNode(this._name));
+
+ li.appendChild(name);
+
+ // Create description
+ if (this._desc !== undefined) {
+ var desc = document.createElement("span");
+ desc.appendChild(document.createTextNode(this._desc));
+ li.appendChild(desc);
+ };
+ return this._el = li;
+ }
+ };
+
+
+ // HintMenu
+ class HintMenu extends menuClass {
+ constructor (context, list) {
+ super(list, {itemClass : HintMenuItem});
+ this._context = context;
+ }
+ };
+
+
+ // The ComplexMenuItem
+ class ComplexMenuItem extends menuItemClass {
+ content (content) {
+ if (arguments.length === 1) {
+ this._content = content;
+ };
+ return this._content;
+ }
+ _init (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() { new menuItemClass([]) }
+ ).toThrow(new Error("Missing parameters"));
+
+ expect(
+ function() { new OwnMenuItem([]) }
+ ).toThrow(new Error("Missing parameters"));
+
+ var mi = new OwnMenuItem(["Baum"]);
+ expect(mi.element().firstChild.nodeValue).toEqual('Baum');
+ expect(mi.lcField()).toEqual(' baum');
+ });
+
+ it('shouldn\'t have a reference to the menu', function () {
+ var menuItem = new OwnMenuItem(['Test']);
+ expect(menuItem.menu()).toBe(undefined);
+ });
+
+ it('should be activatable and deactivateable by class', function () {
+ var menuItem = new OwnMenuItem(['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 = new OwnMenuItem(['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 = new OwnMenuItem(['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 = new OwnMenuItem(['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 = new ComplexMenuItem(['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 = new ComplexMenuItem(['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 = new ComplexMenuItem(['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 = new ComplexMenuItem(['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 = new ComplexMenuItem(['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 = new ComplexMenuItem(['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 = new ComplexMenuItem(['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 = new ComplexMenuItem(['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 = new ComplexMenuItem(['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 = new ComplexMenuItem(['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.Menu', function () {
+ var list = [
+ ["Constituency", "c=", "Example 1"],
+ ["Lemma", "l="],
+ ["Morphology", "m=", "Example 2"],
+ ["Part-of-Speech", "p="],
+ ["Syntax", "syn="]
+ ];
+
+ var listMultiPrefix = [
+ ["PP","PP ","Personal Pronoun"],
+ ["PPP","PPP ","Personal Pronoun, Plural"],
+ ["PPPA","PPPA ","Personal Pronoun, Plural, Acc."],
+ ["PPPD","PPPD ","Personal Pronoun, Plural, Dative"],
+ ["PPPR","PPPR ","Personal Pronoun, Plural, Direct"],
+ ["PPPO","PPPO ","Personal Pronoun, Plural, Oblique"],
+ ["PPS","PPS ","Personal Pronoun, Singular"],
+ ["PPSA","PPSA ","Personal Pronoun, Singular, Accusative"],
+ ["PPSD","PPSD ","Personal Pronoun, Singular, Dative"],
+ ["PPSR","PPSR ","Personal Pronoun, Singular, Direct"],
+ ["PPSN","PPSN ","Personal Pronoun, Singular, Nominative"],
+ ["PPSO","PPSO ","Personal Pronoun, Singular, Oblique"]
+ ];
+
+ var demolist = [
+ ['Titel', 'title'],
+ ['Untertitel', 'subTitle'],
+ ['Veröffentlichungsdatum', 'pubDate'],
+ ['Länge', 'length'],
+ ['Autor', 'author']
+ ];
+
+ var demolonglist = [
+ ['Titel', 'title'],
+ ['Untertitel', 'subTitle'],
+ ['Veröffentlichungsdatum', 'pubDate'],
+ ['Länge', 'length'],
+ ['Autor', 'author'],
+ ['Genre', 'genre'],
+ ['corpusID', 'corpusID'],
+ ['docID', 'docID'],
+ ['textID', 'textID'],
+ ];
+
+ it('should be initializable', function () {
+ var list = [
+ ["Constituency"],
+ ["Lemma"],
+ ["Morphology"],
+ ["Part-of-Speech"],
+ ["Syntax"]
+ ];
+
+ var menu = new OwnMenu(list);
+ expect(menu.dontHide).toBe(false);
+ menu._firstActive = true;
+ expect(menu.itemClass()).toEqual(OwnMenuItem);
+ expect(menu.element().nodeName).toEqual('UL');
+ expect(menu.element().classList.contains('visible')).toBeFalsy();
+ expect(menu.limit()).toEqual(8);
+ expect(menu._notItemElements).toEqual(3);
+
+ menu.limit(9);
+ expect(menu.limit()).toEqual(9);
+
+ menu.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 have a reference to the menu', function () {
+ var menu = new HintMenu("cnx/", list);
+ expect(menu.item(0).menu()).toEqual(menu);
+
+ menu = new HintMenu("cnx/", list);
+ expect(menu.element().menu).toEqual(menu);
+ });
+
+ it('should only remove direct descendants with removeItems', function () {
+ var menu = new HintMenu("cnx/", list);
+ var newUL = document.createElement("ul");
+ var newLI = document.createElement("li");
+ newUL.appendChild(newLI);
+ //This is a very constructed example, but this actually happens within containerMenu
+ menu.element().appendChild(newUL);
+ expect(menu.element().childNodes[3].nodeName).toEqual("UL");
+ expect(menu.element().childNodes[3].childNodes[0]).toEqual(newLI);
+ menu.prefix("a"); //to call show
+ expect(menu.element().childNodes[3].nodeName).toEqual("UL");
+ expect(menu.element().childNodes[3].childNodes[0]).toEqual(newLI);
+
+ });
+
+
+ it('should be visible', function () {
+ var menu = new HintMenu("cnx/", list);
+ expect(menu.removeItems()).toBe(undefined);
+ menu.limit(3);
+
+ expect(menu.show()).toBe(true);
+
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Lemma</strong>");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].getAttribute("data-action")).toEqual("l=");
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(directElementChildrenByTagName(menu.element(),"li")[3]).toBe(undefined);
+
+ // Check boundaries
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].classList.contains("no-more")).toBe(true);
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].classList.contains("no-more")).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].classList.contains("no-more")).toBe(false);
+ });
+
+ it('should be filterable', function () {
+ var menu = new HintMenu("cnx/", list);
+ menu.limit(3);
+ expect(menu.prefix("o").show()).toBe(true);
+ expect(menu.element().getElementsByClassName("pref")[0].innerHTML).toEqual("o");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>C<mark>o</mark>nstituency</strong><span>Example 1</span>");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>M<mark>o</mark>rph<mark>o</mark>l<mark>o</mark>gy</strong><span>Example 2</span>");
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].innerHTML).toEqual("<strong>Part-<mark>o</mark>f-Speech</strong>");
+ expect(directElementChildrenByTagName(menu.element(),"li")[3]).toBe(undefined);
+
+ // Check boundaries
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].classList.contains("no-more")).toBe(true);
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].classList.contains("no-more")).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].classList.contains("no-more")).toBe(true);
+
+ menu.limit(2);
+
+ expect(menu.prefix("o").show()).toBe(true);
+ expect(menu.element().getElementsByClassName("pref")[0].innerHTML).toEqual("o");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>C<mark>o</mark>nstituency</strong><span>Example 1</span>");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>M<mark>o</mark>rph<mark>o</mark>l<mark>o</mark>gy</strong><span>Example 2</span>");
+ expect(directElementChildrenByTagName(menu.element(),"li")[2]).toBe(undefined);
+
+ // Check boundaries
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].classList.contains("no-more")).toBe(true);
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].classList.contains("no-more")).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[2]).toBe(undefined);
+
+ expect(menu.prefix("e").show()).toBe(true);
+ expect(menu.element().getElementsByClassName("pref")[0].innerHTML).toEqual("e");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Constitu<mark>e</mark>ncy</strong><span><mark>E</mark>xampl<mark>e</mark> 1</span>");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Morphology</strong><span><mark>E</mark>xampl<mark>e</mark> 2</span>");
+ expect(directElementChildrenByTagName(menu.element(),"li")[2]).toBe(undefined);
+
+ menu.limit(5);
+ expect(menu.prefix("a").show()).toBe(true);
+ expect(menu.element().getElementsByClassName("pref")[0].innerHTML).toEqual("a");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Constituency</strong><span>Ex<mark>a</mark>mple 1</span>");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Lemm<mark>a</mark></strong>");
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].innerHTML).toEqual("<strong>Morphology</strong><span>Ex<mark>a</mark>mple 2</span>");
+ expect(directElementChildrenByTagName(menu.element(),"li")[3].innerHTML).toEqual("<strong>P<mark>a</mark>rt-of-Speech</strong>");
+ expect(directElementChildrenByTagName(menu.element(),"li")[4].innerHTML).toEqual("<strong>Synt<mark>a</mark>x</strong>");
+ expect(directElementChildrenByTagName(menu.element(),"li")[5]).toBe(undefined);
+ });
+
+ it('should be nextable', function () {
+ var menu = new HintMenu("cnx/", list);
+ menu._firstActive = true;
+
+ // Show only 3 items
+ menu.limit(3);
+ expect(menu.show()).toBe(true);
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Lemma</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[3]).toBe(undefined);
+
+ // Activate next (1)
+ menu.next();
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Lemma</strong>");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[3]).toBe(undefined);
+
+ // Activate next (2)
+ menu.next();
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Lemma</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(directElementChildrenByTagName(menu.element(),"li")[3]).toBe(undefined);
+
+ // Activate next (3)
+ // scroll!
+ menu.next();
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Lemma</strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].innerHTML).toEqual("<strong>Part-of-Speech</strong>");
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(directElementChildrenByTagName(menu.element(),"li")[3]).toBe(undefined);
+
+ // Activate next (4)
+ menu.next();
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Part-of-Speech</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].innerHTML).toEqual("<strong>Syntax</strong>");
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(directElementChildrenByTagName(menu.element(),"li")[3]).toBe(undefined);
+
+ // Activate next (5) - ROLL
+ menu.next();
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Lemma</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[3]).toBe(undefined);
+
+ // Active next (6)
+ menu.next();
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Lemma</strong>");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[3]).toBe(undefined);
+ });
+
+ it('should be nextable without active field', function () {
+ var menu = new HintMenu("cnx/", list);
+ menu.limit(3);
+ expect(menu.show()).toBe(true);
+ menu.next();
+ expect(menu.shownItem(0).active()).toEqual(true);
+ });
+
+
+ it('should be prevable', function () {
+ var menu = new HintMenu("cnx/", list);
+ menu._firstActive = true;
+ menu.limit(3);
+ expect(menu.show()).toBe(true);
+
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Lemma</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[3]).toBe(undefined);
+
+ // Activate prev (1) - roll to bottom
+ menu.prev();
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Part-of-Speech</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].innerHTML).toEqual("<strong>Syntax</strong>");
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(directElementChildrenByTagName(menu.element(),"li")[3]).toBe(undefined);
+
+ // Activate prev (2)
+ menu.prev();
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Part-of-Speech</strong>");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].innerHTML).toEqual("<strong>Syntax</strong>");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(directElementChildrenByTagName(menu.element(),"li")[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(directElementChildrenByTagName(menu.element(),"li")[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(directElementChildrenByTagName(menu.element(),"li")[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(directElementChildrenByTagName(menu.element(),"li")[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(directElementChildrenByTagName(menu.element(),"li")[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(directElementChildrenByTagName(menu.element(),"li")[3]).toBe(undefined);
+ });
+
+ it('should be prevable without active field', function () {
+ var menu = new HintMenu("cnx/", list);
+ menu.limit(3);
+ expect(menu.show()).toBe(true);
+ menu.prev();
+ expect(menu.shownItem(2).active()).toEqual(true);
+ expect(menu.shownItem(2).lcField()).toEqual(' syntax');
+ });
+
+ it('should be navigatable and filterable (prefix = "o")', function () {
+ var menu = new HintMenu("cnx/", list);
+ menu._firstActive = true;
+ menu.limit(2);
+
+ expect(menu.prefix("o").show()).toBe(true);
+ expect(menu.shownItem(0).name()).toEqual("Constituency");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>C<mark>o</mark>nstituency</strong><span>Example 1</span>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Morphology");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>M<mark>o</mark>rph<mark>o</mark>l<mark>o</mark>gy</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(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>C<mark>o</mark>nstituency</strong><span>Example 1</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Morphology");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>M<mark>o</mark>rph<mark>o</mark>l<mark>o</mark>gy</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(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>M<mark>o</mark>rph<mark>o</mark>l<mark>o</mark>gy</strong><span>Example 2</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Part-of-Speech");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Part-<mark>o</mark>f-Speech</strong>");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Next (3) - to prefix
+ menu.next();
+ expect(menu.shownItem(0).name()).toEqual("Morphology");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>M<mark>o</mark>rph<mark>o</mark>l<mark>o</mark>gy</strong><span>Example 2</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Part-of-Speech");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Part-<mark>o</mark>f-Speech</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ menu.next();
+ expect(menu.shownItem(0).name()).toEqual("Constituency");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>C<mark>o</mark>nstituency</strong><span>Example 1</span>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Morphology");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>M<mark>o</mark>rph<mark>o</mark>l<mark>o</mark>gy</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 = new HintMenu("cnx/", list);
+ menu._firstActive = true;
+
+ menu.limit(2);
+ expect(menu.prefix("ex").show()).toBe(true);
+
+ expect(menu.shownItem(0).name()).toEqual("Constituency");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Constituency</strong><span><mark>Ex</mark>ample 1</span>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Morphology");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Morphology</strong><span><mark>Ex</mark>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(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Constituency</strong><span><mark>Ex</mark>ample 1</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Morphology");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Morphology</strong><span><mark>Ex</mark>ample 2</span>");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Next (2)
+ menu.next();
+
+ expect(menu.prefix()).toEqual('ex');
+ expect(menu.shownItem(0).name()).toEqual("Constituency");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Constituency</strong><span><mark>Ex</mark>ample 1</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+
+ expect(menu.shownItem(1).name()).toEqual("Morphology");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Morphology</strong><span><mark>Ex</mark>ample 2</span>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Reset limit
+ menu.limit(5);
+
+ // Change show
+ expect(menu.prefix("e").show()).toBe(true);
+ expect(menu._prefix.active()).toBe(false);
+ expect(menu.shownItem(0).name()).toEqual("Constituency");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Constitu<mark>e</mark>ncy</strong><span><mark>E</mark>xampl<mark>e</mark> 1</span>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Morphology");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Morphology</strong><span><mark>E</mark>xampl<mark>e</mark> 2</span>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Next (1)
+ menu.next();
+ expect(menu._prefix.active()).toBe(false);
+ expect(menu.prefix()).toEqual('e');
+ expect(menu.shownItem(0).name()).toEqual("Constituency");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Constitu<mark>e</mark>ncy</strong><span><mark>E</mark>xampl<mark>e</mark> 1</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Morphology");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Morphology</strong><span><mark>E</mark>xampl<mark>e</mark> 2</span>");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Next (2)
+ menu.next();
+ expect(menu._prefix.active()).toBe(true);
+ expect(menu.shownItem(0).name()).toEqual("Constituency");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Constitu<mark>e</mark>ncy</strong><span><mark>E</mark>xampl<mark>e</mark> 1</span>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Morphology");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Morphology</strong><span><mark>E</mark>xampl<mark>e</mark> 2</span>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Next (3)
+ menu.next();
+ expect(menu._prefix.active()).toBe(false);
+ expect(menu.shownItem(0).name()).toEqual("Constituency");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Constitu<mark>e</mark>ncy</strong><span><mark>E</mark>xampl<mark>e</mark> 1</span>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Morphology");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Morphology</strong><span><mark>E</mark>xampl<mark>e</mark> 2</span>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+ });
+
+ it('should be filterable (multiple prefix = "pro sin")', function () {
+ var menu = new HintMenu("drukola/p=", listMultiPrefix);
+ menu._firstActive = true;
+
+ menu.limit(2);
+ expect(menu.prefix("pro sin").show()).toBe(true);
+ expect(menu.shownItem(0).name()).toEqual("PPS");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual(
+ "<strong>PPS</strong><span>Personal <mark>Pro</mark>noun, <mark>Sin</mark>gular</span>"
+ );
+
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("PPSA");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>PPSA</strong><span>Personal <mark>Pro</mark>noun, <mark>Sin</mark>gular, Accusative</span>");
+ expect(menu.shownItem(1).active()).toBe(false);
+
+ expect(menu.shownItem(2)).toBe(undefined);
+ });
+
+ it('should be filterable (trimming = " p")', function () {
+ var menu = new HintMenu("/p=", listMultiPrefix);
+ // menu._firstActive = true;
+
+ menu.limit(2);
+ expect(menu.show()).toBe(true);
+ menu._prefix.add(" ");
+ expect(menu.show()).toBe(true);
+ menu._prefix.add("p")
+ expect(menu.show()).toBe(true);
+ expect(menu.shownItem(0).name()).toEqual("PP");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual(
+ "<strong><mark>P</mark><mark>P</mark></strong>"+
+ "<span><mark>P</mark>ersonal <mark>P</mark>ronoun</span>"
+ );
+ });
+
+
+ it('should choose prefix with failing prefix (1)', function () {
+ var menu = new HintMenu("cnx/", list);
+ menu.limit(2);
+ expect(menu.prefix("exit").show()).toBe(true);
+ expect(menu.element().querySelector('li')).toBe(null);
+ expect(menu.shownItem(0)).toBeUndefined();
+ expect(menu._prefix.active()).toBe(true);
+ });
+
+
+ it('should choose prefix with failing prefix (2)', function () {
+ var menu = new HintMenu("cnx/", list);
+ menu.limit(2);
+ expect(menu.show()).toBe(true);
+ expect(menu.prefix("exit").show()).toBe(true);
+ expect(menu.element().querySelector('li')).toBe(null);
+ expect(menu.shownItem(0)).toBeUndefined();
+ expect(menu._prefix.active()).toBe(true);
+ });
+
+ it('should ignore navigation with failing prefix', function () {
+ var menu = new HintMenu("cnx/", list);
+ menu.limit(2);
+ expect(menu.show()).toBe(true);
+
+ menu.next();
+
+ expect(menu.prefix("exit").show()).toBe(true);
+ expect(menu.element().querySelector('li')).toBe(null);
+ expect(menu.shownItem(0)).toBeUndefined();
+ expect(menu._prefix.active()).toBe(true);
+
+ menu.next();
+ expect(menu._prefix.active()).toBe(true);
+
+ menu.prev();
+ expect(menu._prefix.active()).toBe(true);
+
+ });
+
+ it('should be navigatable with prefix', function () {
+ var menu = new HintMenu("cnx/", demolist);
+ menu._firstActive = true;
+
+ menu.limit(3);
+
+ expect(menu.show()).toBe(true);
+ expect(menu.prefix()).toEqual("");
+
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Titel</strong>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Untertitel</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).name()).toEqual("Veröffentlichungsdatum");
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].innerHTML).toEqual("<strong>Veröffentlichungsdatum</strong>");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(3)).toBe(undefined);
+
+ menu._prefix.add('a');
+ expect(menu.show()).toBe(true);
+ expect(menu.prefix()).toEqual("a");
+ expect(menu.shownItem(0).name()).toEqual("Autor");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong><mark>A</mark>utor</strong>");
+
+ menu._prefix.add('u');
+ expect(menu.show()).toBe(true);
+ expect(menu.prefix()).toEqual("au");
+ expect(menu.shownItem(0).name()).toEqual("Autor");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong><mark>Au</mark>tor</strong>");
+
+ menu._prefix.chop();
+ expect(menu.show()).toBe(true);
+ expect(menu.prefix()).toEqual("a");
+ expect(menu.shownItem(0).name()).toEqual("Autor");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong><mark>A</mark>utor</strong>");
+
+ menu._prefix.chop();
+ expect(menu.show()).toBe(true);
+ expect(menu.prefix()).toEqual("");
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Titel</strong>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Untertitel</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).name()).toEqual("Veröffentlichungsdatum");
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].innerHTML).toEqual("<strong>Veröffentlichungsdatum</strong>");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(3)).toBe(undefined);
+
+ // Forward
+ menu._prefix.chop();
+ expect(menu.show()).toBe(true);
+ expect(menu.prefix()).toEqual("");
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Titel</strong>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Untertitel</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).name()).toEqual("Veröffentlichungsdatum");
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].innerHTML).toEqual("<strong>Veröffentlichungsdatum</strong>");
+ expect(menu.shownItem(2).active()).toBe(false);
+
+ // Forward
+ menu.next();
+ expect(menu.prefix()).toEqual("");
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Titel</strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Untertitel</strong>");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2).name()).toEqual("Veröffentlichungsdatum");
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].innerHTML).toEqual("<strong>Veröffentlichungsdatum</strong>");
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(3)).toBe(undefined);
+
+ // Forward
+ menu.next();
+ expect(menu.prefix()).toEqual("");
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Titel</strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Untertitel</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).name()).toEqual("Veröffentlichungsdatum");
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].innerHTML).toEqual("<strong>Veröffentlichungsdatum</strong>");
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(menu.shownItem(3)).toBe(undefined);
+
+ // Forward
+ menu.next();
+ expect(menu.prefix()).toEqual("");
+ expect(menu.shownItem(0).name()).toEqual("Untertitel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Untertitel</strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Veröffentlichungsdatum");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Veröffentlichungsdatum</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).name()).toEqual("Länge");
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].innerHTML).toEqual("<strong>Länge</strong>");
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(menu.shownItem(3)).toBe(undefined);
+
+ // Forward
+ menu.next();
+ expect(menu.prefix()).toEqual("");
+ expect(menu.shownItem(0).name()).toEqual("Veröffentlichungsdatum");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Veröffentlichungsdatum</strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Länge");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Länge</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).name()).toEqual("Autor");
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].innerHTML).toEqual("<strong>Autor</strong>");
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(menu.shownItem(3)).toBe(undefined);
+
+ // Forward
+ menu.next();
+ expect(menu.prefix()).toEqual("");
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Titel</strong>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Untertitel</strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).name()).toEqual("Veröffentlichungsdatum");
+ expect(directElementChildrenByTagName(menu.element(),"li")[2].innerHTML).toEqual("<strong>Veröffentlichungsdatum</strong>");
+ expect(menu.shownItem(2).active()).toBe(false);
+ });
+
+
+ it('should be navigatable with a prefix (1)', function () {
+ var menu = new HintMenu("cnx/", demolist);
+ menu._firstActive = true;
+
+ menu.limit(3);
+
+ expect(menu.show()).toBe(true);
+ expect(menu.prefix()).toEqual("");
+
+ // Add prefix in uppercase - but check in lowercase
+ menu.prefix('El');
+ expect(menu.show()).toBe(true);
+
+ expect(menu.prefix()).toEqual("El");
+ expect(menu._prefix.active()).toEqual(false);
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Forward
+ menu.next();
+ expect(menu.prefix()).toEqual("El");
+ expect(menu._prefix.active()).toEqual(false);
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Forward
+ menu.next();
+ expect(menu.prefix()).toEqual("El");
+ expect(menu._prefix.active()).toEqual(true);
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Backward
+ menu.prev();
+ expect(menu.prefix()).toEqual("El");
+ expect(menu._prefix.active()).toEqual(false);
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2)).toBe(undefined);
+ });
+
+
+ it('should be navigatable with a prefix (2)', function () {
+ var menu = new HintMenu("cnx/", demolist);
+ menu._firstActive = true;
+
+ menu.limit(3);
+ expect(menu.show()).toBe(true);
+ expect(menu.prefix()).toEqual("");
+ menu.prefix('el');
+ expect(menu.show()).toBe(true);
+
+ expect(menu.prefix()).toEqual("el");
+ expect(menu._prefix.active()).toEqual(false);
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Backward
+ menu.prev();
+ expect(menu._prefix.active()).toEqual(true);
+
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Backward
+ menu.prev();
+ expect(menu._prefix.active()).toEqual(false);
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2)).toBe(undefined);
+ });
+
+ it('should be navigatable with a prefix (3)', function () {
+ var menu = new HintMenu("cnx/", demolist);
+ menu._firstActive = true;
+ menu.limit(3);
+ expect(menu.show()).toBe(true);
+ expect(menu.prefix()).toEqual("");
+ menu.prefix('el');
+ expect(menu.show()).toBe(true);
+
+ expect(menu.prefix()).toEqual("el");
+ expect(menu._prefix.active()).toEqual(false);
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ // Backward
+ menu.prev();
+ expect(menu._prefix.active()).toEqual(true);
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+
+ // Forward
+ menu.next();
+ expect(menu.prefix()).toEqual("el");
+ expect(menu._prefix.active()).toEqual(false);
+ expect(menu.shownItem(0).name()).toEqual("Titel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[0].innerHTML).toEqual("<strong>Tit<mark>el</mark></strong>");
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).name()).toEqual("Untertitel");
+ expect(directElementChildrenByTagName(menu.element(),"li")[1].innerHTML).toEqual("<strong>Untertit<mark>el</mark></strong>");
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2)).toBe(undefined);
+
+ });
+
+ it('should show screens by offset', function () {
+ var menu = new HintMenu('cnx/', demolist);
+ menu.limit(3);
+ expect(menu.show()).toBe(true);
+
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(false);
+
+ // Highlight the first entry
+ menu.next();
+
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(false);
+
+ // Highlight the second entry
+ menu.next();
+
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(3)).toBe(undefined);
+
+ expect(menu.shownItem(0).lcField()).toEqual(' titel');
+ expect(menu.shownItem(1).lcField()).toEqual(' untertitel');
+
+ // Move to first screen
+ menu.screen(0);
+ expect(menu.shownItem(0).lcField()).toEqual(' titel');
+ expect(menu.shownItem(1).lcField()).toEqual(' untertitel');
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(3)).toBe(undefined);
+
+ // Move to second screen
+ menu.screen(1);
+ expect(menu.shownItem(0).lcField()).toEqual(' untertitel');
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(3)).toBe(undefined);
+
+ menu.screen(2);
+ expect(menu.shownItem(0).lcField()).toEqual(' veröffentlichungsdatum');
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(3)).toBe(undefined);
+
+ menu.screen(1);
+ expect(menu.shownItem(0).lcField()).toEqual(' untertitel');
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(3)).toBe(undefined);
+ });
+
+ it('should show screens by offset when prefixed', function () {
+ var menu = new HintMenu('cnx/', demolist);
+ menu.limit(3);
+ expect(menu.prefix("e").show()).toBe(true);
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(false);
+
+ expect(menu.shownItem(0).element().innerHTML).toEqual('<strong>Tit<mark>e</mark>l</strong>');
+ menu.screen(1);
+ expect(menu.shownItem(0).element().innerHTML).toEqual('<strong>Unt<mark>e</mark>rtit<mark>e</mark>l</strong>');
+ });
+
+
+ it('should be page downable', function () {
+ var menu = new OwnMenu(demolonglist);
+ menu.limit(3);
+
+ expect(menu.show(0)).toBe(true);
+
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(0).lcField()).toEqual(' titel');
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(3)).toBe(undefined);
+
+ menu.pageDown();
+
+ expect(menu.shownItem(0).lcField()).toEqual(' länge');
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(false);
+
+ menu.pageDown();
+
+ expect(menu.shownItem(0).lcField()).toEqual(' corpusid');
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(false);
+
+ // Doesn't matter
+ menu.pageDown();
+
+ expect(menu.shownItem(0).lcField()).toEqual(' corpusid');
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(false);
+ });
+
+ it('should be page downable with prefix', function () {
+ var menu = new OwnMenu(demolonglist);
+ menu.limit(3);
+
+ expect(menu.prefix('e').show(0)).toBe(true);
+
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(0).lcField()).toEqual(' titel');
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(1).lcField()).toEqual(' untertitel');
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(2).lcField()).toEqual(' veröffentlichungsdatum');
+ expect(menu.shownItem(3)).toBe(undefined);
+
+ menu.pageDown();
+
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(0).lcField()).toEqual(' länge');
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(1).lcField()).toEqual(' genre');
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(2).lcField()).toEqual(' textid');
+
+ // Doesn't matter
+ menu.pageDown();
+
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(0).lcField()).toEqual(' länge');
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(1).lcField()).toEqual(' genre');
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(2).lcField()).toEqual(' textid');
+ });
+
+
+ it('should be page upable', function () {
+ var menu = new OwnMenu(demolonglist);
+ menu.limit(3);
+
+ // Choose the final value
+ expect(menu.show(1000)).toBe(true);
+
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(menu.shownItem(2).lcField()).toEqual(' textid');
+ expect(menu.shownItem(3)).toBe(undefined);
+
+ menu.pageUp();
+
+ expect(menu.shownItem(0).lcField()).toEqual(' länge');
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(false);
+
+ menu.pageUp();
+
+ expect(menu.shownItem(0).lcField()).toEqual(' titel');
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(false);
+
+ // Doesn't matter
+ menu.pageUp();
+
+ expect(menu.shownItem(0).lcField()).toEqual(' titel');
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(false);
+ });
+
+ it('should be page upable with prefix', function () {
+ var menu = new OwnMenu(demolonglist);
+ menu.limit(3);
+
+ // Choose the final value
+ expect(menu.prefix("e").show(1000)).toBe(true);
+
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(0).lcField()).toEqual(' länge');
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(1).lcField()).toEqual(' genre');
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(menu.shownItem(2).lcField()).toEqual(' textid');
+ expect(menu.shownItem(3)).toBe(undefined);
+
+ menu.pageUp();
+
+ expect(menu.shownItem(0).lcField()).toEqual(' titel');
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(1).lcField()).toEqual(' untertitel');
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(2).lcField()).toEqual(' veröffentlichungsdatum');
+
+ // Doesn't matter
+ menu.pageUp();
+
+ expect(menu.shownItem(0).lcField()).toEqual(' titel');
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(1).lcField()).toEqual(' untertitel');
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(2).lcField()).toEqual(' veröffentlichungsdatum');
+ });
+
+ it('should be view upable and downable (1)', function () {
+ var menu = new OwnMenu(demolonglist);
+ menu.limit(7);
+
+ // Choose the final value
+ expect(menu.show(1)).toBe(true);
+
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(0).lcField()).toEqual(' titel');
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(2).lcField()).toEqual(' veröffentlichungsdatum');
+ expect(menu.shownItem(6).active()).toBe(false);
+ expect(menu.shownItem(7)).toBe(undefined);
+
+ // Doesn't change anything
+ menu.viewUp();
+
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(0).lcField()).toEqual(' titel');
+ expect(menu.shownItem(1).active()).toBe(true);
+
+ menu.viewDown();
+
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(0).lcField()).toEqual(' untertitel');
+ expect(menu.shownItem(1).active()).toBe(false);
+
+ menu.viewDown();
+
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(0).lcField()).toEqual(' veröffentlichungsdatum');
+ expect(menu.shownItem(1).active()).toBe(false);
+
+ // No effect anymore
+ menu.viewDown();
+
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(0).lcField()).toEqual(' veröffentlichungsdatum');
+ expect(menu.shownItem(1).active()).toBe(false);
+ });
+
+ it('should be view upable and downable (2)', function () {
+
+ // List is longer than limit
+ var menu = new OwnMenu(demolist);
+ menu.limit(7);
+
+ // Choose the final value
+ expect(menu.show(1)).toBe(true);
+
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(0).lcField()).toEqual(' titel');
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(2).lcField()).toEqual(' veröffentlichungsdatum');
+ expect(menu.shownItem(4).active()).toBe(false);
+ expect(menu.shownItem(5)).toBe(undefined);
+
+ // Doesn't change anything
+ menu.viewUp();
+
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(0).lcField()).toEqual(' titel');
+ expect(menu.shownItem(1).active()).toBe(true);
+
+ menu.viewDown();
+
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(0).lcField()).toEqual(' titel');
+ expect(menu.shownItem(1).active()).toBe(true);
+ });
+
+
+ it('should scroll to a chosen value (1)', function () {
+ var menu = new OwnMenu(demolist);
+ menu.limit(3);
+
+ // Choose value 1
+ expect(menu.show(1)).toBe(true);
+
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(0).lcField()).toEqual(' titel');
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(3)).toBe(undefined);
+
+ // Choose value 2
+ expect(menu.show(2)).toBe(true);
+
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(0).lcField()).toEqual(' titel');
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(menu.shownItem(3)).toBe(undefined);
+ });
+
+ it('should scroll to a chosen value (2)', function () {
+ var menu = new OwnMenu(demolonglist);
+
+ // Choose value 3
+ expect(menu.limit(3).show(3)).toBe(true);
+ expect(menu.shownItem(0).lcField()).toEqual(' länge');
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(3)).toBe(undefined);
+ });
+
+ it.skip('should scroll to a chosen value after prefixing, if the chosen value is live');
+
+ it('should be expandable', function () {
+ var menu = new menuClass([]);
+ let entryData = 'empty';
+ menu.readItems([
+ ['a', '', function () { entryData = 'a' }],
+ ['bb', '', function () { entryData = 'bb' }],
+ ['ccc', '', function () { entryData = 'ccc' }],
+ ]);
+
+ expect(menu.limit(3).show(3)).toBe(true);
+ expect(menu.shownItem(0).lcField()).toEqual(' a');
+ expect(menu.shownItem(1).lcField()).toEqual(' bb');
+ expect(menu.shownItem(2).lcField()).toEqual(' ccc');
+ expect(entryData).toEqual('empty');
+ menu.shownItem(1).element().click();
+ expect(entryData).toEqual('bb');
+ expect(menu.lengthField().element().textContent).toEqual("a--bb--ccc--")
+ expect(menu.slider().length()).toEqual(3);
+
+ let obj = new (menu.itemClass())(
+ ['dddd','',function () { entryData = 'dddd'} ]
+ );
+ menu.append(obj);
+
+ expect(menu.limit(2).show(1)).toBe(true);
+ expect(menu.shownItem(0).lcField()).toEqual(' a');
+ expect(menu.shownItem(1).lcField()).toEqual(' bb');
+ menu.next();
+ expect(menu.shownItem(1).lcField()).toEqual(' ccc');
+ menu.next();
+ expect(menu.shownItem(1).lcField()).toEqual(' dddd');
+ menu.next();
+ expect(menu.shownItem(0).lcField()).toEqual(' a');
+ expect(menu.lengthField().element().textContent).toEqual("a--bb--ccc--dddd--")
+ expect(menu.slider().length()).toEqual(4);
+ });
+ });
+
+ describe('KorAP.Prefix', function () {
+ it('should be initializable', function () {
+ var p = new prefixClass();
+ expect(p.element().classList.contains('pref')).toBeTruthy();
+ expect(p.element().classList.contains('non-item')).toBeTruthy();
+ expect(p.isSet()).not.toBeTruthy();
+
+ /*
+ expect(mi.lcField()).toEqual(' baum');
+ */
+
+ });
+
+ it('should be modifiable', function () {
+ var p = new prefixClass();
+ expect(p.value()).toEqual('');
+ expect(p.element().firstChild).toBeNull();
+
+ // Set string
+ expect(p.value('Test')).toEqual('Test');
+ expect(p.value()).toEqual('Test');
+ expect(p.element().firstChild.nodeValue).toEqual('Test');
+
+ // Add string
+ expect(p.add('ified')).toEqual('Testified');
+ expect(p.value()).toEqual('Testified');
+ expect(p.element().firstChild.nodeValue).toEqual('Testified');
+
+ // Clear string
+ p.clear();
+ expect(p.value()).toEqual('');
+ expect(p.element().firstChild).toBeNull();
+
+ // Set string
+ expect(p.value('Test')).toEqual('Test');
+ expect(p.value()).toEqual('Test');
+ expect(p.element().firstChild.nodeValue).toEqual('Test');
+
+ expect(p.chop()).toEqual('Tes');
+ expect(p.value()).toEqual('Tes');
+ expect(p.element().firstChild.nodeValue).toEqual('Tes');
+
+ expect(p.chop()).toEqual('Te');
+ expect(p.value()).toEqual('Te');
+ expect(p.element().firstChild.nodeValue).toEqual('Te');
+
+ expect(p.chop()).toEqual('T');
+ expect(p.value()).toEqual('T');
+ expect(p.element().firstChild.nodeValue).toEqual('T');
+
+ expect(p.chop()).toEqual('');
+ expect(p.value()).toEqual('');
+ expect(p.element().firstChild).toBeNull();
+ });
+
+ it('should be activatable', function () {
+ var p = new prefixClass();
+ expect(p.value()).toEqual('');
+ expect(p.element().firstChild).toBeNull();
+
+ expect(p.value('Test')).toEqual('Test');
+ expect(p.element().firstChild.nodeValue).toEqual('Test');
+
+ expect(p.active()).not.toBeTruthy();
+ expect(p.element().classList.contains('active')).not.toBeTruthy();
+
+ p.active(true);
+ expect(p.active()).toBeTruthy();
+ expect(p.element().classList.contains('active')).toBeTruthy();
+ });
+ });
+
+ describe('KorAP.LengthField', function () {
+ it('should be initializable', function () {
+ var l = new lengthFieldClass();
+ expect(l.element().classList.contains('lengthField')).toBeTruthy();
+ expect(l.element().children.length).toEqual(0);
+ });
+
+ it('should be extensible', function () {
+ var l = new lengthFieldClass();
+ l.add(['Baum']);
+ expect(l.element().children.length).toEqual(1);
+ expect(l.element().children[0].nodeName).toEqual('SPAN');
+ expect(l.element().children[0].textContent).toEqual('Baum--');
+ l.add(['Fragezeichen']);
+ expect(l.element().children.length).toEqual(2);
+ expect(l.element().children[1].nodeName).toEqual('SPAN');
+ expect(l.element().children[1].textContent).toEqual('Fragezeichen--');
+ });
+
+ it('should be correctly initializable', function () {
+ var list = [
+ ["Constituency"],
+ ["Lemma"],
+ ["Morphology"],
+ ["Part-of-Speech"],
+ ["Syntax"]
+ ];
+
+ var menu = new OwnMenu(list);
+
+ expect(menu.lengthField().element().children.length).toEqual(5);
+ });
+ });
+
+ describe('KorAP.Slider', function () {
+
+ var demolonglist = [
+ ['Titel', 'title'],
+ ['Untertitel', 'subTitle'],
+ ['Veröffentlichungsdatum', 'pubDate'],
+ ['Länge', 'length'],
+ ['Autor', 'author'],
+ ['Genre', 'genre'],
+ ['corpusID', 'corpusID'],
+ ['docID', 'docID'],
+ ['textID', 'textID'],
+ ];
+
+ it('should correctly be initializable', function () {
+ var list = [
+ ["Constituency"],
+ ["Lemma"],
+ ["Morphology"],
+ ["Part-of-Speech"],
+ ["Syntax"]
+ ];
+
+ var menu = new OwnMenu(list);
+
+ menu._firstActive = true;
+ menu.limit(3);
+
+ expect(menu.show()).toBe(true);
+
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.slider().offset()).toEqual(0);
+ expect(menu.position).toEqual(0);
+ });
+
+ it('should correctly move on arrow keys', function () {
+ var list = [
+ ["Constituency"],
+ ["Lemma"],
+ ["Morphology"],
+ ["Part-of-Speech"],
+ ["Syntax"]
+ ];
+
+ var menu = new OwnMenu(list);
+
+ menu._firstActive = true;
+ menu.limit(3);
+
+ expect(menu.show()).toBe(true);
+
+ menu.next();
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.slider().offset()).toEqual(0);
+ expect(menu.position).toEqual(1);
+
+ menu.next();
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(menu.slider().offset()).toEqual(0);
+ expect(menu.position).toEqual(2);
+
+ menu.next();
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(menu.slider().offset()).toEqual(1);
+ expect(menu.position).toEqual(3);
+
+ menu.next();
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(menu.slider().offset()).toEqual(2);
+ expect(menu.position).toEqual(4);
+
+ menu.next();
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.slider().offset()).toEqual(0);
+ expect(menu.position).toEqual(0);
+
+ expect(menu.slider()._slider.style.height).toEqual('60%');
+ });
+
+ it('should correctly resize on prefixing', function () {
+ var menu = new OwnMenu(demolonglist);
+ menu._firstActive = true;
+ menu.limit(3);
+
+ expect(menu.show()).toBe(true);
+ expect(menu.slider().offset()).toEqual(0);
+ expect(menu.slider().length()).toEqual(9);
+
+ expect(menu.prefix("e").show()).toBe(true);
+ expect(menu.slider().length()).toEqual(6);
+
+ expect(menu.prefix("el").show()).toBe(true);
+ expect(menu.slider().length()).toEqual(2);
+
+ expect(menu.prefix("e").show()).toBe(true);
+ expect(menu.slider().length()).toEqual(6);
+
+ expect(menu.prefix("").show()).toBe(true);
+ expect(menu.slider().length()).toEqual(9);
+ });
+
+
+ it('should correctly move the list on mousemove', function () {
+ var list = [
+ ["Constituency"],
+ ["Lemma"],
+ ["Morphology"],
+ ["Part-of-Speech"],
+ ["Syntax"]
+ ];
+
+ var menu = new OwnMenu(list);
+
+ menu._firstActive = true;
+ menu.limit(3);
+
+ expect(menu.show()).toBe(true);
+
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.slider().offset()).toEqual(0);
+
+ // This will normally be done on
+ menu.slider()._rulerHeight = 100;
+ menu.slider()._sliderHeight = 40;
+ menu.slider()._event.initOffset = 0;
+ expect(menu.slider().length()).toEqual(5);
+
+ menu.slider().movetoRel(10);
+ expect(menu.slider().offset()).toEqual(0);
+
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(0).lcField()).toEqual(' constituency');
+ menu.slider().movetoRel(24);
+ expect(menu.slider().offset()).toEqual(0);
+ menu.slider().movetoRel(25);
+ expect(menu.slider().offset()).toEqual(0);
+
+ menu.slider().movetoRel(30);
+ expect(menu.slider().offset()).toEqual(1);
+ menu.slider().movetoRel(59);
+ expect(menu.slider().offset()).toEqual(1);
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(0).lcField()).toEqual(' lemma');
+
+ // Everything > 60 is offset 2
+ menu.slider().movetoRel(60);
+ expect(menu.slider().offset()).toEqual(2);
+ menu.slider().movetoRel(180);
+ expect(menu.slider().offset()).toEqual(2);
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(0).lcField()).toEqual(' morphology');
+
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(false);
+
+ // When the active cursor moves again - scroll to viewport
+ // cursor is before viewport
+ menu.next();
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(0).lcField()).toEqual(' lemma');
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).active()).toBe(false);
+
+ menu.next();
+ menu.next();
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(0).lcField()).toEqual(' lemma');
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(1).lcField()).toEqual(' morphology');
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(menu.shownItem(2).lcField()).toEqual(' part-of-speech');
+
+ menu.slider().movetoRel(0);
+ expect(menu.slider().offset()).toEqual(0);
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(0).lcField()).toEqual(' constituency');
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(1).lcField()).toEqual(' lemma');
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(2).lcField()).toEqual(' morphology');
+
+ // cursor is after viewport
+ menu.next();
+ expect(menu.slider().offset()).toEqual(2);
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(0).lcField()).toEqual(' morphology');
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(1).lcField()).toEqual(' part-of-speech');
+ expect(menu.shownItem(2).active()).toBe(true);
+ expect(menu.shownItem(2).lcField()).toEqual(' syntax');
+
+ menu.slider().movetoRel(0);
+ expect(menu.slider().offset()).toEqual(0);
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(0).lcField()).toEqual(' constituency');
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(1).lcField()).toEqual(' lemma');
+ expect(menu.shownItem(2).active()).toBe(false);
+ expect(menu.shownItem(2).lcField()).toEqual(' morphology');
+
+ menu.prev();
+ expect(menu.slider().offset()).toEqual(2);
+ expect(menu.shownItem(0).lcField()).toEqual(' morphology');
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).lcField()).toEqual(' part-of-speech');
+ expect(menu.shownItem(1).active()).toBe(true);
+ expect(menu.shownItem(2).lcField()).toEqual(' syntax');
+ expect(menu.shownItem(2).active()).toBe(false);
+
+ menu.prev();
+ menu.prev();
+ expect(menu.slider().offset()).toEqual(1);
+ expect(menu.shownItem(0).lcField()).toEqual(' lemma');
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).lcField()).toEqual(' morphology');
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).lcField()).toEqual(' part-of-speech');
+ expect(menu.shownItem(2).active()).toBe(false);
+
+ menu.slider().movetoRel(100);
+ expect(menu.slider().offset()).toEqual(2);
+ expect(menu.shownItem(0).lcField()).toEqual(' morphology');
+ expect(menu.shownItem(0).active()).toBe(false);
+ expect(menu.shownItem(1).lcField()).toEqual(' part-of-speech');
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).lcField()).toEqual(' syntax');
+ expect(menu.shownItem(2).active()).toBe(false);
+
+ menu.prev();
+ expect(menu.slider().offset()).toEqual(0);
+ expect(menu.shownItem(0).lcField()).toEqual(' constituency');
+ expect(menu.shownItem(0).active()).toBe(true);
+ expect(menu.shownItem(1).lcField()).toEqual(' lemma');
+ expect(menu.shownItem(1).active()).toBe(false);
+ expect(menu.shownItem(2).lcField()).toEqual(' morphology');
+ expect(menu.shownItem(2).active()).toBe(false);
+ });
+ });
+
+ describe('KorAP.MenuBenchmark', function () {
+ it('should run without errors', function () {
+ var menu = new menuClass([
+ ['Titel', 'title'],
+ ['Untertitel', 'subTitle'],
+ ['Veröffentlichungsdatum', 'pubDate'],
+ ['Länge', 'length'],
+ ['Autor', 'author']
+ ]);
+
+ menu.limit(3).show();
+
+ // Some actions
+ menu.next();
+ menu.next();
+ menu.prev();
+ menu.prev();
+ menu.prev();
+
+ menu.pageDown();
+ menu.pageUp();
+
+ // There is no fourth item in the list!
+ menu.prefix('e').show(4);
+ menu.next();
+ menu.next();
+ menu.prev();
+ menu.prev();
+ menu.prev();
+ });
+ });
diff --git a/dev/js/src-lib/menu/vite.config.js b/dev/js/src-lib/menu/vite.config.js
new file mode 100644
index 0000000..8d048a7
--- /dev/null
+++ b/dev/js/src-lib/menu/vite.config.js
@@ -0,0 +1,22 @@
+import { defineConfig } from 'vite';
+import path from 'path';
+
+const scssDir = path.resolve(__dirname, '../../../scss');
+
+export default defineConfig({
+ server: {
+ fs: {
+ allow: [
+ scssDir,
+ __dirname
+ ]
+ }
+ },
+ css: {
+ preprocessorOptions: {
+ scss: {
+ loadPaths: [scssDir]
+ }
+ }
+ }
+});
diff --git a/dev/js/src-lib/menu/vitest.config.js b/dev/js/src-lib/menu/vitest.config.js
new file mode 100644
index 0000000..2ca17f5
--- /dev/null
+++ b/dev/js/src-lib/menu/vitest.config.js
@@ -0,0 +1,9 @@
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+ test: {
+ environment: 'jsdom',
+ include: ['test/**/*Spec.js'],
+ globals: true
+ }
+});
diff --git a/package.json b/package.json
index bf2b976..03f44e8 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,7 @@
"grunt-shell": "^4.0.0",
"grunt-terser": "^2.0.0",
"imagemin": "^9.0.1",
+ "jsdom": "^29.1.1",
"optipng-bin": "^9.0.0",
"sass": "^1.99.0",
"vinyl-fs": "^4.0.2",