blob: 520b9e47bcf1bb06f7b5a7d017c048dc5111ad6b [file] [log] [blame]
Nils Diewald0e6992a2015-04-14 20:13:52 +00001// Input field for queries
Akron308db382016-05-30 22:34:07 +02002/*
Akronda5bd3a2020-10-16 17:37:49 +02003 * TODO: Support alert for query problems.
Akron308db382016-05-30 22:34:07 +02004 */
5
Akronda5bd3a2020-10-16 17:37:49 +02006"use strict";
Nils Diewald7148c6f2015-05-04 15:07:53 +00007
Akronda5bd3a2020-10-16 17:37:49 +02008define(['util'], function () {
9
10 return {
11
12 /**
13 * Create a new input field.
14 */
15 create : function (element) {
16 return Object.create(this)._init(element);
17 },
Nils Diewald0e6992a2015-04-14 20:13:52 +000018
Nils Diewald0e6992a2015-04-14 20:13:52 +000019
Akronda5bd3a2020-10-16 17:37:49 +020020 // Initialize new input field
21 _init : function (element) {
Akron24aa0052020-11-10 11:00:34 +010022 this._el = element;
Akronda5bd3a2020-10-16 17:37:49 +020023
24 this._container = document.createElement("div");
25 this._container.setAttribute('id', 'hint');
26
27 // Create mirror for searchField
28 // This is important for positioning
29 // if ((this._mirror = document.getElementById("searchMirror")) === null) {
30 this._mirror = document.createElement("div");
31 const m = this._mirror;
32 m.classList.add('hint', 'mirror');
33 m.addE("span");
34 m.appendChild(this._container);
35 m.style.height = "0px";
36 document.getElementsByTagName("body")[0].appendChild(m);
37
38 // Update position of the mirror
39 const re = this.reposition.bind(this);
40 window.addEventListener('resize', re);
Akron24aa0052020-11-10 11:00:34 +010041 this._el.addEventListener('onfocus', re);
Akronda5bd3a2020-10-16 17:37:49 +020042 this.reposition();
Akronb759ee92024-11-19 18:02:56 +010043
44 // Prevent multiselections
45 this._el.addEventListener("select", () => {
46 const start = this._el.selectionStart;
47 const end = this._el.selectionEnd;
48 if (start !== null && end !== null && start !== end) {
49 this._el.setSelectionRange(start, end);
50 }
51 });
52
Akronda5bd3a2020-10-16 17:37:49 +020053 return this;
54 },
Nils Diewald0e6992a2015-04-14 20:13:52 +000055
Nils Diewald7148c6f2015-05-04 15:07:53 +000056
Akronda5bd3a2020-10-16 17:37:49 +020057 /**
58 * Get the mirrored input field.
59 */
60 mirror : function () {
61 return this._mirror;
62 },
Nils Diewald0e6992a2015-04-14 20:13:52 +000063
Nils Diewald7148c6f2015-05-04 15:07:53 +000064
Akronda5bd3a2020-10-16 17:37:49 +020065 /**
66 * Get the container element.
67 * This contains the hint helper / menus
68 * and probably an
69 * error message.
70 */
71 container : function () {
72 return this._container;
73 },
Nils Diewald0e6992a2015-04-14 20:13:52 +000074
Akron308db382016-05-30 22:34:07 +020075
Akronda5bd3a2020-10-16 17:37:49 +020076 /**
77 * Get the input element the
78 * hint helper is attached to.
79 */
80 element : function () {
Akron24aa0052020-11-10 11:00:34 +010081 return this._el;
Akronda5bd3a2020-10-16 17:37:49 +020082 },
Nils Diewald0e6992a2015-04-14 20:13:52 +000083
Akronc14cbfc2018-08-31 13:15:55 +020084
Akronda5bd3a2020-10-16 17:37:49 +020085 /**
86 * Get the value of the input field
87 * the hint helper is attached to.
88 */
89 value : function () {
Akron24aa0052020-11-10 11:00:34 +010090 return this._el.value;
Akronda5bd3a2020-10-16 17:37:49 +020091 },
Akron00cd4d12016-05-31 21:01:11 +020092
Nils Diewald7148c6f2015-05-04 15:07:53 +000093
Akronda5bd3a2020-10-16 17:37:49 +020094 /**
95 * Get the value of the input field mirror.
96 */
97 mirrorValue : function () {
98 return this._mirror.firstChild.textContent;
99 },
Akron95abaf42018-04-26 15:33:22 +0200100
101
Akronda5bd3a2020-10-16 17:37:49 +0200102 /**
103 * Reset the input value
104 */
105 reset : function () {
Akron24aa0052020-11-10 11:00:34 +0100106 this._el.value = "";
Akronda5bd3a2020-10-16 17:37:49 +0200107 },
Nils Diewald0e6992a2015-04-14 20:13:52 +0000108
Akron308db382016-05-30 22:34:07 +0200109
Akronda5bd3a2020-10-16 17:37:49 +0200110 /**
111 * Update the mirror content.
112 */
113 update : function () {
114 this._mirror.firstChild.textContent = this._split()[0];
115 this._container.style.left = this._rightPos() + 'px';
116 return this;
117 },
Nils Diewald7148c6f2015-05-04 15:07:53 +0000118
Akron308db382016-05-30 22:34:07 +0200119
Akronda5bd3a2020-10-16 17:37:49 +0200120 /**
121 * Insert text into the mirror.
122 * This is a prefix of the input field's
123 * value.
124 */
125 insert : function (text) {
126 const splittedText = this._split();
Akron24aa0052020-11-10 11:00:34 +0100127 const s = this._el;
Akronda5bd3a2020-10-16 17:37:49 +0200128 s.value = splittedText[0] + text + splittedText[1];
129 s.selectionStart = (splittedText[0] + text).length;
130 s.selectionEnd = s.selectionStart;
131
132 // Maybe update?
133 this._mirror.firstChild.textContent = splittedText[0] + text;
134 return this;
135 },
Nils Diewald7148c6f2015-05-04 15:07:53 +0000136
Akronc14cbfc2018-08-31 13:15:55 +0200137
Akronda5bd3a2020-10-16 17:37:49 +0200138 /**
139 * Move hinthelper to given character position
140 */
141 moveto : function (charpos) {
Akron24aa0052020-11-10 11:00:34 +0100142 const e = this._el;
Akronda5bd3a2020-10-16 17:37:49 +0200143 e.selectionStart = charpos;
144 e.selectionEnd = charpos;
145 e.focus();
146 return this.update();
147 },
Akron308db382016-05-30 22:34:07 +0200148
Akronc14cbfc2018-08-31 13:15:55 +0200149
Akronda5bd3a2020-10-16 17:37:49 +0200150 /**
151 * Reposition the input mirror directly
152 * below the input box.
153 */
154 reposition : function () {
Akron24aa0052020-11-10 11:00:34 +0100155 const inputClientRect = this._el.getBoundingClientRect();
156 const inputStyle = window.getComputedStyle(this._el, null);
Akronda5bd3a2020-10-16 17:37:49 +0200157
158 const bodyClientRect =
159 document.getElementsByTagName('body')[0].getBoundingClientRect();
160
161 // Reset position
162 const mirrorStyle = this._mirror.style;
163 mirrorStyle.left = parseInt(inputClientRect.left) + "px";
164 mirrorStyle.top = parseInt(inputClientRect.bottom - bodyClientRect.top) + "px";
165 mirrorStyle.width = inputStyle.getPropertyValue("width");
166
167 // These may be relevant in case of media depending css
168 mirrorStyle.paddingLeft = inputStyle.getPropertyValue("padding-left");
169 mirrorStyle.marginLeft = inputStyle.getPropertyValue("margin-left");
170 mirrorStyle.borderLeftWidth = inputStyle.getPropertyValue("border-left-width");
171 mirrorStyle.borderLeftStyle = inputStyle.getPropertyValue("border-left-style");
172 mirrorStyle.fontSize = inputStyle.getPropertyValue("font-size");
173 mirrorStyle.fontFamily = inputStyle.getPropertyValue("font-family");
174 },
Akron308db382016-05-30 22:34:07 +0200175
176
Akronda5bd3a2020-10-16 17:37:49 +0200177 /**
178 * Get the context, which is the input
179 * field's value bounded to the
180 * cursor position.
181 */
182 context : function () {
183 return this._split()[0];
184 },
Akron308db382016-05-30 22:34:07 +0200185
Akron308db382016-05-30 22:34:07 +0200186
Akronda5bd3a2020-10-16 17:37:49 +0200187 // Get the right position
188 _rightPos : function () {
189 const box = this._mirror.firstChild.getBoundingClientRect();
190 return box.right - box.left;
191 },
192
193
194 /*
195 * Return two substrings,
196 * splitted at a given position
197 * or at the current cursor position.
198 */
199 _split : function (start) {
Akron24aa0052020-11-10 11:00:34 +0100200 const s = this._el;
Akronda5bd3a2020-10-16 17:37:49 +0200201 const value = s.value;
202
203 // Get start from cursor position
204 if (arguments.length === 0)
205 start = s.selectionStart;
206
207 return new Array(
208 value.substring(0, start),
209 value.substring(start, value.length)
210 );
Akronb759ee92024-11-19 18:02:56 +0100211 },
212
213 /**
214 * Return the cursor character offsets
215 */
216 selectionRange : function () {
217 return [this._el.selectionStart, this._el.selectionEnd];
Akronda5bd3a2020-10-16 17:37:49 +0200218 }
Nils Diewald0e6992a2015-04-14 20:13:52 +0000219 }
220});