blob: 8011beeec96f440bdaa9a1c9736fbae8dbddd5b5 [file] [log] [blame]
Nils Diewald19ccee92014-12-08 11:30:08 +00001/**
Nils Diewald5c5a7472015-04-02 22:13:38 +00002 * Hint menu for Kalamar.
Nils Diewald19ccee92014-12-08 11:30:08 +00003 *
4 * @author Nils Diewald
5 */
6
Nils Diewald5c5a7472015-04-02 22:13:38 +00007// requires menu.js
Nils Diewald19ccee92014-12-08 11:30:08 +00008
Nils Diewald19ccee92014-12-08 11:30:08 +00009var KorAP = KorAP || {};
10
Nils Diewald19ccee92014-12-08 11:30:08 +000011(function (KorAP) {
12 "use strict";
13
Nils Diewald19ccee92014-12-08 11:30:08 +000014 // Default log message
Nils Diewalddc3862c2014-12-16 02:44:59 +000015 KorAP.log = KorAP.log || function (type, msg) {
Nils Diewald19ccee92014-12-08 11:30:08 +000016 console.log(type + ": " + msg);
17 };
18
Nils Diewald19ccee92014-12-08 11:30:08 +000019 /**
20 * @define {regex} Regular expression for context
21 */
22 KorAP.context =
23 "(?:^|[^-_a-zA-Z0-9])" + // Anchor
24 "((?:[-_a-zA-Z0-9]+?)\/" + // Foundry
25 "(?:" +
26 "(?:[-_a-zA-Z0-9]+?)=" + // Layer
27 "(?:(?:[^:=\/ ]+?):)?" + // Key
28 ")?" +
29 ")$";
30
31 // Initialize hint array
32 KorAP.hintArray = KorAP.hintArray || {};
33
Nils Diewald5c5a7472015-04-02 22:13:38 +000034 // Input field for queries
Nils Diewald19ccee92014-12-08 11:30:08 +000035 KorAP.InputField = {
36 create : function (element) {
37 return Object.create(KorAP.InputField)._init(element);
38 },
Nils Diewald5c5a7472015-04-02 22:13:38 +000039
Nils Diewald19ccee92014-12-08 11:30:08 +000040 _init : function (element) {
41 this._element = element;
42
43 // Create mirror for searchField
44 if ((this._mirror = document.getElementById("searchMirror")) === null) {
45 this._mirror = document.createElement("div");
46 this._mirror.setAttribute("id", "searchMirror");
47 this._mirror.appendChild(document.createElement("span"));
Nils Diewald5c5a7472015-04-02 22:13:38 +000048 this._container = this._mirror.appendChild(document.createElement("div"));
Nils Diewald19ccee92014-12-08 11:30:08 +000049 this._mirror.style.height = "1px";
50 document.getElementsByTagName("body")[0].appendChild(this._mirror);
51 };
52
53 // Update position of the mirror
54 var that = this;
55 window.resize = function () {
56 that.reposition();
57 };
Nils Diewald5c5a7472015-04-02 22:13:38 +000058
59 that.reposition();
60
Nils Diewald19ccee92014-12-08 11:30:08 +000061 return this;
62 },
Nils Diewald5c5a7472015-04-02 22:13:38 +000063
64 rightPos : function () {
65 var box = this._mirror.getBoundingClientRect();
66 return box.right - box.left;
67 },
68
69 mirror : function () {
Nils Diewald19ccee92014-12-08 11:30:08 +000070 return this._mirror;
71 },
Nils Diewald5c5a7472015-04-02 22:13:38 +000072
73 container : function () {
74 return this._container;
75 },
76
77 element : function () {
Nils Diewald19ccee92014-12-08 11:30:08 +000078 return this._element;
79 },
Nils Diewald5c5a7472015-04-02 22:13:38 +000080
81 value : function () {
Nils Diewald19ccee92014-12-08 11:30:08 +000082 return this._element.value;
83 },
Nils Diewald5c5a7472015-04-02 22:13:38 +000084
Nils Diewald19ccee92014-12-08 11:30:08 +000085 update : function () {
86 this._mirror.firstChild.textContent = this.split()[0];
87 },
Nils Diewald5c5a7472015-04-02 22:13:38 +000088
Nils Diewald19ccee92014-12-08 11:30:08 +000089 insert : function (text) {
90 var splittedText = this.split();
Nils Diewald5c5a7472015-04-02 22:13:38 +000091 var s = this._element;
Nils Diewald19ccee92014-12-08 11:30:08 +000092 s.value = splittedText[0] + text + splittedText[1];
93 s.selectionStart = (splittedText[0] + text).length;
94 s.selectionEnd = s.selectionStart;
95 this._mirror.firstChild.textContent = splittedText[0] + text;
96 },
Nils Diewald5c5a7472015-04-02 22:13:38 +000097
98 // Return two substrings, splitted at current cursor position
Nils Diewald19ccee92014-12-08 11:30:08 +000099 split : function () {
100 var s = this._element;
101 var value = s.value;
102 var start = s.selectionStart;
103 return new Array(
104 value.substring(0, start),
105 value.substring(start, value.length)
106 );
107 },
Nils Diewald5c5a7472015-04-02 22:13:38 +0000108
Nils Diewald19ccee92014-12-08 11:30:08 +0000109 // Position the input mirror directly below the input box
110 reposition : function () {
111 var inputClientRect = this._element.getBoundingClientRect();
112 var inputStyle = window.getComputedStyle(this._element, null);
Nils Diewald5c5a7472015-04-02 22:13:38 +0000113
114 // Reset position
Nils Diewald19ccee92014-12-08 11:30:08 +0000115 var mirrorStyle = this._mirror.style;
116 mirrorStyle.left = inputClientRect.left + "px";
Nils Diewald5c5a7472015-04-02 22:13:38 +0000117 mirrorStyle.top = inputClientRect.bottom + "px";
Nils Diewald19ccee92014-12-08 11:30:08 +0000118
119 // These may be relevant in case of media depending css
120 mirrorStyle.paddingLeft = inputStyle.getPropertyValue("padding-left");
121 mirrorStyle.marginLeft = inputStyle.getPropertyValue("margin-left");
122 mirrorStyle.borderLeftWidth = inputStyle.getPropertyValue("border-left-width");
123 mirrorStyle.borderLeftStyle = inputStyle.getPropertyValue("border-left-style");
124 mirrorStyle.fontSize = inputStyle.getPropertyValue("font-size");
125 mirrorStyle.fontFamily = inputStyle.getPropertyValue("font-family");
126 },
Nils Diewald5c5a7472015-04-02 22:13:38 +0000127 context : function () {
Nils Diewald19ccee92014-12-08 11:30:08 +0000128 return this.split()[0];
129 }
130 };
131
Nils Diewald19ccee92014-12-08 11:30:08 +0000132
133 /**
Nils Diewald5c5a7472015-04-02 22:13:38 +0000134 * Regex object for checking the context of the hint
Nils Diewald19ccee92014-12-08 11:30:08 +0000135 */
136 KorAP.ContextAnalyzer = {
137 create : function (regex) {
138 return Object.create(KorAP.ContextAnalyzer)._init(regex);
139 },
140 _init : function (regex) {
141 try {
142 this._regex = new RegExp(regex);
143 }
144 catch (e) {
145 KorAP.log("error", e);
146 return;
147 };
148 return this;
149 },
150 test : function (text) {
151 if (!this._regex.exec(text))
152 return;
153 return RegExp.$1;
154 }
155 };
156
157
158 /**
Nils Diewald5c5a7472015-04-02 22:13:38 +0000159 * Hint menu item based on MenuItem
Nils Diewald19ccee92014-12-08 11:30:08 +0000160 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000161 KorAP.HintMenuItem = {
162 create : function (params) {
163 return Object.create(KorAP.MenuItem)
164 .upgradeTo(KorAP.HintMenuItem)
165 ._init(params);
Nils Diewald19ccee92014-12-08 11:30:08 +0000166 },
Nils Diewald5c5a7472015-04-02 22:13:38 +0000167 content : function (content) {
168 if (arguments.length === 1) {
169 this._content = content;
Nils Diewald19ccee92014-12-08 11:30:08 +0000170 };
Nils Diewald5c5a7472015-04-02 22:13:38 +0000171 return this._content;
Nils Diewald19ccee92014-12-08 11:30:08 +0000172 },
Nils Diewald5c5a7472015-04-02 22:13:38 +0000173 _init : function (params) {
174 if (params[0] === undefined ||
175 params[1] === undefined)
176 throw new Error("Missing parameters");
177
178 this._name = params[0];
179 this._action = params[1];
180 this._lcField = ' ' + this._name.toLowerCase();
181
182 if (params.length > 2) {
183 this._desc = params[2];
184 this._lcField += " " + this._desc.toLowerCase();
Nils Diewald19ccee92014-12-08 11:30:08 +0000185 };
186
Nils Diewald19ccee92014-12-08 11:30:08 +0000187 return this;
188 },
Nils Diewald5c5a7472015-04-02 22:13:38 +0000189 onclick : function () {
190 var m = this.menu();
191 var h = m.hint();
192 m.hide();
Nils Diewald19ccee92014-12-08 11:30:08 +0000193
Nils Diewald5c5a7472015-04-02 22:13:38 +0000194 h.inputField().insert(this._action);
195 h.active = false;
Nils Diewald19ccee92014-12-08 11:30:08 +0000196
Nils Diewald5c5a7472015-04-02 22:13:38 +0000197 h.show(true);
Nils Diewald19ccee92014-12-08 11:30:08 +0000198 },
Nils Diewald5c5a7472015-04-02 22:13:38 +0000199 name : function () {
Nils Diewald19ccee92014-12-08 11:30:08 +0000200 return this._name;
201 },
Nils Diewald5c5a7472015-04-02 22:13:38 +0000202 action : function () {
Nils Diewald19ccee92014-12-08 11:30:08 +0000203 return this._action;
204 },
Nils Diewald5c5a7472015-04-02 22:13:38 +0000205 desc : function () {
Nils Diewald19ccee92014-12-08 11:30:08 +0000206 return this._desc;
207 },
Nils Diewald5c5a7472015-04-02 22:13:38 +0000208 element : function () {
Nils Diewald19ccee92014-12-08 11:30:08 +0000209 // already defined
210 if (this._element !== undefined)
211 return this._element;
212
213 // Create list item
214 var li = document.createElement("li");
Nils Diewald5c5a7472015-04-02 22:13:38 +0000215
216 if (this.onclick !== undefined) {
217 li["onclick"] = this.onclick.bind(this);
218 };
Nils Diewald19ccee92014-12-08 11:30:08 +0000219
220 // Create title
Nils Diewald5c5a7472015-04-02 22:13:38 +0000221 var name = document.createElement("span");
Nils Diewald19ccee92014-12-08 11:30:08 +0000222 name.appendChild(document.createTextNode(this._name));
Nils Diewald5c5a7472015-04-02 22:13:38 +0000223
Nils Diewald19ccee92014-12-08 11:30:08 +0000224 li.appendChild(name);
225
226 // Create description
227 if (this._desc !== undefined) {
228 var desc = document.createElement("span");
Nils Diewald5c5a7472015-04-02 22:13:38 +0000229 desc.classList.add('desc');
Nils Diewald19ccee92014-12-08 11:30:08 +0000230 desc.appendChild(document.createTextNode(this._desc));
231 li.appendChild(desc);
232 };
233 return this._element = li;
Nils Diewald5c5a7472015-04-02 22:13:38 +0000234 }
235 };
236
237 KorAP.HintMenuPrefix = {
238 create : function (params) {
239 return Object.create(KorAP.MenuPrefix).upgradeTo(KorAP.HintMenuPrefix)._init(params);
Nils Diewald19ccee92014-12-08 11:30:08 +0000240 },
Nils Diewald5c5a7472015-04-02 22:13:38 +0000241 onclick : function () {
242 var m = this.menu();
243 var h = m.hint();
244 m.hide();
Nils Diewald19ccee92014-12-08 11:30:08 +0000245
Nils Diewald5c5a7472015-04-02 22:13:38 +0000246 h.inputField().insert(this.value());
247 h.active = false;
248 }
249 };
Nils Diewald19ccee92014-12-08 11:30:08 +0000250
Nils Diewald5c5a7472015-04-02 22:13:38 +0000251 KorAP.HintMenu = {
252 create : function (hint, context, params) {
253 var obj = Object.create(KorAP.Menu)
254 .upgradeTo(KorAP.HintMenu)
255 ._init(KorAP.HintMenuItem, KorAP.HintMenuPrefix, params);
256 obj._context = context;
257 obj._element.classList.add('hint');
258 obj._hint = hint;
Nils Diewald19ccee92014-12-08 11:30:08 +0000259
Nils Diewald5c5a7472015-04-02 22:13:38 +0000260 // This is only domspecific
261 obj.element().addEventListener('blur', function (e) {
262 this.menu.hide();
263 });
Nils Diewald19ccee92014-12-08 11:30:08 +0000264
Nils Diewald5c5a7472015-04-02 22:13:38 +0000265 // Focus on input field on hide
266 obj.onHide = function () {
267 var input = this._hint.inputField();
268 input.element().focus();
Nils Diewald19ccee92014-12-08 11:30:08 +0000269 };
Nils Diewald5c5a7472015-04-02 22:13:38 +0000270
271 return obj;
272 },
273 // Todo: Is this necessary?
274 context : function () {
275 return this._context;
276 },
277 hint : function () {
278 return this._hint;
279 }
280 };
281
282
283 /**
284 * KorAP.Hint.create({
285 * inputField : node,
286 * context : context regex
287 * });
288 */
289 KorAP.Hint = {
290
291 // Some variables
292 // _firstTry : true,
293 active : false,
294
295 create : function (param) {
296 return Object.create(KorAP.Hint)._init(param);
297 },
298
299 _init : function (param) {
300 param = param || {};
301
302 // Holds all menus per prefix context
303 this._menu = {};
304
305 // Get input field
306 this._inputField = KorAP.InputField.create(
307 param["inputField"] || document.getElementById("q-field")
308 );
309
310 var inputFieldElement = this._inputField.element();
311
312 var that = this;
313
314 // Add event listener for key pressed down
315 inputFieldElement.addEventListener(
316 "keypress", function (e) {
317 var code = _codeFromEvent(e);
318 if (code === 40) {
319 that.show(false);
320 e.halt();
321 };
322 }, false
323 );
324
325 // Move infobox
326 inputFieldElement.addEventListener(
327 "keyup", function (e) {
328 var input = that._inputField;
329 input.update();
330 input.container().style.left = input.rightPos() + 'px';
331 }
332 );
333
334 // Set Analyzer for context
335 this._analyzer = KorAP.ContextAnalyzer.create(
336 param["context"] || KorAP.context
337 );
Nils Diewald19ccee92014-12-08 11:30:08 +0000338 return this;
339 },
340
Nils Diewald5c5a7472015-04-02 22:13:38 +0000341 inputField : function () {
342 return this._inputField;
343 },
Nils Diewald19ccee92014-12-08 11:30:08 +0000344
Nils Diewald5c5a7472015-04-02 22:13:38 +0000345 /**
346 * A new update by keypress
347 */
348 /*
349updateKeyPress : function (e) {
350 if (!this._active)
351 return;
Nils Diewald19ccee92014-12-08 11:30:08 +0000352
Nils Diewald5c5a7472015-04-02 22:13:38 +0000353 var character = String.fromCharCode(_codeFromEvent(e));
354
355 e.halt(); // No event propagation
356
357 // Only relevant for key down
358 console.log("TODO: filter view");
359 },
360 */
361
362 // updateKeyDown : function (e) {},
363
364 /**
365 * Return hint menu and probably init based on an action
366 */
367 menu : function (action) {
368
369 if (this._menu[action] === undefined) {
370
371 // No matching hint menu
372 if (KorAP.hintArray[action] === undefined)
373 return;
374
375 // Create matching hint menu
376 this._menu[action] = KorAP.HintMenu.create(
377 this, action, KorAP.hintArray[action]
Nils Diewald19ccee92014-12-08 11:30:08 +0000378 );
Nils Diewald19ccee92014-12-08 11:30:08 +0000379
Nils Diewald5c5a7472015-04-02 22:13:38 +0000380 };
381
382 // Return matching hint menu
383 return this._menu[action];
384 },
385
386 /**
387 * Get the correct menu based on the context
388 */
389 contextMenu : function (ifContext) {
390 var context = this._inputField.context();
391 if (context === undefined || context.length == 0)
392 return ifContext ? undefined : this.menu("-");
393
394 context = this._analyzer.test(context);
395 if (context === undefined || context.length == 0)
396 return ifContext ? undefined : this.menu("-");
397
398 return this.menu(context);
399 },
400
401
402 /**
403 * Show the menu
404 */
405 show : function (ifContext) {
406
407 // Menu is already active
408 if (this.active)
409 return;
410
411 // Initialize the menus position
412 /*
413 if (this._firstTry) {
414 this._inputField.reposition();
415 this._firstTry = false;
416 };
417 */
418
419 // update
420
421 // Get the menu
422 var menu;
423 if (menu = this.contextMenu(ifContext)) {
424 this._inputField.container().appendChild(menu.element());
425 menu.show('');
426 menu.focus();
427// Update bounding box
428/*
429 }
430 else if (!ifContext) {
431 // this.hide();
432 };
433*/
434 // Focus on input field
435 // this.inputField.element.focus();
Nils Diewald19ccee92014-12-08 11:30:08 +0000436 };
437 }
438 };
Nils Diewald5c5a7472015-04-02 22:13:38 +0000439
440
441 /**
442 * Return keycode based on event
443 */
444 function _codeFromEvent (e) {
445 if ((e.charCode) && (e.keyCode==0))
446 return e.charCode
447 return e.keyCode;
448 };
449
Nils Diewald19ccee92014-12-08 11:30:08 +0000450}(this.KorAP));