blob: c5c689cbaf1bde6ecfd36b20c3a1e68325961bd0 [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/*
Akron65c74352016-09-02 17:23:39 +02008 * TODO: Check for cnx/syn=
Akron308db382016-05-30 22:34:07 +02009 * TODO: List can be shown when prefix is like 'base/s=pcorenlp/'
10 * TODO: Sometimes the drop-down box down vanish when list is shown
11 * TODO: Create should expect an input text field
12 * TODO: Embed only one single menu (not multiple)
Akron02360e42016-06-07 13:41:12 +020013 * By holding the current menu in _active
14 * TODO: show() should accept a context field (especially for no-context fields,
15 * like fragments)
16 * TODO: Improve context analyzer from hint!
17 * TODO: Marked annotations should be addable to "fragments"
Akron308db382016-05-30 22:34:07 +020018 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000019define([
20 'hint/input',
21 'hint/menu',
22 'hint/contextanalyzer',
Akron00cd4d12016-05-31 21:01:11 +020023 'hint/alert',
Nils Diewald0e6992a2015-04-14 20:13:52 +000024 'util'
25], function (inputClass,
Akron00cd4d12016-05-31 21:01:11 +020026 menuClass,
27 analyzerClass,
28 alertClass) {
Nils Diewald19ccee92014-12-08 11:30:08 +000029 "use strict";
30
Nils Diewald19ccee92014-12-08 11:30:08 +000031 /**
32 * @define {regex} Regular expression for context
33 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000034 KorAP.context = KorAP.context ||
Nils Diewald19ccee92014-12-08 11:30:08 +000035 "(?:^|[^-_a-zA-Z0-9])" + // Anchor
36 "((?:[-_a-zA-Z0-9]+?)\/" + // Foundry
37 "(?:" +
38 "(?:[-_a-zA-Z0-9]+?)=" + // Layer
Akron113cc1a2016-01-22 21:17:57 +010039 "(?:"+
40 "(?:[^:=\/ ]+?):|" + // Key
Akron308db382016-05-30 22:34:07 +020041 "(?:[^-=\/ ]+?)-" + // Node
Akron113cc1a2016-01-22 21:17:57 +010042 ")?" +
Nils Diewald19ccee92014-12-08 11:30:08 +000043 ")?" +
44 ")$";
Nils Diewald19ccee92014-12-08 11:30:08 +000045 KorAP.hintArray = KorAP.hintArray || {};
46
Nils Diewald0e6992a2015-04-14 20:13:52 +000047 /**
48 * Return keycode based on event
49 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000050
51 // Initialize hint array
Nils Diewald5c5a7472015-04-02 22:13:38 +000052
53 /**
54 * KorAP.Hint.create({
55 * inputField : node,
56 * context : context regex
57 * });
58 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000059 return {
Nils Diewald5c5a7472015-04-02 22:13:38 +000060
61 // Some variables
62 // _firstTry : true,
Akron00cd4d12016-05-31 21:01:11 +020063 // active : false,
Nils Diewald5c5a7472015-04-02 22:13:38 +000064
Nils Diewald0e6992a2015-04-14 20:13:52 +000065 /**
66 * Create new hint helper.
67 */
Nils Diewald5c5a7472015-04-02 22:13:38 +000068 create : function (param) {
Nils Diewald0e6992a2015-04-14 20:13:52 +000069 return Object.create(this)._init(param);
Nils Diewald5c5a7472015-04-02 22:13:38 +000070 },
71
Nils Diewald0e6992a2015-04-14 20:13:52 +000072 // Initialize hint helper
Nils Diewald5c5a7472015-04-02 22:13:38 +000073 _init : function (param) {
74 param = param || {};
75
76 // Holds all menus per prefix context
Akron00cd4d12016-05-31 21:01:11 +020077 this._menu = {};
78 this._alert = alertClass.create();
Akron02360e42016-06-07 13:41:12 +020079 this._active = null;
Nils Diewald5c5a7472015-04-02 22:13:38 +000080
81 // Get input field
Nils Diewald0e6992a2015-04-14 20:13:52 +000082 var qfield = param["inputField"] || document.getElementById("q-field");
83 if (!qfield)
Akron8eaeb2e2016-08-29 18:26:28 +020084 return null;
Nils Diewald0e6992a2015-04-14 20:13:52 +000085
Akron00cd4d12016-05-31 21:01:11 +020086 // Create input field
Nils Diewald0e6992a2015-04-14 20:13:52 +000087 this._inputField = inputClass.create(qfield);
Nils Diewald5c5a7472015-04-02 22:13:38 +000088
89 var inputFieldElement = this._inputField.element();
90
Akron00cd4d12016-05-31 21:01:11 +020091 var c = this._inputField.container();
Nils Diewald5c5a7472015-04-02 22:13:38 +000092
Akron00cd4d12016-05-31 21:01:11 +020093 // create alert
94 c.appendChild(this._alert.element());
95
Akron00cd4d12016-05-31 21:01:11 +020096 var that = this;
Nils Diewald5c5a7472015-04-02 22:13:38 +000097
Nils Diewald47f366b2015-04-15 20:06:35 +000098 this._inputField.container().addEventListener('click', function (e) {
Akron8eaeb2e2016-08-29 18:26:28 +020099 if (!this.classList.contains('active')) {
100 that.show(false);
101 };
Nils Diewald47f366b2015-04-15 20:06:35 +0000102 });
103
Akron00cd4d12016-05-31 21:01:11 +0200104 var _down = function (e) {
Akron8eaeb2e2016-08-29 18:26:28 +0200105 var code = _codeFromEvent(e);
106 if (code === 40) {
107 this.show(false);
108 e.halt();
109 };
Nils Diewald47f366b2015-04-15 20:06:35 +0000110 };
Akron8eaeb2e2016-08-29 18:26:28 +0200111
Nils Diewald47f366b2015-04-15 20:06:35 +0000112 // Move infobox
Akron00cd4d12016-05-31 21:01:11 +0200113 inputFieldElement.addEventListener("keyup", this.update.bind(this));
114 inputFieldElement.addEventListener("click", this.update.bind(this));
115
116 // Add event listener for key pressed down
117 inputFieldElement.addEventListener(
Akron8eaeb2e2016-08-29 18:26:28 +0200118 "keydown", _down.bind(this), false
Akron00cd4d12016-05-31 21:01:11 +0200119 );
Nils Diewald5c5a7472015-04-02 22:13:38 +0000120
121 // Set Analyzer for context
Nils Diewald0e6992a2015-04-14 20:13:52 +0000122 this._analyzer = analyzerClass.create(
Akron8eaeb2e2016-08-29 18:26:28 +0200123 param["context"] || KorAP.context
Nils Diewald5c5a7472015-04-02 22:13:38 +0000124 );
Nils Diewald19ccee92014-12-08 11:30:08 +0000125 return this;
126 },
127
Akron308db382016-05-30 22:34:07 +0200128
129 /**
130 * Return the input field attached to the hint helper.
131 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000132 inputField : function () {
133 return this._inputField;
134 },
Nils Diewald19ccee92014-12-08 11:30:08 +0000135
Akron308db382016-05-30 22:34:07 +0200136
137 /**
138 * Altert at a specific character position.
139 */
Akron00cd4d12016-05-31 21:01:11 +0200140 alert : function (charPos, msg) {
141
142 if (arguments.length === 0)
Akron8eaeb2e2016-08-29 18:26:28 +0200143 return this._alert;
Akron00cd4d12016-05-31 21:01:11 +0200144
145 // Do not alert if already alerted!
146 if (this._alert.active)
Akron8eaeb2e2016-08-29 18:26:28 +0200147 return false;
Akron00cd4d12016-05-31 21:01:11 +0200148
149 // Move to the correct position
Akron308db382016-05-30 22:34:07 +0200150 this._inputField.moveto(charPos);
Akron00cd4d12016-05-31 21:01:11 +0200151
152 // Set container to active (aka hide the hint helper button)
153
154 this._alert.show(msg);
Akron02360e42016-06-07 13:41:12 +0200155 this.active(this._alert);
Akron00cd4d12016-05-31 21:01:11 +0200156 return true;
Akron308db382016-05-30 22:34:07 +0200157 },
Akron8eaeb2e2016-08-29 18:26:28 +0200158
Akron00cd4d12016-05-31 21:01:11 +0200159 _unshowAlert : function () {
Akron02360e42016-06-07 13:41:12 +0200160 this._alert.hide();
161 this.active(null);
Akron00cd4d12016-05-31 21:01:11 +0200162 },
163
164 update : function () {
165 this._inputField.update();
Akron02360e42016-06-07 13:41:12 +0200166 if (this._alert.hide())
Akron8eaeb2e2016-08-29 18:26:28 +0200167 this.active(null);
Akron00cd4d12016-05-31 21:01:11 +0200168 },
169
170
Nils Diewald5c5a7472015-04-02 22:13:38 +0000171 /**
Nils Diewald5c5a7472015-04-02 22:13:38 +0000172 * Return hint menu and probably init based on an action
173 */
174 menu : function (action) {
Nils Diewald5c5a7472015-04-02 22:13:38 +0000175 if (this._menu[action] === undefined) {
176
Akron8eaeb2e2016-08-29 18:26:28 +0200177 // No matching hint menu
178 if (KorAP.hintArray[action] === undefined)
179 return;
Nils Diewald5c5a7472015-04-02 22:13:38 +0000180
Akron8eaeb2e2016-08-29 18:26:28 +0200181 // Create matching hint menu
182 this._menu[action] = menuClass.create(
183 this, action, KorAP.hintArray[action]
184 );
Nils Diewald5c5a7472015-04-02 22:13:38 +0000185 };
186
187 // Return matching hint menu
188 return this._menu[action];
189 },
190
191 /**
192 * Get the correct menu based on the context
193 */
194 contextMenu : function (ifContext) {
Akron02360e42016-06-07 13:41:12 +0200195
196 // Get context (aka left text)
Nils Diewald5c5a7472015-04-02 22:13:38 +0000197 var context = this._inputField.context();
Akronee9ef4a2016-06-03 12:50:08 +0200198
Akron65c74352016-09-02 17:23:39 +0200199 if (context === undefined || context.length === 0) {
200 return ifContext ? false : this.menu("-");
201 };
Nils Diewald5c5a7472015-04-02 22:13:38 +0000202
Akron02360e42016-06-07 13:41:12 +0200203 // Get context (aka left text matching regex)
Nils Diewald5c5a7472015-04-02 22:13:38 +0000204 context = this._analyzer.test(context);
Akronee9ef4a2016-06-03 12:50:08 +0200205
Nils Diewald5c5a7472015-04-02 22:13:38 +0000206 if (context === undefined || context.length == 0)
Akron8eaeb2e2016-08-29 18:26:28 +0200207 return ifContext ? undefined : this.menu("-");
Nils Diewald5c5a7472015-04-02 22:13:38 +0000208
Akron02360e42016-06-07 13:41:12 +0200209 return this.menu(context) || this.menu('-');
Nils Diewald5c5a7472015-04-02 22:13:38 +0000210 },
211
Akron8eaeb2e2016-08-29 18:26:28 +0200212 /**
213 * Activate a certain menu.
214 * If a menu is passed, the menu will be activated.
215 * If null is passed, the active menu will be deactivated.
216 * If nothing is passed, returns the active menu.
217 */
Akron02360e42016-06-07 13:41:12 +0200218 active : function (obj) {
Akron8eaeb2e2016-08-29 18:26:28 +0200219
220 // A menu or null was passed
Akron00cd4d12016-05-31 21:01:11 +0200221 if (arguments.length === 1) {
Akron8eaeb2e2016-08-29 18:26:28 +0200222 var c = this._inputField.container();
223
224 // Make the menu active
225 if (obj !== null) {
226 c.classList.add('active');
227 this._active = obj;
228 }
229
230 // Make the menu inactive
231 else {
232 c.classList.remove('active');
233 this._active = null;
234 }
Akron00cd4d12016-05-31 21:01:11 +0200235 };
Akron8eaeb2e2016-08-29 18:26:28 +0200236
237 // Return
Akron00cd4d12016-05-31 21:01:11 +0200238 return this._active;
239 },
240
Nils Diewald5c5a7472015-04-02 22:13:38 +0000241
242 /**
Akron308db382016-05-30 22:34:07 +0200243 * Show the menu.
Akron65c74352016-09-02 17:23:39 +0200244 * Remove all old menus.
Akron02360e42016-06-07 13:41:12 +0200245 *
246 * @param {boolean} Boolean value to indicate if context
247 * is necessary (true) or if the main context should
248 * be shown if context fails.
249 *
Nils Diewald5c5a7472015-04-02 22:13:38 +0000250 */
251 show : function (ifContext) {
252
Akron02360e42016-06-07 13:41:12 +0200253 var c = this._inputField.container();
254
Nils Diewald5c5a7472015-04-02 22:13:38 +0000255 // Menu is already active
Akron02360e42016-06-07 13:41:12 +0200256 if (this.active() !== null) {
257
Akron8eaeb2e2016-08-29 18:26:28 +0200258 // This does not work for alert currently!
259 if (this._active._type !== 'alert') {
260 c.removeChild(this._active.element());
261 };
Akron02360e42016-06-07 13:41:12 +0200262
Akron8eaeb2e2016-08-29 18:26:28 +0200263 // This may already be hidden!
Akron65c74352016-09-02 17:23:39 +0200264 // this._active.hide();
Akron8eaeb2e2016-08-29 18:26:28 +0200265 this.active(null);
Akron00cd4d12016-05-31 21:01:11 +0200266
Akron8eaeb2e2016-08-29 18:26:28 +0200267 // Alert is not active
268 /*
269 if (!this._alert.unshow())
270 return;
271 */
Akron00cd4d12016-05-31 21:01:11 +0200272 };
Nils Diewald5c5a7472015-04-02 22:13:38 +0000273
Nils Diewald5c5a7472015-04-02 22:13:38 +0000274 // Get the menu
275 var menu;
276 if (menu = this.contextMenu(ifContext)) {
Akron02360e42016-06-07 13:41:12 +0200277
Akron8eaeb2e2016-08-29 18:26:28 +0200278 // TODO: Remove old element!
279
280 this.active(menu);
Akron02360e42016-06-07 13:41:12 +0200281
Akron8eaeb2e2016-08-29 18:26:28 +0200282 c.appendChild(menu.element());
283 menu.show();
284 menu.focus();
285 // Focus on input field
286 // this.inputField.element.focus();
Akron65c74352016-09-02 17:23:39 +0200287 }
288 else {
289 this._inputField.element().focus();
Nils Diewald19ccee92014-12-08 11:30:08 +0000290 };
Akron00cd4d12016-05-31 21:01:11 +0200291 },
Akron8eaeb2e2016-08-29 18:26:28 +0200292
Akron02360e42016-06-07 13:41:12 +0200293 // This will get the context of the field
294 getContext : function () {},
295
Akron8eaeb2e2016-08-29 18:26:28 +0200296 /**
297 * Deactivate the current menu and focus on the input field.
298 */
Akron65c74352016-09-02 17:23:39 +0200299 unshow_old : function () {
Akron02360e42016-06-07 13:41:12 +0200300 this.active(null);
Akron00cd4d12016-05-31 21:01:11 +0200301 this.inputField().element().focus();
Akron65c74352016-09-02 17:23:39 +0200302 },
303
304 /**
305 * Deactivate the current menu and focus on the input field.
306 */
307 unshow : function () {
308 var c = this._inputField.container();
309
310 if (this.active() !== null) {
311 var act = this.active();
312
313 // This does not work for alert currently!
314 if (act._type !== 'alert') {
315 c.removeChild(this._active.element());
316 };
317 // this._active.hide();
318 this.active(null);
319 };
320 this._inputField.element().focus();
Nils Diewald19ccee92014-12-08 11:30:08 +0000321 }
322 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000323});