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