blob: 3267b6909f242a3f4c765f3f055f783cde6f7bcf [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
Nils Diewaldd2b57372015-03-10 20:09:48 +000013 if (Event.halt === undefined) {
14 // Don't let events bubble up
15 Event.prototype.halt = function () {
16 this.stopPropagation();
17 this.preventDefault();
18 };
Nils Diewald59c02fc2015-03-07 01:29:09 +000019 };
20
Nils Diewald86dad5b2015-01-28 15:09:07 +000021 // Default maximum number of menu items
22 KorAP.menuLimit = 8;
23
24 /**
25 * List of items for drop down menu (complete).
26 * Only a sublist of the menu is filtered (live).
27 * Only a sublist of the filtered menu is visible (shown).
28 */
29 KorAP.Menu = {
30 /**
31 * Create new Menu based on the action prefix
32 * and a list of menu items.
33 *
34 * @this {Menu}
35 * @constructor
36 * @param {string} Context prefix
37 * @param {Array.<Array.<string>>} List of menu items
38 */
39 create : function (params) {
40 return Object.create(KorAP.Menu)._init(params);
41 },
42
Nils Diewald6e43ffd2015-03-25 18:55:39 +000043 /**
44 * Destroy this menu
45 * (in case you don't trust the
46 * mark and sweep GC)!
47 */
48 destroy : function () {
49 if (this._element != undefined)
50 delete this._element["menu"];
51
52 for (var i = 0; i < this._items.length; i++) {
53 delete this._items[i]["_menu"];
54 };
Nils Diewald5c5a7472015-04-02 22:13:38 +000055 delete this._prefix['_menu'];
Nils Diewald6e43ffd2015-03-25 18:55:39 +000056 },
57
Nils Diewald2fe12e12015-03-06 16:47:06 +000058 focus : function () {
59 this._element.focus();
60 },
61
Nils Diewald59c02fc2015-03-07 01:29:09 +000062 // mouse wheel treatment
63 _mousewheel : function (e) {
64 var delta = 0;
Nils Diewald5975d702015-03-09 17:45:42 +000065
66 delta = e.deltaY / 120;
67 if (delta > 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +000068 this.next();
Nils Diewald5975d702015-03-09 17:45:42 +000069 else if (delta < 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +000070 this.prev();
Nils Diewald59c02fc2015-03-07 01:29:09 +000071 e.halt();
72 },
73
74 // Arrow key and prefix treatment
Nils Diewald5975d702015-03-09 17:45:42 +000075 _keypress : function (e) {
Nils Diewald59c02fc2015-03-07 01:29:09 +000076 var code = _codeFromEvent(e);
77
Nils Diewald59c02fc2015-03-07 01:29:09 +000078 switch (code) {
79 case 27: // 'Esc'
80 e.halt();
81 this.hide();
82 break;
Nils Diewald5975d702015-03-09 17:45:42 +000083
Nils Diewald59c02fc2015-03-07 01:29:09 +000084 case 38: // 'Up'
85 e.halt();
86 this.prev();
87 break;
Nils Diewald5975d702015-03-09 17:45:42 +000088 case 33: // 'Page up'
Nils Diewald59c02fc2015-03-07 01:29:09 +000089 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +000090 this.prev();
91 break;
92 case 40: // 'Down'
93 e.halt();
94 this.next();
95 break;
96 case 34: // 'Page down'
97 e.halt();
98 this.next();
99 break;
100 case 39: // 'Right'
Nils Diewalde8518f82015-03-18 22:41:49 +0000101 if (this._prefix.active())
102 break;
103
Nils Diewald5975d702015-03-09 17:45:42 +0000104 var item = this.liveItem(this._position);
105 if (item["further"] !== undefined) {
106 item["further"].bind(item).apply();
107 e.halt();
108 };
109 break;
110 case 13: // 'Enter'
111
112 // Click on prefix
113 if (this._prefix.active())
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000114 this._prefix.onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000115
116 // Click on item
117 else
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000118 this.liveItem(this._position).onclick(e);
Nils Diewald5975d702015-03-09 17:45:42 +0000119 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000120 break;
121 case 8: // 'Backspace'
Nils Diewald5975d702015-03-09 17:45:42 +0000122 this._prefix.backspace();
123 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000124 e.halt();
125 break;
126 default:
Nils Diewald5975d702015-03-09 17:45:42 +0000127 if (e.key !== undefined &&
128 e.key.length != 1)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000129 return;
130
131 // Add prefix
Nils Diewald5975d702015-03-09 17:45:42 +0000132 this._prefix.add(e.key.toLowerCase());
133
134 if (!this.show()) {
135 this.prefix('').show();
136 };
Nils Diewald59c02fc2015-03-07 01:29:09 +0000137 };
138 },
139
Nils Diewald2fe12e12015-03-06 16:47:06 +0000140 // Initialize list
Nils Diewald5975d702015-03-09 17:45:42 +0000141 _init : function (itemClass, prefixClass, params) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000142 var that = this;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000143 this._itemClass = itemClass;
Nils Diewald5975d702015-03-09 17:45:42 +0000144
145 if (prefixClass !== undefined)
146 this._prefix = prefixClass.create();
147 else
148 this._prefix = KorAP.MenuPrefix.create();
149
Nils Diewald5c5a7472015-04-02 22:13:38 +0000150 this._prefix._menu = this;
151
Nils Diewald5975d702015-03-09 17:45:42 +0000152 var e = document.createElement("ul");
Nils Diewald59c02fc2015-03-07 01:29:09 +0000153 e.style.opacity = 0;
154 e.style.outline = 0;
155 e.setAttribute('tabindex', 0);
Nils Diewald5c5a7472015-04-02 22:13:38 +0000156 e.classList.add('menu');
Nils Diewald5975d702015-03-09 17:45:42 +0000157 e.appendChild(this._prefix.element());
Nils Diewald86dad5b2015-01-28 15:09:07 +0000158
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000159 // This has to be cleaned up later on
160 e["menu"] = this;
161
Nils Diewald59c02fc2015-03-07 01:29:09 +0000162 // Arrow keys
163 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000164 'keypress',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000165 function (ev) {
Nils Diewald5975d702015-03-09 17:45:42 +0000166 that._keypress(ev)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000167 },
168 false
169 );
170
171 // Mousewheel
172 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000173 'wheel',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000174 function (ev) {
175 that._mousewheel(ev)
Nils Diewald2fe12e12015-03-06 16:47:06 +0000176 },
177 false
178 );
179
Nils Diewald5975d702015-03-09 17:45:42 +0000180 this._element = e;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000181 this.active = false;
182 this._items = new Array();
183 var i;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000184
185 // Initialize item list based on parameters
Nils Diewald86dad5b2015-01-28 15:09:07 +0000186 for (i in params) {
187 var obj = itemClass.create(params[i]);
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000188
189 // This may become circular
190 obj["_menu"] = this;
191
Nils Diewald2fe12e12015-03-06 16:47:06 +0000192 this._items.push(obj);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000193 };
194 this._limit = KorAP.menuLimit;
195 this._position = 0; // position in the active list
196 this._active = -1; // active item in the item list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000197 this._reset();
198 return this;
199 },
200
Nils Diewald2fe12e12015-03-06 16:47:06 +0000201 /**
202 * Get the instantiated HTML element
203 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000204 element : function () {
205 return this._element;
206 },
207
Nils Diewald2fe12e12015-03-06 16:47:06 +0000208 /**
209 * Get the creator object for items
210 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000211 itemClass : function () {
212 return this._itemClass;
213 },
214
215 /**
Nils Diewald2fe12e12015-03-06 16:47:06 +0000216 * Get and set numerical value for limit,
217 * i.e. the number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000218 */
219 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000220 if (arguments.length === 1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000221 this._limit = limit;
Nils Diewald5975d702015-03-09 17:45:42 +0000222 return this;
223 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000224 return this._limit;
225 },
226
227 /**
228 * Upgrade this object to another object,
229 * while private data stays intact.
230 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000231 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000232 */
233 upgradeTo : function (props) {
234 for (var prop in props) {
235 this[prop] = props[prop];
236 };
237 return this;
238 },
239
Nils Diewald2fe12e12015-03-06 16:47:06 +0000240 // Reset chosen item and prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000241 _reset : function () {
242 this._offset = 0;
243 this._pos = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000244 this._prefix.value('');
Nils Diewald86dad5b2015-01-28 15:09:07 +0000245 },
246
247 /**
248 * Filter the list and make it visible
249 *
250 * @param {string} Prefix for filtering the list
251 */
Nils Diewald5975d702015-03-09 17:45:42 +0000252 show : function () {
Nils Diewalde8518f82015-03-18 22:41:49 +0000253
Nils Diewald86dad5b2015-01-28 15:09:07 +0000254 // Initialize the list
255 if (!this._initList())
Nils Diewald59c02fc2015-03-07 01:29:09 +0000256 return false;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000257
Nils Diewald2fe12e12015-03-06 16:47:06 +0000258 // show based on initial offset
Nils Diewald86dad5b2015-01-28 15:09:07 +0000259 this._showItems(0);
260
261 // Set the first element to active
Nils Diewald2fe12e12015-03-06 16:47:06 +0000262 // Todo: Or the last element chosen
Nils Diewald86dad5b2015-01-28 15:09:07 +0000263 this.liveItem(0).active(true);
Nils Diewalde8518f82015-03-18 22:41:49 +0000264 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000265
Nils Diewald86dad5b2015-01-28 15:09:07 +0000266 this._active = this._list[0];
Nils Diewald5975d702015-03-09 17:45:42 +0000267 this._position = 0;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000268 this._element.style.opacity = 1;
269
Nils Diewald86dad5b2015-01-28 15:09:07 +0000270 // Add classes for rolling menus
271 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000272 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000273 },
274
Nils Diewald2fe12e12015-03-06 16:47:06 +0000275 hide : function () {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000276 this.active = false;
277 this.delete();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000278 this._element.style.opacity = 0;
Nils Diewald5c5a7472015-04-02 22:13:38 +0000279 this.onHide();
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000280 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000281 },
282
Nils Diewald5c5a7472015-04-02 22:13:38 +0000283 // To be override
284 onHide : function () {},
285
Nils Diewald2fe12e12015-03-06 16:47:06 +0000286 // Initialize the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000287 _initList : function () {
288
Nils Diewald2fe12e12015-03-06 16:47:06 +0000289 // Create a new list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000290 if (this._list === undefined) {
291 this._list = [];
292 }
293 else if (this._list.length != 0) {
294 this._boundary(false);
295 this._list.length = 0;
296 };
297
298 // Offset is initially zero
299 this._offset = 0;
300
Nils Diewald2fe12e12015-03-06 16:47:06 +0000301 // There is no prefix set
Nils Diewald86dad5b2015-01-28 15:09:07 +0000302 if (this.prefix().length <= 0) {
Nils Diewald5975d702015-03-09 17:45:42 +0000303 var i = 0;
304 for (; i < this._items.length; i++)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000305 this._list.push(i);
Nils Diewald5975d702015-03-09 17:45:42 +0000306 while (this._items[++i] !== undefined) {
307 this._items[i].lowlight();
308 console.log(this._item);
309 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000310 return true;
311 };
312
Nils Diewald2fe12e12015-03-06 16:47:06 +0000313 // There is a prefix set, so filter the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000314 var pos;
315 var paddedPrefix = " " + this.prefix();
316
Nils Diewald2fe12e12015-03-06 16:47:06 +0000317 // Iterate over all items and choose preferred matching items
318 // i.e. the matching happens at the word start
Nils Diewald86dad5b2015-01-28 15:09:07 +0000319 for (pos = 0; pos < this._items.length; pos++) {
320 if ((this.item(pos).lcField().indexOf(paddedPrefix)) >= 0)
321 this._list.push(pos);
322 };
323
Nils Diewald2fe12e12015-03-06 16:47:06 +0000324 // The list is empty - so lower your expectations
325 // Iterate over all items and choose matching items
326 // i.e. the matching happens anywhere in the word
Nils Diewald86dad5b2015-01-28 15:09:07 +0000327 if (this._list.length == 0) {
328 for (pos = 0; pos < this._items.length; pos++) {
329 if ((this.item(pos).lcField().indexOf(this.prefix())) >= 0)
330 this._list.push(pos);
331 };
332 };
333
Nils Diewald2fe12e12015-03-06 16:47:06 +0000334 // Filter was successful - yeah!
Nils Diewald86dad5b2015-01-28 15:09:07 +0000335 return this._list.length > 0 ? true : false;
336 },
337
338 // Set boundary for viewport
339 _boundary : function (bool) {
340 this.item(this._list[0]).noMore(bool);
341 this.item(this._list[this._list.length - 1]).noMore(bool);
342 },
343
344 /**
345 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000346 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000347 */
Nils Diewald5975d702015-03-09 17:45:42 +0000348 prefix : function (pref) {
349 if (arguments.length === 1) {
350 this._prefix.value(pref);
351 return this;
352 };
353 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000354 },
355
Nils Diewald2fe12e12015-03-06 16:47:06 +0000356 // Append Items that should be shown
Nils Diewald86dad5b2015-01-28 15:09:07 +0000357 _showItems : function (offset) {
358 this.delete();
359
360 // Use list
361 var shown = 0;
362 var i;
363 for (i in this._list) {
364
365 // Don't show - it's before offset
366 if (shown++ < offset)
367 continue;
368
369 this._append(this._list[i]);
370
371 if (shown >= (this.limit() + this._offset))
372 break;
373 };
374 },
375
376 /**
377 * Delete all visible items from the menu element
378 */
379 delete : function () {
380 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000381
Nils Diewald5975d702015-03-09 17:45:42 +0000382 /*
Nils Diewald2fe12e12015-03-06 16:47:06 +0000383 // Iterate over all visible items
Nils Diewald86dad5b2015-01-28 15:09:07 +0000384 for (var i = 0; i <= this.limit(); i++) {
385
Nils Diewald5975d702015-03-09 17:45:42 +0000386 // there is a visible element
387 // unhighlight!
Nils Diewald59c02fc2015-03-07 01:29:09 +0000388 if (child = this.shownItem(i)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000389 child.lowlight();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000390 child.active(false);
391 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000392 };
Nils Diewald5975d702015-03-09 17:45:42 +0000393 */
394
395 for (var i in this._list) {
396 var item = this._items[this._list[i]];
397 item.lowlight();
398 item.active(false);
399 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000400
Nils Diewald2fe12e12015-03-06 16:47:06 +0000401 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000402 var children = this._element.childNodes;
403 for (var i = children.length - 1; i >= 1; i--) {
404 this._element.removeChild(
405 children[i]
406 );
407 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000408 },
409
410
411 // Append item to the shown list based on index
412 _append : function (i) {
413 var item = this.item(i);
414
415 // Highlight based on prefix
416 if (this.prefix().length > 0)
417 item.highlight(this.prefix());
418
419 // Append element
420 this.element().appendChild(item.element());
421 },
422
423
Nils Diewald2fe12e12015-03-06 16:47:06 +0000424 // Prepend item to the shown list based on index
425 _prepend : function (i) {
426 var item = this.item(i);
427
428 // Highlight based on prefix
429 if (this.prefix().length > 0)
430 item.highlight(this.prefix());
431
432 var e = this.element();
433 // Append element
434 e.insertBefore(
435 item.element(),
Nils Diewald5975d702015-03-09 17:45:42 +0000436 e.children[1]
Nils Diewald2fe12e12015-03-06 16:47:06 +0000437 );
438 },
439
440
441 /**
442 * Get a specific item from the complete list
443 *
444 * @param {number} index of the list item
445 */
446 item : function (index) {
447 return this._items[index]
448 },
449
450
Nils Diewald86dad5b2015-01-28 15:09:07 +0000451 /**
452 * Get a specific item from the filtered list
453 *
454 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000455 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000456 */
457 liveItem : function (index) {
458 if (this._list === undefined)
459 if (!this._initList())
460 return;
461
462 return this._items[this._list[index]];
463 },
464
Nils Diewald86dad5b2015-01-28 15:09:07 +0000465
466 /**
467 * Get a specific item from the visible list
468 *
469 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000470 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000471 */
472 shownItem : function (index) {
473 if (index >= this.limit())
474 return;
475 return this.liveItem(this._offset + index);
476 },
477
478
Nils Diewald2fe12e12015-03-06 16:47:06 +0000479 /**
480 * Get the length of the full list
481 */
482 length : function () {
483 return this._items.length;
484 },
485
486
487 /**
488 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000489 */
490 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000491
Nils Diewald86dad5b2015-01-28 15:09:07 +0000492 // No active element set
Nils Diewald5975d702015-03-09 17:45:42 +0000493 if (this._position === -1)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000494 return;
495
Nils Diewald5975d702015-03-09 17:45:42 +0000496 var newItem;
497
Nils Diewald86dad5b2015-01-28 15:09:07 +0000498 // Set new live item
Nils Diewalde8518f82015-03-18 22:41:49 +0000499 if (!this._prefix.active()) {
500 var oldItem = this.liveItem(this._position);
501 oldItem.active(false);
502 };
503
504 this._position++;
505
Nils Diewald5975d702015-03-09 17:45:42 +0000506 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000507
Nils Diewald5975d702015-03-09 17:45:42 +0000508 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000509 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000510
511 // Activate prefix
512 var prefix = this._prefix;
513
514 // Mark prefix
515 if (prefix.isSet() && !prefix.active()) {
516 this._position--;
517 prefix.active(true);
518 return;
519 }
520 else {
521 this._offset = 0;
522 this._position = 0;
523 newItem = this.liveItem(0);
524 this._showItems(0);
525 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000526 }
527
528 // The next element is outside the view - roll down
Nils Diewald2fe12e12015-03-06 16:47:06 +0000529 else if (this._position >= (this.limit() + this._offset)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000530 this._removeFirst();
531 this._offset++;
532 this._append(this._list[this._position]);
533 };
Nils Diewald5975d702015-03-09 17:45:42 +0000534
535 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000536 newItem.active(true);
537 },
538
Nils Diewalde8518f82015-03-18 22:41:49 +0000539 /*
540 * Page down to the first item on the next page
541 */
542 /*
543 nextPage : function () {
544
545 // Prefix is active
546 if (this._prefix.active()) {
547 this._prefix.active(false);
548 }
549
550 // Last item is chosen
551 else if (this._position >= this.limit() + this._offset) {
552
553 this._position = this.limit() + this._offset - 1;
554 newItem = this.liveItem(this._position);
555 var oldItem = this.liveItem(this._position--);
556 oldItem.active(false);
557 }
558
559 // Last item of page is chosen
560 else if (0) {
561
562 // Jump to last item
563 else {
564 var oldItem = this.liveItem(this._position);
565 oldItem.active(false);
566
567 this._position = this.limit() + this._offset - 1;
568 newItem = this.liveItem(this._position);
569 };
570
571 newItem.active(true);
572 },
573 */
574
Nils Diewald86dad5b2015-01-28 15:09:07 +0000575
576 /*
577 * Make the previous item in the menu active
578 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000579 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000580
Nils Diewald2fe12e12015-03-06 16:47:06 +0000581 // No active element set
Nils Diewalde8518f82015-03-18 22:41:49 +0000582 if (this._position === -1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000583 return;
Nils Diewalde8518f82015-03-18 22:41:49 +0000584 // TODO: Choose last item
585 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000586
Nils Diewald5975d702015-03-09 17:45:42 +0000587 var newItem;
588
Nils Diewald86dad5b2015-01-28 15:09:07 +0000589 // Set new live item
Nils Diewald2d210752015-03-09 19:01:15 +0000590 if (!this._prefix.active()) {
591 var oldItem = this.liveItem(this._position--);
592 oldItem.active(false);
593 };
594
Nils Diewald5975d702015-03-09 17:45:42 +0000595 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000596
597 // The previous element is undefined - roll to bottom
598 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000599
600 // Activate prefix
601 var prefix = this._prefix;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000602 this._offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000603
604 // Normalize offset
605 this._offset = this._offset < 0 ? 0 : this._offset;
606
Nils Diewald2fe12e12015-03-06 16:47:06 +0000607 this._position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000608
609 if (prefix.isSet() && !prefix.active()) {
610 this._position++;
611 prefix.active(true);
612 return;
613 }
614 else {
615 newItem = this.liveItem(this._position);
616 this._showItems(this._offset);
617 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000618 }
619
620 // The previous element is outside the view - roll up
621 else if (this._position < this._offset) {
622 this._removeLast();
623 this._offset--;
624 this._prepend(this._list[this._position]);
625 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000626
Nils Diewald5975d702015-03-09 17:45:42 +0000627 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000628 newItem.active(true);
629 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000630
631
Nils Diewald5975d702015-03-09 17:45:42 +0000632 // Length of the filtered list
633 liveLength : function () {
634 if (this._list === undefined)
635 this._initList();
636 return this._list.length;
637 },
638
639
Nils Diewald2fe12e12015-03-06 16:47:06 +0000640 // Remove the HTML node from the first item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000641 _removeFirst : function () {
642 this.item(this._list[this._offset]).lowlight();
Nils Diewald5975d702015-03-09 17:45:42 +0000643 this._element.removeChild(this._element.children[1]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000644 },
645
Nils Diewald2fe12e12015-03-06 16:47:06 +0000646
647 // Remove the HTML node from the last item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000648 _removeLast : function () {
Nils Diewald2fe12e12015-03-06 16:47:06 +0000649 this.item(this._list[this._offset + this.limit() - 1]).lowlight();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000650 this._element.removeChild(this._element.lastChild);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000651 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000652 };
653
654
Nils Diewaldfda29d92015-01-22 17:28:01 +0000655 /**
656 * Item in the Dropdown menu
657 */
658 KorAP.MenuItem = {
659
660 /**
661 * Create a new MenuItem object.
662 *
663 * @constructor
664 * @this {MenuItem}
665 * @param {Array.<string>} An array object of name, action and
666 * optionally a description
667 */
668 create : function (params) {
669 return Object.create(KorAP.MenuItem)._init(params);
670 },
671
672 /**
673 * Upgrade this object to another object,
674 * while private data stays intact.
675 *
676 * @param {Object] An object with properties.
677 */
678 upgradeTo : function (props) {
679 for (var prop in props) {
680 this[prop] = props[prop];
681 };
682 return this;
683 },
684
685 content : function (content) {
686 if (arguments.length === 1)
687 this._content = document.createTextNode(content);
688 return this._content;
689 },
690
691 lcField : function () {
692 return this._lcField;
693 },
694
695 action : function (action) {
696 if (arguments.length === 1)
697 this._action = action;
698 return this._action;
699 },
700
701 /**
702 * Check or set if the item is active
703 *
704 * @param {boolean|null} State of activity
705 */
706 active : function (bool) {
707 var cl = this.element().classList;
708 if (bool === undefined)
709 return cl.contains("active");
710 else if (bool)
711 cl.add("active");
712 else
713 cl.remove("active");
714 },
715
716 /**
717 * Check or set if the item is
718 * at the boundary of the menu
719 * list
720 *
721 * @param {boolean|null} State of activity
722 */
723 noMore : function (bool) {
724 var cl = this.element().classList;
725 if (bool === undefined)
726 return cl.contains("no-more");
727 else if (bool)
728 cl.add("no-more");
729 else
730 cl.remove("no-more");
731 },
732
733 /**
734 * Get the document element of the menu item
735 */
736 element : function () {
737 // already defined
738 if (this._element !== undefined)
739 return this._element;
740
741 // Create list item
742 var li = document.createElement("li");
743
744 // Connect action
Nils Diewald5c5a7472015-04-02 22:13:38 +0000745 if (this["onclick"] !== undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000746 li["onclick"] = this.onclick.bind(this);
Nils Diewald5c5a7472015-04-02 22:13:38 +0000747 };
Nils Diewaldfda29d92015-01-22 17:28:01 +0000748
749 // Append template
750 li.appendChild(this.content());
751
752 return this._element = li;
753 },
754
755 /**
756 * Highlight parts of the item
757 *
758 * @param {string} Prefix string for highlights
759 */
760 highlight : function (prefix) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000761 var children = this.element().childNodes;
762 for (var i = children.length -1; i >= 0; i--) {
763 this._highlight(children[i], prefix);
764 };
Nils Diewaldfda29d92015-01-22 17:28:01 +0000765 },
766
767 // Highlight a certain substring of the menu item
768 _highlight : function (elem, prefix) {
769
770 if (elem.nodeType === 3) {
771
772 var text = elem.nodeValue;
773 var textlc = text.toLowerCase();
774 var pos = textlc.indexOf(prefix);
775 if (pos >= 0) {
776
777 // First element
778 if (pos > 0) {
779 elem.parentNode.insertBefore(
780 document.createTextNode(text.substr(0, pos)),
781 elem
782 );
783 };
784
785 // Second element
786 var hl = document.createElement("mark");
787 hl.appendChild(
788 document.createTextNode(text.substr(pos, prefix.length))
789 );
790 elem.parentNode.insertBefore(hl, elem);
791
792 // Third element
793 var third = text.substr(pos + prefix.length);
794 if (third.length > 0) {
795 var thirdE = document.createTextNode(third);
796 elem.parentNode.insertBefore(
797 thirdE,
798 elem
799 );
800 this._highlight(thirdE, prefix);
801 };
802
803 var p = elem.parentNode;
804 p.removeChild(elem);
805 };
806 }
807 else {
808 var children = elem.childNodes;
809 for (var i = children.length -1; i >= 0; i--) {
810 this._highlight(children[i], prefix);
811 };
812 };
813 },
814
815
816 /**
817 * Remove highlight of the menu item
818 */
819 lowlight : function () {
820 var e = this.element();
821
822 var marks = e.getElementsByTagName("mark");
823 for (var i = marks.length - 1; i >= 0; i--) {
824 // Create text node clone
825 var x = document.createTextNode(
826 marks[i].firstChild.nodeValue
827 );
828
829 // Replace with content
830 marks[i].parentNode.replaceChild(
831 x,
832 marks[i]
833 );
834 };
835
836 // Remove consecutive textnodes
837 e.normalize();
838 },
839
840 // Initialize menu item
841 _init : function (params) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000842
Nils Diewaldfda29d92015-01-22 17:28:01 +0000843 if (params[0] === undefined)
844 throw new Error("Missing parameters");
845
846 this.content(params[0]);
847
848 if (params.length === 2)
849 this._action = params[1];
850
851 this._lcField = ' ' + this.content().textContent.toLowerCase();
852
853 return this;
854 },
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000855
856 /**
857 * Return menu list.
858 */
859 menu : function () {
860 return this._menu;
861 }
Nils Diewaldfda29d92015-01-22 17:28:01 +0000862 };
863
Nils Diewald5975d702015-03-09 17:45:42 +0000864 KorAP.MenuPrefix = {
865 create : function (params) {
866 return Object.create(KorAP.MenuPrefix)._init();
867 },
868 _init : function () {
869 this._string = '';
870
871 // Add prefix span
872 this._element = document.createElement('span');
873 this._element.classList.add('pref');
Nils Diewalde8518f82015-03-18 22:41:49 +0000874 // Connect action
Nils Diewald5c5a7472015-04-02 22:13:38 +0000875
876 if (this["onclick"] !== undefined)
Nils Diewalde8518f82015-03-18 22:41:49 +0000877 this._element["onclick"] = this.onclick.bind(this);
878
Nils Diewald5975d702015-03-09 17:45:42 +0000879 return this;
880 },
881 _update : function () {
882 this._element.innerHTML
883 = this._string;
884 },
885
886 /**
887 * Upgrade this object to another object,
888 * while private data stays intact.
889 *
890 * @param {Object} An object with properties.
891 */
892 upgradeTo : function (props) {
893 for (var prop in props) {
894 this[prop] = props[prop];
895 };
896 return this;
897 },
898
899 active : function (bool) {
900 var cl = this.element().classList;
901 if (bool === undefined)
902 return cl.contains("active");
903 else if (bool)
904 cl.add("active");
905 else
906 cl.remove("active");
907 },
Nils Diewald5c5a7472015-04-02 22:13:38 +0000908
Nils Diewald5975d702015-03-09 17:45:42 +0000909 element : function () {
910 return this._element;
911 },
Nils Diewald5c5a7472015-04-02 22:13:38 +0000912
Nils Diewald5975d702015-03-09 17:45:42 +0000913 isSet : function () {
914 return this._string.length > 0 ?
915 true : false;
916 },
Nils Diewald5c5a7472015-04-02 22:13:38 +0000917
Nils Diewald5975d702015-03-09 17:45:42 +0000918 value : function (string) {
919 if (arguments.length === 1) {
920 this._string = string;
921 this._update();
922 };
923 return this._string;
924 },
Nils Diewald5c5a7472015-04-02 22:13:38 +0000925
Nils Diewald5975d702015-03-09 17:45:42 +0000926 add : function (string) {
927 this._string += string;
928 this._update();
929 },
Nils Diewald5c5a7472015-04-02 22:13:38 +0000930
931 onclick : function () {},
932
Nils Diewald5975d702015-03-09 17:45:42 +0000933 backspace : function () {
934 if (this._string.length > 1) {
935 this._string = this._string.substring(
936 0, this._string.length - 1
937 );
938 }
939 else {
940 this._string = '';
941 };
942
943 this._update();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000944 },
945
946 /**
947 * Return menu list.
948 */
949 menu : function () {
950 return this._menu;
Nils Diewald5975d702015-03-09 17:45:42 +0000951 }
952 };
953
Nils Diewald59c02fc2015-03-07 01:29:09 +0000954 function _codeFromEvent (e) {
Nils Diewald5975d702015-03-09 17:45:42 +0000955 if (e.charCode && (e.keyCode == 0))
Nils Diewald59c02fc2015-03-07 01:29:09 +0000956 return e.charCode
957 return e.keyCode;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000958 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000959
Nils Diewaldfda29d92015-01-22 17:28:01 +0000960}(this.KorAP));
Nils Diewald5975d702015-03-09 17:45:42 +0000961
962/**
963 * MenuItems may define:
964 *
965 * onclick: action happen on click and enter.
966 * further: action happen on right arrow
967 */