blob: 3d0017dccad2dd9180a5625b46455c74696b075c [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'
86 var item = this.liveItem(this._position);
87 if (item["further"] !== undefined) {
88 item["further"].bind(item).apply();
89 e.halt();
90 };
91 break;
92 case 13: // 'Enter'
93
94 // Click on prefix
95 if (this._prefix.active())
96 this._prefix.onclick();
97
98 // Click on item
99 else
100 this.liveItem(this._position).onclick();
101 e.halt();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000102 break;
103 case 8: // 'Backspace'
Nils Diewald5975d702015-03-09 17:45:42 +0000104 this._prefix.backspace();
105 this.show();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000106 e.halt();
107 break;
108 default:
Nils Diewald5975d702015-03-09 17:45:42 +0000109 if (e.key !== undefined &&
110 e.key.length != 1)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000111 return;
112
113 // Add prefix
Nils Diewald5975d702015-03-09 17:45:42 +0000114 this._prefix.add(e.key.toLowerCase());
115
116 if (!this.show()) {
117 this.prefix('').show();
118 };
Nils Diewald59c02fc2015-03-07 01:29:09 +0000119 };
120 },
121
Nils Diewald2fe12e12015-03-06 16:47:06 +0000122 // Initialize list
Nils Diewald5975d702015-03-09 17:45:42 +0000123 _init : function (itemClass, prefixClass, params) {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000124 var that = this;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000125 this._itemClass = itemClass;
Nils Diewald5975d702015-03-09 17:45:42 +0000126
127 if (prefixClass !== undefined)
128 this._prefix = prefixClass.create();
129 else
130 this._prefix = KorAP.MenuPrefix.create();
131
132 var e = document.createElement("ul");
Nils Diewald59c02fc2015-03-07 01:29:09 +0000133 e.style.opacity = 0;
134 e.style.outline = 0;
135 e.setAttribute('tabindex', 0);
Nils Diewald5975d702015-03-09 17:45:42 +0000136 e.setAttribute('class', 'menu');
137 e.appendChild(this._prefix.element());
Nils Diewald86dad5b2015-01-28 15:09:07 +0000138
Nils Diewald59c02fc2015-03-07 01:29:09 +0000139 // Arrow keys
140 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000141 'keypress',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000142 function (ev) {
Nils Diewald5975d702015-03-09 17:45:42 +0000143 that._keypress(ev)
Nils Diewald59c02fc2015-03-07 01:29:09 +0000144 },
145 false
146 );
147
148 // Mousewheel
149 e.addEventListener(
Nils Diewald5975d702015-03-09 17:45:42 +0000150 'wheel',
Nils Diewald59c02fc2015-03-07 01:29:09 +0000151 function (ev) {
152 that._mousewheel(ev)
Nils Diewald2fe12e12015-03-06 16:47:06 +0000153 },
154 false
155 );
156
Nils Diewald5975d702015-03-09 17:45:42 +0000157 this._element = e;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000158 this.active = false;
159 this._items = new Array();
160 var i;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000161
162 // Initialize item list based on parameters
Nils Diewald86dad5b2015-01-28 15:09:07 +0000163 for (i in params) {
164 var obj = itemClass.create(params[i]);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000165 this._items.push(obj);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000166 };
167 this._limit = KorAP.menuLimit;
168 this._position = 0; // position in the active list
169 this._active = -1; // active item in the item list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000170 this._reset();
171 return this;
172 },
173
Nils Diewald2fe12e12015-03-06 16:47:06 +0000174 /**
175 * Get the instantiated HTML element
176 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000177 element : function () {
178 return this._element;
179 },
180
Nils Diewald2fe12e12015-03-06 16:47:06 +0000181 /**
182 * Get the creator object for items
183 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000184 itemClass : function () {
185 return this._itemClass;
186 },
187
188 /**
Nils Diewald2fe12e12015-03-06 16:47:06 +0000189 * Get and set numerical value for limit,
190 * i.e. the number of items visible.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000191 */
192 limit : function (limit) {
Nils Diewald5975d702015-03-09 17:45:42 +0000193 if (arguments.length === 1) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000194 this._limit = limit;
Nils Diewald5975d702015-03-09 17:45:42 +0000195 return this;
196 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000197 return this._limit;
198 },
199
200 /**
201 * Upgrade this object to another object,
202 * while private data stays intact.
203 *
Nils Diewald2fe12e12015-03-06 16:47:06 +0000204 * @param {Object} An object with properties.
Nils Diewald86dad5b2015-01-28 15:09:07 +0000205 */
206 upgradeTo : function (props) {
207 for (var prop in props) {
208 this[prop] = props[prop];
209 };
210 return this;
211 },
212
Nils Diewald2fe12e12015-03-06 16:47:06 +0000213 // Reset chosen item and prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000214 _reset : function () {
215 this._offset = 0;
216 this._pos = 0;
Nils Diewald5975d702015-03-09 17:45:42 +0000217 this._prefix.value('');
Nils Diewald86dad5b2015-01-28 15:09:07 +0000218 },
219
220 /**
221 * Filter the list and make it visible
222 *
223 * @param {string} Prefix for filtering the list
224 */
Nils Diewald5975d702015-03-09 17:45:42 +0000225 show : function () {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000226 // Initialize the list
227 if (!this._initList())
Nils Diewald59c02fc2015-03-07 01:29:09 +0000228 return false;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000229
Nils Diewald2fe12e12015-03-06 16:47:06 +0000230 // show based on initial offset
Nils Diewald86dad5b2015-01-28 15:09:07 +0000231 this._showItems(0);
232
233 // Set the first element to active
Nils Diewald2fe12e12015-03-06 16:47:06 +0000234 // Todo: Or the last element chosen
Nils Diewald86dad5b2015-01-28 15:09:07 +0000235 this.liveItem(0).active(true);
236
Nils Diewald86dad5b2015-01-28 15:09:07 +0000237 this._active = this._list[0];
Nils Diewald5975d702015-03-09 17:45:42 +0000238 this._position = 0;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000239 this._element.style.opacity = 1;
240
Nils Diewald86dad5b2015-01-28 15:09:07 +0000241 // Add classes for rolling menus
242 this._boundary(true);
Nils Diewald59c02fc2015-03-07 01:29:09 +0000243 return true;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000244 },
245
Nils Diewald2fe12e12015-03-06 16:47:06 +0000246 hide : function () {
Nils Diewald59c02fc2015-03-07 01:29:09 +0000247 this.active = false;
248 this.delete();
Nils Diewald2fe12e12015-03-06 16:47:06 +0000249 this._element.style.opacity = 0;
Nils Diewald59c02fc2015-03-07 01:29:09 +0000250
251/*
252 this._element.blur();
253*/
Nils Diewald86dad5b2015-01-28 15:09:07 +0000254 },
255
Nils Diewald2fe12e12015-03-06 16:47:06 +0000256 // Initialize the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000257 _initList : function () {
258
Nils Diewald2fe12e12015-03-06 16:47:06 +0000259 // Create a new list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000260 if (this._list === undefined) {
261 this._list = [];
262 }
263 else if (this._list.length != 0) {
264 this._boundary(false);
265 this._list.length = 0;
266 };
267
268 // Offset is initially zero
269 this._offset = 0;
270
Nils Diewald2fe12e12015-03-06 16:47:06 +0000271 // There is no prefix set
Nils Diewald86dad5b2015-01-28 15:09:07 +0000272 if (this.prefix().length <= 0) {
Nils Diewald5975d702015-03-09 17:45:42 +0000273 var i = 0;
274 for (; i < this._items.length; i++)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000275 this._list.push(i);
Nils Diewald5975d702015-03-09 17:45:42 +0000276 while (this._items[++i] !== undefined) {
277 this._items[i].lowlight();
278 console.log(this._item);
279 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000280 return true;
281 };
282
Nils Diewald2fe12e12015-03-06 16:47:06 +0000283 // There is a prefix set, so filter the list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000284 var pos;
285 var paddedPrefix = " " + this.prefix();
286
Nils Diewald2fe12e12015-03-06 16:47:06 +0000287 // Iterate over all items and choose preferred matching items
288 // i.e. the matching happens at the word start
Nils Diewald86dad5b2015-01-28 15:09:07 +0000289 for (pos = 0; pos < this._items.length; pos++) {
290 if ((this.item(pos).lcField().indexOf(paddedPrefix)) >= 0)
291 this._list.push(pos);
292 };
293
Nils Diewald2fe12e12015-03-06 16:47:06 +0000294 // The list is empty - so lower your expectations
295 // Iterate over all items and choose matching items
296 // i.e. the matching happens anywhere in the word
Nils Diewald86dad5b2015-01-28 15:09:07 +0000297 if (this._list.length == 0) {
298 for (pos = 0; pos < this._items.length; pos++) {
299 if ((this.item(pos).lcField().indexOf(this.prefix())) >= 0)
300 this._list.push(pos);
301 };
302 };
303
Nils Diewald2fe12e12015-03-06 16:47:06 +0000304 // Filter was successful - yeah!
Nils Diewald86dad5b2015-01-28 15:09:07 +0000305 return this._list.length > 0 ? true : false;
306 },
307
308 // Set boundary for viewport
309 _boundary : function (bool) {
310 this.item(this._list[0]).noMore(bool);
311 this.item(this._list[this._list.length - 1]).noMore(bool);
312 },
313
314 /**
315 * Get the prefix for filtering,
Nils Diewald2fe12e12015-03-06 16:47:06 +0000316 * e.g. &quot;ve&quot; for &quot;verb&quot;
Nils Diewald86dad5b2015-01-28 15:09:07 +0000317 */
Nils Diewald5975d702015-03-09 17:45:42 +0000318 prefix : function (pref) {
319 if (arguments.length === 1) {
320 this._prefix.value(pref);
321 return this;
322 };
323 return this._prefix.value();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000324 },
325
Nils Diewald2fe12e12015-03-06 16:47:06 +0000326 // Append Items that should be shown
Nils Diewald86dad5b2015-01-28 15:09:07 +0000327 _showItems : function (offset) {
328 this.delete();
329
330 // Use list
331 var shown = 0;
332 var i;
333 for (i in this._list) {
334
335 // Don't show - it's before offset
336 if (shown++ < offset)
337 continue;
338
339 this._append(this._list[i]);
340
341 if (shown >= (this.limit() + this._offset))
342 break;
343 };
344 },
345
346 /**
347 * Delete all visible items from the menu element
348 */
349 delete : function () {
350 var child;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000351
Nils Diewald5975d702015-03-09 17:45:42 +0000352 /*
Nils Diewald2fe12e12015-03-06 16:47:06 +0000353 // Iterate over all visible items
Nils Diewald86dad5b2015-01-28 15:09:07 +0000354 for (var i = 0; i <= this.limit(); i++) {
355
Nils Diewald5975d702015-03-09 17:45:42 +0000356 // there is a visible element
357 // unhighlight!
Nils Diewald59c02fc2015-03-07 01:29:09 +0000358 if (child = this.shownItem(i)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000359 child.lowlight();
Nils Diewald59c02fc2015-03-07 01:29:09 +0000360 child.active(false);
361 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000362 };
Nils Diewald5975d702015-03-09 17:45:42 +0000363 */
364
365 for (var i in this._list) {
366 var item = this._items[this._list[i]];
367 item.lowlight();
368 item.active(false);
369 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000370
Nils Diewald2fe12e12015-03-06 16:47:06 +0000371 // Remove all children
Nils Diewald5975d702015-03-09 17:45:42 +0000372 var children = this._element.childNodes;
373 for (var i = children.length - 1; i >= 1; i--) {
374 this._element.removeChild(
375 children[i]
376 );
377 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000378 },
379
380
381 // Append item to the shown list based on index
382 _append : function (i) {
383 var item = this.item(i);
384
385 // Highlight based on prefix
386 if (this.prefix().length > 0)
387 item.highlight(this.prefix());
388
389 // Append element
390 this.element().appendChild(item.element());
391 },
392
393
Nils Diewald2fe12e12015-03-06 16:47:06 +0000394 // Prepend item to the shown list based on index
395 _prepend : function (i) {
396 var item = this.item(i);
397
398 // Highlight based on prefix
399 if (this.prefix().length > 0)
400 item.highlight(this.prefix());
401
402 var e = this.element();
403 // Append element
404 e.insertBefore(
405 item.element(),
Nils Diewald5975d702015-03-09 17:45:42 +0000406 e.children[1]
Nils Diewald2fe12e12015-03-06 16:47:06 +0000407 );
408 },
409
410
411 /**
412 * Get a specific item from the complete list
413 *
414 * @param {number} index of the list item
415 */
416 item : function (index) {
417 return this._items[index]
418 },
419
420
Nils Diewald86dad5b2015-01-28 15:09:07 +0000421 /**
422 * Get a specific item from the filtered list
423 *
424 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000425 * in the filtered list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000426 */
427 liveItem : function (index) {
428 if (this._list === undefined)
429 if (!this._initList())
430 return;
431
432 return this._items[this._list[index]];
433 },
434
Nils Diewald86dad5b2015-01-28 15:09:07 +0000435
436 /**
437 * Get a specific item from the visible list
438 *
439 * @param {number} index of the list item
Nils Diewald2fe12e12015-03-06 16:47:06 +0000440 * in the visible list
Nils Diewald86dad5b2015-01-28 15:09:07 +0000441 */
442 shownItem : function (index) {
443 if (index >= this.limit())
444 return;
445 return this.liveItem(this._offset + index);
446 },
447
448
Nils Diewald2fe12e12015-03-06 16:47:06 +0000449 /**
450 * Get the length of the full list
451 */
452 length : function () {
453 return this._items.length;
454 },
455
456
457 /**
458 * Make the next item in the filtered menu active
Nils Diewald86dad5b2015-01-28 15:09:07 +0000459 */
460 next : function () {
Nils Diewald5975d702015-03-09 17:45:42 +0000461
Nils Diewald86dad5b2015-01-28 15:09:07 +0000462 // No active element set
Nils Diewald5975d702015-03-09 17:45:42 +0000463 if (this._position === -1)
Nils Diewald86dad5b2015-01-28 15:09:07 +0000464 return;
465
Nils Diewald5975d702015-03-09 17:45:42 +0000466 var newItem;
467
Nils Diewald86dad5b2015-01-28 15:09:07 +0000468 // Set new live item
469 var oldItem = this.liveItem(this._position++);
470 oldItem.active(false);
Nils Diewald5975d702015-03-09 17:45:42 +0000471 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000472
Nils Diewald5975d702015-03-09 17:45:42 +0000473 // The next element is undefined - roll to top or to prefix
Nils Diewald86dad5b2015-01-28 15:09:07 +0000474 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000475
476 // Activate prefix
477 var prefix = this._prefix;
478
479 // Mark prefix
480 if (prefix.isSet() && !prefix.active()) {
481 this._position--;
482 prefix.active(true);
483 return;
484 }
485 else {
486 this._offset = 0;
487 this._position = 0;
488 newItem = this.liveItem(0);
489 this._showItems(0);
490 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000491 }
492
493 // The next element is outside the view - roll down
Nils Diewald2fe12e12015-03-06 16:47:06 +0000494 else if (this._position >= (this.limit() + this._offset)) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000495 this._removeFirst();
496 this._offset++;
497 this._append(this._list[this._position]);
498 };
Nils Diewald5975d702015-03-09 17:45:42 +0000499
500 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000501 newItem.active(true);
502 },
503
504
505 /*
506 * Make the previous item in the menu active
507 */
Nils Diewald86dad5b2015-01-28 15:09:07 +0000508 prev : function () {
Nils Diewald2d210752015-03-09 19:01:15 +0000509
Nils Diewald2fe12e12015-03-06 16:47:06 +0000510 // No active element set
Nils Diewald86dad5b2015-01-28 15:09:07 +0000511 if (this._position == -1)
512 return;
513
Nils Diewald5975d702015-03-09 17:45:42 +0000514 var newItem;
515
Nils Diewald86dad5b2015-01-28 15:09:07 +0000516 // Set new live item
Nils Diewald2d210752015-03-09 19:01:15 +0000517 if (!this._prefix.active()) {
518 var oldItem = this.liveItem(this._position--);
519 oldItem.active(false);
520 };
521
Nils Diewald5975d702015-03-09 17:45:42 +0000522 newItem = this.liveItem(this._position);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000523
524 // The previous element is undefined - roll to bottom
525 if (newItem === undefined) {
Nils Diewald5975d702015-03-09 17:45:42 +0000526
527 // Activate prefix
528 var prefix = this._prefix;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000529 this._offset = this.liveLength() - this.limit();
Nils Diewald2d210752015-03-09 19:01:15 +0000530
531 // Normalize offset
532 this._offset = this._offset < 0 ? 0 : this._offset;
533
Nils Diewald2fe12e12015-03-06 16:47:06 +0000534 this._position = this.liveLength() - 1;
Nils Diewald5975d702015-03-09 17:45:42 +0000535
536 if (prefix.isSet() && !prefix.active()) {
Nils Diewald2d210752015-03-09 19:01:15 +0000537
Nils Diewald5975d702015-03-09 17:45:42 +0000538 this._position++;
539 prefix.active(true);
540 return;
541 }
542 else {
543 newItem = this.liveItem(this._position);
544 this._showItems(this._offset);
545 };
Nils Diewald86dad5b2015-01-28 15:09:07 +0000546 }
547
548 // The previous element is outside the view - roll up
549 else if (this._position < this._offset) {
550 this._removeLast();
551 this._offset--;
552 this._prepend(this._list[this._position]);
553 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000554
Nils Diewald5975d702015-03-09 17:45:42 +0000555 this._prefix.active(false);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000556 newItem.active(true);
557 },
Nils Diewald86dad5b2015-01-28 15:09:07 +0000558
559
Nils Diewald5975d702015-03-09 17:45:42 +0000560 // Length of the filtered list
561 liveLength : function () {
562 if (this._list === undefined)
563 this._initList();
564 return this._list.length;
565 },
566
567
Nils Diewald2fe12e12015-03-06 16:47:06 +0000568 // Remove the HTML node from the first item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000569 _removeFirst : function () {
570 this.item(this._list[this._offset]).lowlight();
Nils Diewald5975d702015-03-09 17:45:42 +0000571 this._element.removeChild(this._element.children[1]);
Nils Diewald86dad5b2015-01-28 15:09:07 +0000572 },
573
Nils Diewald2fe12e12015-03-06 16:47:06 +0000574
575 // Remove the HTML node from the last item
Nils Diewald86dad5b2015-01-28 15:09:07 +0000576 _removeLast : function () {
Nils Diewald2fe12e12015-03-06 16:47:06 +0000577 this.item(this._list[this._offset + this.limit() - 1]).lowlight();
Nils Diewald86dad5b2015-01-28 15:09:07 +0000578 this._element.removeChild(this._element.lastChild);
Nils Diewald2fe12e12015-03-06 16:47:06 +0000579 }
Nils Diewald86dad5b2015-01-28 15:09:07 +0000580 };
581
582
Nils Diewaldfda29d92015-01-22 17:28:01 +0000583 /**
584 * Item in the Dropdown menu
585 */
586 KorAP.MenuItem = {
587
588 /**
589 * Create a new MenuItem object.
590 *
591 * @constructor
592 * @this {MenuItem}
593 * @param {Array.<string>} An array object of name, action and
594 * optionally a description
595 */
596 create : function (params) {
597 return Object.create(KorAP.MenuItem)._init(params);
598 },
599
600 /**
601 * Upgrade this object to another object,
602 * while private data stays intact.
603 *
604 * @param {Object] An object with properties.
605 */
606 upgradeTo : function (props) {
607 for (var prop in props) {
608 this[prop] = props[prop];
609 };
610 return this;
611 },
612
Nils Diewald5975d702015-03-09 17:45:42 +0000613
Nils Diewaldfda29d92015-01-22 17:28:01 +0000614 content : function (content) {
615 if (arguments.length === 1)
616 this._content = document.createTextNode(content);
617 return this._content;
618 },
619
620 lcField : function () {
621 return this._lcField;
622 },
623
624 action : function (action) {
625 if (arguments.length === 1)
626 this._action = action;
627 return this._action;
628 },
629
630 /**
631 * Check or set if the item is active
632 *
633 * @param {boolean|null} State of activity
634 */
635 active : function (bool) {
636 var cl = this.element().classList;
637 if (bool === undefined)
638 return cl.contains("active");
639 else if (bool)
640 cl.add("active");
641 else
642 cl.remove("active");
643 },
644
645 /**
646 * Check or set if the item is
647 * at the boundary of the menu
648 * list
649 *
650 * @param {boolean|null} State of activity
651 */
652 noMore : function (bool) {
653 var cl = this.element().classList;
654 if (bool === undefined)
655 return cl.contains("no-more");
656 else if (bool)
657 cl.add("no-more");
658 else
659 cl.remove("no-more");
660 },
661
662 /**
663 * Get the document element of the menu item
664 */
665 element : function () {
666 // already defined
667 if (this._element !== undefined)
668 return this._element;
669
670 // Create list item
671 var li = document.createElement("li");
672
673 // Connect action
Nils Diewald5975d702015-03-09 17:45:42 +0000674 if (this.onclick !== undefined)
675 li["onclick"] = this.onclick.bind(this);
Nils Diewaldfda29d92015-01-22 17:28:01 +0000676
677 // Append template
678 li.appendChild(this.content());
679
680 return this._element = li;
681 },
682
683 /**
684 * Highlight parts of the item
685 *
686 * @param {string} Prefix string for highlights
687 */
688 highlight : function (prefix) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000689 var children = this.element().childNodes;
690 for (var i = children.length -1; i >= 0; i--) {
691 this._highlight(children[i], prefix);
692 };
Nils Diewaldfda29d92015-01-22 17:28:01 +0000693 },
694
695 // Highlight a certain substring of the menu item
696 _highlight : function (elem, prefix) {
697
698 if (elem.nodeType === 3) {
699
700 var text = elem.nodeValue;
701 var textlc = text.toLowerCase();
702 var pos = textlc.indexOf(prefix);
703 if (pos >= 0) {
704
705 // First element
706 if (pos > 0) {
707 elem.parentNode.insertBefore(
708 document.createTextNode(text.substr(0, pos)),
709 elem
710 );
711 };
712
713 // Second element
714 var hl = document.createElement("mark");
715 hl.appendChild(
716 document.createTextNode(text.substr(pos, prefix.length))
717 );
718 elem.parentNode.insertBefore(hl, elem);
719
720 // Third element
721 var third = text.substr(pos + prefix.length);
722 if (third.length > 0) {
723 var thirdE = document.createTextNode(third);
724 elem.parentNode.insertBefore(
725 thirdE,
726 elem
727 );
728 this._highlight(thirdE, prefix);
729 };
730
731 var p = elem.parentNode;
732 p.removeChild(elem);
733 };
734 }
735 else {
736 var children = elem.childNodes;
737 for (var i = children.length -1; i >= 0; i--) {
738 this._highlight(children[i], prefix);
739 };
740 };
741 },
742
743
744 /**
745 * Remove highlight of the menu item
746 */
747 lowlight : function () {
748 var e = this.element();
749
750 var marks = e.getElementsByTagName("mark");
751 for (var i = marks.length - 1; i >= 0; i--) {
752 // Create text node clone
753 var x = document.createTextNode(
754 marks[i].firstChild.nodeValue
755 );
756
757 // Replace with content
758 marks[i].parentNode.replaceChild(
759 x,
760 marks[i]
761 );
762 };
763
764 // Remove consecutive textnodes
765 e.normalize();
766 },
767
768 // Initialize menu item
769 _init : function (params) {
Nils Diewald86dad5b2015-01-28 15:09:07 +0000770
Nils Diewaldfda29d92015-01-22 17:28:01 +0000771 if (params[0] === undefined)
772 throw new Error("Missing parameters");
773
774 this.content(params[0]);
775
776 if (params.length === 2)
777 this._action = params[1];
778
779 this._lcField = ' ' + this.content().textContent.toLowerCase();
780
781 return this;
782 },
783 };
784
Nils Diewald5975d702015-03-09 17:45:42 +0000785 KorAP.MenuPrefix = {
786 create : function (params) {
787 return Object.create(KorAP.MenuPrefix)._init();
788 },
789 _init : function () {
790 this._string = '';
791
792 // Add prefix span
793 this._element = document.createElement('span');
794 this._element.classList.add('pref');
795 return this;
796 },
797 _update : function () {
798 this._element.innerHTML
799 = this._string;
800 },
801
802 /**
803 * Upgrade this object to another object,
804 * while private data stays intact.
805 *
806 * @param {Object} An object with properties.
807 */
808 upgradeTo : function (props) {
809 for (var prop in props) {
810 this[prop] = props[prop];
811 };
812 return this;
813 },
814
815 active : function (bool) {
816 var cl = this.element().classList;
817 if (bool === undefined)
818 return cl.contains("active");
819 else if (bool)
820 cl.add("active");
821 else
822 cl.remove("active");
823 },
824 element : function () {
825 return this._element;
826 },
827 isSet : function () {
828 return this._string.length > 0 ?
829 true : false;
830 },
831 value : function (string) {
832 if (arguments.length === 1) {
833 this._string = string;
834 this._update();
835 };
836 return this._string;
837 },
838 add : function (string) {
839 this._string += string;
840 this._update();
841 },
842 onclick : function () {},
843 backspace : function () {
844 if (this._string.length > 1) {
845 this._string = this._string.substring(
846 0, this._string.length - 1
847 );
848 }
849 else {
850 this._string = '';
851 };
852
853 this._update();
854 }
855 };
856
Nils Diewald59c02fc2015-03-07 01:29:09 +0000857 function _codeFromEvent (e) {
Nils Diewald5975d702015-03-09 17:45:42 +0000858 if (e.charCode && (e.keyCode == 0))
Nils Diewald59c02fc2015-03-07 01:29:09 +0000859 return e.charCode
860 return e.keyCode;
Nils Diewald2fe12e12015-03-06 16:47:06 +0000861 };
Nils Diewald2fe12e12015-03-06 16:47:06 +0000862
Nils Diewaldfda29d92015-01-22 17:28:01 +0000863}(this.KorAP));
Nils Diewald5975d702015-03-09 17:45:42 +0000864
865/**
866 * MenuItems may define:
867 *
868 * onclick: action happen on click and enter.
869 * further: action happen on right arrow
870 */