blob: c5bd2f8138c7528f21d23558d44ad59a3ef21d25 [file] [log] [blame]
Nils Diewaldfda29d92015-01-22 17:28:01 +00001var KorAP = KorAP || {};
2
Nils Diewald2fe12e12015-03-06 16:47:06 +00003/**
4 * Create scrollable drop-down menus.
5 *
6 * @author Nils Diewald
7 */
8
Nils Diewaldfda29d92015-01-22 17:28:01 +00009(function (KorAP) {
10 "use strict";
11
Nils Diewald59c02fc2015-03-07 01:29:09 +000012 // Don't let events bubble up
13 Event.prototype.halt = function () {
14 this.stopPropagation();
15 this.preventDefault();
16 };
17
Nils Diewald86dad5b2015-01-28 15:09:07 +000018 // Default maximum number of menu items
19 KorAP.menuLimit = 8;
20
21 /**
22 * List of items for drop down menu (complete).
23 * Only a sublist of the menu is filtered (live).
24 * Only a sublist of the filtered menu is visible (shown).
25 */
26 KorAP.Menu = {
27 /**
28 * Create new Menu based on the action prefix
29 * and a list of menu items.
30 *
31 * @this {Menu}
32 * @constructor
33 * @param {string} Context prefix
34 * @param {Array.<Array.<string>>} List of menu items
35 */
36 create : function (params) {
37 return Object.create(KorAP.Menu)._init(params);
38 },
39
Nils Diewald2fe12e12015-03-06 16:47:06 +000040 focus : function () {
41 this._element.focus();
42 },
43
Nils Diewald59c02fc2015-03-07 01:29:09 +000044 // mouse wheel treatment
45 _mousewheel : function (e) {
46 var delta = 0;
Nils Diewald5975d702015-03-09 17:45:42 +000047
48 delta = e.deltaY / 120;
49 if (delta > 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +000050 this.next();
Nils Diewald5975d702015-03-09 17:45:42 +000051 else if (delta < 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +000052 this.prev();
Nils Diewald59c02fc2015-03-07 01:29:09 +000053 e.halt();
54 },
55
56 // Arrow key and prefix treatment
Nils Diewald5975d702015-03-09 17:45:42 +000057 _keypress : function (e) {
Nils Diewald59c02fc2015-03-07 01:29:09 +000058 var code = _codeFromEvent(e);
59
Nils Diewald59c02fc2015-03-07 01:29:09 +000060 switch (code) {
61 case 27: // 'Esc'
62 e.halt();
63 this.hide();
64 break;
Nils Diewald5975d702015-03-09 17:45:42 +000065
Nils Diewald59c02fc2015-03-07 01:29:09 +000066 case 38: // 'Up'
67 e.halt();
68 this.prev();
69 break;
Nils Diewald5975d702015-03-09 17:45:42 +000070 case 33: // 'Page up'
Nils Diewald59c02fc2015-03-07 01:29:09 +000071 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +000072 this.prev();
73 break;
74 case 40: // 'Down'
75 e.halt();
76 this.next();
77 break;
78 case 34: // 'Page down'
79 e.halt();
80 this.next();
81 break;
82 case 39: // 'Right'
83 var item = this.liveItem(this._position);
84 if (item["further"] !== undefined) {
85 item["further"].bind(item).apply();
86 e.halt();
87 };
88 break;
89 case 13: // 'Enter'
90
91 // Click on prefix
92 if (this._prefix.active())
93 this._prefix.onclick();
94
95 // Click on item
96 else
97 this.liveItem(this._position).onclick();
98 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +000099 break;
100 case 8: // 'Backspace'
Nils Diewald5975d702015-03-09 17:45:42 +0000101 this._prefix.backspace();
102 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000103 e.halt();
104 break;
105 default:
Nils Diewald5975d702015-03-09 17:45:42 +0000106 if (e.key !== undefined &&
107 e.key.length != 1)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000108 return;
109
110 // Add prefix
Nils Diewald5975d702015-03-09 17:45:42 +0000111 this._prefix.add(e.key.toLowerCase());
112
113 if (!this.show()) {
114 this.prefix('').show();
115 };
Nils Diewald59c02fc2015-03-07 01:29:09 +0000116 };
117 },
118
Nils Diewald2fe12e12015-03-06 16:47:06 +0000119 // Initialize list
Nils Diewald5975d702015-03-09 17:45:42 +0000120 _init : function (itemClass, prefixClass, params) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000121 var that = this;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000122 this._itemClass = itemClass;
Nils Diewald5975d702015-03-09 17:45:42 +0000123
124 if (prefixClass !== undefined)
125 this._prefix = prefixClass.create();
126 else
127 this._prefix = KorAP.MenuPrefix.create();
128
129 var e = document.createElement("ul");
Nils Diewald59c02fc2015-03-07 01:29:09 +0000130 e.style.opacity = 0;
131 e.style.outline = 0;
132 e.setAttribute('tabindex', 0);
Nils Diewald5975d702015-03-09 17:45:42 +0000133 e.setAttribute('class', 'menu');
134 e.appendChild(this._prefix.element());
Nils Diewald86dad5b2015-01-28 15:09:07 +0000135
Nils Diewald59c02fc2015-03-07 01:29:09 +0000136 // Arrow keys
137 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000138 'keypress',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000139 function (ev) {
Nils Diewald5975d702015-03-09 17:45:42 +0000140 that._keypress(ev)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000141 },
142 false
143 );
144
145 // Mousewheel
146 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000147 'wheel',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000148 function (ev) {
149 that._mousewheel(ev)
Nils Diewald2fe12e12015-03-06 16:47:06 +0000150 },
151 false
152 );
153
Nils Diewald5975d702015-03-09 17:45:42 +0000154 this._element = e;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000155 this.active = false;
156 this._items = new Array();
157 var i;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000158
159 // Initialize item list based on parameters
Nils Diewald86dad5b2015-01-28 15:09:07 +0000160 for (i in params) {
161 var obj = itemClass.create(params[i]);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000162 this._items.push(obj);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000163 };
164 this._limit = KorAP.menuLimit;
165 this._position = 0; // position in the active list
166 this._active = -1; // active item in the item list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000167 this._reset();
168 return this;
169 },
170
Nils Diewald2fe12e12015-03-06 16:47:06 +0000171 /**
172 * Get the instantiated HTML element
173 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000174 element : function () {
175 return this._element;
176 },
177
Nils Diewald2fe12e12015-03-06 16:47:06 +0000178 /**
179 * Get the creator object for items
180 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000181 itemClass : function () {
182 return this._itemClass;
183 },
184
185 /**
Nils Diewald2fe12e12015-03-06 16:47:06 +0000186 * Get and set numerical value for limit,
187 * i.e. the number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000188 */
189 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000190 if (arguments.length === 1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000191 this._limit = limit;
Nils Diewald5975d702015-03-09 17:45:42 +0000192 return this;
193 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000194 return this._limit;
195 },
196
197 /**
198 * Upgrade this object to another object,
199 * while private data stays intact.
200 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000201 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000202 */
203 upgradeTo : function (props) {
204 for (var prop in props) {
205 this[prop] = props[prop];
206 };
207 return this;
208 },
209
Nils Diewald2fe12e12015-03-06 16:47:06 +0000210 // Reset chosen item and prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000211 _reset : function () {
212 this._offset = 0;
213 this._pos = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000214 this._prefix.value('');
Nils Diewald86dad5b2015-01-28 15:09:07 +0000215 },
216
217 /**
218 * Filter the list and make it visible
219 *
220 * @param {string} Prefix for filtering the list
221 */
Nils Diewald5975d702015-03-09 17:45:42 +0000222 show : function () {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000223 // Initialize the list
224 if (!this._initList())
Nils Diewald59c02fc2015-03-07 01:29:09 +0000225 return false;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000226
Nils Diewald2fe12e12015-03-06 16:47:06 +0000227 // show based on initial offset
Nils Diewald86dad5b2015-01-28 15:09:07 +0000228 this._showItems(0);
229
230 // Set the first element to active
Nils Diewald2fe12e12015-03-06 16:47:06 +0000231 // Todo: Or the last element chosen
Nils Diewald86dad5b2015-01-28 15:09:07 +0000232 this.liveItem(0).active(true);
233
Nils Diewald86dad5b2015-01-28 15:09:07 +0000234 this._active = this._list[0];
Nils Diewald5975d702015-03-09 17:45:42 +0000235 this._position = 0;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000236 this._element.style.opacity = 1;
237
Nils Diewald86dad5b2015-01-28 15:09:07 +0000238 // Add classes for rolling menus
239 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000240 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000241 },
242
Nils Diewald2fe12e12015-03-06 16:47:06 +0000243 hide : function () {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000244 this.active = false;
245 this.delete();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000246 this._element.style.opacity = 0;
Nils Diewald59c02fc2015-03-07 01:29:09 +0000247
248/*
249 this._element.blur();
250*/
Nils Diewald86dad5b2015-01-28 15:09:07 +0000251 },
252
Nils Diewald2fe12e12015-03-06 16:47:06 +0000253 // Initialize the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000254 _initList : function () {
255
Nils Diewald2fe12e12015-03-06 16:47:06 +0000256 // Create a new list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000257 if (this._list === undefined) {
258 this._list = [];
259 }
260 else if (this._list.length != 0) {
261 this._boundary(false);
262 this._list.length = 0;
263 };
264
265 // Offset is initially zero
266 this._offset = 0;
267
Nils Diewald2fe12e12015-03-06 16:47:06 +0000268 // There is no prefix set
Nils Diewald86dad5b2015-01-28 15:09:07 +0000269 if (this.prefix().length <= 0) {
Nils Diewald5975d702015-03-09 17:45:42 +0000270 var i = 0;
271 for (; i < this._items.length; i++)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000272 this._list.push(i);
Nils Diewald5975d702015-03-09 17:45:42 +0000273 while (this._items[++i] !== undefined) {
274 this._items[i].lowlight();
275 console.log(this._item);
276 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000277 return true;
278 };
279
Nils Diewald2fe12e12015-03-06 16:47:06 +0000280 // There is a prefix set, so filter the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000281 var pos;
282 var paddedPrefix = " " + this.prefix();
283
Nils Diewald2fe12e12015-03-06 16:47:06 +0000284 // Iterate over all items and choose preferred matching items
285 // i.e. the matching happens at the word start
Nils Diewald86dad5b2015-01-28 15:09:07 +0000286 for (pos = 0; pos < this._items.length; pos++) {
287 if ((this.item(pos).lcField().indexOf(paddedPrefix)) >= 0)
288 this._list.push(pos);
289 };
290
Nils Diewald2fe12e12015-03-06 16:47:06 +0000291 // The list is empty - so lower your expectations
292 // Iterate over all items and choose matching items
293 // i.e. the matching happens anywhere in the word
Nils Diewald86dad5b2015-01-28 15:09:07 +0000294 if (this._list.length == 0) {
295 for (pos = 0; pos < this._items.length; pos++) {
296 if ((this.item(pos).lcField().indexOf(this.prefix())) >= 0)
297 this._list.push(pos);
298 };
299 };
300
Nils Diewald2fe12e12015-03-06 16:47:06 +0000301 // Filter was successful - yeah!
Nils Diewald86dad5b2015-01-28 15:09:07 +0000302 return this._list.length > 0 ? true : false;
303 },
304
305 // Set boundary for viewport
306 _boundary : function (bool) {
307 this.item(this._list[0]).noMore(bool);
308 this.item(this._list[this._list.length - 1]).noMore(bool);
309 },
310
311 /**
312 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000313 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000314 */
Nils Diewald5975d702015-03-09 17:45:42 +0000315 prefix : function (pref) {
316 if (arguments.length === 1) {
317 this._prefix.value(pref);
318 return this;
319 };
320 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000321 },
322
Nils Diewald2fe12e12015-03-06 16:47:06 +0000323 // Append Items that should be shown
Nils Diewald86dad5b2015-01-28 15:09:07 +0000324 _showItems : function (offset) {
325 this.delete();
326
327 // Use list
328 var shown = 0;
329 var i;
330 for (i in this._list) {
331
332 // Don't show - it's before offset
333 if (shown++ < offset)
334 continue;
335
336 this._append(this._list[i]);
337
338 if (shown >= (this.limit() + this._offset))
339 break;
340 };
341 },
342
343 /**
344 * Delete all visible items from the menu element
345 */
346 delete : function () {
347 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000348
Nils Diewald5975d702015-03-09 17:45:42 +0000349 /*
Nils Diewald2fe12e12015-03-06 16:47:06 +0000350 // Iterate over all visible items
Nils Diewald86dad5b2015-01-28 15:09:07 +0000351 for (var i = 0; i <= this.limit(); i++) {
352
Nils Diewald5975d702015-03-09 17:45:42 +0000353 // there is a visible element
354 // unhighlight!
Nils Diewald59c02fc2015-03-07 01:29:09 +0000355 if (child = this.shownItem(i)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000356 child.lowlight();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000357 child.active(false);
358 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000359 };
Nils Diewald5975d702015-03-09 17:45:42 +0000360 */
361
362 for (var i in this._list) {
363 var item = this._items[this._list[i]];
364 item.lowlight();
365 item.active(false);
366 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000367
Nils Diewald2fe12e12015-03-06 16:47:06 +0000368 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000369 var children = this._element.childNodes;
370 for (var i = children.length - 1; i >= 1; i--) {
371 this._element.removeChild(
372 children[i]
373 );
374 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000375 },
376
377
378 // Append item to the shown list based on index
379 _append : function (i) {
380 var item = this.item(i);
381
382 // Highlight based on prefix
383 if (this.prefix().length > 0)
384 item.highlight(this.prefix());
385
386 // Append element
387 this.element().appendChild(item.element());
388 },
389
390
Nils Diewald2fe12e12015-03-06 16:47:06 +0000391 // Prepend item to the shown list based on index
392 _prepend : function (i) {
393 var item = this.item(i);
394
395 // Highlight based on prefix
396 if (this.prefix().length > 0)
397 item.highlight(this.prefix());
398
399 var e = this.element();
400 // Append element
401 e.insertBefore(
402 item.element(),
Nils Diewald5975d702015-03-09 17:45:42 +0000403 e.children[1]
Nils Diewald2fe12e12015-03-06 16:47:06 +0000404 );
405 },
406
407
408 /**
409 * Get a specific item from the complete list
410 *
411 * @param {number} index of the list item
412 */
413 item : function (index) {
414 return this._items[index]
415 },
416
417
Nils Diewald86dad5b2015-01-28 15:09:07 +0000418 /**
419 * Get a specific item from the filtered list
420 *
421 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000422 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000423 */
424 liveItem : function (index) {
425 if (this._list === undefined)
426 if (!this._initList())
427 return;
428
429 return this._items[this._list[index]];
430 },
431
Nils Diewald86dad5b2015-01-28 15:09:07 +0000432
433 /**
434 * Get a specific item from the visible list
435 *
436 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000437 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000438 */
439 shownItem : function (index) {
440 if (index >= this.limit())
441 return;
442 return this.liveItem(this._offset + index);
443 },
444
445
Nils Diewald2fe12e12015-03-06 16:47:06 +0000446 /**
447 * Get the length of the full list
448 */
449 length : function () {
450 return this._items.length;
451 },
452
453
454 /**
455 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000456 */
457 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000458
Nils Diewald86dad5b2015-01-28 15:09:07 +0000459 // No active element set
Nils Diewald5975d702015-03-09 17:45:42 +0000460 if (this._position === -1)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000461 return;
462
Nils Diewald5975d702015-03-09 17:45:42 +0000463 var newItem;
464
Nils Diewald86dad5b2015-01-28 15:09:07 +0000465 // Set new live item
466 var oldItem = this.liveItem(this._position++);
467 oldItem.active(false);
Nils Diewald5975d702015-03-09 17:45:42 +0000468 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000469
Nils Diewald5975d702015-03-09 17:45:42 +0000470 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000471 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000472
473 // Activate prefix
474 var prefix = this._prefix;
475
476 // Mark prefix
477 if (prefix.isSet() && !prefix.active()) {
478 this._position--;
479 prefix.active(true);
480 return;
481 }
482 else {
483 this._offset = 0;
484 this._position = 0;
485 newItem = this.liveItem(0);
486 this._showItems(0);
487 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000488 }
489
490 // The next element is outside the view - roll down
Nils Diewald2fe12e12015-03-06 16:47:06 +0000491 else if (this._position >= (this.limit() + this._offset)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000492 this._removeFirst();
493 this._offset++;
494 this._append(this._list[this._position]);
495 };
Nils Diewald5975d702015-03-09 17:45:42 +0000496
497 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000498 newItem.active(true);
499 },
500
501
502 /*
503 * Make the previous item in the menu active
504 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000505 prev : function () {
Nils Diewald2fe12e12015-03-06 16:47:06 +0000506 // No active element set
Nils Diewald86dad5b2015-01-28 15:09:07 +0000507 if (this._position == -1)
508 return;
509
Nils Diewald5975d702015-03-09 17:45:42 +0000510 var newItem;
511
Nils Diewald86dad5b2015-01-28 15:09:07 +0000512 // Set new live item
513 var oldItem = this.liveItem(this._position--);
514 oldItem.active(false);
Nils Diewald5975d702015-03-09 17:45:42 +0000515 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000516
517 // The previous element is undefined - roll to bottom
518 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000519
520 // Activate prefix
521 var prefix = this._prefix;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000522 this._offset = this.liveLength() - this.limit();
523 this._position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000524
525 if (prefix.isSet() && !prefix.active()) {
526 this._position++;
527 prefix.active(true);
528 return;
529 }
530 else {
531 newItem = this.liveItem(this._position);
532 this._showItems(this._offset);
533 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000534 }
535
536 // The previous element is outside the view - roll up
537 else if (this._position < this._offset) {
538 this._removeLast();
539 this._offset--;
540 this._prepend(this._list[this._position]);
541 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000542
Nils Diewald5975d702015-03-09 17:45:42 +0000543 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000544 newItem.active(true);
545 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000546
547
Nils Diewald5975d702015-03-09 17:45:42 +0000548 // Length of the filtered list
549 liveLength : function () {
550 if (this._list === undefined)
551 this._initList();
552 return this._list.length;
553 },
554
555
Nils Diewald2fe12e12015-03-06 16:47:06 +0000556 // Remove the HTML node from the first item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000557 _removeFirst : function () {
558 this.item(this._list[this._offset]).lowlight();
Nils Diewald5975d702015-03-09 17:45:42 +0000559 this._element.removeChild(this._element.children[1]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000560 },
561
Nils Diewald2fe12e12015-03-06 16:47:06 +0000562
563 // Remove the HTML node from the last item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000564 _removeLast : function () {
Nils Diewald2fe12e12015-03-06 16:47:06 +0000565 this.item(this._list[this._offset + this.limit() - 1]).lowlight();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000566 this._element.removeChild(this._element.lastChild);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000567 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000568 };
569
570
Nils Diewaldfda29d92015-01-22 17:28:01 +0000571 /**
572 * Item in the Dropdown menu
573 */
574 KorAP.MenuItem = {
575
576 /**
577 * Create a new MenuItem object.
578 *
579 * @constructor
580 * @this {MenuItem}
581 * @param {Array.<string>} An array object of name, action and
582 * optionally a description
583 */
584 create : function (params) {
585 return Object.create(KorAP.MenuItem)._init(params);
586 },
587
588 /**
589 * Upgrade this object to another object,
590 * while private data stays intact.
591 *
592 * @param {Object] An object with properties.
593 */
594 upgradeTo : function (props) {
595 for (var prop in props) {
596 this[prop] = props[prop];
597 };
598 return this;
599 },
600
Nils Diewald5975d702015-03-09 17:45:42 +0000601
Nils Diewaldfda29d92015-01-22 17:28:01 +0000602 content : function (content) {
603 if (arguments.length === 1)
604 this._content = document.createTextNode(content);
605 return this._content;
606 },
607
608 lcField : function () {
609 return this._lcField;
610 },
611
612 action : function (action) {
613 if (arguments.length === 1)
614 this._action = action;
615 return this._action;
616 },
617
618 /**
619 * Check or set if the item is active
620 *
621 * @param {boolean|null} State of activity
622 */
623 active : function (bool) {
624 var cl = this.element().classList;
625 if (bool === undefined)
626 return cl.contains("active");
627 else if (bool)
628 cl.add("active");
629 else
630 cl.remove("active");
631 },
632
633 /**
634 * Check or set if the item is
635 * at the boundary of the menu
636 * list
637 *
638 * @param {boolean|null} State of activity
639 */
640 noMore : function (bool) {
641 var cl = this.element().classList;
642 if (bool === undefined)
643 return cl.contains("no-more");
644 else if (bool)
645 cl.add("no-more");
646 else
647 cl.remove("no-more");
648 },
649
650 /**
651 * Get the document element of the menu item
652 */
653 element : function () {
654 // already defined
655 if (this._element !== undefined)
656 return this._element;
657
658 // Create list item
659 var li = document.createElement("li");
660
661 // Connect action
Nils Diewald5975d702015-03-09 17:45:42 +0000662 if (this.onclick !== undefined)
663 li["onclick"] = this.onclick.bind(this);
Nils Diewaldfda29d92015-01-22 17:28:01 +0000664
665 // Append template
666 li.appendChild(this.content());
667
668 return this._element = li;
669 },
670
671 /**
672 * Highlight parts of the item
673 *
674 * @param {string} Prefix string for highlights
675 */
676 highlight : function (prefix) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000677 var children = this.element().childNodes;
678 for (var i = children.length -1; i >= 0; i--) {
679 this._highlight(children[i], prefix);
680 };
Nils Diewaldfda29d92015-01-22 17:28:01 +0000681 },
682
683 // Highlight a certain substring of the menu item
684 _highlight : function (elem, prefix) {
685
686 if (elem.nodeType === 3) {
687
688 var text = elem.nodeValue;
689 var textlc = text.toLowerCase();
690 var pos = textlc.indexOf(prefix);
691 if (pos >= 0) {
692
693 // First element
694 if (pos > 0) {
695 elem.parentNode.insertBefore(
696 document.createTextNode(text.substr(0, pos)),
697 elem
698 );
699 };
700
701 // Second element
702 var hl = document.createElement("mark");
703 hl.appendChild(
704 document.createTextNode(text.substr(pos, prefix.length))
705 );
706 elem.parentNode.insertBefore(hl, elem);
707
708 // Third element
709 var third = text.substr(pos + prefix.length);
710 if (third.length > 0) {
711 var thirdE = document.createTextNode(third);
712 elem.parentNode.insertBefore(
713 thirdE,
714 elem
715 );
716 this._highlight(thirdE, prefix);
717 };
718
719 var p = elem.parentNode;
720 p.removeChild(elem);
721 };
722 }
723 else {
724 var children = elem.childNodes;
725 for (var i = children.length -1; i >= 0; i--) {
726 this._highlight(children[i], prefix);
727 };
728 };
729 },
730
731
732 /**
733 * Remove highlight of the menu item
734 */
735 lowlight : function () {
736 var e = this.element();
737
738 var marks = e.getElementsByTagName("mark");
739 for (var i = marks.length - 1; i >= 0; i--) {
740 // Create text node clone
741 var x = document.createTextNode(
742 marks[i].firstChild.nodeValue
743 );
744
745 // Replace with content
746 marks[i].parentNode.replaceChild(
747 x,
748 marks[i]
749 );
750 };
751
752 // Remove consecutive textnodes
753 e.normalize();
754 },
755
756 // Initialize menu item
757 _init : function (params) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000758
Nils Diewaldfda29d92015-01-22 17:28:01 +0000759 if (params[0] === undefined)
760 throw new Error("Missing parameters");
761
762 this.content(params[0]);
763
764 if (params.length === 2)
765 this._action = params[1];
766
767 this._lcField = ' ' + this.content().textContent.toLowerCase();
768
769 return this;
770 },
771 };
772
Nils Diewald5975d702015-03-09 17:45:42 +0000773 KorAP.MenuPrefix = {
774 create : function (params) {
775 return Object.create(KorAP.MenuPrefix)._init();
776 },
777 _init : function () {
778 this._string = '';
779
780 // Add prefix span
781 this._element = document.createElement('span');
782 this._element.classList.add('pref');
783 return this;
784 },
785 _update : function () {
786 this._element.innerHTML
787 = this._string;
788 },
789
790 /**
791 * Upgrade this object to another object,
792 * while private data stays intact.
793 *
794 * @param {Object} An object with properties.
795 */
796 upgradeTo : function (props) {
797 for (var prop in props) {
798 this[prop] = props[prop];
799 };
800 return this;
801 },
802
803 active : function (bool) {
804 var cl = this.element().classList;
805 if (bool === undefined)
806 return cl.contains("active");
807 else if (bool)
808 cl.add("active");
809 else
810 cl.remove("active");
811 },
812 element : function () {
813 return this._element;
814 },
815 isSet : function () {
816 return this._string.length > 0 ?
817 true : false;
818 },
819 value : function (string) {
820 if (arguments.length === 1) {
821 this._string = string;
822 this._update();
823 };
824 return this._string;
825 },
826 add : function (string) {
827 this._string += string;
828 this._update();
829 },
830 onclick : function () {},
831 backspace : function () {
832 if (this._string.length > 1) {
833 this._string = this._string.substring(
834 0, this._string.length - 1
835 );
836 }
837 else {
838 this._string = '';
839 };
840
841 this._update();
842 }
843 };
844
Nils Diewald59c02fc2015-03-07 01:29:09 +0000845 function _codeFromEvent (e) {
Nils Diewald5975d702015-03-09 17:45:42 +0000846 if (e.charCode && (e.keyCode == 0))
Nils Diewald59c02fc2015-03-07 01:29:09 +0000847 return e.charCode
848 return e.keyCode;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000849 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000850
Nils Diewaldfda29d92015-01-22 17:28:01 +0000851}(this.KorAP));
Nils Diewald5975d702015-03-09 17:45:42 +0000852
853/**
854 * MenuItems may define:
855 *
856 * onclick: action happen on click and enter.
857 * further: action happen on right arrow
858 */