blob: 1cf34553ff47a9ef9d424daf66ba88b8d6f3d5a4 [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 Diewald58141332015-04-07 16:18:45 +0000157 e.classList.add('roll');
Nils Diewald5975d702015-03-09 17:45:42 +0000158 e.appendChild(this._prefix.element());
Nils Diewald86dad5b2015-01-28 15:09:07 +0000159
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000160 // This has to be cleaned up later on
161 e["menu"] = this;
162
Nils Diewald59c02fc2015-03-07 01:29:09 +0000163 // Arrow keys
164 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000165 'keypress',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000166 function (ev) {
Nils Diewald5975d702015-03-09 17:45:42 +0000167 that._keypress(ev)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000168 },
169 false
170 );
171
172 // Mousewheel
173 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000174 'wheel',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000175 function (ev) {
176 that._mousewheel(ev)
Nils Diewald2fe12e12015-03-06 16:47:06 +0000177 },
178 false
179 );
180
Nils Diewald5975d702015-03-09 17:45:42 +0000181 this._element = e;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000182 this.active = false;
183 this._items = new Array();
184 var i;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000185
186 // Initialize item list based on parameters
Nils Diewald86dad5b2015-01-28 15:09:07 +0000187 for (i in params) {
188 var obj = itemClass.create(params[i]);
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000189
190 // This may become circular
191 obj["_menu"] = this;
192
Nils Diewald2fe12e12015-03-06 16:47:06 +0000193 this._items.push(obj);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000194 };
195 this._limit = KorAP.menuLimit;
196 this._position = 0; // position in the active list
197 this._active = -1; // active item in the item list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000198 this._reset();
199 return this;
200 },
201
Nils Diewald2fe12e12015-03-06 16:47:06 +0000202 /**
203 * Get the instantiated HTML element
204 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000205 element : function () {
206 return this._element;
207 },
208
Nils Diewald2fe12e12015-03-06 16:47:06 +0000209 /**
210 * Get the creator object for items
211 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000212 itemClass : function () {
213 return this._itemClass;
214 },
215
216 /**
Nils Diewald2fe12e12015-03-06 16:47:06 +0000217 * Get and set numerical value for limit,
218 * i.e. the number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000219 */
220 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000221 if (arguments.length === 1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000222 this._limit = limit;
Nils Diewald5975d702015-03-09 17:45:42 +0000223 return this;
224 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000225 return this._limit;
226 },
227
228 /**
229 * Upgrade this object to another object,
230 * while private data stays intact.
231 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000232 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000233 */
234 upgradeTo : function (props) {
235 for (var prop in props) {
236 this[prop] = props[prop];
237 };
238 return this;
239 },
240
Nils Diewald2fe12e12015-03-06 16:47:06 +0000241 // Reset chosen item and prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000242 _reset : function () {
243 this._offset = 0;
244 this._pos = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000245 this._prefix.value('');
Nils Diewald86dad5b2015-01-28 15:09:07 +0000246 },
247
248 /**
249 * Filter the list and make it visible
250 *
251 * @param {string} Prefix for filtering the list
252 */
Nils Diewald5975d702015-03-09 17:45:42 +0000253 show : function () {
Nils Diewalde8518f82015-03-18 22:41:49 +0000254
Nils Diewald86dad5b2015-01-28 15:09:07 +0000255 // Initialize the list
256 if (!this._initList())
Nils Diewald59c02fc2015-03-07 01:29:09 +0000257 return false;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000258
Nils Diewald2fe12e12015-03-06 16:47:06 +0000259 // show based on initial offset
Nils Diewald86dad5b2015-01-28 15:09:07 +0000260 this._showItems(0);
261
262 // Set the first element to active
Nils Diewald2fe12e12015-03-06 16:47:06 +0000263 // Todo: Or the last element chosen
Nils Diewald86dad5b2015-01-28 15:09:07 +0000264 this.liveItem(0).active(true);
Nils Diewalde8518f82015-03-18 22:41:49 +0000265 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000266
Nils Diewald86dad5b2015-01-28 15:09:07 +0000267 this._active = this._list[0];
Nils Diewald5975d702015-03-09 17:45:42 +0000268 this._position = 0;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000269 this._element.style.opacity = 1;
270
Nils Diewald86dad5b2015-01-28 15:09:07 +0000271 // Add classes for rolling menus
272 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000273 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000274 },
275
Nils Diewald2fe12e12015-03-06 16:47:06 +0000276 hide : function () {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000277 this.active = false;
278 this.delete();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000279 this._element.style.opacity = 0;
Nils Diewald5c5a7472015-04-02 22:13:38 +0000280 this.onHide();
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000281 /* this._element.blur(); */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000282 },
283
Nils Diewald5c5a7472015-04-02 22:13:38 +0000284 // To be override
285 onHide : function () {},
286
Nils Diewald2fe12e12015-03-06 16:47:06 +0000287 // Initialize the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000288 _initList : function () {
289
Nils Diewald2fe12e12015-03-06 16:47:06 +0000290 // Create a new list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000291 if (this._list === undefined) {
292 this._list = [];
293 }
294 else if (this._list.length != 0) {
295 this._boundary(false);
296 this._list.length = 0;
297 };
298
299 // Offset is initially zero
300 this._offset = 0;
301
Nils Diewald2fe12e12015-03-06 16:47:06 +0000302 // There is no prefix set
Nils Diewald86dad5b2015-01-28 15:09:07 +0000303 if (this.prefix().length <= 0) {
Nils Diewald5975d702015-03-09 17:45:42 +0000304 var i = 0;
305 for (; i < this._items.length; i++)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000306 this._list.push(i);
Nils Diewald5975d702015-03-09 17:45:42 +0000307 while (this._items[++i] !== undefined) {
308 this._items[i].lowlight();
309 console.log(this._item);
310 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000311 return true;
312 };
313
Nils Diewald2fe12e12015-03-06 16:47:06 +0000314 // There is a prefix set, so filter the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000315 var pos;
316 var paddedPrefix = " " + this.prefix();
317
Nils Diewald2fe12e12015-03-06 16:47:06 +0000318 // Iterate over all items and choose preferred matching items
319 // i.e. the matching happens at the word start
Nils Diewald86dad5b2015-01-28 15:09:07 +0000320 for (pos = 0; pos < this._items.length; pos++) {
321 if ((this.item(pos).lcField().indexOf(paddedPrefix)) >= 0)
322 this._list.push(pos);
323 };
324
Nils Diewald2fe12e12015-03-06 16:47:06 +0000325 // The list is empty - so lower your expectations
326 // Iterate over all items and choose matching items
327 // i.e. the matching happens anywhere in the word
Nils Diewald86dad5b2015-01-28 15:09:07 +0000328 if (this._list.length == 0) {
329 for (pos = 0; pos < this._items.length; pos++) {
330 if ((this.item(pos).lcField().indexOf(this.prefix())) >= 0)
331 this._list.push(pos);
332 };
333 };
334
Nils Diewald2fe12e12015-03-06 16:47:06 +0000335 // Filter was successful - yeah!
Nils Diewald86dad5b2015-01-28 15:09:07 +0000336 return this._list.length > 0 ? true : false;
337 },
338
339 // Set boundary for viewport
340 _boundary : function (bool) {
341 this.item(this._list[0]).noMore(bool);
342 this.item(this._list[this._list.length - 1]).noMore(bool);
343 },
344
345 /**
346 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000347 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000348 */
Nils Diewald5975d702015-03-09 17:45:42 +0000349 prefix : function (pref) {
350 if (arguments.length === 1) {
351 this._prefix.value(pref);
352 return this;
353 };
354 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000355 },
356
Nils Diewald2fe12e12015-03-06 16:47:06 +0000357 // Append Items that should be shown
Nils Diewald86dad5b2015-01-28 15:09:07 +0000358 _showItems : function (offset) {
359 this.delete();
360
361 // Use list
362 var shown = 0;
363 var i;
364 for (i in this._list) {
365
366 // Don't show - it's before offset
367 if (shown++ < offset)
368 continue;
369
370 this._append(this._list[i]);
371
372 if (shown >= (this.limit() + this._offset))
373 break;
374 };
375 },
376
377 /**
378 * Delete all visible items from the menu element
379 */
380 delete : function () {
381 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000382
Nils Diewald5975d702015-03-09 17:45:42 +0000383 /*
Nils Diewald2fe12e12015-03-06 16:47:06 +0000384 // Iterate over all visible items
Nils Diewald86dad5b2015-01-28 15:09:07 +0000385 for (var i = 0; i <= this.limit(); i++) {
386
Nils Diewald5975d702015-03-09 17:45:42 +0000387 // there is a visible element
388 // unhighlight!
Nils Diewald59c02fc2015-03-07 01:29:09 +0000389 if (child = this.shownItem(i)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000390 child.lowlight();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000391 child.active(false);
392 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000393 };
Nils Diewald5975d702015-03-09 17:45:42 +0000394 */
395
396 for (var i in this._list) {
397 var item = this._items[this._list[i]];
398 item.lowlight();
399 item.active(false);
400 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000401
Nils Diewald2fe12e12015-03-06 16:47:06 +0000402 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000403 var children = this._element.childNodes;
404 for (var i = children.length - 1; i >= 1; i--) {
405 this._element.removeChild(
406 children[i]
407 );
408 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000409 },
410
411
412 // Append item to the shown list based on index
413 _append : function (i) {
414 var item = this.item(i);
415
416 // Highlight based on prefix
417 if (this.prefix().length > 0)
418 item.highlight(this.prefix());
419
420 // Append element
421 this.element().appendChild(item.element());
422 },
423
424
Nils Diewald2fe12e12015-03-06 16:47:06 +0000425 // Prepend item to the shown list based on index
426 _prepend : function (i) {
427 var item = this.item(i);
428
429 // Highlight based on prefix
430 if (this.prefix().length > 0)
431 item.highlight(this.prefix());
432
433 var e = this.element();
434 // Append element
435 e.insertBefore(
436 item.element(),
Nils Diewald5975d702015-03-09 17:45:42 +0000437 e.children[1]
Nils Diewald2fe12e12015-03-06 16:47:06 +0000438 );
439 },
440
441
442 /**
443 * Get a specific item from the complete list
444 *
445 * @param {number} index of the list item
446 */
447 item : function (index) {
448 return this._items[index]
449 },
450
451
Nils Diewald86dad5b2015-01-28 15:09:07 +0000452 /**
453 * Get a specific item from the filtered list
454 *
455 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000456 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000457 */
458 liveItem : function (index) {
459 if (this._list === undefined)
460 if (!this._initList())
461 return;
462
463 return this._items[this._list[index]];
464 },
465
Nils Diewald86dad5b2015-01-28 15:09:07 +0000466
467 /**
468 * Get a specific item from the visible list
469 *
470 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000471 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000472 */
473 shownItem : function (index) {
474 if (index >= this.limit())
475 return;
476 return this.liveItem(this._offset + index);
477 },
478
479
Nils Diewald2fe12e12015-03-06 16:47:06 +0000480 /**
481 * Get the length of the full list
482 */
483 length : function () {
484 return this._items.length;
485 },
486
487
488 /**
489 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000490 */
491 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000492
Nils Diewald86dad5b2015-01-28 15:09:07 +0000493 // No active element set
Nils Diewald5975d702015-03-09 17:45:42 +0000494 if (this._position === -1)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000495 return;
496
Nils Diewald5975d702015-03-09 17:45:42 +0000497 var newItem;
498
Nils Diewald86dad5b2015-01-28 15:09:07 +0000499 // Set new live item
Nils Diewalde8518f82015-03-18 22:41:49 +0000500 if (!this._prefix.active()) {
501 var oldItem = this.liveItem(this._position);
502 oldItem.active(false);
503 };
504
505 this._position++;
506
Nils Diewald5975d702015-03-09 17:45:42 +0000507 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000508
Nils Diewald5975d702015-03-09 17:45:42 +0000509 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000510 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000511
512 // Activate prefix
513 var prefix = this._prefix;
514
515 // Mark prefix
516 if (prefix.isSet() && !prefix.active()) {
517 this._position--;
518 prefix.active(true);
519 return;
520 }
521 else {
522 this._offset = 0;
523 this._position = 0;
524 newItem = this.liveItem(0);
525 this._showItems(0);
526 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000527 }
528
529 // The next element is outside the view - roll down
Nils Diewald2fe12e12015-03-06 16:47:06 +0000530 else if (this._position >= (this.limit() + this._offset)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000531 this._removeFirst();
532 this._offset++;
533 this._append(this._list[this._position]);
534 };
Nils Diewald5975d702015-03-09 17:45:42 +0000535
536 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000537 newItem.active(true);
538 },
539
Nils Diewalde8518f82015-03-18 22:41:49 +0000540 /*
541 * Page down to the first item on the next page
542 */
543 /*
544 nextPage : function () {
545
546 // Prefix is active
547 if (this._prefix.active()) {
548 this._prefix.active(false);
549 }
550
551 // Last item is chosen
552 else if (this._position >= this.limit() + this._offset) {
553
554 this._position = this.limit() + this._offset - 1;
555 newItem = this.liveItem(this._position);
556 var oldItem = this.liveItem(this._position--);
557 oldItem.active(false);
558 }
559
560 // Last item of page is chosen
561 else if (0) {
562
563 // Jump to last item
564 else {
565 var oldItem = this.liveItem(this._position);
566 oldItem.active(false);
567
568 this._position = this.limit() + this._offset - 1;
569 newItem = this.liveItem(this._position);
570 };
571
572 newItem.active(true);
573 },
574 */
575
Nils Diewald86dad5b2015-01-28 15:09:07 +0000576
577 /*
578 * Make the previous item in the menu active
579 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000580 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000581
Nils Diewald2fe12e12015-03-06 16:47:06 +0000582 // No active element set
Nils Diewalde8518f82015-03-18 22:41:49 +0000583 if (this._position === -1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000584 return;
Nils Diewalde8518f82015-03-18 22:41:49 +0000585 // TODO: Choose last item
586 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000587
Nils Diewald5975d702015-03-09 17:45:42 +0000588 var newItem;
589
Nils Diewald86dad5b2015-01-28 15:09:07 +0000590 // Set new live item
Nils Diewald2d210752015-03-09 19:01:15 +0000591 if (!this._prefix.active()) {
592 var oldItem = this.liveItem(this._position--);
593 oldItem.active(false);
594 };
595
Nils Diewald5975d702015-03-09 17:45:42 +0000596 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000597
598 // The previous element is undefined - roll to bottom
599 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000600
601 // Activate prefix
602 var prefix = this._prefix;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000603 this._offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000604
605 // Normalize offset
606 this._offset = this._offset < 0 ? 0 : this._offset;
607
Nils Diewald2fe12e12015-03-06 16:47:06 +0000608 this._position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000609
610 if (prefix.isSet() && !prefix.active()) {
611 this._position++;
612 prefix.active(true);
613 return;
614 }
615 else {
616 newItem = this.liveItem(this._position);
617 this._showItems(this._offset);
618 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000619 }
620
621 // The previous element is outside the view - roll up
622 else if (this._position < this._offset) {
623 this._removeLast();
624 this._offset--;
625 this._prepend(this._list[this._position]);
626 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000627
Nils Diewald5975d702015-03-09 17:45:42 +0000628 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000629 newItem.active(true);
630 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000631
632
Nils Diewald5975d702015-03-09 17:45:42 +0000633 // Length of the filtered list
634 liveLength : function () {
635 if (this._list === undefined)
636 this._initList();
637 return this._list.length;
638 },
639
640
Nils Diewald2fe12e12015-03-06 16:47:06 +0000641 // Remove the HTML node from the first item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000642 _removeFirst : function () {
643 this.item(this._list[this._offset]).lowlight();
Nils Diewald5975d702015-03-09 17:45:42 +0000644 this._element.removeChild(this._element.children[1]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000645 },
646
Nils Diewald2fe12e12015-03-06 16:47:06 +0000647
648 // Remove the HTML node from the last item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000649 _removeLast : function () {
Nils Diewald2fe12e12015-03-06 16:47:06 +0000650 this.item(this._list[this._offset + this.limit() - 1]).lowlight();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000651 this._element.removeChild(this._element.lastChild);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000652 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000653 };
654
655
Nils Diewaldfda29d92015-01-22 17:28:01 +0000656 /**
657 * Item in the Dropdown menu
658 */
659 KorAP.MenuItem = {
660
661 /**
662 * Create a new MenuItem object.
663 *
664 * @constructor
665 * @this {MenuItem}
666 * @param {Array.<string>} An array object of name, action and
667 * optionally a description
668 */
669 create : function (params) {
670 return Object.create(KorAP.MenuItem)._init(params);
671 },
672
673 /**
674 * Upgrade this object to another object,
675 * while private data stays intact.
676 *
677 * @param {Object] An object with properties.
678 */
679 upgradeTo : function (props) {
680 for (var prop in props) {
681 this[prop] = props[prop];
682 };
683 return this;
684 },
685
686 content : function (content) {
687 if (arguments.length === 1)
688 this._content = document.createTextNode(content);
689 return this._content;
690 },
691
692 lcField : function () {
693 return this._lcField;
694 },
695
696 action : function (action) {
697 if (arguments.length === 1)
698 this._action = action;
699 return this._action;
700 },
701
702 /**
703 * Check or set if the item is active
704 *
705 * @param {boolean|null} State of activity
706 */
707 active : function (bool) {
708 var cl = this.element().classList;
709 if (bool === undefined)
710 return cl.contains("active");
711 else if (bool)
712 cl.add("active");
713 else
714 cl.remove("active");
715 },
716
717 /**
718 * Check or set if the item is
719 * at the boundary of the menu
720 * list
721 *
722 * @param {boolean|null} State of activity
723 */
724 noMore : function (bool) {
725 var cl = this.element().classList;
726 if (bool === undefined)
727 return cl.contains("no-more");
728 else if (bool)
729 cl.add("no-more");
730 else
731 cl.remove("no-more");
732 },
733
734 /**
735 * Get the document element of the menu item
736 */
737 element : function () {
738 // already defined
739 if (this._element !== undefined)
740 return this._element;
741
742 // Create list item
743 var li = document.createElement("li");
744
745 // Connect action
Nils Diewald5c5a7472015-04-02 22:13:38 +0000746 if (this["onclick"] !== undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000747 li["onclick"] = this.onclick.bind(this);
Nils Diewald5c5a7472015-04-02 22:13:38 +0000748 };
Nils Diewaldfda29d92015-01-22 17:28:01 +0000749
750 // Append template
751 li.appendChild(this.content());
752
753 return this._element = li;
754 },
755
756 /**
757 * Highlight parts of the item
758 *
759 * @param {string} Prefix string for highlights
760 */
761 highlight : function (prefix) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000762 var children = this.element().childNodes;
763 for (var i = children.length -1; i >= 0; i--) {
764 this._highlight(children[i], prefix);
765 };
Nils Diewaldfda29d92015-01-22 17:28:01 +0000766 },
767
768 // Highlight a certain substring of the menu item
769 _highlight : function (elem, prefix) {
770
771 if (elem.nodeType === 3) {
772
773 var text = elem.nodeValue;
774 var textlc = text.toLowerCase();
775 var pos = textlc.indexOf(prefix);
776 if (pos >= 0) {
777
778 // First element
779 if (pos > 0) {
780 elem.parentNode.insertBefore(
781 document.createTextNode(text.substr(0, pos)),
782 elem
783 );
784 };
785
786 // Second element
787 var hl = document.createElement("mark");
788 hl.appendChild(
789 document.createTextNode(text.substr(pos, prefix.length))
790 );
791 elem.parentNode.insertBefore(hl, elem);
792
793 // Third element
794 var third = text.substr(pos + prefix.length);
795 if (third.length > 0) {
796 var thirdE = document.createTextNode(third);
797 elem.parentNode.insertBefore(
798 thirdE,
799 elem
800 );
801 this._highlight(thirdE, prefix);
802 };
803
804 var p = elem.parentNode;
805 p.removeChild(elem);
806 };
807 }
808 else {
809 var children = elem.childNodes;
810 for (var i = children.length -1; i >= 0; i--) {
811 this._highlight(children[i], prefix);
812 };
813 };
814 },
815
816
817 /**
818 * Remove highlight of the menu item
819 */
820 lowlight : function () {
821 var e = this.element();
822
823 var marks = e.getElementsByTagName("mark");
824 for (var i = marks.length - 1; i >= 0; i--) {
825 // Create text node clone
826 var x = document.createTextNode(
827 marks[i].firstChild.nodeValue
828 );
829
830 // Replace with content
831 marks[i].parentNode.replaceChild(
832 x,
833 marks[i]
834 );
835 };
836
837 // Remove consecutive textnodes
838 e.normalize();
839 },
840
841 // Initialize menu item
842 _init : function (params) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000843
Nils Diewaldfda29d92015-01-22 17:28:01 +0000844 if (params[0] === undefined)
845 throw new Error("Missing parameters");
846
847 this.content(params[0]);
848
849 if (params.length === 2)
850 this._action = params[1];
851
852 this._lcField = ' ' + this.content().textContent.toLowerCase();
853
854 return this;
855 },
Nils Diewald6e43ffd2015-03-25 18:55:39 +0000856
857 /**
858 * Return menu list.
859 */
860 menu : function () {
861 return this._menu;
862 }
Nils Diewaldfda29d92015-01-22 17:28:01 +0000863 };
864
Nils Diewald5975d702015-03-09 17:45:42 +0000865 KorAP.MenuPrefix = {
866 create : function (params) {
867 return Object.create(KorAP.MenuPrefix)._init();
868 },
869 _init : function () {
870 this._string = '';
871
872 // Add prefix span
873 this._element = document.createElement('span');
874 this._element.classList.add('pref');
Nils Diewalde8518f82015-03-18 22:41:49 +0000875 // Connect action
Nils Diewald5c5a7472015-04-02 22:13:38 +0000876
877 if (this["onclick"] !== undefined)
Nils Diewalde8518f82015-03-18 22:41:49 +0000878 this._element["onclick"] = this.onclick.bind(this);
879
Nils Diewald5975d702015-03-09 17:45:42 +0000880 return this;
881 },
882 _update : function () {
883 this._element.innerHTML
884 = this._string;
885 },
886
887 /**
888 * Upgrade this object to another object,
889 * while private data stays intact.
890 *
891 * @param {Object} An object with properties.
892 */
893 upgradeTo : function (props) {
894 for (var prop in props) {
895 this[prop] = props[prop];
896 };
897 return this;
898 },
899
900 active : function (bool) {
901 var cl = this.element().classList;
902 if (bool === undefined)
903 return cl.contains("active");
904 else if (bool)
905 cl.add("active");
906 else
907 cl.remove("active");
908 },
Nils Diewald5c5a7472015-04-02 22:13:38 +0000909
Nils Diewald5975d702015-03-09 17:45:42 +0000910 element : function () {
911 return this._element;
912 },
Nils Diewald5c5a7472015-04-02 22:13:38 +0000913
Nils Diewald5975d702015-03-09 17:45:42 +0000914 isSet : function () {
915 return this._string.length > 0 ?
916 true : false;
917 },
Nils Diewald5c5a7472015-04-02 22:13:38 +0000918
Nils Diewald5975d702015-03-09 17:45:42 +0000919 value : function (string) {
920 if (arguments.length === 1) {
921 this._string = string;
922 this._update();
923 };
924 return this._string;
925 },
Nils Diewald5c5a7472015-04-02 22:13:38 +0000926
Nils Diewald5975d702015-03-09 17:45:42 +0000927 add : function (string) {
928 this._string += string;
929 this._update();
930 },
Nils Diewald5c5a7472015-04-02 22:13:38 +0000931
932 onclick : function () {},
933
Nils Diewald5975d702015-03-09 17:45:42 +0000934 backspace : function () {
935 if (this._string.length > 1) {
936 this._string = this._string.substring(
937 0, this._string.length - 1
938 );
939 }
940 else {
941 this._string = '';
942 };
943
944 this._update();
Nils Diewald5c5a7472015-04-02 22:13:38 +0000945 },
946
947 /**
948 * Return menu list.
949 */
950 menu : function () {
951 return this._menu;
Nils Diewald5975d702015-03-09 17:45:42 +0000952 }
953 };
954
Nils Diewald59c02fc2015-03-07 01:29:09 +0000955 function _codeFromEvent (e) {
Nils Diewald5975d702015-03-09 17:45:42 +0000956 if (e.charCode && (e.keyCode == 0))
Nils Diewald59c02fc2015-03-07 01:29:09 +0000957 return e.charCode
958 return e.keyCode;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000959 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000960
Nils Diewaldfda29d92015-01-22 17:28:01 +0000961}(this.KorAP));
Nils Diewald5975d702015-03-09 17:45:42 +0000962
963/**
964 * MenuItems may define:
965 *
966 * onclick: action happen on click and enter.
967 * further: action happen on right arrow
968 */