Adde early breakout to hinting.js.
[vimprobable2.git] / hinting.js
blob90b453e808a5fc698a49a0c23527d6b864ac1e18
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 hintElements;
10 var hintCount;
11 var focusedHint;
12 var colors;
13 var backgrounds;
15 this.createHints = function (inputText)
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 this.genHintContainer();
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 + "')]";
36 iterator type isn't suitable here, because: "DOMException NVALID_STATE_ERR:
37 The document has been mutated since the result was returned."
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 /* due to the different XPath result type, we will need two counter variables */
45 this.hintCount = 0;
46 var i;
47 this.hintElements = [];
48 this.colors = [];
49 this.backgrounds = [];
50 for (i = 0; i < r.snapshotLength; i++)
52 var elem = r.snapshotItem(i);
53 var rect = elem.getBoundingClientRect();
54 if (!rect || rect.top > height || rect.bottom < 0 || rect.left > width || rect.right < 0 || !(elem.getClientRects()[0]))
55 continue;
56 var computedStyle = document.defaultView.getComputedStyle(elem, null);
57 if (computedStyle.getPropertyValue("visibility") != "visible" || computedStyle.getPropertyValue("display") == "none")
58 continue;
59 var leftpos = Math.max((rect.left + scrollX), scrollX);
60 var toppos = Math.max((rect.top + scrollY), scrollY);
61 this.hintElements.push(elem);
62 /* making this block DOM compliant */
63 var hint = document.createElement("span");
64 hint.setAttribute("class", "hinting_mode_hint");
65 hint.setAttribute("id", "vimprobablehint" + this.hintCount);
66 hint.style.position = "absolute";
67 hint.style.left = leftpos + "px";
68 hint.style.top = toppos + "px";
69 hint.style.background = "red";
70 hint.style.color = "#fff";
71 hint.style.font = "bold 10px monospace";
72 hint.style.zIndex = "99";
73 var text = document.createTextNode(this.hintCount + 1);
74 hint.appendChild(text);
75 this.hintContainer.appendChild(hint);
76 /* remember site-defined colour of this element */
77 this.colors[this.hintCount] = elem.style.color;
78 this.backgrounds[this.hintCount] = elem.style.background;
79 /* make the link black to ensure it's readable */
80 elem.style.color = "#000";
81 elem.style.background = "#ff0";
82 this.hintCount++;
84 this.clearFocus();
85 this.focusedHint = null;
86 if (this.hintCount == 1) {
87 /* just one hinted element - might as well follow it */
88 return this.fire(1);
92 this.updateHints = function (n)
94 if(this.focusedHint != null) {
95 this.focusedHint.className = this.focusedHint.className.replace("_focus","");
96 this.focusedHint.style.background = "#ff0";
98 if (this.hintCount - 1 < n * 10 && typeof(this.hintElements[n - 1]) != "undefined") {
99 /* return signal to follow the link */
100 return "fire;" + n;
101 } else {
102 if (typeof(this.hintElements[n - 1]) != "undefined") {
103 (this.focusedHint = this.hintElements[n - 1]).className = this.hintElements[n - 1].className.replace("hinting_mode_hint", "hinting_mode_hint_focus");
104 this.focusedHint.style.background = "#8f0";
109 this.clearFocus = function ()
111 if (document.activeElement && document.activeElement.blur)
112 document.activeElement.blur();
115 this.clearHints = function ()
117 for(e in this.hintElements) {
118 if (typeof(this.hintElements[e].className) != "undefined") {
119 this.hintElements[e].className = this.hintElements[e].className.replace(/hinting_mode_hint/,'');
120 /* reset to site-defined colour */
121 this.hintElements[e].style.color = this.colors[e];
122 this.hintElements[e].style.background = this.backgrounds[e];
125 this.hintContainer.parentNode.removeChild(this.hintContainer);
126 window.onkeyup = null;
129 this.fire = function (n)
131 if (typeof(this.hintElements[n - 1]) == "undefined")
132 return;
134 var el = this.hintElements[n - 1];
135 var tag = el.nodeName.toLowerCase();
136 this.clearHints();
137 if(tag == "iframe" || tag == "frame" || tag == "textarea" || tag == "input" && (el.type == "text" || el.type == "password" || el.type == "checkbox" || el.type == "radio") || tag == "select") {
138 el.focus();
139 return;
141 if (!el.onclick && el.href && !el.href.match('/^javascript:/')) {
142 /* send signal to open link */
143 return "open;" + el.href;
145 var evObj = document.createEvent('MouseEvents');
146 evObj.initMouseEvent('click', true, true, window, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
147 el.dispatchEvent(evObj);
150 this.focusInput = function ()
152 if (document.getElementsByTagName("body")[0] === null || typeof(document.getElementsByTagName("body")[0]) != "object")
153 return;
155 /* prefixing html: will result in namespace error */
156 var hinttags = "//input[@type='text'] | //input[@type='password'] | //textarea";
157 var r = document.evaluate(hinttags, document,
158 function(p) {
159 return 'http://www.w3.org/1999/xhtml';
160 }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
161 var i;
162 var j = 0;
163 var k = 0;
164 var first = null;
165 for (i = 0; i < r.snapshotLength; i++) {
166 var elem = r.snapshotItem(i);
167 if (k == 0) {
168 if (elem.style.display != "none" && elem.style.visibility != "hidden") {
169 first = elem;
170 } else {
171 k--;
174 if (j == 1 && elem.style.display != "none" && elem.style.visibility != "hidden") {
175 elem.focus();
176 break;
178 if (elem == document.activeElement) {
179 j = 1;
181 k++;
183 /* no appropriate field found focused - focus the first one */
184 if (j == 0 && first !== null)
185 first.focus();
188 this.genHintContainer = function ()
190 var body = document.getElementsByTagName('body')[0];
191 if (document.getElementById('hint_container'))
192 return;
194 this.hintContainer = document.createElement('div');
195 this.hintContainer.id = "hint_container";
197 if (body)
198 body.appendChild(this.hintContainer);
201 hints = new Hints();