blob: 91381f5fefdcc2cdda4d99f24b6330a484c503e9 [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,
Akronc14cbfc2018-08-31 13:15:55 +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 /**
Nils Diewald0e6992a2015-04-14 20:13:52 +000032 * Return keycode based on event
33 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000034
35 // Initialize hint array
Nils Diewald5c5a7472015-04-02 22:13:38 +000036
37 /**
38 * KorAP.Hint.create({
39 * inputField : node,
40 * context : context regex
41 * });
42 */
Nils Diewald0e6992a2015-04-14 20:13:52 +000043 return {
Nils Diewald5c5a7472015-04-02 22:13:38 +000044
45 // Some variables
46 // _firstTry : true,
Akron00cd4d12016-05-31 21:01:11 +020047 // active : false,
Nils Diewald5c5a7472015-04-02 22:13:38 +000048
Nils Diewald0e6992a2015-04-14 20:13:52 +000049 /**
50 * Create new hint helper.
51 */
Nils Diewald5c5a7472015-04-02 22:13:38 +000052 create : function (param) {
Nils Diewald0e6992a2015-04-14 20:13:52 +000053 return Object.create(this)._init(param);
Nils Diewald5c5a7472015-04-02 22:13:38 +000054 },
55
Nils Diewald0e6992a2015-04-14 20:13:52 +000056 // Initialize hint helper
Nils Diewald5c5a7472015-04-02 22:13:38 +000057 _init : function (param) {
58 param = param || {};
59
60 // Holds all menus per prefix context
Akron00cd4d12016-05-31 21:01:11 +020061 this._menu = {};
62 this._alert = alertClass.create();
Akron02360e42016-06-07 13:41:12 +020063 this._active = null;
Nils Diewald5c5a7472015-04-02 22:13:38 +000064
Akron80055992017-12-20 16:30:52 +010065 // No annotation helper available
66 if (!KorAP.annotationHelper) {
67 console.log("No annotationhelper defined");
68 return;
69 };
Akronfac16472018-07-26 16:47:21 +020070
71 /**
72 * @define {regex} Regular expression for context
73 */
74 KorAP.context = KorAP.context ||
75 "(?:^|[^-_a-zA-Z0-9])" + // Anchor
76 "((?:[-_a-zA-Z0-9]+?)\/" + // Foundry
77 "(?:" +
78 "(?:[-_a-zA-Z0-9]+?)=" + // Layer
79 "(?:"+
80 "(?:[^:=\/ ]+?):|" + // Key
81 "(?:[^-=\/ ]+?)-" + // Node
82 ")?" +
83 ")?" +
84 ")$";
Akron80055992017-12-20 16:30:52 +010085
Nils Diewald5c5a7472015-04-02 22:13:38 +000086 // Get input field
Nils Diewald0e6992a2015-04-14 20:13:52 +000087 var qfield = param["inputField"] || document.getElementById("q-field");
88 if (!qfield)
Akron8eaeb2e2016-08-29 18:26:28 +020089 return null;
Nils Diewald0e6992a2015-04-14 20:13:52 +000090
Akron00cd4d12016-05-31 21:01:11 +020091 // Create input field
Nils Diewald0e6992a2015-04-14 20:13:52 +000092 this._inputField = inputClass.create(qfield);
Nils Diewald5c5a7472015-04-02 22:13:38 +000093
94 var inputFieldElement = this._inputField.element();
95
Akron00cd4d12016-05-31 21:01:11 +020096 var c = this._inputField.container();
Nils Diewald5c5a7472015-04-02 22:13:38 +000097
Akron00cd4d12016-05-31 21:01:11 +020098 // create alert
99 c.appendChild(this._alert.element());
100
Akron00cd4d12016-05-31 21:01:11 +0200101 var that = this;
Nils Diewald5c5a7472015-04-02 22:13:38 +0000102
Nils Diewald47f366b2015-04-15 20:06:35 +0000103 this._inputField.container().addEventListener('click', function (e) {
Akronc14cbfc2018-08-31 13:15:55 +0200104 if (!this.classList.contains('active')) {
105 that.show(false);
106 };
Nils Diewald47f366b2015-04-15 20:06:35 +0000107 });
108
Akron00cd4d12016-05-31 21:01:11 +0200109 var _down = function (e) {
Akronc14cbfc2018-08-31 13:15:55 +0200110 var code = _codeFromEvent(e);
111 if (code === 40) {
112 this.show(false);
113 e.halt();
114 };
Nils Diewald47f366b2015-04-15 20:06:35 +0000115 };
Akron8eaeb2e2016-08-29 18:26:28 +0200116
Nils Diewald47f366b2015-04-15 20:06:35 +0000117 // Move infobox
Akron00cd4d12016-05-31 21:01:11 +0200118 inputFieldElement.addEventListener("keyup", this.update.bind(this));
119 inputFieldElement.addEventListener("click", this.update.bind(this));
120
121 // Add event listener for key pressed down
122 inputFieldElement.addEventListener(
Akronc14cbfc2018-08-31 13:15:55 +0200123 "keydown", _down.bind(this), false
124 );
125
126 // Add touch events
127 inputFieldElement.addEventListener(
128 'touchstart',
129 this._touch.bind(this),
130 false
131 );
132 inputFieldElement.addEventListener(
133 'touchend',
134 this._touch.bind(this),
135 false
136 );
137 inputFieldElement.addEventListener(
138 'touchmove',
139 this._touch.bind(this),
140 false
Akron00cd4d12016-05-31 21:01:11 +0200141 );
Nils Diewald5c5a7472015-04-02 22:13:38 +0000142
143 // Set Analyzer for context
Nils Diewald0e6992a2015-04-14 20:13:52 +0000144 this._analyzer = analyzerClass.create(
Akronc14cbfc2018-08-31 13:15:55 +0200145 param["context"] || KorAP.context
Nils Diewald5c5a7472015-04-02 22:13:38 +0000146 );
Nils Diewald19ccee92014-12-08 11:30:08 +0000147 return this;
148 },
149
Akron308db382016-05-30 22:34:07 +0200150
151 /**
152 * Return the input field attached to the hint helper.
153 */
Nils Diewald5c5a7472015-04-02 22:13:38 +0000154 inputField : function () {
155 return this._inputField;
156 },
Nils Diewald19ccee92014-12-08 11:30:08 +0000157
Akron308db382016-05-30 22:34:07 +0200158
159 /**
160 * Altert at a specific character position.
161 */
Akron00cd4d12016-05-31 21:01:11 +0200162 alert : function (charPos, msg) {
163
164 if (arguments.length === 0)
Akronc14cbfc2018-08-31 13:15:55 +0200165 return this._alert;
Akron00cd4d12016-05-31 21:01:11 +0200166
167 // Do not alert if already alerted!
168 if (this._alert.active)
Akronc14cbfc2018-08-31 13:15:55 +0200169 return false;
Akron00cd4d12016-05-31 21:01:11 +0200170
171 // Move to the correct position
Akron308db382016-05-30 22:34:07 +0200172 this._inputField.moveto(charPos);
Akron00cd4d12016-05-31 21:01:11 +0200173
174 // Set container to active (aka hide the hint helper button)
175
176 this._alert.show(msg);
Akron02360e42016-06-07 13:41:12 +0200177 this.active(this._alert);
Akron00cd4d12016-05-31 21:01:11 +0200178 return true;
Akron308db382016-05-30 22:34:07 +0200179 },
Akron8eaeb2e2016-08-29 18:26:28 +0200180
Akron00cd4d12016-05-31 21:01:11 +0200181 _unshowAlert : function () {
Akron02360e42016-06-07 13:41:12 +0200182 this._alert.hide();
183 this.active(null);
Akron00cd4d12016-05-31 21:01:11 +0200184 },
185
186 update : function () {
187 this._inputField.update();
Akron02360e42016-06-07 13:41:12 +0200188 if (this._alert.hide())
Akronc14cbfc2018-08-31 13:15:55 +0200189 this.active(null);
Akron00cd4d12016-05-31 21:01:11 +0200190 },
191
192
Nils Diewald5c5a7472015-04-02 22:13:38 +0000193 /**
Nils Diewald5c5a7472015-04-02 22:13:38 +0000194 * Return hint menu and probably init based on an action
195 */
196 menu : function (action) {
Nils Diewald5c5a7472015-04-02 22:13:38 +0000197 if (this._menu[action] === undefined) {
198
Akronc14cbfc2018-08-31 13:15:55 +0200199 // No matching hint menu
200 if (KorAP.annotationHelper[action] === undefined)
201 return;
Nils Diewald5c5a7472015-04-02 22:13:38 +0000202
Akronc14cbfc2018-08-31 13:15:55 +0200203 // Create matching hint menu
204 this._menu[action] = menuClass.create(
205 this, action, KorAP.annotationHelper[action]
206 );
Nils Diewald5c5a7472015-04-02 22:13:38 +0000207 };
208
209 // Return matching hint menu
210 return this._menu[action];
211 },
212
213 /**
214 * Get the correct menu based on the context
215 */
216 contextMenu : function (ifContext) {
Akron1a5a5872016-09-05 20:17:14 +0200217
Akron02360e42016-06-07 13:41:12 +0200218 // Get context (aka left text)
Nils Diewald5c5a7472015-04-02 22:13:38 +0000219 var context = this._inputField.context();
Akronee9ef4a2016-06-03 12:50:08 +0200220
Akron65c74352016-09-02 17:23:39 +0200221 if (context === undefined || context.length === 0) {
Akron1a5a5872016-09-05 20:17:14 +0200222 return ifContext ? undefined : this.menu("-");
Akron65c74352016-09-02 17:23:39 +0200223 };
Nils Diewald5c5a7472015-04-02 22:13:38 +0000224
Akron02360e42016-06-07 13:41:12 +0200225 // Get context (aka left text matching regex)
Nils Diewald5c5a7472015-04-02 22:13:38 +0000226 context = this._analyzer.test(context);
Akronee9ef4a2016-06-03 12:50:08 +0200227
Akron1a5a5872016-09-05 20:17:14 +0200228 if (context === undefined || context.length == 0) {
Akronc14cbfc2018-08-31 13:15:55 +0200229 return ifContext ? undefined : this.menu("-");
Akron1a5a5872016-09-05 20:17:14 +0200230 };
Nils Diewald5c5a7472015-04-02 22:13:38 +0000231
Akron1a5a5872016-09-05 20:17:14 +0200232 return this.menu(context) || (ifContext ? undefined : this.menu('-'));
Nils Diewald5c5a7472015-04-02 22:13:38 +0000233 },
234
Akron8eaeb2e2016-08-29 18:26:28 +0200235 /**
236 * Activate a certain menu.
237 * If a menu is passed, the menu will be activated.
238 * If null is passed, the active menu will be deactivated.
239 * If nothing is passed, returns the active menu.
240 */
Akron02360e42016-06-07 13:41:12 +0200241 active : function (obj) {
Akron8eaeb2e2016-08-29 18:26:28 +0200242
243 // A menu or null was passed
Akron00cd4d12016-05-31 21:01:11 +0200244 if (arguments.length === 1) {
Akronc14cbfc2018-08-31 13:15:55 +0200245 var c = this._inputField.container();
Akron8eaeb2e2016-08-29 18:26:28 +0200246
247 // Make the menu active
Akronc14cbfc2018-08-31 13:15:55 +0200248 if (obj !== null) {
249 c.classList.add('active');
250 this._active = obj;
251 }
Akron8eaeb2e2016-08-29 18:26:28 +0200252
253 // Make the menu inactive
Akronc14cbfc2018-08-31 13:15:55 +0200254 else {
255 c.classList.remove('active');
256 this._active = null;
257 }
Akron00cd4d12016-05-31 21:01:11 +0200258 };
Akron8eaeb2e2016-08-29 18:26:28 +0200259
260 // Return
Akron00cd4d12016-05-31 21:01:11 +0200261 return this._active;
262 },
263
Nils Diewald5c5a7472015-04-02 22:13:38 +0000264
265 /**
Akron308db382016-05-30 22:34:07 +0200266 * Show the menu.
Akron65c74352016-09-02 17:23:39 +0200267 * Remove all old menus.
Akron02360e42016-06-07 13:41:12 +0200268 *
269 * @param {boolean} Boolean value to indicate if context
270 * is necessary (true) or if the main context should
271 * be shown if context fails.
272 *
Nils Diewald5c5a7472015-04-02 22:13:38 +0000273 */
274 show : function (ifContext) {
275
Akron1a5a5872016-09-05 20:17:14 +0200276 // Remove the active object
277 this._unshow();
Akronc14cbfc2018-08-31 13:15:55 +0200278
Nils Diewald5c5a7472015-04-02 22:13:38 +0000279 // Get the menu
280 var menu;
281 if (menu = this.contextMenu(ifContext)) {
Akronc14cbfc2018-08-31 13:15:55 +0200282 this.active(menu);
Akron02360e42016-06-07 13:41:12 +0200283
Akroncae907d2018-08-20 17:22:15 +0200284 var c = this._inputField.container();
Akronc14cbfc2018-08-31 13:15:55 +0200285 c.appendChild(menu.element());
Akroncae907d2018-08-20 17:22:15 +0200286
Akronc14cbfc2018-08-31 13:15:55 +0200287 menu.show();
288 menu.focus();
289 // Focus on input field
290 // this.inputField.element.focus();
Akron65c74352016-09-02 17:23:39 +0200291 }
292 else {
293 this._inputField.element().focus();
Nils Diewald19ccee92014-12-08 11:30:08 +0000294 };
Akron00cd4d12016-05-31 21:01:11 +0200295 },
Akron8eaeb2e2016-08-29 18:26:28 +0200296
Akron02360e42016-06-07 13:41:12 +0200297 // 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 */
Akron65c74352016-09-02 17:23:39 +0200303 unshow_old : function () {
Akron02360e42016-06-07 13:41:12 +0200304 this.active(null);
Akron00cd4d12016-05-31 21:01:11 +0200305 this.inputField().element().focus();
Akron65c74352016-09-02 17:23:39 +0200306 },
307
308 /**
309 * Deactivate the current menu and focus on the input field.
310 */
311 unshow : function () {
Akron1a5a5872016-09-05 20:17:14 +0200312 this._unshow();
313 this._inputField.element().focus();
314 },
Akron65c74352016-09-02 17:23:39 +0200315
Akronc14cbfc2018-08-31 13:15:55 +0200316
317 // Catch touch events
318 _touch : function (e) {
319
320 if (e.type === 'touchstart') {
321 var t = e.touches[0];
322 this._lastTouch = t.clientY;
323 }
324 else if (e.type === 'touchend') {
325 this._lastTouch = undefined;
326 }
327 else if (e.type == 'touchmove') {
328 var t = e.touches[0];
329 if ((this._lastTouch + 10) < t.clientY) {
330 this.show();
331 this._lastTouch = undefined;
332 };
333 e.halt();
334 }
335 },
336
Akron1a5a5872016-09-05 20:17:14 +0200337
338 _unshow : function () {
Akron65c74352016-09-02 17:23:39 +0200339 if (this.active() !== null) {
Akron1a5a5872016-09-05 20:17:14 +0200340 // var act = this.active();
Akrone39cc862018-04-25 15:16:11 +0200341
Akron65c74352016-09-02 17:23:39 +0200342 // This does not work for alert currently!
Akronc14cbfc2018-08-31 13:15:55 +0200343 //if (act._type !== 'alert') {
Akron1a5a5872016-09-05 20:17:14 +0200344 if (!this._alert.active) {
Akroncae907d2018-08-20 17:22:15 +0200345
346 // This does not work for webkit!
Akron1a5a5872016-09-05 20:17:14 +0200347 var c = this._inputField.container();
Akrone39cc862018-04-25 15:16:11 +0200348 c.removeChild(this._active.element());
Akronc14cbfc2018-08-31 13:15:55 +0200349 }
Akron1a5a5872016-09-05 20:17:14 +0200350 else {
351 this._unshowAlert();
352 };
353
Akron65c74352016-09-02 17:23:39 +0200354 // this._active.hide();
355 this.active(null);
356 };
Nils Diewald19ccee92014-12-08 11:30:08 +0000357 }
358 };
Nils Diewald0e6992a2015-04-14 20:13:52 +0000359});