blob: 6f20c67203398bb1fb096c7c92882583f5288004 [file] [log] [blame]
Nils Diewald44a72782014-06-20 16:03:21 +00001/*
2TODO:
3 - Limit the size to a certain number of elements
4 - addEventListener("click", ... , false);
5 - addEventListener("paste", ... , false);
6*/
7
8var Hint = function (param) {
9 var foundryRegex = new RegExp("(?:^|[^a-zA-Z0-9])([-a-zA-Z0-9]+?)\/(?:([^=]+?)=)?$");
10
11 var search = document.getElementById(param["ref"]);
12 var mirror = document.createElement("div");
13 var hint = document.createElement("ul");
14 var hintSize = param["hintSize"] ? param["hintSize"] : 10;
15 var hints = param["hints"];
16 var that = this;
17
18 // Build the mirror element
19 // <div id="searchMirror"><span></span><ul></ul></div>
20 mirror.setAttribute("id", "searchMirror");
21 mirror.appendChild(document.createElement("span"));
22 mirror.appendChild(hint);
23 search.parentNode.insertBefore(mirror, search);
24
25 // Default active state
26 this.active = -2;
27
28 // Show hint table
29 this.show = function (topic) {
30 if (!hints[topic])
31 return;
32 this.active = -1;
33 this.list(hints[topic]);
34 var searchRight = search.getBoundingClientRect().right;
35 var infoRight = hint.getBoundingClientRect().right;
36 if (infoRight > searchRight) {
37 hint.style.marginLeft = '-' + (infoRight - searchRight) + 'px';
38 };
39 hint.style.opacity = 1;
40 };
41
42 // Initialize the mirror element
43 function init () {
44 // Copy input style
45 var searchRect = search.getBoundingClientRect();
46 var searchStyle = window.getComputedStyle(search, null);
47
48 with (mirror.style) {
49 left = searchRect.left + "px";
50 top = searchRect.bottom + "px";
51 borderLeftColor = "transparent";
52 paddingLeft = searchStyle.getPropertyValue("padding-left");
53 marginLeft = searchStyle.getPropertyValue("margin-left");
54 borderLeftWidth = searchStyle.getPropertyValue("border-left-width");
55 borderLeftStyle = searchStyle.getPropertyValue("border-left-style");
56 fontSize = searchStyle.getPropertyValue("font-size");
57 fontFamily = searchStyle.getPropertyValue("font-family");
58 };
59 };
60
61 // Hide hint table
62 this.hide = function () {
63 this.active = -2;
64 hint.style.opacity = 0;
65 hint.style.marginLeft = 0;
66
67 // Remove all children
68 var lis = hint.childNodes;
69 for (var li = lis.length - 1; li >= 0; li--) {
70 hint.removeChild(lis[li])
71 };
72 };
73
74 // List elements in the hint table
75 this.list = function (hintList) {
76 var li, title;
77 var arrayType = hintList instanceof Array;
78 for (var i in hintList) {
79 // Create list items
80 li = document.createElement("li");
81 li.setAttribute("data-action", arrayType ? hintList[i] : hintList[i][0]);
82 title = document.createElement("strong");
83 title.appendChild(document.createTextNode(arrayType ? hintList[i] : i));
84 li.appendChild(title);
85 hint.appendChild(li);
86
87 // Include descriptions
88 if (!arrayType && hintList[i][1]) {
89 var desc = document.createElement("span");
90 desc.appendChild(document.createTextNode(hintList[i][1]));
91 li.appendChild(desc);
92 };
93 };
94 };
95
96 // Choose next item in list
97 this.next = function () {
98 if (this.active === -2)
99 return;
100 var lis = hint.getElementsByTagName("li");
101 if (this.active === -1) {
102 lis[0].setAttribute("class", "active");
103 this.active = 0;
104 }
105 else if (this.active === lis.length - 1) {
106 lis[this.active].removeAttribute("class");
107 lis[0].setAttribute("class", "active");
108 this.active = 0;
109 }
110 else {
111 lis[this.active].removeAttribute("class");
112 lis[++this.active].setAttribute("class", "active");
113 };
114 };
115
116 // Choose previous item in list
117 this.previous = function () {
118 if (this.active === -2)
119 return;
120
121 var lis = hint.getElementsByTagName("li");
122 if (this.active === -1) {
123 this.active = lis.length - 1;
124 lis[this.active].setAttribute("class", "active");
125 }
126 else if (this.active === 0) {
127 lis[0].removeAttribute("class");
128 this.active = lis.length - 1;
129 lis[this.active].setAttribute("class", "active");
130 }
131 else {
132 lis[this.active].removeAttribute("class");
133 lis[--this.active].setAttribute("class", "active");
134 };
135 };
136
137 // Choose item from list
138 this.choose = function () {
139 if (this.active < 0)
140 return;
141
142 var action = hint.getElementsByTagName("li")[this.active].getAttribute("data-action");
143
144 var value = search.value;
145 var start = search.selectionStart;
146 var begin = value.substring(0, start);
147 var end = value.substring(start, value.length);
148 search.value = begin + action + end;
149 search.selectionStart = search.selectionEnd = (begin + action).length;
150
151 this.hide();
152
153 // Check for new changes
154 mirror.firstChild.innerHTML = begin + action;
155 if (foundryRegex.exec(begin + action))
156 this.show(RegExp.$1 + (RegExp.$2 ? '/' + RegExp.$2 : ''));
157 };
158
159 function changed (e) {
160 var el = e.target;
161
162 if (e.key === '/' || e.key === '=') {
163 var start = el.selectionStart;
164 mirror.firstChild.innerHTML = el.value.substring(0, start);
165 var sub = el.value.substring(start - 128 >= 0 ? start - 128 : 0, start);
166
167 if (foundryRegex.exec(sub))
168 that.show(RegExp.$1 + (RegExp.$2 ? '/' + RegExp.$2 : ''));
169 }
170 else if (e.key !== 'Shift' &&
171 e.key !== 'Up' &&
172 e.key !== 'Down' &&
173 e.key !== 'Enter') {
174 that.hide();
175 };
176 };
177
178 // Select item from suggestion list
179 function select (e) {
180 if (that.active === -2)
181 return;
182 if (e.key === 'Down') {
183 that.next();
184 }
185 else if (e.key === 'Up') {
186 that.previous();
187 }
188 else if (e.key === 'Enter') {
189 that.choose();
190 e.stopPropagation();
191 e.preventDefault();
192 return false;
193 };
194 };
195
196 // Initialize style
197 init();
198 search.addEventListener("keyup", changed, false);
199 search.addEventListener("keypress", select, false);
200};