Added rewrite capabilities and separated menu
diff --git a/public/js/demo/vc.html b/public/js/demo/vc.html
index 79f7d0e..9254d68 100644
--- a/public/js/demo/vc.html
+++ b/public/js/demo/vc.html
@@ -52,7 +52,14 @@
"@type":"korap:doc",
"key":"Veröffentlichungsort",
"value":"hihi",
- "match":"match:eq"
+ "match":"match:eq",
+ "rewrites" : [
+ {
+ "@type": "korap:rewrite",
+ "src" : "policy",
+ "operation" : "operation:injection",
+ }
+ ]
}
]
}
diff --git a/public/js/spec/menuSpec.js b/public/js/spec/menuSpec.js
index 0477442..98f6e72 100644
--- a/public/js/spec/menuSpec.js
+++ b/public/js/spec/menuSpec.js
@@ -49,6 +49,88 @@
}
};
+KorAP.OwnMenu = {
+ create : function (params) {
+ return Object.create(KorAP.Menu)
+ .upgradeTo(KorAP.OwnMenu)
+ ._init(KorAP.OwnMenuItem, params);
+ }
+};
+
+
+// Support for hint
+KorAP.HintMenu = {
+ create : function (context, params) {
+ var obj = Object.create(KorAP.Menu)
+ .upgradeTo(KorAP.HintMenu)
+ ._init(KorAP.HintMenuItem, params);
+ obj._context = context;
+ return obj;
+ }
+};
+
+KorAP.HintMenuItem = {
+ create : function (params) {
+ return Object.create(KorAP.MenuItem)
+ .upgradeTo(KorAP.HintMenuItem)
+ ._init(params);
+ },
+ content : function (content) {
+ if (arguments.length === 1) {
+ this._content = content;
+ };
+ return this._content;
+ },
+ _init : function (params) {
+ if (params[0] === undefined || params[1] === undefined)
+ throw new Error("Missing parameters");
+
+ this._name = params[0];
+ this._action = params[1];
+ this._lcField = ' ' + this._name.toLowerCase();
+
+ if (params.length > 2) {
+ this._desc = params[2];
+ this._lcField += " " + this._desc.toLowerCase();
+ };
+
+ return this;
+ },
+ name : function () {
+ return this._name;
+ },
+ action : function () {
+ return this._action;
+ },
+ desc : function () {
+ return this._desc;
+ },
+ element : function () {
+ // already defined
+ if (this._element !== undefined)
+ return this._element;
+
+ // Create list item
+ var li = document.createElement("li");
+ 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._element = li;
+ }
+};
+
+
describe('KorAP.MenuItem', function () {
it('should be initializable', function () {
@@ -219,36 +301,29 @@
menuItem.lowlight();
expect(menuItem.element().innerHTML).toEqual(plain);
});
-
-});
-
-
-/*
-
-describe('KorAP.MenuItem', function () {
-
});
describe('KorAP.Menu', function () {
-
- var list = [
- ["Constituency", "c=", "Example 1"],
- ["Lemma", "l="],
- ["Morphology", "m=", "Example 2"],
- ["Part-of-Speech", "p="],
- ["Syntax", "syn="]
- ];
-
-
it('should be initializable', function () {
+ var list = [
+ ["Constituency"],
+ ["Lemma"],
+ ["Morphology"],
+ ["Part-of-Speech"],
+ ["Syntax"]
+ ];
- var menu = KorAP.Menu.create("cnx/", list);
- expect(menu.context).toEqual('cnx/');
- expect(menu.element.nodeName).toEqual('UL');
- expect(menu.element.style.opacity).toEqual("0");
+ var menu = KorAP.OwnMenu.create(list);
+ expect(menu.itemClass()).toEqual(KorAP.OwnMenuItem);
+ expect(menu.element().nodeName).toEqual('UL');
+ expect(menu.element().style.opacity).toEqual("0");
+ expect(menu.limit()).toEqual(8);
- KorAP.limit = 8;
+ menu.limit(9);
+ expect(menu.limit()).toEqual(9);
+
+ menu.limit(8);
// view
menu.show();
@@ -256,109 +331,155 @@
// 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);
+ expect(menu.item(menu.length() - 1).active()).toBe(false);
+ expect(menu.item(menu.length() - 1).noMore()).toBe(true);
});
it('should be visible', function () {
- var menu = KorAP.Menu.create("cnx/", list);
+ var list = [
+ ["Constituency", "c=", "Example 1"],
+ ["Lemma", "l="],
+ ["Morphology", "m=", "Example 2"],
+ ["Part-of-Speech", "p="],
+ ["Syntax", "syn="]
+ ];
+ var menu = KorAP.HintMenu.create("cnx/", list);
expect(menu.delete()).toBe(undefined);
-
- KorAP.limit = 3;
+ menu.limit(3);
expect(menu.show()).toBe(undefined);
- expect(menu.element.firstChild.innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
- expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Lemma</strong>");
- expect(menu.element.childNodes[1].getAttribute("data-action")).toEqual("l=");
- expect(menu.element.childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
- expect(menu.element.childNodes[3]).toBe(undefined);
+ expect(menu.element().firstChild.innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Lemma</strong>");
+ expect(menu.element().childNodes[1].getAttribute("data-action")).toEqual("l=");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.element().childNodes[3]).toBe(undefined);
// Check boundaries
- expect(menu.element.childNodes[0].classList.contains("no-more")).toBe(true);
- expect(menu.element.childNodes[1].classList.contains("no-more")).toBe(false);
- expect(menu.element.childNodes[2].classList.contains("no-more")).toBe(false);
+ expect(menu.element().childNodes[0].classList.contains("no-more")).toBe(true);
+ expect(menu.element().childNodes[1].classList.contains("no-more")).toBe(false);
+ expect(menu.element().childNodes[2].classList.contains("no-more")).toBe(false);
});
-
it('should be filterable', function () {
- var menu = KorAP.Menu.create("cnx/", list);
+ var list = [
+ ["Constituency", "c=", "Example 1"],
+ ["Lemma", "l="],
+ ["Morphology", "m=", "Example 2"],
+ ["Part-of-Speech", "p="],
+ ["Syntax", "syn="]
+ ];
- KorAP.limit = 3;
+ var menu = KorAP.HintMenu.create("cnx/", list);
+ menu.limit(3);
expect(menu.show("o")).toBe(undefined);
- expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>C<em>o</em>nstituency</strong><span>Example 1</span>");
- expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>M<em>o</em>rphology</strong><span>Example 2</span>");
- expect(menu.element.childNodes[2].innerHTML).toEqual("<strong>Part-<em>o</em>f-Speech</strong>");
- expect(menu.element.childNodes[3]).toBe(undefined);
+ expect(menu.element().childNodes[0].innerHTML).toEqual("<strong>C<mark>o</mark>nstituency</strong><span>Example 1</span>");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>M<mark>o</mark>rph<mark>o</mark>l<mark>o</mark>gy</strong><span>Example 2</span>");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Part-<mark>o</mark>f-Speech</strong>");
+ expect(menu.element().childNodes[3]).toBe(undefined);
// Check boundaries
- expect(menu.element.childNodes[0].classList.contains("no-more")).toBe(true);
- expect(menu.element.childNodes[1].classList.contains("no-more")).toBe(false);
- expect(menu.element.childNodes[2].classList.contains("no-more")).toBe(true);
+ expect(menu.element().childNodes[0].classList.contains("no-more")).toBe(true);
+ expect(menu.element().childNodes[1].classList.contains("no-more")).toBe(false);
+ expect(menu.element().childNodes[2].classList.contains("no-more")).toBe(true);
-
- KorAP.limit = 2;
+ menu.limit(2);
expect(menu.show("o")).toBe(undefined);
- expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>C<em>o</em>nstituency</strong><span>Example 1</span>");
- expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>M<em>o</em>rphology</strong><span>Example 2</span>");
- expect(menu.element.childNodes[2]).toBe(undefined);
+ expect(menu.element().childNodes[0].innerHTML).toEqual("<strong>C<mark>o</mark>nstituency</strong><span>Example 1</span>");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>M<mark>o</mark>rph<mark>o</mark>l<mark>o</mark>gy</strong><span>Example 2</span>");
+ expect(menu.element().childNodes[2]).toBe(undefined);
// Check boundaries
- expect(menu.element.childNodes[0].classList.contains("no-more")).toBe(true);
- expect(menu.element.childNodes[1].classList.contains("no-more")).toBe(false);
- expect(menu.element.childNodes[2]).toBe(undefined);
+ expect(menu.element().childNodes[0].classList.contains("no-more")).toBe(true);
+ expect(menu.element().childNodes[1].classList.contains("no-more")).toBe(false);
+ expect(menu.element().childNodes[2]).toBe(undefined);
+
+ expect(menu.show("e")).toBe(undefined);
+ expect(menu.element().childNodes[0].innerHTML).toEqual("<strong>Constitu<mark>e</mark>ncy</strong><span><mark>E</mark>xampl<mark>e</mark> 1</span>");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Morphology</strong><span><mark>E</mark>xampl<mark>e</mark> 2</span>");
+ expect(menu.element().childNodes[2]).toBe(undefined);
+
+ menu.limit(5);
+
+ expect(menu.show("a")).toBe(undefined);
+ expect(menu.element().childNodes[0].innerHTML).toEqual("<strong>Constituency</strong><span>Ex<mark>a</mark>mple 1</span>");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Lemm<mark>a</mark></strong>");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span>Ex<mark>a</mark>mple 2</span>");
+ expect(menu.element().childNodes[3].innerHTML).toEqual("<strong>P<mark>a</mark>rt-of-Speech</strong>");
+ expect(menu.element().childNodes[4].innerHTML).toEqual("<strong>Synt<mark>a</mark>x</strong>");
+ expect(menu.element().childNodes[5]).toBe(undefined);
+
});
+
it('should be nextable', function () {
- var menu = KorAP.Menu.create("cnx/", list);
+ var list = [
+ ["Constituency", "c=", "Example 1"],
+ ["Lemma", "l="],
+ ["Morphology", "m=", "Example 2"],
+ ["Part-of-Speech", "p="],
+ ["Syntax", "syn="]
+ ];
- KorAP.limit = 3;
+ var menu = KorAP.HintMenu.create("cnx/", list);
+
+ // Show only 3 items
+ menu.limit(3);
+
expect(menu.show()).toBe(undefined);
-
- expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+ expect(menu.element().childNodes[0].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
expect(menu.shownItem(0).active()).toBe(true);
- expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Lemma</strong>");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Lemma</strong>");
expect(menu.shownItem(1).active()).toBe(false);
- expect(menu.element.childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
expect(menu.shownItem(2).active()).toBe(false);
- expect(menu.element.childNodes[3]).toBe(undefined);
+ expect(menu.element().childNodes[3]).toBe(undefined);
// Activate next (1)
menu.next();
- expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+ expect(menu.element().childNodes[0].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
expect(menu.shownItem(0).active()).toBe(false);
- expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Lemma</strong>");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Lemma</strong>");
expect(menu.shownItem(1).active()).toBe(true);
- expect(menu.element.childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
expect(menu.shownItem(2).active()).toBe(false);
- expect(menu.element.childNodes[3]).toBe(undefined);
+ expect(menu.element().childNodes[3]).toBe(undefined);
// Activate next (2)
menu.next();
- expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
+ expect(menu.element().childNodes[0].innerHTML).toEqual("<strong>Constituency</strong><span>Example 1</span>");
expect(menu.shownItem(0).active()).toBe(false);
- expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Lemma</strong>");
+ expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Lemma</strong>");
expect(menu.shownItem(1).active()).toBe(false);
- expect(menu.element.childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+ expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
expect(menu.shownItem(2).active()).toBe(true);
- expect(menu.element.childNodes[3]).toBe(undefined);
+ expect(menu.element().childNodes[3]).toBe(undefined);
// Activate next (3)
menu.next();
- expect(menu.element.childNodes[0].innerHTML).toEqual("<strong>Lemma</strong>");
+// expect(menu.element().childNodes[0].innerHTML).toEqual("<strong>Lemma</strong>");
expect(menu.shownItem(0).active()).toBe(false);
- expect(menu.element.childNodes[1].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
+// expect(menu.element().childNodes[1].innerHTML).toEqual("<strong>Morphology</strong><span>Example 2</span>");
expect(menu.shownItem(1).active()).toBe(false);
- expect(menu.element.childNodes[2].innerHTML).toEqual("<strong>Part-of-Speech</strong>");
+// expect(menu.element().childNodes[2].innerHTML).toEqual("<strong>Part-of-Speech</strong>");
expect(menu.shownItem(2).active()).toBe(true);
- expect(menu.element.childNodes[3]).toBe(undefined);
+ expect(menu.element().childNodes[3]).toBe(undefined);
+ });
+});
+
+
+/*
+describe('KorAP.Menu', function () {
+
+
// Activate next (4)
menu.next();
diff --git a/public/js/spec/vcSpec.js b/public/js/spec/vcSpec.js
index 11ada63..49e9275 100644
--- a/public/js/spec/vcSpec.js
+++ b/public/js/spec/vcSpec.js
@@ -1,6 +1,5 @@
/*
Todo: In demoSpec: Create "and" on the last element of the top "or"-Group
-
*/
@@ -1113,6 +1112,63 @@
vc.clean();
expect(vc.toQuery()).toEqual('');
});
+
+ it('should flatten on import', function () {
+ var vc = KorAP.VirtualCollection.create().render({
+ "@type":"korap:docGroup",
+ "operation":"operation:or",
+ "operands":[
+ {
+ "@type":"korap:docGroup",
+ "operation":"operation:or",
+ "operands":[
+ {
+ "@type":"korap:doc",
+ "key":"Titel",
+ "value":"Baum",
+ "match":"match:eq"
+ },
+ {
+ "@type":"korap:doc",
+ "key":"Veröffentlichungsort",
+ "value":"hihi",
+ "match":"match:eq"
+ },
+ {
+ "@type":"korap:docGroup",
+ "operation":"operation:or",
+ "operands":[
+ {
+ "@type":"korap:doc",
+ "key":"Titel",
+ "value":"Baum",
+ "match":"match:eq"
+ },
+ {
+ "@type":"korap:doc",
+ "key":"Veröffentlichungsort",
+ "value":"hihi",
+ "match":"match:eq"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "@type":"korap:doc",
+ "key":"Untertitel",
+ "value":"huhu",
+ "match":"match:eq"
+ }
+ ]
+ });
+
+ expect(vc.toQuery()).toEqual(
+ 'Titel = "Baum" | Veröffentlichungsort = "hihi" | Untertitel = "huhu"'
+ );
+
+
+ });
});
describe('KorAP.Operators', function () {
@@ -1784,3 +1840,51 @@
});
});
+describe('KorAP.Rewrite', function () {
+
+ it('should be initializable', function () {
+ var rewrite = KorAP.Rewrite.create({
+ "@type" : "korap:rewrite",
+ "operation" : "operation:modification",
+ "src" : "querySerializer",
+ "scope" : "tree"
+ });
+ expect(rewrite.toString()).toEqual('Modification of "tree" by "querySerializer"');
+ });
+
+ it('should be deserialized by docs', function () {
+ var doc = KorAP.Doc.create(undefined,
+ {
+ "@type":"korap:doc",
+ "key":"Titel",
+ "value":"Baum",
+ "match":"match:eq"
+ });
+
+ expect(doc.element().classList.contains('doc')).toBeTruthy();
+ expect(doc.element().classList.contains('rewritten')).toBe(false);
+
+ doc = KorAP.Doc.create(undefined,
+ {
+ "@type":"korap:doc",
+ "key":"Titel",
+ "value":"Baum",
+ "match":"match:eq",
+ "rewrites" : [
+ {
+ "@type" : "korap:rewrite",
+ "operation" : "operation:modification",
+ "src" : "querySerializer",
+ "scope" : "tree"
+ }
+ ]
+ });
+
+ expect(doc.element().classList.contains('doc')).toBeTruthy();
+ expect(doc.element().classList.contains('rewritten')).toBeTruthy();
+ });
+/*
+ it('should be deserialized by docGroups', function () {
+ });
+*/
+});
diff --git a/public/js/src/hint.js b/public/js/src/hint.js
index 4f5a8b5..0b696d1 100644
--- a/public/js/src/hint.js
+++ b/public/js/src/hint.js
@@ -564,6 +564,9 @@
this.item(this._list[this._list.length - 1]).noMore(bool);
},
_initList : function () {
+
+console.log("..." + this._items.length);
+
if (this._list === undefined)
this._list = [];
else if (this._list.length != 0) {
@@ -573,7 +576,7 @@
this._offset = 0;
- if (this.prefix.length <= 0) {
+ if (this.prefix().length <= 0) {
for (var i = 0; i < this._items.length; i++)
this._list.push(i);
return true;
diff --git a/public/js/src/menu.js b/public/js/src/menu.js
index 3b79bdd..b251000 100644
--- a/public/js/src/menu.js
+++ b/public/js/src/menu.js
@@ -3,6 +3,388 @@
(function (KorAP) {
"use strict";
+ // Default maximum number of menu items
+ KorAP.menuLimit = 8;
+
+ /**
+ * 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).
+ */
+ KorAP.Menu = {
+ /**
+ * Create new Menu based on the action prefix
+ * and a list of menu items.
+ *
+ * @this {Menu}
+ * @constructor
+ * @param {string} Context prefix
+ * @param {Array.<Array.<string>>} List of menu items
+ */
+ create : function (params) {
+ return Object.create(KorAP.Menu)._init(params);
+ },
+
+ _init : function (itemClass, params) {
+ // this._element.addEventListener("click", chooseHint, false);
+ this._itemClass = itemClass;
+ this._element = document.createElement("ul");
+ this._element.style.opacity = 0;
+
+ this.active = false;
+ this._items = new Array();
+ var i;
+ for (i in params) {
+ var obj = itemClass.create(params[i]);
+ this._items.push(
+ obj
+ );
+ };
+ this._limit = KorAP.menuLimit;
+ this._position = 0; // position in the active list
+ this._active = -1; // active item in the item list
+
+ this._reset();
+ return this;
+ },
+
+ element : function () {
+ return this._element;
+ },
+
+ itemClass : function () {
+ return this._itemClass;
+ },
+
+ /**
+ * Get and set numerical value for limit
+ */
+ limit : function (limit) {
+ if (arguments.length === 1)
+ this._limit = limit;
+ return this._limit;
+ },
+
+ /**
+ * Upgrade this object to another object,
+ * while private data stays intact.
+ *
+ * @param {Object] An object with properties.
+ */
+ upgradeTo : function (props) {
+ for (var prop in props) {
+ this[prop] = props[prop];
+ };
+ return this;
+ },
+
+ _reset : function () {
+ this._offset = 0;
+ this._pos = 0;
+ this._prefix = undefined;
+ },
+
+ /**
+ * Filter the list and make it visible
+ *
+ * @param {string} Prefix for filtering the list
+ */
+ show : function (prefix) {
+ this._prefix = prefix;
+
+ // Initialize the list
+ if (!this._initList())
+ return;
+
+ // show based on offset
+ this._showItems(0);
+
+ // Set the first element to active
+ this.liveItem(0).active(true);
+
+ this._position = 0;
+ this._active = this._list[0];
+
+ // Add classes for rolling menus
+ this._boundary(true);
+ },
+
+ /**
+ * Get a specific item from the complete list
+ *
+ * @param {number} index of the list item
+ */
+ item : function (index) {
+ return this._items[index]
+ },
+
+ _initList : function () {
+
+ if (this._list === undefined) {
+ this._list = [];
+ }
+ else if (this._list.length != 0) {
+ this._boundary(false);
+ this._list.length = 0;
+ };
+
+ // Offset is initially zero
+ this._offset = 0;
+
+ if (this.prefix().length <= 0) {
+ for (var i = 0; i < this._items.length; i++)
+ this._list.push(i);
+ return true;
+ };
+
+ var pos;
+ var paddedPrefix = " " + this.prefix();
+
+ for (pos = 0; pos < this._items.length; pos++) {
+ if ((this.item(pos).lcField().indexOf(paddedPrefix)) >= 0)
+ this._list.push(pos);
+ };
+
+ if (this._list.length == 0) {
+ for (pos = 0; pos < this._items.length; pos++) {
+ if ((this.item(pos).lcField().indexOf(this.prefix())) >= 0)
+ this._list.push(pos);
+ };
+ };
+
+ // Filter was successful
+ return this._list.length > 0 ? true : false;
+ },
+
+ // Set boundary for viewport
+ _boundary : function (bool) {
+ this.item(this._list[0]).noMore(bool);
+ this.item(this._list[this._list.length - 1]).noMore(bool);
+ },
+
+ /**
+ * Get the prefix for filtering,
+ * e.g. "ve"" for "verb"
+ */
+ prefix : function () {
+ return this._prefix || '';
+ },
+
+ _showItems : function (offset) {
+ this.delete();
+
+ // Use list
+ var shown = 0;
+ var i;
+ for (i in this._list) {
+
+ // Don't show - it's before offset
+ if (shown++ < offset)
+ continue;
+
+ this._append(this._list[i]);
+
+ if (shown >= (this.limit() + this._offset))
+ break;
+ };
+ },
+
+ /**
+ * Delete all visible items from the menu element
+ */
+ delete : function () {
+ var child;
+ for (var i = 0; i <= this.limit(); i++) {
+
+ if (child = this.shownItem(i))
+ child.lowlight();
+ };
+
+ while (child = this._element.firstChild)
+ this._element.removeChild(child);
+ },
+
+
+ // Append item to the shown list based on index
+ _append : function (i) {
+ var item = this.item(i);
+
+ // Highlight based on prefix
+ if (this.prefix().length > 0)
+ item.highlight(this.prefix());
+
+ // Append element
+ this.element().appendChild(item.element());
+ },
+
+
+ /**
+ * Get a specific item from the filtered list
+ *
+ * @param {number} index of the list item
+ */
+ liveItem : function (index) {
+ if (this._list === undefined)
+ if (!this._initList())
+ return;
+
+ return this._items[this._list[index]];
+ },
+
+ length : function () {
+ return this._items.length;
+ },
+
+
+ /**
+ * Get a specific item from the visible list
+ *
+ * @param {number} index of the list item
+ */
+ shownItem : function (index) {
+ if (index >= this.limit())
+ return;
+ return this.liveItem(this._offset + index);
+ },
+
+
+ /*
+ * Make the next item in the menu active
+ */
+ next : function () {
+ // No active element set
+ if (this._position == -1)
+ return;
+
+ // Set new live item
+ var oldItem = this.liveItem(this._position++);
+ oldItem.active(false);
+ var newItem = this.liveItem(this._position);
+
+ // The next element is undefined - roll to top
+ if (newItem === undefined) {
+ this._offset = 0;
+ this._position = 0;
+ newItem = this.liveItem(0);
+ this._showItems(0);
+ }
+
+ // The next element is outside the view - roll down
+ else if (this._position >= (this.limit + this._offset)) {
+ this._removeFirst();
+ this._offset++;
+ this._append(this._list[this._position]);
+ };
+ newItem.active(true);
+ },
+
+
+ /*
+ * Make the previous item in the menu active
+ */
+/*
+ prev : function () {
+ if (this._position == -1)
+ return;
+
+ // Set new live item
+ var oldItem = this.liveItem(this._position--);
+ oldItem.active(false);
+ var newItem = this.liveItem(this._position);
+
+ // The previous element is undefined - roll to bottom
+ if (newItem === undefined) {
+ this._position = this.liveLength - 1;
+ newItem = this.liveItem(this._position);
+ this._offset = this.liveLength - this.limit;
+ this._showItems(this._offset);
+ }
+
+ // The previous element is outside the view - roll up
+ else if (this._position < this._offset) {
+ this._removeLast();
+ this._offset--;
+ this._prepend(this._list[this._position]);
+ };
+ newItem.active(true);
+ },
+*/
+
+
+ /**
+ * Get the context of the menue,
+ * e.g. "tt/" for the tree tagger menu
+ */
+/*
+ get context () {
+ return this._context;
+ },
+*/
+/*
+ get liveLength () {
+ if (this._list === undefined)
+ this._initList();
+ return this._list.length;
+ },
+*/
+/*
+ chooseHint : function (e) {
+ var element = e.target;
+ while (element.nodeName == "STRONG" || element.nodeName == "SPAN")
+ element = element.parentNode;
+
+ if (element === undefined || element.nodeName != "LI")
+ return;
+
+ var action = element.getAttribute('data-action');
+ hint.insertText(action);
+ var menu = hint.menu();
+ menu.hide();
+
+ // Fill this with the correct value
+ var show;
+ if ((show = hint.analyzeContext()) != "-") {
+ menu.show(show);
+ menu.update(
+ hint._search.getBoundingClientRect().right
+ );
+ };
+
+ hint._search.focus();
+ },
+
+ _removeFirst : function () {
+ this.item(this._list[this._offset]).lowlight();
+ this._element.removeChild(this._element.firstChild);
+ },
+
+ _removeLast : function () {
+ this.item(this._list[this._offset + this.limit - 1]).lowlight();
+ this._element.removeChild(this._element.lastChild);
+ },
+
+
+ // Prepend item to the shown list based on index
+ _prepend : function (i) {
+ var item = this.item(i);
+
+ // Highlight based on prefix
+ if (this.prefix.length > 0)
+ item.highlight(this.prefix);
+
+ // Append element
+ this.element.insertBefore(
+ item.element,
+ this.element.firstChild
+ );
+ },
+*/
+
+ };
+
+
+
+
/**
* Item in the Dropdown menu
*/
@@ -107,7 +489,10 @@
* @param {string} Prefix string for highlights
*/
highlight : function (prefix) {
- this._highlight(this.element().firstChild, prefix);
+ var children = this.element().childNodes;
+ for (var i = children.length -1; i >= 0; i--) {
+ this._highlight(children[i], prefix);
+ };
},
// Highlight a certain substring of the menu item
@@ -185,6 +570,7 @@
// Initialize menu item
_init : function (params) {
+
if (params[0] === undefined)
throw new Error("Missing parameters");
diff --git a/public/js/src/vc.js b/public/js/src/vc.js
index b68f0aa..d226fc4 100644
--- a/public/js/src/vc.js
+++ b/public/js/src/vc.js
@@ -1,11 +1,16 @@
+/**
+ * Create virtual collections with a visual user interface.
+ *
+ * @author Nils Diewald
+ */
+
var KorAP = KorAP || {};
-// TODO: Implement a working localization solution!
-// TODO: Remove "and" or "or" in case it's followed
-// by an unspecified document
-// TODO: Add 'or' or 'and' on root
-
/*
+ TODO: Implement a working localization solution!
+ TODO: Disable "and" or "or" in case it's followed
+ by an unspecified document
+
Error codes:
701: "JSON-LD group has no @type attribute"
704: "Operation needs operand list"
@@ -18,6 +23,8 @@
811: "Document group expects operation" (like 703)
812: "Operand not supported in document group" (like 744)
813: "Collection type is not supported" (like 713)
+ 814: "Unknown rewrite operation"
+ 815: "Rewrite expects source"
*/
(function (KorAP) {
@@ -28,11 +35,12 @@
console.log(type + ": " + msg);
};
- KorAP._validStringMatchRE = new RegExp("^(?:eq|ne|contains)$");
+ KorAP._validStringMatchRE = new RegExp("^(?:eq|ne|contains|excludes)$");
KorAP._validRegexMatchRE = new RegExp("^(?:eq|ne)$");
KorAP._validDateMatchRE = new RegExp("^[lg]?eq$");
KorAP._validDateRE = new RegExp("^(?:\\d{4})(?:-\\d\\d(?:-\\d\\d)?)?$");
KorAP._validGroupOpRE = new RegExp("^(?:and|or)$");
+ KorAP._validRewriteOpRE = new RegExp("^(?:injec|modifica)tion$");
KorAP._quote = new RegExp("([\"\\\\])", 'g');
// Localization values
@@ -60,7 +68,6 @@
// Add new unspecified document
KorAP._add = function (obj, type) {
var ref = obj.parentNode.refTo;
- console.log("DEBUG: " + type + " on " + ref.toQuery());
var parent = ref.parent();
if (ref.ldType() === 'docGroup') {
@@ -107,7 +114,6 @@
// Remove doc or docGroup
KorAP._delete = function () {
var ref = this.parentNode.refTo;
-console.log("DEBUG: delete " + ref.toQuery());
if (ref.parent().ldType() !== null) {
return ref.parent().delOperand(ref).update();
}
@@ -413,7 +419,7 @@
if (parent !== undefined)
obj._parent = parent;
- obj._changed = true;
+ obj.__changed = true;
return obj;
},
@@ -425,7 +431,12 @@
var e = this._element;
// Check if there is a change
- if (this._changed) {
+ if (this.__changed) {
+
+ // Was rewritten
+ if (this.rewrites() !== undefined) {
+ e.classList.add("rewritten");
+ };
// Added key
var key = document.createElement('span');
@@ -458,7 +469,11 @@
e.appendChild(matchop);
e.appendChild(value);
- this._changed = false;
+ this.__changed = false;
+ };
+
+ if (this._rewrites !== undefined) {
+ e.appendChild(this._rewrites.element());
};
if (this._parent !== undefined) {
@@ -502,7 +517,7 @@
if (json === undefined)
return this;
- if (json["@type"] !== "korap:doc") {
+ if (json["@type"] === undefined) {
KorAP.log(701, "JSON-LD group has no @type attribute");
return;
};
@@ -595,15 +610,20 @@
KorAP.log(804, "Unknown value type");
return;
};
+
};
+ if (json["rewrites"] !== undefined) {
+ this._rewrites = KorAP.RewriteList.create(json["rewrites"]);
+ };
+
return this;
},
key : function (value) {
if (arguments.length === 1) {
this._key = value;
- this._changed = true;
+ this._changed();
return this;
};
return this._key;
@@ -612,7 +632,7 @@
matchop : function (match) {
if (arguments.length === 1) {
this._matchop = match.replace(/^match:/, '');
- this._changed = true;
+ this._changed();
return this;
};
return this._matchop || "eq";
@@ -621,7 +641,7 @@
type : function (type) {
if (arguments.length === 1) {
this._type = type;
- this._changed = true;
+ this._changed();
return this;
};
return this._type || "string";
@@ -630,12 +650,27 @@
value : function (value) {
if (arguments.length === 1) {
this._value = value;
- this._changed = true;
+ this._changed();
return this;
};
return this._value;
},
+ rewrites : function () {
+ return this._rewrites;
+ },
+
+ _changed : function () {
+ this.__changed = true;
+
+ if (this._rewrites === undefined)
+ return;
+ delete this["_rewrites"];
+ if (this._element === undefined)
+ return;
+ this._element.classList.remove("rewritten");
+ },
+
toJson : function () {
if (!this.matchop() || !this.key())
return {};
@@ -664,6 +699,9 @@
case "contains":
string += '~';
break;
+ case "excludes":
+ string += '!~';
+ break;
case "geq":
string += 'since';
break;
@@ -720,6 +758,23 @@
};
},
+ // The doc is already set in the group
+ _duplicate : function (operand) {
+ if (operand.ldType() !== 'doc')
+ return null;
+
+ for (var i = 0; i < this._operands.length; i++) {
+ var op = this.getOperand(i);
+ if (op.ldType() === 'doc'
+ && operand.key() === op.key()
+ && operand.matchop() === op.matchop()
+ && operand.value() === op.value()) {
+ return op;
+ };
+ };
+ return null;
+ },
+
append : function (operand) {
// Append unspecified object
@@ -732,7 +787,9 @@
};
switch (operand["@type"]) {
+
case undefined:
+ // No @type defined
if (operand["ldType"] !== undefined) {
if (operand.ldType() !== 'doc' &&
operand.ldType() !== 'docGroup') {
@@ -741,8 +798,13 @@
};
// Be aware of cyclic structures!
operand.parent(this);
- this._operands.push(operand);
- return operand;
+
+ var dupl = this._duplicate(operand);
+ if (dupl === null) {
+ this._operands.push(operand);
+ return operand;
+ };
+ return dupl;
};
KorAP.log(701, "JSON-LD group has no @type attribute");
@@ -753,14 +815,33 @@
var doc = KorAP.Doc.create(this, operand);
if (doc === undefined)
return;
- this._operands.push(doc);
- return doc;
+ var dupl = this._duplicate(doc);
+ if (dupl === null) {
+ this._operands.push(doc);
+ return doc;
+ };
+ return dupl;
case "korap:docGroup":
// Be aware of cyclic structures!
var docGroup = KorAP.DocGroup.create(this, operand);
if (docGroup === undefined)
return;
+
+ // Flatten group
+ if (docGroup.operation() === this.operation()) {
+ for (var op in docGroup.operands()) {
+ op = docGroup.getOperand(op);
+ var dupl = this._duplicate(op);
+ if (dupl === null) {
+ this._operands.push(op);
+ op.parent(this);
+ };
+ };
+ docGroup._operands = [];
+ docGroup.destroy();
+ return this;
+ };
this._operands.push(docGroup);
return docGroup;
@@ -875,15 +956,16 @@
newOp.parent(this);
}
- // Flatten the group
+ // Flatten group
else {
// Remove old group
this._operands.splice(i, 1);
// Inject new operands
for (var op in newOp.operands().reverse()) {
- this._operands.splice(i, 0, newOp.getOperand(op));
- newOp.getOperand(0).parent(this);
+ op = newOp.getOperand(op);
+ this._operands.splice(i, 0, op);
+ op.parent(this);
};
// Prevent destruction of operands
newOp._operands = [];
@@ -920,7 +1002,7 @@
if (json === undefined)
return this;
- if (json["@type"] !== "korap:docGroup") {
+ if (json["@type"] === undefined) {
KorAP.log(701, "JSON-LD group has no @type attribute");
return;
};
@@ -984,11 +1066,157 @@
};
+ KorAP.RewriteList = {
+ // Construction method
+ create : function (json) {
+ var obj = Object(KorAP.JsonLD).
+ create().
+ upgradeTo(KorAP.RewriteList).
+ fromJson(json);
+ return obj;
+ },
+ fromJson : function (json) {
+ this._list = new Array();
+ for (var i = 0; i < json.length; i++) {
+ this._list.push(
+ KorAP.Rewrite.create(json[i])
+ );
+ };
+ return this;
+ },
+ element : function () {
+ if (this._element !== undefined)
+ return this._element;
+
+ this._element = document.createElement('div');
+ this._element.setAttribute('class', 'rewrite');
+ for (var x in this._list) {
+ var rewrite = this._list[x];
+ var span = document.createElement('span');
+
+ // Set class attribute
+ span.setAttribute('class', rewrite.operation());
+
+ // Append source information
+ span.appendChild(document.createTextNode(rewrite.src()));
+
+ // Append scope information
+ if (rewrite.scope() !== undefined) {
+ span.appendChild(
+ document.createTextNode(
+ ': ' + rewrite.scope()
+ )
+ );
+ };
+ this._element.appendChild(span);
+ };
+ return this._element;
+ }
+ };
+
+
+ /**
+ * Implementation of rewrite objects.
+ */
+ KorAP.Rewrite = {
+
+ // Construction method
+ create : function (json) {
+ var obj = Object(KorAP.JsonLD).
+ create().
+ upgradeTo(KorAP.Rewrite).
+ fromJson(json);
+ return obj;
+ },
+
+ // Get or set source
+ src : function (string) {
+ if (arguments.length === 1)
+ this._src = string;
+ return this._src;
+ },
+
+ // Get or set operation
+ operation : function (op) {
+ if (arguments.length === 1) {
+ if (KorAP._validRewriteOpRE.test(op)) {
+ this._op = op;
+ }
+ else {
+ KorAP.log(814, "Unknown rewrite operation");
+ return;
+ };
+ };
+ return this._op || 'injection';
+ },
+
+ // Get or set scope
+ scope : function (attr) {
+ if (arguments.length === 1)
+ this._scope = attr;
+ return this._scope;
+ },
+
+ // Serialize from Json
+ fromJson : function (json) {
+ if (json === undefined)
+ return this;
+
+ // Missing @type
+ if (json["@type"] === undefined) {
+ KorAP.log(701, "JSON-LD group has no @type attribute");
+ return;
+ };
+
+ // Missing source
+ if (json["src"] === undefined ||
+ typeof json["src"] !== 'string') {
+ KorAP.log(815, "Rewrite expects source");
+ return;
+ };
+
+ // Set source
+ this.src(json["src"]);
+
+ // Set operation
+ if (json["operation"] !== undefined) {
+ var operation = json["operation"];
+ this.operation(operation.replace(/^operation:/,''));
+ };
+
+ // Set scope
+ if (json["scope"] !== undefined &&
+ typeof json["scope"] === 'string')
+ this.scope(json["scope"]);
+
+ return this;
+ },
+
+ toString : function () {
+ var str = '';
+ var op = this.operation();
+ str += op.charAt(0).toUpperCase() + op.slice(1);
+ str += ' of ' + (
+ this._scope === null ?
+ 'object' :
+ '"' +
+ this.scope().replace(KorAP._quote, '\\$1') +
+ '"'
+ );
+ str += ' by ' +
+ '"' +
+ this.src().replace(KorAP._quote, '\\$1') +
+ '"';
+ return str;
+ }
+ };
+
+
/**
* Abstract JsonLD criterion object
*/
KorAP.JsonLD = {
- _changed : false,
+ __changed : false,
create : function () {
return Object.create(KorAP.JsonLD);
@@ -1014,14 +1242,14 @@
parent : function (obj) {
if (arguments.length === 1) {
this._parent = obj;
- this._changed = true;
+ this.__changed = true;
};
return this._parent;
},
// Destroy object - especially for
// acyclic structures!
- // I'm a paranoid chicken!
+ // I'm paranoid!
destroy : function () {
if (this._ops != undefined) {
this._ops._parent = undefined;