blob: 8ccf740c17607dac7ee45fa055b785ba6ae7836a [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 Diewald2fe12e12015-03-06 16:47:06 +000043 focus : function () {
44 this._element.focus();
45 },
46
Nils Diewald59c02fc2015-03-07 01:29:09 +000047 // mouse wheel treatment
48 _mousewheel : function (e) {
49 var delta = 0;
Nils Diewald5975d702015-03-09 17:45:42 +000050
51 delta = e.deltaY / 120;
52 if (delta > 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +000053 this.next();
Nils Diewald5975d702015-03-09 17:45:42 +000054 else if (delta < 0)
Nils Diewald59c02fc2015-03-07 01:29:09 +000055 this.prev();
Nils Diewald59c02fc2015-03-07 01:29:09 +000056 e.halt();
57 },
58
59 // Arrow key and prefix treatment
Nils Diewald5975d702015-03-09 17:45:42 +000060 _keypress : function (e) {
Nils Diewald59c02fc2015-03-07 01:29:09 +000061 var code = _codeFromEvent(e);
62
Nils Diewald59c02fc2015-03-07 01:29:09 +000063 switch (code) {
64 case 27: // 'Esc'
65 e.halt();
66 this.hide();
67 break;
Nils Diewald5975d702015-03-09 17:45:42 +000068
Nils Diewald59c02fc2015-03-07 01:29:09 +000069 case 38: // 'Up'
70 e.halt();
71 this.prev();
72 break;
Nils Diewald5975d702015-03-09 17:45:42 +000073 case 33: // 'Page up'
Nils Diewald59c02fc2015-03-07 01:29:09 +000074 e.halt();
Nils Diewald5975d702015-03-09 17:45:42 +000075 this.prev();
76 break;
77 case 40: // 'Down'
78 e.halt();
79 this.next();
80 break;
81 case 34: // 'Page down'
82 e.halt();
83 this.next();
84 break;
85 case 39: // 'Right'
Nils Diewalde8518f82015-03-18 22:41:49 +000086 if (this._prefix.active())
87 break;
88
Nils Diewald5975d702015-03-09 17:45:42 +000089 var item = this.liveItem(this._position);
90 if (item["further"] !== undefined) {
91 item["further"].bind(item).apply();
92 e.halt();
93 };
94 break;
95 case 13: // 'Enter'
96
97 // Click on prefix
98 if (this._prefix.active())
99 this._prefix.onclick();
100
101 // Click on item
102 else
103 this.liveItem(this._position).onclick();
104 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000105 break;
106 case 8: // 'Backspace'
Nils Diewald5975d702015-03-09 17:45:42 +0000107 this._prefix.backspace();
108 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000109 e.halt();
110 break;
111 default:
Nils Diewald5975d702015-03-09 17:45:42 +0000112 if (e.key !== undefined &&
113 e.key.length != 1)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000114 return;
115
116 // Add prefix
Nils Diewald5975d702015-03-09 17:45:42 +0000117 this._prefix.add(e.key.toLowerCase());
118
119 if (!this.show()) {
120 this.prefix('').show();
121 };
Nils Diewald59c02fc2015-03-07 01:29:09 +0000122 };
123 },
124
Nils Diewald2fe12e12015-03-06 16:47:06 +0000125 // Initialize list
Nils Diewald5975d702015-03-09 17:45:42 +0000126 _init : function (itemClass, prefixClass, params) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000127 var that = this;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000128 this._itemClass = itemClass;
Nils Diewald5975d702015-03-09 17:45:42 +0000129
130 if (prefixClass !== undefined)
131 this._prefix = prefixClass.create();
132 else
133 this._prefix = KorAP.MenuPrefix.create();
134
135 var e = document.createElement("ul");
Nils Diewald59c02fc2015-03-07 01:29:09 +0000136 e.style.opacity = 0;
137 e.style.outline = 0;
138 e.setAttribute('tabindex', 0);
Nils Diewald5975d702015-03-09 17:45:42 +0000139 e.setAttribute('class', 'menu');
140 e.appendChild(this._prefix.element());
Nils Diewald86dad5b2015-01-28 15:09:07 +0000141
Nils Diewald59c02fc2015-03-07 01:29:09 +0000142 // Arrow keys
143 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000144 'keypress',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000145 function (ev) {
Nils Diewald5975d702015-03-09 17:45:42 +0000146 that._keypress(ev)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000147 },
148 false
149 );
150
151 // Mousewheel
152 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000153 'wheel',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000154 function (ev) {
155 that._mousewheel(ev)
Nils Diewald2fe12e12015-03-06 16:47:06 +0000156 },
157 false
158 );
159
Nils Diewald5975d702015-03-09 17:45:42 +0000160 this._element = e;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000161 this.active = false;
162 this._items = new Array();
163 var i;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000164
165 // Initialize item list based on parameters
Nils Diewald86dad5b2015-01-28 15:09:07 +0000166 for (i in params) {
167 var obj = itemClass.create(params[i]);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000168 this._items.push(obj);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000169 };
170 this._limit = KorAP.menuLimit;
171 this._position = 0; // position in the active list
172 this._active = -1; // active item in the item list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000173 this._reset();
174 return this;
175 },
176
Nils Diewald2fe12e12015-03-06 16:47:06 +0000177 /**
178 * Get the instantiated HTML element
179 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000180 element : function () {
181 return this._element;
182 },
183
Nils Diewald2fe12e12015-03-06 16:47:06 +0000184 /**
185 * Get the creator object for items
186 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000187 itemClass : function () {
188 return this._itemClass;
189 },
190
191 /**
Nils Diewald2fe12e12015-03-06 16:47:06 +0000192 * Get and set numerical value for limit,
193 * i.e. the number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000194 */
195 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000196 if (arguments.length === 1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000197 this._limit = limit;
Nils Diewald5975d702015-03-09 17:45:42 +0000198 return this;
199 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000200 return this._limit;
201 },
202
203 /**
204 * Upgrade this object to another object,
205 * while private data stays intact.
206 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000207 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000208 */
209 upgradeTo : function (props) {
210 for (var prop in props) {
211 this[prop] = props[prop];
212 };
213 return this;
214 },
215
Nils Diewald2fe12e12015-03-06 16:47:06 +0000216 // Reset chosen item and prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000217 _reset : function () {
218 this._offset = 0;
219 this._pos = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000220 this._prefix.value('');
Nils Diewald86dad5b2015-01-28 15:09:07 +0000221 },
222
223 /**
224 * Filter the list and make it visible
225 *
226 * @param {string} Prefix for filtering the list
227 */
Nils Diewald5975d702015-03-09 17:45:42 +0000228 show : function () {
Nils Diewalde8518f82015-03-18 22:41:49 +0000229
Nils Diewald86dad5b2015-01-28 15:09:07 +0000230 // Initialize the list
231 if (!this._initList())
Nils Diewald59c02fc2015-03-07 01:29:09 +0000232 return false;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000233
Nils Diewald2fe12e12015-03-06 16:47:06 +0000234 // show based on initial offset
Nils Diewald86dad5b2015-01-28 15:09:07 +0000235 this._showItems(0);
236
237 // Set the first element to active
Nils Diewald2fe12e12015-03-06 16:47:06 +0000238 // Todo: Or the last element chosen
Nils Diewald86dad5b2015-01-28 15:09:07 +0000239 this.liveItem(0).active(true);
Nils Diewalde8518f82015-03-18 22:41:49 +0000240 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000241
Nils Diewald86dad5b2015-01-28 15:09:07 +0000242 this._active = this._list[0];
Nils Diewald5975d702015-03-09 17:45:42 +0000243 this._position = 0;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000244 this._element.style.opacity = 1;
245
Nils Diewald86dad5b2015-01-28 15:09:07 +0000246 // Add classes for rolling menus
247 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000248 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000249 },
250
Nils Diewald2fe12e12015-03-06 16:47:06 +0000251 hide : function () {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000252 this.active = false;
253 this.delete();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000254 this._element.style.opacity = 0;
Nils Diewald59c02fc2015-03-07 01:29:09 +0000255
256/*
257 this._element.blur();
258*/
Nils Diewald86dad5b2015-01-28 15:09:07 +0000259 },
260
Nils Diewald2fe12e12015-03-06 16:47:06 +0000261 // Initialize the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000262 _initList : function () {
263
Nils Diewald2fe12e12015-03-06 16:47:06 +0000264 // Create a new list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000265 if (this._list === undefined) {
266 this._list = [];
267 }
268 else if (this._list.length != 0) {
269 this._boundary(false);
270 this._list.length = 0;
271 };
272
273 // Offset is initially zero
274 this._offset = 0;
275
Nils Diewald2fe12e12015-03-06 16:47:06 +0000276 // There is no prefix set
Nils Diewald86dad5b2015-01-28 15:09:07 +0000277 if (this.prefix().length <= 0) {
Nils Diewald5975d702015-03-09 17:45:42 +0000278 var i = 0;
279 for (; i < this._items.length; i++)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000280 this._list.push(i);
Nils Diewald5975d702015-03-09 17:45:42 +0000281 while (this._items[++i] !== undefined) {
282 this._items[i].lowlight();
283 console.log(this._item);
284 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000285 return true;
286 };
287
Nils Diewald2fe12e12015-03-06 16:47:06 +0000288 // There is a prefix set, so filter the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000289 var pos;
290 var paddedPrefix = " " + this.prefix();
291
Nils Diewald2fe12e12015-03-06 16:47:06 +0000292 // Iterate over all items and choose preferred matching items
293 // i.e. the matching happens at the word start
Nils Diewald86dad5b2015-01-28 15:09:07 +0000294 for (pos = 0; pos < this._items.length; pos++) {
295 if ((this.item(pos).lcField().indexOf(paddedPrefix)) >= 0)
296 this._list.push(pos);
297 };
298
Nils Diewald2fe12e12015-03-06 16:47:06 +0000299 // The list is empty - so lower your expectations
300 // Iterate over all items and choose matching items
301 // i.e. the matching happens anywhere in the word
Nils Diewald86dad5b2015-01-28 15:09:07 +0000302 if (this._list.length == 0) {
303 for (pos = 0; pos < this._items.length; pos++) {
304 if ((this.item(pos).lcField().indexOf(this.prefix())) >= 0)
305 this._list.push(pos);
306 };
307 };
308
Nils Diewald2fe12e12015-03-06 16:47:06 +0000309 // Filter was successful - yeah!
Nils Diewald86dad5b2015-01-28 15:09:07 +0000310 return this._list.length > 0 ? true : false;
311 },
312
313 // Set boundary for viewport
314 _boundary : function (bool) {
315 this.item(this._list[0]).noMore(bool);
316 this.item(this._list[this._list.length - 1]).noMore(bool);
317 },
318
319 /**
320 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000321 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000322 */
Nils Diewald5975d702015-03-09 17:45:42 +0000323 prefix : function (pref) {
324 if (arguments.length === 1) {
325 this._prefix.value(pref);
326 return this;
327 };
328 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000329 },
330
Nils Diewald2fe12e12015-03-06 16:47:06 +0000331 // Append Items that should be shown
Nils Diewald86dad5b2015-01-28 15:09:07 +0000332 _showItems : function (offset) {
333 this.delete();
334
335 // Use list
336 var shown = 0;
337 var i;
338 for (i in this._list) {
339
340 // Don't show - it's before offset
341 if (shown++ < offset)
342 continue;
343
344 this._append(this._list[i]);
345
346 if (shown >= (this.limit() + this._offset))
347 break;
348 };
349 },
350
351 /**
352 * Delete all visible items from the menu element
353 */
354 delete : function () {
355 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000356
Nils Diewald5975d702015-03-09 17:45:42 +0000357 /*
Nils Diewald2fe12e12015-03-06 16:47:06 +0000358 // Iterate over all visible items
Nils Diewald86dad5b2015-01-28 15:09:07 +0000359 for (var i = 0; i <= this.limit(); i++) {
360
Nils Diewald5975d702015-03-09 17:45:42 +0000361 // there is a visible element
362 // unhighlight!
Nils Diewald59c02fc2015-03-07 01:29:09 +0000363 if (child = this.shownItem(i)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000364 child.lowlight();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000365 child.active(false);
366 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000367 };
Nils Diewald5975d702015-03-09 17:45:42 +0000368 */
369
370 for (var i in this._list) {
371 var item = this._items[this._list[i]];
372 item.lowlight();
373 item.active(false);
374 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000375
Nils Diewald2fe12e12015-03-06 16:47:06 +0000376 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000377 var children = this._element.childNodes;
378 for (var i = children.length - 1; i >= 1; i--) {
379 this._element.removeChild(
380 children[i]
381 );
382 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000383 },
384
385
386 // Append item to the shown list based on index
387 _append : function (i) {
388 var item = this.item(i);
389
390 // Highlight based on prefix
391 if (this.prefix().length > 0)
392 item.highlight(this.prefix());
393
394 // Append element
395 this.element().appendChild(item.element());
396 },
397
398
Nils Diewald2fe12e12015-03-06 16:47:06 +0000399 // Prepend item to the shown list based on index
400 _prepend : function (i) {
401 var item = this.item(i);
402
403 // Highlight based on prefix
404 if (this.prefix().length > 0)
405 item.highlight(this.prefix());
406
407 var e = this.element();
408 // Append element
409 e.insertBefore(
410 item.element(),
Nils Diewald5975d702015-03-09 17:45:42 +0000411 e.children[1]
Nils Diewald2fe12e12015-03-06 16:47:06 +0000412 );
413 },
414
415
416 /**
417 * Get a specific item from the complete list
418 *
419 * @param {number} index of the list item
420 */
421 item : function (index) {
422 return this._items[index]
423 },
424
425
Nils Diewald86dad5b2015-01-28 15:09:07 +0000426 /**
427 * Get a specific item from the filtered list
428 *
429 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000430 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000431 */
432 liveItem : function (index) {
433 if (this._list === undefined)
434 if (!this._initList())
435 return;
436
437 return this._items[this._list[index]];
438 },
439
Nils Diewald86dad5b2015-01-28 15:09:07 +0000440
441 /**
442 * Get a specific item from the visible list
443 *
444 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000445 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000446 */
447 shownItem : function (index) {
448 if (index >= this.limit())
449 return;
450 return this.liveItem(this._offset + index);
451 },
452
453
Nils Diewald2fe12e12015-03-06 16:47:06 +0000454 /**
455 * Get the length of the full list
456 */
457 length : function () {
458 return this._items.length;
459 },
460
461
462 /**
463 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000464 */
465 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000466
Nils Diewald86dad5b2015-01-28 15:09:07 +0000467 // No active element set
Nils Diewald5975d702015-03-09 17:45:42 +0000468 if (this._position === -1)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000469 return;
470
Nils Diewald5975d702015-03-09 17:45:42 +0000471 var newItem;
472
Nils Diewald86dad5b2015-01-28 15:09:07 +0000473 // Set new live item
Nils Diewalde8518f82015-03-18 22:41:49 +0000474 if (!this._prefix.active()) {
475 var oldItem = this.liveItem(this._position);
476 oldItem.active(false);
477 };
478
479 this._position++;
480
Nils Diewald5975d702015-03-09 17:45:42 +0000481 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000482
Nils Diewald5975d702015-03-09 17:45:42 +0000483 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000484 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000485
486 // Activate prefix
487 var prefix = this._prefix;
488
489 // Mark prefix
490 if (prefix.isSet() && !prefix.active()) {
491 this._position--;
492 prefix.active(true);
493 return;
494 }
495 else {
496 this._offset = 0;
497 this._position = 0;
498 newItem = this.liveItem(0);
499 this._showItems(0);
500 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000501 }
502
503 // The next element is outside the view - roll down
Nils Diewald2fe12e12015-03-06 16:47:06 +0000504 else if (this._position >= (this.limit() + this._offset)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000505 this._removeFirst();
506 this._offset++;
507 this._append(this._list[this._position]);
508 };
Nils Diewald5975d702015-03-09 17:45:42 +0000509
510 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000511 newItem.active(true);
512 },
513
Nils Diewalde8518f82015-03-18 22:41:49 +0000514 /*
515 * Page down to the first item on the next page
516 */
517 /*
518 nextPage : function () {
519
520 // Prefix is active
521 if (this._prefix.active()) {
522 this._prefix.active(false);
523 }
524
525 // Last item is chosen
526 else if (this._position >= this.limit() + this._offset) {
527
528 this._position = this.limit() + this._offset - 1;
529 newItem = this.liveItem(this._position);
530 var oldItem = this.liveItem(this._position--);
531 oldItem.active(false);
532 }
533
534 // Last item of page is chosen
535 else if (0) {
536
537 // Jump to last item
538 else {
539 var oldItem = this.liveItem(this._position);
540 oldItem.active(false);
541
542 this._position = this.limit() + this._offset - 1;
543 newItem = this.liveItem(this._position);
544 };
545
546 newItem.active(true);
547 },
548 */
549
Nils Diewald86dad5b2015-01-28 15:09:07 +0000550
551 /*
552 * Make the previous item in the menu active
553 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000554 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000555
Nils Diewald2fe12e12015-03-06 16:47:06 +0000556 // No active element set
Nils Diewalde8518f82015-03-18 22:41:49 +0000557 if (this._position === -1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000558 return;
Nils Diewalde8518f82015-03-18 22:41:49 +0000559 // TODO: Choose last item
560 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000561
Nils Diewald5975d702015-03-09 17:45:42 +0000562 var newItem;
563
Nils Diewald86dad5b2015-01-28 15:09:07 +0000564 // Set new live item
Nils Diewald2d210752015-03-09 19:01:15 +0000565 if (!this._prefix.active()) {
566 var oldItem = this.liveItem(this._position--);
567 oldItem.active(false);
568 };
569
Nils Diewald5975d702015-03-09 17:45:42 +0000570 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000571
572 // The previous element is undefined - roll to bottom
573 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000574
575 // Activate prefix
576 var prefix = this._prefix;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000577 this._offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000578
579 // Normalize offset
580 this._offset = this._offset < 0 ? 0 : this._offset;
581
Nils Diewald2fe12e12015-03-06 16:47:06 +0000582 this._position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000583
584 if (prefix.isSet() && !prefix.active()) {
585 this._position++;
586 prefix.active(true);
587 return;
588 }
589 else {
590 newItem = this.liveItem(this._position);
591 this._showItems(this._offset);
592 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000593 }
594
595 // The previous element is outside the view - roll up
596 else if (this._position < this._offset) {
597 this._removeLast();
598 this._offset--;
599 this._prepend(this._list[this._position]);
600 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000601
Nils Diewald5975d702015-03-09 17:45:42 +0000602 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000603 newItem.active(true);
604 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000605
606
Nils Diewald5975d702015-03-09 17:45:42 +0000607 // Length of the filtered list
608 liveLength : function () {
609 if (this._list === undefined)
610 this._initList();
611 return this._list.length;
612 },
613
614
Nils Diewald2fe12e12015-03-06 16:47:06 +0000615 // Remove the HTML node from the first item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000616 _removeFirst : function () {
617 this.item(this._list[this._offset]).lowlight();
Nils Diewald5975d702015-03-09 17:45:42 +0000618 this._element.removeChild(this._element.children[1]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000619 },
620
Nils Diewald2fe12e12015-03-06 16:47:06 +0000621
622 // Remove the HTML node from the last item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000623 _removeLast : function () {
Nils Diewald2fe12e12015-03-06 16:47:06 +0000624 this.item(this._list[this._offset + this.limit() - 1]).lowlight();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000625 this._element.removeChild(this._element.lastChild);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000626 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000627 };
628
629
Nils Diewaldfda29d92015-01-22 17:28:01 +0000630 /**
631 * Item in the Dropdown menu
632 */
633 KorAP.MenuItem = {
634
635 /**
636 * Create a new MenuItem object.
637 *
638 * @constructor
639 * @this {MenuItem}
640 * @param {Array.<string>} An array object of name, action and
641 * optionally a description
642 */
643 create : function (params) {
644 return Object.create(KorAP.MenuItem)._init(params);
645 },
646
647 /**
648 * Upgrade this object to another object,
649 * while private data stays intact.
650 *
651 * @param {Object] An object with properties.
652 */
653 upgradeTo : function (props) {
654 for (var prop in props) {
655 this[prop] = props[prop];
656 };
657 return this;
658 },
659
Nils Diewald5975d702015-03-09 17:45:42 +0000660
Nils Diewaldfda29d92015-01-22 17:28:01 +0000661 content : function (content) {
662 if (arguments.length === 1)
663 this._content = document.createTextNode(content);
664 return this._content;
665 },
666
667 lcField : function () {
668 return this._lcField;
669 },
670
671 action : function (action) {
672 if (arguments.length === 1)
673 this._action = action;
674 return this._action;
675 },
676
677 /**
678 * Check or set if the item is active
679 *
680 * @param {boolean|null} State of activity
681 */
682 active : function (bool) {
683 var cl = this.element().classList;
684 if (bool === undefined)
685 return cl.contains("active");
686 else if (bool)
687 cl.add("active");
688 else
689 cl.remove("active");
690 },
691
692 /**
693 * Check or set if the item is
694 * at the boundary of the menu
695 * list
696 *
697 * @param {boolean|null} State of activity
698 */
699 noMore : function (bool) {
700 var cl = this.element().classList;
701 if (bool === undefined)
702 return cl.contains("no-more");
703 else if (bool)
704 cl.add("no-more");
705 else
706 cl.remove("no-more");
707 },
708
709 /**
710 * Get the document element of the menu item
711 */
712 element : function () {
713 // already defined
714 if (this._element !== undefined)
715 return this._element;
716
717 // Create list item
718 var li = document.createElement("li");
719
720 // Connect action
Nils Diewald5975d702015-03-09 17:45:42 +0000721 if (this.onclick !== undefined)
722 li["onclick"] = this.onclick.bind(this);
Nils Diewaldfda29d92015-01-22 17:28:01 +0000723
724 // Append template
725 li.appendChild(this.content());
726
727 return this._element = li;
728 },
729
730 /**
731 * Highlight parts of the item
732 *
733 * @param {string} Prefix string for highlights
734 */
735 highlight : function (prefix) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000736 var children = this.element().childNodes;
737 for (var i = children.length -1; i >= 0; i--) {
738 this._highlight(children[i], prefix);
739 };
Nils Diewaldfda29d92015-01-22 17:28:01 +0000740 },
741
742 // Highlight a certain substring of the menu item
743 _highlight : function (elem, prefix) {
744
745 if (elem.nodeType === 3) {
746
747 var text = elem.nodeValue;
748 var textlc = text.toLowerCase();
749 var pos = textlc.indexOf(prefix);
750 if (pos >= 0) {
751
752 // First element
753 if (pos > 0) {
754 elem.parentNode.insertBefore(
755 document.createTextNode(text.substr(0, pos)),
756 elem
757 );
758 };
759
760 // Second element
761 var hl = document.createElement("mark");
762 hl.appendChild(
763 document.createTextNode(text.substr(pos, prefix.length))
764 );
765 elem.parentNode.insertBefore(hl, elem);
766
767 // Third element
768 var third = text.substr(pos + prefix.length);
769 if (third.length > 0) {
770 var thirdE = document.createTextNode(third);
771 elem.parentNode.insertBefore(
772 thirdE,
773 elem
774 );
775 this._highlight(thirdE, prefix);
776 };
777
778 var p = elem.parentNode;
779 p.removeChild(elem);
780 };
781 }
782 else {
783 var children = elem.childNodes;
784 for (var i = children.length -1; i >= 0; i--) {
785 this._highlight(children[i], prefix);
786 };
787 };
788 },
789
790
791 /**
792 * Remove highlight of the menu item
793 */
794 lowlight : function () {
795 var e = this.element();
796
797 var marks = e.getElementsByTagName("mark");
798 for (var i = marks.length - 1; i >= 0; i--) {
799 // Create text node clone
800 var x = document.createTextNode(
801 marks[i].firstChild.nodeValue
802 );
803
804 // Replace with content
805 marks[i].parentNode.replaceChild(
806 x,
807 marks[i]
808 );
809 };
810
811 // Remove consecutive textnodes
812 e.normalize();
813 },
814
815 // Initialize menu item
816 _init : function (params) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000817
Nils Diewaldfda29d92015-01-22 17:28:01 +0000818 if (params[0] === undefined)
819 throw new Error("Missing parameters");
820
821 this.content(params[0]);
822
823 if (params.length === 2)
824 this._action = params[1];
825
826 this._lcField = ' ' + this.content().textContent.toLowerCase();
827
828 return this;
829 },
830 };
831
Nils Diewald5975d702015-03-09 17:45:42 +0000832 KorAP.MenuPrefix = {
833 create : function (params) {
834 return Object.create(KorAP.MenuPrefix)._init();
835 },
836 _init : function () {
837 this._string = '';
838
839 // Add prefix span
840 this._element = document.createElement('span');
841 this._element.classList.add('pref');
Nils Diewalde8518f82015-03-18 22:41:49 +0000842 // Connect action
843 if (this.onclick !== undefined)
844 this._element["onclick"] = this.onclick.bind(this);
845
Nils Diewald5975d702015-03-09 17:45:42 +0000846 return this;
847 },
848 _update : function () {
849 this._element.innerHTML
850 = this._string;
851 },
852
853 /**
854 * Upgrade this object to another object,
855 * while private data stays intact.
856 *
857 * @param {Object} An object with properties.
858 */
859 upgradeTo : function (props) {
860 for (var prop in props) {
861 this[prop] = props[prop];
862 };
863 return this;
864 },
865
866 active : function (bool) {
867 var cl = this.element().classList;
868 if (bool === undefined)
869 return cl.contains("active");
870 else if (bool)
871 cl.add("active");
872 else
873 cl.remove("active");
874 },
875 element : function () {
876 return this._element;
877 },
878 isSet : function () {
879 return this._string.length > 0 ?
880 true : false;
881 },
882 value : function (string) {
883 if (arguments.length === 1) {
884 this._string = string;
885 this._update();
886 };
887 return this._string;
888 },
889 add : function (string) {
890 this._string += string;
891 this._update();
892 },
893 onclick : function () {},
894 backspace : function () {
895 if (this._string.length > 1) {
896 this._string = this._string.substring(
897 0, this._string.length - 1
898 );
899 }
900 else {
901 this._string = '';
902 };
903
904 this._update();
905 }
906 };
907
Nils Diewald59c02fc2015-03-07 01:29:09 +0000908 function _codeFromEvent (e) {
Nils Diewald5975d702015-03-09 17:45:42 +0000909 if (e.charCode && (e.keyCode == 0))
Nils Diewald59c02fc2015-03-07 01:29:09 +0000910 return e.charCode
911 return e.keyCode;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000912 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000913
Nils Diewaldfda29d92015-01-22 17:28:01 +0000914}(this.KorAP));
Nils Diewald5975d702015-03-09 17:45:42 +0000915
916/**
917 * MenuItems may define:
918 *
919 * onclick: action happen on click and enter.
920 * further: action happen on right arrow
921 */