blob: 8859c0ac2dc7d32903610cbec9e7a64cdcb07b59 [file] [log] [blame]
Nils Diewald19ccee92014-12-08 11:30:08 +00001/**
Nils Diewald5c5a7472015-04-02 22:13:38 +00002 * Hint menu for Kalamar.
Nils Diewald47f366b2015-04-15 20:06:35 +00003 * Based on menu object.
Nils Diewald19ccee92014-12-08 11:30:08 +00004 *
5 * @author Nils Diewald
6 */
Akron308db382016-05-30 22:34:07 +02007/*
8 * TODO: List can be shown when prefix is like 'base/s=pcorenlp/'
9 * TODO: Sometimes the drop-down box down vanish when list is shown
10 * TODO: Create should expect an input text field
11 * TODO: Embed only one single menu (not multiple)
Akron02360e42016-06-07 13:41:12 +020012 * By holding the current menu in _active
13 * TODO: show() should accept a context field (especially for no-context fields,
14 * like fragments)
15 * TODO: Improve context analyzer from hint!
16 * TODO: Marked annotations should be addable to "fragments"
Akron308db382016-05-30 22:34:07 +020017 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000018define([
19 'hint/input',
20 'hint/menu',
21 'hint/contextanalyzer',
Akron00cd4d12016-05-31 21:01:11 +020022 'hint/alert',
Nils Diewald0e6992a2015-04-14 20:13:52 +000023 'util'
24], function (inputClass,
Akron00cd4d12016-05-31 21:01:11 +020025 menuClass,
26 analyzerClass,
27 alertClass) {
Nils Diewald19ccee92014-12-08 11:30:08 +000028 "use strict";
29
Nils Diewald19ccee92014-12-08 11:30:08 +000030 /**
31 * @define {regex} Regular expression for context
32 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000033 KorAP.context = KorAP.context ||
Nils Diewald19ccee92014-12-08 11:30:08 +000034 "(?:^|[^-_a-zA-Z0-9])" + // Anchor
35 "((?:[-_a-zA-Z0-9]+?)\/" + // Foundry
36 "(?:" +
37 "(?:[-_a-zA-Z0-9]+?)=" + // Layer
Akron113cc1a2016-01-22 21:17:57 +010038 "(?:"+
39 "(?:[^:=\/ ]+?):|" + // Key
Akron308db382016-05-30 22:34:07 +020040 "(?:[^-=\/ ]+?)-" + // Node
Akron113cc1a2016-01-22 21:17:57 +010041 ")?" +
Nils Diewald19ccee92014-12-08 11:30:08 +000042 ")?" +
43 ")$";
Nils Diewald19ccee92014-12-08 11:30:08 +000044 KorAP.hintArray = KorAP.hintArray || {};
45
Nils Diewald0e6992a2015-04-14 20:13:52 +000046 /**
47 * Return keycode based on event
48 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000049
50 // Initialize hint array
Nils Diewald5c5a7472015-04-02 22:13:38 +000051
52 /**
53 * KorAP.Hint.create({
54 * inputField : node,
55 * context : context regex
56 * });
57 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000058 return {
Nils Diewald5c5a7472015-04-02 22:13:38 +000059
60 // Some variables
61 // _firstTry : true,
Akron00cd4d12016-05-31 21:01:11 +020062 // active : false,
Nils Diewald5c5a7472015-04-02 22:13:38 +000063
Nils Diewald0e6992a2015-04-14 20:13:52 +000064 /**
65 * Create new hint helper.
66 */
Nils Diewald5c5a7472015-04-02 22:13:38 +000067 create : function (param) {
Nils Diewald0e6992a2015-04-14 20:13:52 +000068 return Object.create(this)._init(param);
Nils Diewald5c5a7472015-04-02 22:13:38 +000069 },
70
Nils Diewald0e6992a2015-04-14 20:13:52 +000071 // Initialize hint helper
Nils Diewald5c5a7472015-04-02 22:13:38 +000072 _init : function (param) {
73 param = param || {};
74
75 // Holds all menus per prefix context
Akron00cd4d12016-05-31 21:01:11 +020076 this._menu = {};
77 this._alert = alertClass.create();
Akron02360e42016-06-07 13:41:12 +020078 this._active = null;
Nils Diewald5c5a7472015-04-02 22:13:38 +000079
80 // Get input field
Nils Diewald0e6992a2015-04-14 20:13:52 +000081 var qfield = param["inputField"] || document.getElementById("q-field");
82 if (!qfield)
Akron8eaeb2e2016-08-29 18:26:28 +020083 return null;
Nils Diewald0e6992a2015-04-14 20:13:52 +000084
Akron00cd4d12016-05-31 21:01:11 +020085 // Create input field
Nils Diewald0e6992a2015-04-14 20:13:52 +000086 this._inputField = inputClass.create(qfield);
Nils Diewald5c5a7472015-04-02 22:13:38 +000087
88 var inputFieldElement = this._inputField.element();
89
Akron00cd4d12016-05-31 21:01:11 +020090 var c = this._inputField.container();
Nils Diewald5c5a7472015-04-02 22:13:38 +000091
Akron00cd4d12016-05-31 21:01:11 +020092 // create alert
93 c.appendChild(this._alert.element());
94
Akron00cd4d12016-05-31 21:01:11 +020095 var that = this;
Nils Diewald5c5a7472015-04-02 22:13:38 +000096
Nils Diewald47f366b2015-04-15 20:06:35 +000097 this._inputField.container().addEventListener('click', function (e) {
Akron8eaeb2e2016-08-29 18:26:28 +020098 if (!this.classList.contains('active')) {
99 that.show(false);
100 };
Nils Diewald47f366b2015-04-15 20:06:35 +0000101 });
102
Akron00cd4d12016-05-31 21:01:11 +0200103 var _down = function (e) {
Akron8eaeb2e2016-08-29 18:26:28 +0200104 var code = _codeFromEvent(e);
105 if (code === 40) {
106 this.show(false);
107 e.halt();
108 };
Nils Diewald47f366b2015-04-15 20:06:35 +0000109 };
Akron8eaeb2e2016-08-29 18:26:28 +0200110
Nils Diewald47f366b2015-04-15 20:06:35 +0000111 // Move infobox
Akron00cd4d12016-05-31 21:01:11 +0200112 inputFieldElement.addEventListener("keyup", this.update.bind(this));
113 inputFieldElement.addEventListener("click", this.update.bind(this));
114
115 // Add event listener for key pressed down
116 inputFieldElement.addEventListener(
Akron8eaeb2e2016-08-29 18:26:28 +0200117 "keydown", _down.bind(this), false
Akron00cd4d12016-05-31 21:01:11 +0200118 );
Nils Diewald5c5a7472015-04-02 22:13:38 +0000119
120 // Set Analyzer for context
Nils Diewald0e6992a2015-04-14 20:13:52 +0000121 this._analyzer = analyzerClass.create(
Akron8eaeb2e2016-08-29 18:26:28 +0200122 param["context"] || KorAP.context
Nils Diewald5c5a7472015-04-02 22:13:38 +0000123 );
Nils Diewald19ccee92014-12-08 11:30:08 +0000124 return this;
125 },
126
Akron308db382016-05-30 22:34:07 +0200127
128 /**
129 * Return the input field attached to the hint helper.
130 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000131 inputField : function () {
132 return this._inputField;
133 },
Nils Diewald19ccee92014-12-08 11:30:08 +0000134
Akron308db382016-05-30 22:34:07 +0200135
136 /**
137 * Altert at a specific character position.
138 */
Akron00cd4d12016-05-31 21:01:11 +0200139 alert : function (charPos, msg) {
140
141 if (arguments.length === 0)
Akron8eaeb2e2016-08-29 18:26:28 +0200142 return this._alert;
Akron00cd4d12016-05-31 21:01:11 +0200143
144 // Do not alert if already alerted!
145 if (this._alert.active)
Akron8eaeb2e2016-08-29 18:26:28 +0200146 return false;
Akron00cd4d12016-05-31 21:01:11 +0200147
148 // Move to the correct position
Akron308db382016-05-30 22:34:07 +0200149 this._inputField.moveto(charPos);
Akron00cd4d12016-05-31 21:01:11 +0200150
151 // Set container to active (aka hide the hint helper button)
152
153 this._alert.show(msg);
Akron02360e42016-06-07 13:41:12 +0200154 this.active(this._alert);
Akron00cd4d12016-05-31 21:01:11 +0200155 return true;
Akron308db382016-05-30 22:34:07 +0200156 },
Akron8eaeb2e2016-08-29 18:26:28 +0200157
Akron00cd4d12016-05-31 21:01:11 +0200158 _unshowAlert : function () {
Akron02360e42016-06-07 13:41:12 +0200159 this._alert.hide();
160 this.active(null);
Akron00cd4d12016-05-31 21:01:11 +0200161 },
162
163 update : function () {
164 this._inputField.update();
Akron02360e42016-06-07 13:41:12 +0200165 if (this._alert.hide())
Akron8eaeb2e2016-08-29 18:26:28 +0200166 this.active(null);
Akron00cd4d12016-05-31 21:01:11 +0200167 },
168
169
Nils Diewald5c5a7472015-04-02 22:13:38 +0000170 /**
Nils Diewald5c5a7472015-04-02 22:13:38 +0000171 * Return hint menu and probably init based on an action
172 */
173 menu : function (action) {
Nils Diewald5c5a7472015-04-02 22:13:38 +0000174 if (this._menu[action] === undefined) {
175
Akron8eaeb2e2016-08-29 18:26:28 +0200176 // No matching hint menu
177 if (KorAP.hintArray[action] === undefined)
178 return;
Nils Diewald5c5a7472015-04-02 22:13:38 +0000179
Akron8eaeb2e2016-08-29 18:26:28 +0200180 // Create matching hint menu
181 this._menu[action] = menuClass.create(
182 this, action, KorAP.hintArray[action]
183 );
Nils Diewald5c5a7472015-04-02 22:13:38 +0000184 };
185
186 // Return matching hint menu
187 return this._menu[action];
188 },
189
190 /**
191 * Get the correct menu based on the context
192 */
193 contextMenu : function (ifContext) {
Akron02360e42016-06-07 13:41:12 +0200194
195 // Get context (aka left text)
Nils Diewald5c5a7472015-04-02 22:13:38 +0000196 var context = this._inputField.context();
Akronee9ef4a2016-06-03 12:50:08 +0200197
Akron02360e42016-06-07 13:41:12 +0200198 if (context === undefined || context.length === 0)
Akron8eaeb2e2016-08-29 18:26:28 +0200199 return ifContext ? undefined : this.menu("-");
Nils Diewald5c5a7472015-04-02 22:13:38 +0000200
Akron02360e42016-06-07 13:41:12 +0200201 // Get context (aka left text matching regex)
Nils Diewald5c5a7472015-04-02 22:13:38 +0000202 context = this._analyzer.test(context);
Akronee9ef4a2016-06-03 12:50:08 +0200203
Nils Diewald5c5a7472015-04-02 22:13:38 +0000204 if (context === undefined || context.length == 0)
Akron8eaeb2e2016-08-29 18:26:28 +0200205 return ifContext ? undefined : this.menu("-");
Nils Diewald5c5a7472015-04-02 22:13:38 +0000206
Akron02360e42016-06-07 13:41:12 +0200207 return this.menu(context) || this.menu('-');
Nils Diewald5c5a7472015-04-02 22:13:38 +0000208 },
209
Akron8eaeb2e2016-08-29 18:26:28 +0200210 /**
211 * Activate a certain menu.
212 * If a menu is passed, the menu will be activated.
213 * If null is passed, the active menu will be deactivated.
214 * If nothing is passed, returns the active menu.
215 */
Akron02360e42016-06-07 13:41:12 +0200216 active : function (obj) {
Akron8eaeb2e2016-08-29 18:26:28 +0200217
218 // A menu or null was passed
Akron00cd4d12016-05-31 21:01:11 +0200219 if (arguments.length === 1) {
Akron8eaeb2e2016-08-29 18:26:28 +0200220 var c = this._inputField.container();
221
222 // Make the menu active
223 if (obj !== null) {
224 c.classList.add('active');
225 this._active = obj;
226 }
227
228 // Make the menu inactive
229 else {
230 c.classList.remove('active');
231 this._active = null;
232 }
Akron00cd4d12016-05-31 21:01:11 +0200233 };
Akron8eaeb2e2016-08-29 18:26:28 +0200234
235 // Return
Akron00cd4d12016-05-31 21:01:11 +0200236 return this._active;
237 },
238
Nils Diewald5c5a7472015-04-02 22:13:38 +0000239
240 /**
Akron308db382016-05-30 22:34:07 +0200241 * Show the menu.
242 * Currently this means that multiple menus may be loaded
243 * but not shown.
Akron02360e42016-06-07 13:41:12 +0200244 *
245 * @param {boolean} Boolean value to indicate if context
246 * is necessary (true) or if the main context should
247 * be shown if context fails.
248 *
Nils Diewald5c5a7472015-04-02 22:13:38 +0000249 */
250 show : function (ifContext) {
251
Akron02360e42016-06-07 13:41:12 +0200252 var c = this._inputField.container();
253
Nils Diewald5c5a7472015-04-02 22:13:38 +0000254 // Menu is already active
Akron02360e42016-06-07 13:41:12 +0200255 if (this.active() !== null) {
256
Akron8eaeb2e2016-08-29 18:26:28 +0200257 // This does not work for alert currently!
258 if (this._active._type !== 'alert') {
259 c.removeChild(this._active.element());
260 };
Akron02360e42016-06-07 13:41:12 +0200261
Akron8eaeb2e2016-08-29 18:26:28 +0200262 // This may already be hidden!
263 this._active.hide();
264 this.active(null);
Akron00cd4d12016-05-31 21:01:11 +0200265
Akron8eaeb2e2016-08-29 18:26:28 +0200266 // Alert is not active
267 /*
268 if (!this._alert.unshow())
269 return;
270 */
Akron00cd4d12016-05-31 21:01:11 +0200271 };
Nils Diewald5c5a7472015-04-02 22:13:38 +0000272
Nils Diewald5c5a7472015-04-02 22:13:38 +0000273 // Get the menu
274 var menu;
275 if (menu = this.contextMenu(ifContext)) {
Akron02360e42016-06-07 13:41:12 +0200276
Akron8eaeb2e2016-08-29 18:26:28 +0200277 // TODO: Remove old element!
278
279 this.active(menu);
Akron02360e42016-06-07 13:41:12 +0200280
Akron8eaeb2e2016-08-29 18:26:28 +0200281 c.appendChild(menu.element());
282 menu.show();
283 menu.focus();
284 // Focus on input field
285 // this.inputField.element.focus();
Nils Diewald19ccee92014-12-08 11:30:08 +0000286 };
Akron00cd4d12016-05-31 21:01:11 +0200287 },
Akron8eaeb2e2016-08-29 18:26:28 +0200288
Akron02360e42016-06-07 13:41:12 +0200289 // Show an object in the containerField
290 // This will hide all other objects
291 // Accepts menus as well as alerts
Akron8eaeb2e2016-08-29 18:26:28 +0200292 show2 : function (obj) {
293 var c = this._inputField.container();
294
295 },
Akron02360e42016-06-07 13:41:12 +0200296
297 // This will get the context of the field
298 getContext : function () {},
299
Akron8eaeb2e2016-08-29 18:26:28 +0200300 /**
301 * Deactivate the current menu and focus on the input field.
302 */
Akron00cd4d12016-05-31 21:01:11 +0200303 unshow : function () {
Akron02360e42016-06-07 13:41:12 +0200304 this.active(null);
Akron00cd4d12016-05-31 21:01:11 +0200305 this.inputField().element().focus();
Nils Diewald19ccee92014-12-08 11:30:08 +0000306 }
307 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000308});