Improved slider behaviour and fixed prefix handling
diff --git a/Changes b/Changes
index 985c69e..c861526 100755
--- a/Changes
+++ b/Changes
@@ -1,3 +1,9 @@
+0.20 2016-05-23
+ - Improved menu using sliders.
+ - Improved menu to make prefixes chooseable,
+ even if list can't be filtered.
+ - Fixed choosable prefixes in hint menu.
+
0.19 2016-04-28
- Improved datepicker to enter date strings.
- Improved menus to have fixed length depending
diff --git a/dev/demo/menudemo.js b/dev/demo/menudemo.js
index 1e7fd7e..244aff4 100644
--- a/dev/demo/menudemo.js
+++ b/dev/demo/menudemo.js
@@ -74,8 +74,7 @@
document.getElementById('menu').appendChild(menu.element());
- menu._active = 3;
menu.limit(3);
- menu.show('');
+ menu.show(0); // Make this a 3
menu.focus();
});
diff --git a/dev/js/spec/hintSpec.js b/dev/js/spec/hintSpec.js
index acfa6f9..6cfd218 100644
--- a/dev/js/spec/hintSpec.js
+++ b/dev/js/spec/hintSpec.js
@@ -376,6 +376,7 @@
menuItem.lowlight();
expect(menuItem.element().innerHTML).toEqual("<span>CoreNLP</span><span class=\"desc\">This is my Example</span>");
+
});
});
@@ -390,7 +391,6 @@
];
it('should be initializable', function () {
-
var menu = menuClass.create(null, "cnx/", list);
expect(menu.element().nodeName).toEqual('UL');
expect(menu.element().style.opacity).toEqual("0");
@@ -399,6 +399,7 @@
// view
menu.show();
+ expect(menu.prefix()).toBe('');
// First element in list
expect(menu.item(0).active()).toBe(true);
@@ -411,6 +412,18 @@
// Last element in list
expect(menu.item(menu.length() - 1).active()).toBe(false);
expect(menu.item(menu.length() - 1).noMore()).toBe(true);
+
+ expect(menu.shownItem(0).active()).toBeTruthy();
+ expect(menu.shownItem(0).lcField()).toEqual(' constituency example 1');
+ expect(menu.shownItem(1).lcField()).toEqual(' lemma');
+ expect(menu.shownItem(2).lcField()).toEqual(' morphology example 2');
+
+ menu.next();
+ expect(menu.shownItem(1).active()).toBeTruthy();
+
+ menu.next();
+ expect(menu.shownItem(2).active()).toBeTruthy();
+
});
});
});
diff --git a/dev/js/spec/menuSpec.js b/dev/js/spec/menuSpec.js
index b8c7d44..e6d5049 100644
--- a/dev/js/spec/menuSpec.js
+++ b/dev/js/spec/menuSpec.js
@@ -767,10 +767,12 @@
});
- it('shouldn\'t be viewable with failing prefix', function () {
+ it('should choose prefix with failing prefix', function () {
var menu = KorAP.HintMenu.create("cnx/", list);
menu.limit(2);
- expect(menu.prefix("exit").show()).toBe(false);
+ expect(menu.prefix("exit").show()).toBe(true);
+ expect(menu.shownItem(0)).toBeUndefined();
+ expect(menu._prefix.active()).toBe(true);
});
@@ -1121,17 +1123,50 @@
expect(menu.shownItem(3)).toBe(undefined);
});
+ it('should show screens by offset when prefixed', function () {
+ var menu = KorAP.HintMenu.create('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>');
+ });
+
xit('should be page downable');
xit('should be page upable');
- xit('should scroll to a chosen value', function () {
+ it('should scroll to a chosen value', function () {
var menu = KorAP.OwnMenu.create(demolist);
menu.limit(3);
- this._active = 5;
+
+ // 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);
+
});
xit('should highlight a chosen value');
+
+ xit('should move the viewport to active, if active is not in the viewport');
});
@@ -1243,7 +1278,7 @@
});
describe('KorAP.Slider', function () {
- it('should be correctly initializable', function () {
+ it('should correctly be initializable', function () {
var list = [
["Constituency"],
["Lemma"],
@@ -1264,6 +1299,23 @@
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 = KorAP.OwnMenu.create(list);
+
+ menu._firstActive = true;
+ menu.limit(3);
+
+ expect(menu.show()).toBe(true);
menu.next();
expect(menu.shownItem(0).active()).toBe(false);
@@ -1301,7 +1353,57 @@
expect(menu.position).toEqual(0);
expect(menu.slider()._slider.style.height).toEqual('60%');
+ });
+ it('should correctly move the list on mousemove', function () {
+ var list = [
+ ["Constituency"],
+ ["Lemma"],
+ ["Morphology"],
+ ["Part-of-Speech"],
+ ["Syntax"]
+ ];
+
+ var menu = KorAP.OwnMenu.create(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;
+ 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');
});
});
});
diff --git a/dev/js/src/hint.js b/dev/js/src/hint.js
index 45d2169..1db2fd0 100644
--- a/dev/js/src/hint.js
+++ b/dev/js/src/hint.js
@@ -162,7 +162,7 @@
var c = this._inputField.container();
c.classList.add('active');
c.appendChild(menu.element());
- menu.show('');
+ menu.show();
menu.focus();
// Focus on input field
// this.inputField.element.focus();
diff --git a/dev/js/src/hint/prefix.js b/dev/js/src/hint/prefix.js
index 7586617..bdc7bdf 100644
--- a/dev/js/src/hint/prefix.js
+++ b/dev/js/src/hint/prefix.js
@@ -14,10 +14,11 @@
*/
onclick : function () {
var m = this.menu();
+ var value = this.value();
var h = m.hint();
m.hide();
- h.inputField().insert(this.value());
+ h.inputField().insert(value);
h.active = false;
}
};
diff --git a/dev/js/src/init.js b/dev/js/src/init.js
index 1ead697..61e783b 100644
--- a/dev/js/src/init.js
+++ b/dev/js/src/init.js
@@ -123,7 +123,7 @@
};
var result = document.getElementById('resultinfo');
- var resultButton;
+ var resultButton = null;
if (result != null) {
resultButton = result.appendChild(document.createElement('div'));
resultButton.classList.add('result', 'button');
@@ -248,11 +248,12 @@
};
// Initialize queries for document
- if (obj.tutorial)
+ if (obj.tutorial) {
obj.tutorial.initQueries(document);
- // Initialize documentation links
- obj.tutorial.initDocLinks(document);
+ // Initialize documentation links
+ obj.tutorial.initDocLinks(document);
+ };
/**
diff --git a/dev/js/src/match/info.js b/dev/js/src/match/info.js
index 97624dc..9d1922a 100644
--- a/dev/js/src/match/info.js
+++ b/dev/js/src/match/info.js
@@ -288,7 +288,7 @@
span.appendChild(treeElement);
span.addEventListener('click', function (e) {
- treemenu.show('');
+ treemenu.show();
treemenu.focus();
});
diff --git a/dev/js/src/menu.js b/dev/js/src/menu.js
index b9672c1..484f47c 100644
--- a/dev/js/src/menu.js
+++ b/dev/js/src/menu.js
@@ -7,6 +7,11 @@
* TODO: space is not a valid prefix!
* TODO: Prefix should be case sensitive!
* TODO: What is _pos and what is position?
+ * TODO: What is the difference between position
+ * and _active?
+ * TODO: if the prefix is not found, it should be visible and active!
+ * TODO: Bug: The slider vanishes, if the prefix wasn't found in the demo
+ * (example: type "elt")
*/
define([
'menu/item',
@@ -192,6 +197,8 @@
};
};
+ this._slider.length(this._list.length);
+
// Filter was successful - yeah!
return this._list.length > 0 ? true : false;
},
@@ -372,38 +379,71 @@
*
* @param {string} Prefix for filtering the list
*/
- show : function () {
-
- // Initialize the list
- if (!this._initList())
- return false;
+ show : function (active) {
// show menu based on initial offset
this._unmark(); // Unmark everything that was marked before
this.unshow(); // Delete everything that is shown
- this._showItems(0); // Show new item list
+
+ // Initialize the list
+ if (!this._initList()) {
+ // The prefix is not active
+ this._prefix.active(true);
+
+ // finally show the element
+ this._element.style.opacity = 1;
+
+ return true;
+ };
+
+ var offset = 0;
// Set the first element to active
// Todo: Or the last element chosen
- if (this._firstActive) {
- this.liveItem(0).active(true);
+ if (arguments.length === 1) {
+
+ // Normalize active value
+ if (active < 0)
+ active = 0;
+ else if (active > this.length())
+ active = this.length();
+
+ if (active > this._limit) {
+ offset = active;
+ if (offset > (this.length() - this._limit)) {
+ offset = this.length() - this._limit;
+ };
+ };
+
+ this.position = active;
+ this._active = active;
+ }
+
+ else if (this._firstActive) {
this.position = 0;
this._active = 0;
}
else {
this.position = -1;
- }
+ };
+
+ this._offset = offset;
+ this._showItems(offset); // Show new item list
+
+ // Make chosen value active
+ if (this.position !== -1) {
+ this.shownItem(this.position).active(true);
+ };
// The prefix is not active
this._prefix.active(false);
-
// finally show the element
this._element.style.opacity = 1;
// Show the slider
- this._slider.show();
+ //this._slider.show();
// Iterate to the active item
if (this._active !== -1 && !this._prefix.isSet()) {
diff --git a/dev/js/src/menu/item.js b/dev/js/src/menu/item.js
index b8e8808..8278d17 100644
--- a/dev/js/src/menu/item.js
+++ b/dev/js/src/menu/item.js
@@ -122,10 +122,50 @@
* @param {string} Prefix string for highlights
*/
highlight : function (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();
+ }
+
var children = this.element().childNodes;
for (var i = children.length -1; i >= 0; i--) {
this._highlight(children[i], prefix);
};
+
+ this._prefix = prefix;
+ },
+
+ /**
+ * Remove highlight of the menu item
+ */
+ lowlight : function () {
+ if (this._prefix === null)
+ return;
+
+ var e = this.element();
+
+ var marks = e.getElementsByTagName("mark");
+ for (var i = marks.length - 1; i >= 0; i--) {
+ // Create text node clone
+ var x = document.createTextNode(
+ marks[i].firstChild.nodeValue
+ );
+
+ // Replace with content
+ marks[i].parentNode.replaceChild(
+ x,
+ marks[i]
+ );
+ };
+
+ // Remove consecutive textnodes
+ e.normalize();
+ this._prefix = null;
},
// Highlight a certain substring of the menu item
@@ -176,30 +216,6 @@
};
},
- /**
- * Remove highlight of the menu item
- */
- lowlight : function () {
- var e = this.element();
-
- var marks = e.getElementsByTagName("mark");
- for (var i = marks.length - 1; i >= 0; i--) {
- // Create text node clone
- var x = document.createTextNode(
- marks[i].firstChild.nodeValue
- );
-
- // Replace with content
- marks[i].parentNode.replaceChild(
- x,
- marks[i]
- );
- };
-
- // Remove consecutive textnodes
- e.normalize();
- },
-
// Initialize menu item
_init : function (params) {
@@ -212,6 +228,7 @@
this._action = params[1];
this._lcField = ' ' + this.content().textContent.toLowerCase();
+ this._highlight = null;
return this;
},
diff --git a/dev/js/src/menu/slider.js b/dev/js/src/menu/slider.js
index c35f284..d71bae1 100644
--- a/dev/js/src/menu/slider.js
+++ b/dev/js/src/menu/slider.js
@@ -1,14 +1,33 @@
define({
/**
- * Create new prefix object.
+ * Create new slider object.
+ * The slider will only be used by mouse - touch support
+ * shouldn't be necessary.
*/
create : function (menu) {
return Object.create(this)._init(menu);
},
- _mousemove : function (e) {
- var relativePos = e.clientY - this._event.init;
+ length : function (i) {
+ if (arguments.length === 0)
+ return this._length;
+ if (i == this._length)
+ return;
+ this._length = i;
+ this._initSize();
+ },
+
+ limit : function (i) {
+ if (arguments.length === 0)
+ return this._limit;
+ if (i == this._limit)
+ return;
+ this._limit = i;
+ this._initSize();
+ },
+
+ movetoRel : function (relativePos) {
var diffHeight = (this._rulerHeight - this._sliderHeight);
var relativeOffset = (relativePos / diffHeight);
@@ -16,34 +35,38 @@
if (off !== undefined) {
this._menu.screen(off);
};
-
- e.halt();
-
- // Support touch!
},
- _mouseup : function (e) {
- this._element.classList.remove('active');
- window.removeEventListener('mousemove', this._event.mov);
- window.removeEventListener('mouseup', this._event.up);
+ movetoAbs : function (absPos) {
+ var absOffset = (absPos / this._rulerHeight);
+
+ var off = this.offset(parseInt(absOffset * (this._screens + 1)));
+ if (off !== undefined) {
+ this._menu.screen(off);
+ };
},
- _mousedown : function (e) {
- // Bind drag handler
- var ev = this._event;
- ev.init = e.clientY - (this._step * this._offset);
- ev.mov = this._mousemove.bind(this);
- ev.up = this._mouseup.bind(this);
+ offset : function (off) {
+ if (arguments.length === 0)
+ return this._offset;
- this._rulerHeight = this._element.clientHeight; // offsetHeight?
- this._sliderHeight = this._slider.clientHeight; // offsetHeight?
+ if (off > this._screens) {
+ off = this._screens;
+ }
+ else if (off < 0) {
+ off = 0;
+ };
- this._element.classList.add('active');
+ if (off === this._offset)
+ return undefined;
- window.addEventListener('mousemove', ev.mov);
- window.addEventListener('mouseup', ev.up);
+ this._offset = off;
+ this._slider.style.top = (this._step * off) + '%';
+ return off;
+ },
- e.halt();
+ element : function () {
+ return this._element;
},
// Initialize prefix object
@@ -61,51 +84,78 @@
document.createElement('span')
);
- this._element.appendChild(document.createElement('div'))
- // Do not mark the menu on mousedown
- .addEventListener('mousedown', function (e) {
- e.halt()
- });
+ this._ruler = this._element.appendChild(document.createElement('div'));
- // TODO: Support touch!
+ // Do not mark the menu on mousedown
+ this._ruler.addEventListener('mousedown', function (e) {
+ e.halt()
+ }, false);
+
+ // Move the slider to the click position
+ this._ruler.addEventListener('click', this._mouseclick.bind(this), false);
+
this._slider.addEventListener('mousedown', this._mousedown.bind(this), false);
return this;
},
_initSize : function () {
+ if (this._length <= this._limit) {
+ this._element.style.display = 'none';
+ return;
+ }
+ else {
+ this._element.style.display = 'block';
+ };
+
this._height = ((this._limit / this._length) * 100);
this._screens = this._length - this._limit;
this._step = (100 - this._height) / this._screens;
- },
-
- show : function (i) {
this._slider.style.height = this._height + '%';
},
- length : function (i) {
- this._length = i;
- this._initSize();
+ _initClientHeight : function () {
+ this._rulerHeight = this._element.clientHeight; // offsetHeight?
+ this._sliderHeight = this._slider.clientHeight; // offsetHeight?
},
- limit : function (i) {
- this._limit = i;
- this._initSize();
+ _mousemove : function (e) {
+ this.movetoRel(e.clientY - this._event.init);
+ e.halt();
+ // Support touch!
},
- offset : function (off) {
- if (arguments.length === 0)
- return this._offset;
-
- if (off === this._offset || off > this._screens || off < 0)
- return undefined;
-
- this._offset = off;
- this._slider.style.top = (this._step * off) + '%';
- return off;
+ _mouseup : function (e) {
+ this._element.classList.remove('active');
+ window.removeEventListener('mousemove', this._event.mov);
+ window.removeEventListener('mouseup', this._event.up);
+ this._menu.focus();
},
- element : function () {
- return this._element;
+ _mousedown : function (e) {
+ // Bind drag handler
+ var ev = this._event;
+ ev.init = e.clientY - (this._step * this._offset);
+ ev.mov = this._mousemove.bind(this);
+ ev.up = this._mouseup.bind(this);
+
+ // TODO: This may not be necessary all the time
+ this._initClientHeight();
+
+ this._element.classList.add('active');
+
+ window.addEventListener('mousemove', ev.mov);
+ window.addEventListener('mouseup', ev.up);
+
+ e.halt();
+ },
+
+ _mouseclick : function (e) {
+ this._initClientHeight();
+
+ this.movetoAbs(
+ e.clientY - this._ruler.getClientRects()[0].top
+ );
+ e.halt();
}
});
diff --git a/dev/scss/header/menu.scss b/dev/scss/header/menu.scss
index 5cfbad4..582e543 100644
--- a/dev/scss/header/menu.scss
+++ b/dev/scss/header/menu.scss
@@ -67,9 +67,9 @@
> div.ruler {
position: absolute;
right: 0px;
- margin-right: -12px;
+ margin-right: -14px;
background-color: transparent;
- width: 12px;
+ width: 14px;
height: 100%;
opacity: 0;
cursor: pointer;
@@ -77,25 +77,27 @@
position: absolute;
@include choose-active;
display: block;
- right: 1px;
- width: 6px;
+ right: 0;
+ width: 10px;
z-index: 600;
- border-radius: 4px;
+ border: {
+ radius: 4px;
+ width: 2px;
+ style: solid;
+ }
}
-
&.active > span {
@include choose-hover;
}
-
> div {
box-shadow: $choose-box-shadow;
position: absolute;
right: 0;
border: {
- width: 1px;
+ width: 2px;
style: solid;
}
- width: 8px;
+ width: 10px;
@include choose-item;
// background-color: -grey;
height: 100%;
diff --git a/lib/Kalamar.pm b/lib/Kalamar.pm
index d6e0cb7..ee19cd7 100644
--- a/lib/Kalamar.pm
+++ b/lib/Kalamar.pm
@@ -4,7 +4,7 @@
use Mojo::JSON 'decode_json';
# Minor version - may be patched from package.json
-our $VERSION = '0.19';
+our $VERSION = '0.20';
# TODO: The FAQ-Page has a contact form for new questions
# TODO: Embed query serialization
@@ -152,7 +152,7 @@
=head2 COPYRIGHT AND LICENSE
-Copyright (C) 2015, L<IDS Mannheim|http://www.ids-mannheim.de/>
+Copyright (C) 2015-2016, L<IDS Mannheim|http://www.ids-mannheim.de/>
Author: L<Nils Diewald|http://nils-diewald.de/>
Kalamar is developed as part of the L<KorAP|http://korap.ids-mannheim.de/>
diff --git a/package.json b/package.json
index 885590b..d50b1de 100755
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "Kalamar",
"description": "Mojolicious-based Frontend for KorAP",
- "version": "0.19.0",
+ "version": "0.20.0",
"repository" : {
"type": "git",
"url": "https://github.com/KorAP/Kalamar.git"