blob: 711008f2e81629ffd639f39b0ea9a892349647d8 [file] [log] [blame]
Nils Diewald86dad5b2015-01-28 15:09:07 +00001/**
hebastaa79d69d2018-07-24 12:13:02 +02002 * Create virtual collections with a visual user interface. This resembles the
3 * collection type objects of a KoralQuery "collection" object.
4 *
Nils Diewald4c221252015-04-21 20:19:25 +00005 * KoralQuery v0.3 is expected.
hebastaa79d69d2018-07-24 12:13:02 +02006 *
Nils Diewald86dad5b2015-01-28 15:09:07 +00007 * @author Nils Diewald
8 */
Nils Diewald2fe12e12015-03-06 16:47:06 +00009/*
Nils Diewald1fcb2ad2015-04-20 19:19:18 +000010 * This replaces a previous version written by Mengfei Zhou
Nils Diewald2fe12e12015-03-06 16:47:06 +000011 */
Nils Diewald2fe12e12015-03-06 16:47:06 +000012
Nils Diewaldd0770492014-12-19 03:55:00 +000013/*
hebasta86759392018-07-25 15:44:37 +020014 TODO: Disable "and" or "or" in case it's followed
15 by an unspecified document
16 TODO: Add "and"-method to root to add further constraints
17 based on match-input (like clicking on a pubDate timestamp in a match)
18 TODO: Implement "persistence"-Option, injecting the current creation
19 date stamp
20 TODO: Implement vec-Type for document-id vectors like docID in [1,2,3,4 ...]
21
22 Error codes:
23 701: "JSON-LD group has no @type attribute"
24 704: "Operation needs operand list"
25 802: "Match type is not supported by value type"
26 804: "Unknown value type"
27 805: "Value is invalid"
28 806: "Value is not a valid date string"
29 807: "Value is not a valid regular expression"
30 810: "Unknown document group operation" (like 711)
31 811: "Document group expects operation" (like 703)
32 812: "Operand not supported in document group" (like 744)
33 813: "Collection type is not supported" (like 713)
34 814: "Unknown rewrite operation"
35 815: "Rewrite expects source"
36
37 Localization strings:
38 KorAP.Locale = {
39 EMPTY : '...',
40 AND : 'and',
41 OR : 'or',
42 DELETE : 'x' }
43
44 and various field names with the prefix 'VC_'
hebastaa79d69d2018-07-24 12:13:02 +020045 */
Nils Diewald86dad5b2015-01-28 15:09:07 +000046
Akronb19803c2018-08-16 16:39:42 +020047define([
48 'vc/unspecified',
49 'vc/doc',
50 'vc/docgroup',
51 'vc/docgroupref',
52 'vc/menu',
53 'vc/statistic',
54 'datepicker',
55 'buttongroup',
hebasta2535c762018-11-21 16:27:33 +010056 'panel/vcpanel',
Akronb19803c2018-08-16 16:39:42 +020057 'view/corpstatv',
Akronec6bb8e2018-08-29 13:07:56 +020058 'buttongroup',
Akronb19803c2018-08-16 16:39:42 +020059 'util'
60], function(
61 unspecDocClass,
62 docClass,
63 docGroupClass,
64 docGroupRefClass,
65 menuClass,
66 statClass,
67 dpClass,
68 buttonGrClass,
hebasta2535c762018-11-21 16:27:33 +010069 vcPanelClass,
Akronec6bb8e2018-08-29 13:07:56 +020070 corpStatVClass,
71 buttonGroupClass) {
Akronb19803c2018-08-16 16:39:42 +020072 "use strict";
Nils Diewald1fcb2ad2015-04-20 19:19:18 +000073
Akronb19803c2018-08-16 16:39:42 +020074 KorAP._validUnspecMatchRE = new RegExp(
75 "^(?:eq|ne|contains(?:not)?|excludes)$");
76 KorAP._validStringMatchRE = new RegExp("^(?:eq|ne)$");
77 KorAP._validTextMatchRE = KorAP._validUnspecMatchRE;
78 KorAP._validTextOnlyMatchRE = new RegExp(
79 "^(?:contains(?:not)?|excludes)$");
80 KorAP._overrideStyles = false;
81 // KorAP._validDateMatchRE is defined in datepicker.js!
Nils Diewaldd0770492014-12-19 03:55:00 +000082
Akronb19803c2018-08-16 16:39:42 +020083 const loc = KorAP.Locale;
Akron8a670162018-08-28 10:09:13 +020084 loc.VC_allCorpora = loc.VC_allCorpora || 'all corpora';
85 loc.VC_oneCollection = loc.VC_oneCollection || 'a virtual corpus';
Akronec6bb8e2018-08-29 13:07:56 +020086 loc.MINIMIZE = loc.MINIMIZE || 'Minimize';
Nils Diewald3a2d8022014-12-16 02:45:41 +000087
Akronb19803c2018-08-16 16:39:42 +020088 KorAP._vcKeyMenu = undefined;
89 KorAP._vcDatePicker = dpClass.create();
Nils Diewald3a2d8022014-12-16 02:45:41 +000090
Akronb19803c2018-08-16 16:39:42 +020091 // Create match menus ....
92 KorAP._vcMatchopMenu = {
93 'string' : menuClass.create([
94 [ 'eq', null ],
95 [ 'ne', null ]
96 ]),
97 'text' : menuClass.create([
98 [ 'eq', null ], // Requires exact match
99 [ 'ne', null ],
100 [ 'contains', null ], // Requires token sequence match
101 [ 'containsnot', null ]
102 ]),
103 'date' : menuClass.create([
104 [ 'eq', null ],
105 [ 'ne', null ],
106 [ 'geq', null ],
107 [ 'leq', null ]
108 ]),
109 'regex' : menuClass.create([
110 [ 'eq', null ],
111 [ 'ne', null ]
112 ])
113 };
114
115 /**
116 * Virtual Collection
117 */
118 return {
119
120 /**
121 * The JSON-LD type of the virtual collection
122 */
123 ldType : function() {
124 return null;
125 },
126
127 // Initialize virtual collection
128 _init : function(keyList) {
129
130 // Inject localized css styles
131 if (!KorAP._overrideStyles) {
132 var sheet = KorAP.newStyleSheet();
133
134 // Add css rule for OR operations
135 sheet.insertRule('.vc .docGroup[data-operation=or] > .doc::before,'
136 + '.vc .docGroup[data-operation=or] > .docGroup::before '
137 + '{ content: "' + loc.OR + '" }', 0);
138
139 // Add css rule for AND operations
140 sheet.insertRule(
141 '.vc .docGroup[data-operation=and] > .doc::before,'
142 + '.vc .docGroup[data-operation=and] > .docGroup::before '
143 + '{ content: "' + loc.AND + '" }', 1);
144
145 KorAP._overrideStyles = true;
Nils Diewald359a72c2015-04-20 17:40:29 +0000146 };
147
Akron3ad46942018-08-22 16:47:14 +0200148 var l;
149 if (keyList) {
Akronadab5e52018-08-20 13:50:53 +0200150 l = keyList.slice();
Akron3ad46942018-08-22 16:47:14 +0200151 l.unshift(['referTo', 'ref']);
152 }
153 else {
154 l = [['referTo', 'ref']];
155 }
Akronadab5e52018-08-20 13:50:53 +0200156
Akronb19803c2018-08-16 16:39:42 +0200157 // Create key menu
Akron3ad46942018-08-22 16:47:14 +0200158 KorAP._vcKeyMenu = menuClass.create(l);
Akronb19803c2018-08-16 16:39:42 +0200159 KorAP._vcKeyMenu.limit(6);
Akron712733a2018-04-05 18:17:47 +0200160
Akronb19803c2018-08-16 16:39:42 +0200161 return this;
162 },
Nils Diewald359a72c2015-04-20 17:40:29 +0000163
Akronb19803c2018-08-16 16:39:42 +0200164 /**
165 * Create a new virtual collection.
166 */
167 create : function(keyList) {
168 var obj = Object.create(this)._init(keyList);
169 obj._root = unspecDocClass.create(obj);
170 return obj;
171 },
Nils Diewaldd599d542015-01-08 20:41:34 +0000172
Akron8a670162018-08-28 10:09:13 +0200173
Akronb19803c2018-08-16 16:39:42 +0200174 /**
175 * Create and render a new virtual collection based on a KoralQuery
176 * collection document
177 */
178 fromJson : function(json) {
179 if (json !== undefined) {
180 // Parse root document
181 if (json['@type'] == 'koral:doc') {
182 this._root = docClass.create(this, json);
hebastaa79d69d2018-07-24 12:13:02 +0200183 }
Akronb19803c2018-08-16 16:39:42 +0200184 // parse root group
185 else if (json['@type'] == 'koral:docGroup') {
186 this._root = docGroupClass.create(this, json);
187 }
188
189 // parse root reference
190 else if (json['@type'] == 'koral:docGroupRef') {
191 this._root = docGroupRefClass.create(this, json);
192 }
193
194 // Unknown collection type
195 else {
196 KorAP.log(813, "Collection type is not supported");
197 return;
198 };
199 }
200
201 else {
202 // Add unspecified object
203 this._root = unspecDocClass.create(this);
Nils Diewald845282c2015-05-14 07:53:03 +0000204 };
Akronb19803c2018-08-16 16:39:42 +0200205
206 // Init element and update
207 this.update();
208
209 return this;
210 },
Akrond2474aa2018-08-28 12:06:27 +0200211
212
213 // Check if the virtual corpus contains a rerite
214 wasRewritten : function (obj) {
215
216 var obj;
217 if (arguments.length === 1) {
218 obj = arguments[0];
219 }
220 else {
221 obj = this._root;
222 };
223
224 // Check for rewrite
225 if (obj.rewrites() && obj.rewrites().length() > 0) {
226 return true;
227 }
228
229 // Check recursively
230 else if (obj.ldType() === 'docGroup') {
231 for (var i in obj.operands()) {
232 if (this.wasRewritten(obj.getOperand(i))) {
233 return true;
234 }
235 };
236 };
237
238 return false;
239 },
Akron43c5cc62018-08-28 13:10:25 +0200240
Akron8a670162018-08-28 10:09:13 +0200241
Akronb19803c2018-08-16 16:39:42 +0200242 /**
243 * Clean the virtual document to uspecified doc.
244 */
245 clean : function() {
246 if (this._root.ldType() !== "non") {
247 this._root.destroy();
248 this.root(unspecDocClass.create(this));
249 };
250 return this;
251 },
252
253 /**
254 * Get or set the root object of the virtual collection.
255 */
256 root : function(obj) {
257 if (arguments.length === 1) {
Akronadab5e52018-08-20 13:50:53 +0200258 var e = this.builder();
Akronb19803c2018-08-16 16:39:42 +0200259
260 if (e.firstChild !== null) {
Akronadab5e52018-08-20 13:50:53 +0200261
262 // Object not yet set
Akronb19803c2018-08-16 16:39:42 +0200263 if (e.firstChild !== obj.element()) {
264 e.replaceChild(obj.element(), e.firstChild);
265 };
266 }
267
268 // Append root element
269 else {
270 e.appendChild(obj.element());
271 };
272
273 // Update parent child relations
274 this._root = obj;
275 obj.parent(this);
276
277 this.update();
278 };
279 return this._root;
280 },
281
Akronadab5e52018-08-20 13:50:53 +0200282
283 /**
284 * Get the wrapper element associated with the vc
285 */
286 builder : function () {
287
288 // Initialize if necessary
289 if (this._builder !== undefined)
290 return this._builder;
291
292 this.element();
293 return this._builder;
294 },
295
Akronb19803c2018-08-16 16:39:42 +0200296 /**
Akron68d28322018-08-27 15:02:42 +0200297 * Get the element associated with the virtual corpus
Akronb19803c2018-08-16 16:39:42 +0200298 */
299 element : function() {
Akronb19803c2018-08-16 16:39:42 +0200300 if (this._element !== undefined) {
301 return this._element;
302 };
303
304 this._element = document.createElement('div');
Akronec6bb8e2018-08-29 13:07:56 +0200305 this._element.classList.add('vc');
306
Akronb19803c2018-08-16 16:39:42 +0200307
Akronadab5e52018-08-20 13:50:53 +0200308 this._builder = this._element.addE('div');
309 this._builder.setAttribute('class', 'builder');
310
Akronec6bb8e2018-08-29 13:07:56 +0200311 var btn = buttonGroupClass.create(
312 ['action','button-view']
313 );
314 var that = this;
315 btn.add(loc.MINIMIZE, ['button-icon','minimize'], function () {
316 that.minimize();
317 });
318 this._element.appendChild(btn.element());
319
320
321
Akronb19803c2018-08-16 16:39:42 +0200322 // Initialize root
Akronadab5e52018-08-20 13:50:53 +0200323 this._builder.appendChild(this._root.element());
Akronb19803c2018-08-16 16:39:42 +0200324
325 // Add panel to display corpus statistic, ...
326 this.addVcInfPanel();
327
328 return this._element;
329 },
330
Akronec6bb8e2018-08-29 13:07:56 +0200331
332 /**
333 * Check, if the VC is open
334 */
335 isOpen : function () {
336 if (!this._element)
337 return false;
338 return this._element.classList.contains('active');
339 },
340
341 /**
342 * Open the VC view
343 */
344 open : function () {
345 this.element().classList.add('active');
346 if (this.onOpen)
347 this.onOpen();
348 },
349
350
351 /**
352 * Minimize the VC view
353 */
354 minimize : function () {
355 this.element().classList.remove('active');
356 if (this.onMinimize)
357 this.onMinimize();
358 },
359
360
Akronb19803c2018-08-16 16:39:42 +0200361 /**
362 * Update the whole object based on the underlying data structure
hebasta2535c762018-11-21 16:27:33 +0100363 */
Akronb19803c2018-08-16 16:39:42 +0200364 update : function() {
365 this._root.update();
366 return this;
367 },
hebasta2535c762018-11-21 16:27:33 +0100368
369
Akronb19803c2018-08-16 16:39:42 +0200370 /**
371 * Make the vc persistant by injecting the current timestamp as a
372 * creation date limit criterion.
373 * THIS IS CURRENTLY NOT USED
374 */
375 makePersistant : function() {
376 // this.root().wrapOnRoot('and');
377 var todayStr = KorAP._vcDatePicker.today();
378 var doc = docClass.create();
379 var root = this.root();
380
381 if (root.ldType() === 'docGroup' && root.operation === 'and') {
382 root.append(cond);
383 } else {
384 root.wrapOnRoot('and');
385 root.append(doc);
386 };
387
388 doc.key("creationDate");
389 doc.type("date");
390 doc.matchop("leq");
391 doc.value(todayStr);
392
393 /*
394 * { "@type" : "koral:doc", "key" : "creationDate", "type" :
395 * "type:date", "match" : "match:leq", "value" : todayStr }
396 * this.root().append(cond);
397 */
398 this.update();
399 },
400
Akron8a670162018-08-28 10:09:13 +0200401
402 // Get the reference name
403 getName : function () {
404 if (this._root.ldType() === 'non') {
405 return loc.VC_allCorpora;
406 }
407 else if (this._root.ldType() === 'docGroupRef') {
408 return this._root.ref();
409 }
410 else {
411 return loc.VC_oneCollection;
412 }
413 },
414
Akron4feec9d2018-11-20 17:00:50 +0100415 // Add "and" constraint to VC
416 addRequired : function (doc) {
417
418 let root = this.root();
419 let ldType = root.ldType();
420
421 let parent = root.parent();
422 if (ldType === 'non') {
423 parent.root(doc);
424 }
425
426 // root is doc
427 else if (
428 ldType === 'doc' ||
429 ldType === 'docGroupRef' ||
430 (ldType === 'docGroup' &&
431 root.operation() === 'or'
432 )) {
433 let group = require('vc/docgroup').create(
434 parent
435 );
436 group.operation("and");
437 group.append(root);
438 group.append(doc);
439 group.element(); // Init (seems to be necessary)
440 parent.root(group);
441 }
442
443 // root is a docGroup
444 // and is already an 'and'-Group
445 else if (ldType === 'docGroup') {
446 root.append(doc);
447 }
448
449 // Unknown
450 else {
451 console.log("Unknown root object");
452 };
453
454 // Init element and update
455 this.update();
456 },
Akron8a670162018-08-28 10:09:13 +0200457
Akronb19803c2018-08-16 16:39:42 +0200458 /**
459 * Get the generated json string
460 */
461 toJson : function() {
462 return this._root.toJson();
463 },
464
465 /**
466 * Get the generated query string
467 */
468 toQuery : function() {
469 return this._root.toQuery();
470 },
471
hebasta2535c762018-11-21 16:27:33 +0100472
473 /**
Akronb19803c2018-08-16 16:39:42 +0200474 * Add panel to display virtual corpus information
475 */
476 addVcInfPanel : function() {
Akronb19803c2018-08-16 16:39:42 +0200477 var dv = this._element.addE('div');
hebasta2535c762018-11-21 16:27:33 +0100478 //Create panel
479 this.panel = vcPanelClass.create(this);
480 dv.appendChild(this.panel.element());
481 },
482
483
Akronb19803c2018-08-16 16:39:42 +0200484 };
485});