Implemented removing of hints if a number is selected.
[vimprobable2.git] / hinting.js
blobdc475dca8b151eb0dcd00daf7165db8bb58d86e1
1 /*
2     (c) 2009 by Leon Winter
3     (c) 2009, 2010 by Hannes Schueller
4     (c) 2010 by Hans-Peter Deifel
5     see LICENSE file
6 */
7 function Hints() {
8     var hintContainer;
9     var hintCount;
10     var currentFocusNum = 1;
12     /* hints[] = [elem, number, text, span, backgroundColor, color] */
13     var hints = [];
15     this.createHints = function(inputText)
16     {
17         if (document.getElementsByTagName("body")[0] === null || typeof(document.getElementsByTagName("body")[0]) != "object")
18             return;
20         var height = window.innerHeight;
21         var width = window.innerWidth;
22         var scrollX = document.defaultView.scrollX;
23         var scrollY = document.defaultView.scrollY;
24         _generateHintContainer();
26         /* prefixing html: will result in namespace error */
27         var hinttags;
28         if (typeof(inputText) == "undefined" || inputText == "") {
29             hinttags = "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href] | //input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select";
30         } else {
31             /* only elements which match the text entered so far */
32             hinttags = "//*[(@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href) and contains(., '" + inputText + "')] | //input[not(@type='hidden') and contains(., '" + inputText + "')] | //a[contains(., '" + inputText + "')] | //area[contains(., '" + inputText + "')] | //iframe[contains(@name, '" + inputText + "')] | //textarea[contains(., '" + inputText + "')] | //button[contains(@value, '" + inputText + "')] | //select[contains(., '" + inputText + "')]";
33         }
35         /*
36         iterator type isn't suitable here, because: "DOMException NVALID_STATE_ERR:
37         The document has been mutated since the result was returned."
38         */
39         var r = document.evaluate(hinttags, document,
40             function(p) {
41                 return "http://www.w3.org/1999/xhtml";
42             }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
44         /* generate basic hint element which will be cloned and updated later */
45         var hintSpan = document.createElement("span");
46         hintSpan.setAttribute("class", "hinting_mode_hint");
47         hintSpan.style.position = "absolute";
48         hintSpan.style.background = "red";
49         hintSpan.style.color = "#fff";
50         hintSpan.style.font = "bold 10px monospace";
51         hintSpan.style.zIndex = "10000000";
53         /* due to the different XPath result type, we will need two counter variables */
54         this.hintCount = 0;
55         var i;
56         hints = [];
57         for (i = 0; i < r.snapshotLength; i++)
58         {
59             var elem = r.snapshotItem(i);
60             var rect = elem.getBoundingClientRect();
61             if (!rect || rect.top > height || rect.bottom < 0 || rect.left > width || rect.right < 0 || !(elem.getClientRects()[0]))
62                 continue;
63             var computedStyle = document.defaultView.getComputedStyle(elem, null);
64             if (computedStyle.getPropertyValue("visibility") != "visible" || computedStyle.getPropertyValue("display") == "none")
65                 continue;
66             var leftpos = Math.max((rect.left + scrollX), scrollX);
67             var toppos = Math.max((rect.top + scrollY), scrollY);
69             /* process elements text */
70             var text = _getTextFromElement(elem);
72             /* making this block DOM compliant */
73             var hint = hintSpan.cloneNode(false);
74             hint.setAttribute("id", "vimprobablehint" + this.hintCount);
75             hint.style.left = leftpos + "px";
76             hint.style.top =  toppos + "px";
77             var text = document.createTextNode(this.hintCount + 1);
78             hint.appendChild(text);
79             hintContainer.appendChild(hint);
80             this.hintCount++;
81             hints.push([elem, this.hintCount, text, hint, elem.style.background, elem.style.color]);
82             /* make the link black to ensure it's readable */
83             elem.style.color = "#000";
84             elem.style.background = "#ff0";
85         }
86         this.clearFocus();
87         this.focusHint(1);
88         if (this.hintCount == 1) {
89             /* just one hinted element - might as well follow it */
90             return this.fire(1);
91         }
92     };
94     /* set focus on hint with given number */
95     this.focusHint = function(n)
96     {
97         /* reset previous focused hint */
98         var hint = _getHintByNumber(currentFocusNum);
99         if (hint !== null) {
100             hint[0].className = hint[0].className.replace("hinting_mode_hint_focus", "hinting_mode_hint");
101             hint[0].style.background = "#ff0";
102         }
104         currentFocusNum = n;
106         /* mark new hint as focused */
107         var hint = _getHintByNumber(currentFocusNum);
108         if (hint !== null) {
109             hint[0].className = hint[0].className.replace("hinting_mode_hint", "hinting_mode_hint_focus");
110             hint[0].style.background = "#8f0";
111         }
112     };
114     this.focusNextHint = function()
115     {
116         var index = _getHintIdByNumber(currentFocusNum);
118         if (typeof(hints[index + 1]) != "undefined") {
119             this.focusHint(hints[index + 1][1]);
120         } else {
121             this.focusHint(hints[0][1]);
122         }
123     };
125     this.focusPreviousHint = function()
126     {
127         var index = _getHintIdByNumber(currentFocusNum);
129         if (typeof(hints[index - 1][1]) != "undefined") {
130             this.focusHint(hints[index - 1][1]);
131         } else {
132             this.focusHint(hints[hints.length - 1][1]);
133         }
134     };
136     this.updateHints = function(n)
137     {
138         /* remove none matching hints */
139         var remove = [];
140         for (e in hints) {
141             var hint = hints[e];
142             if (0 != hint[1].toString().indexOf(n.toString())) {
143                 remove.push(hint[1]);
144             }
145         }
147         for (var i = 0; i < remove.length; ++i) {
148             _removeHint(remove[i]);
149         }
151         if (hints.length === 1) {
152             return "fire;" + hints[0][1];
153         }
155         this.focusHint(n);
156     };
158     this.clearFocus = function()
159     {
160         if (document.activeElement && document.activeElement.blur) {
161             document.activeElement.blur();
162         }
163     };
165     this.clearHints = function()
166     {
167         for (e in hints) {
168             var hint = hints[e];
169             if (typeof(hint[0]) != "undefined") {
170                 hint[0].style.background = hint[4];
171                 hint[0].style.color = hint[5];
172             }
173         }
174         hintContainer.parentNode.removeChild(hintContainer);
175         window.onkeyup = null;
176     };
178     this.fire = function(n)
179     {
180         if (!n) {
181             var n = this.currentFocusNum;
182         }
183         var hint = _getHintByNumber(n);
184         if (typeof(hint[0]) == "undefined")
185             return;
187         var el = hint[0];
188         var tag = el.nodeName.toLowerCase();
189         this.clearHints();
190         if (tag == "iframe" || tag == "frame" || tag == "textarea" || tag == "input" && (el.type == "text" || el.type == "password" || el.type == "checkbox" || el.type == "radio") || tag == "select") {
191             el.focus();
192             return;
193         }
194         if (!el.onclick && el.href && !el.href.match("/^javascript:/")) {
195             /* send signal to open link */
196             return "open;" + el.href;
197         }
198         var evObj = document.createEvent("MouseEvents");
199         evObj.initMouseEvent("click", true, true, window, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
200         el.dispatchEvent(evObj);
201     };
203     this.focusInput = function()
204     {
205         if (document.getElementsByTagName("body")[0] === null || typeof(document.getElementsByTagName("body")[0]) != "object")
206             return;
208         /* prefixing html: will result in namespace error */
209         var hinttags = "//input[@type='text'] | //input[@type='password'] | //textarea";
210         var r = document.evaluate(hinttags, document,
211             function(p) {
212                 return "http://www.w3.org/1999/xhtml";
213             }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
214         var i;
215         var j = 0;
216         var k = 0;
217         var first = null;
218         for (i = 0; i < r.snapshotLength; i++) {
219             var elem = r.snapshotItem(i);
220             if (k == 0) {
221                 if (elem.style.display != "none" && elem.style.visibility != "hidden") {
222                     first = elem;
223                 } else {
224                     k--;
225                 }
226             }
227             if (j == 1 && elem.style.display != "none" && elem.style.visibility != "hidden") {
228                 elem.focus();
229                 break;
230             }
231             if (elem == document.activeElement) {
232                 j = 1;
233             }
234             k++;
235         }
236         /* no appropriate field found focused - focus the first one */
237         if (j == 0 && first !== null)
238             first.focus();
239     };
241     function _generateHintContainer()
242     {
243         var body = document.getElementsByTagName("body")[0];
244         if (document.getElementById("hint_container")) {
245             return;
246         }
248         hintContainer = document.createElement("div");
249         hintContainer.id = "hint_container";
251         if (body)
252             body.appendChild(hintContainer);
253     }
255     function _getTextFromElement(el)
256     {
257         var tagname = el.tagName.toLowerCase();
258         if (tagname == "input" || tagname == "textarea") {
259             text = el.value;
260         } else if (tagname == "select") {
261             if (el.selectedIndex >= 0) {
262                 text = el.item(el.selectedIndex).text;
263             } else{
264                 text = "";
265             }
266         } else {
267             text = el.textContent;
268         }
269         return text.toLowerCase();;
270     }
272     function _getHintByNumber(n)
273     {
274         var index = _getHintIdByNumber(n);
275         if (index !== null) {
276             return hints[index];
277         }
278         return null;
279     }
281     function _getHintIdByNumber(n)
282     {
283         for (var i = 0; i < hints.length; ++i) {
284             var hint = hints[i];
285             if (hint[1] === n) {
286                 return i;
287             }
288         }
289         return null;
290     }
292     function _removeHint(n)
293     {
294         var index = _getHintIdByNumber(n);
295         if (index === null) {
296             return;
297         }
298         var hint = hints[index];
299         if (hint[1] == n) {
300             hint[0].style.background = hint[4];
301             hint[0].style.color = hint[5];
302             hintContainer.removeChild(hint[3]);
304             /* remove hints from all hints */
305             hints.splice(index, 1);
306         }
307     }
309 hints = new Hints();