2 (c) 2009 by Leon Winter
3 (c) 2009, 2010 by Hannes Schueller
4 (c) 2010 by Hans-Peter Deifel
10 hintCss: "z-index:10000000;font-family:monospace;font-size:10px;"
11 + "font-weight:bold;color:white;background-color:red;"
12 + "padding:0px 1px;position:absolute;",
13 hintClass: "hinting_mode_hint",
14 hintClassFocus: "hinting_mode_hint_focus",
15 elemBackground: "#ff0",
16 elemBackgroundFocus: "#8f0",
21 var currentFocusNum = 1;
25 this.createHints = function(inputText, hintMode)
32 var top_height = topwin.innerHeight;
33 var top_width = topwin.innerWidth;
39 function helper (win, offsetX, offsetY) {
40 var doc = win.document;
42 var win_height = win.height;
43 var win_width = win.width;
46 var minX = offsetX < 0 ? -offsetX : 0;
47 var minY = offsetY < 0 ? -offsetY : 0;
48 var maxX = offsetX + win_width > top_width ? top_width - offsetX : top_width;
49 var maxY = offsetY + win_height > top_height ? top_height - offsetY : top_height;
51 var scrollX = win.scrollX;
52 var scrollY = win.scrollY;
54 hintContainer = doc.createElement("div");
55 hintContainer.id = "hint_container";
57 if (typeof(inputText) == "undefined" || inputText == "") {
58 xpath_expr = "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href] | //input[not(@type='hidden')] | //a[href] | //area | //textarea | //button | //select";
60 xpath_expr = "//*[(@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[@href and contains(., '" + inputText + "')] | //area[contains(., '" + inputText + "')] | //textarea[contains(., '" + inputText + "')] | //button[contains(@value, '" + inputText + "')] | //select[contains(., '" + inputText + "')]";
63 var res = doc.evaluate(xpath_expr, doc,
65 return "http://www.w3.org/1999/xhtml";
66 }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
68 /* generate basic hint element which will be cloned and updated later */
69 var hintSpan = doc.createElement("span");
70 hintSpan.setAttribute("class", config.hintClass);
71 hintSpan.style.cssText = config.hintCss;
73 /* due to the different XPath result type, we will need two counter variables */
74 var rect, elem, text, node, show_text;
75 for (var i = 0; i < res.snapshotLength; i++)
77 if (hintCount >= config.maxAllowedHints)
80 elem = res.snapshotItem(i);
81 rect = elem.getBoundingClientRect();
82 if (!rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY)
85 var style = topwin.getComputedStyle(elem, "");
86 if (style.display == "none" || style.visibility != "visible")
89 var leftpos = Math.max((rect.left + scrollX), scrollX);
90 var toppos = Math.max((rect.top + scrollY), scrollY);
92 /* process elements text */
93 text = _getTextFromElement(elem);
95 /* making this block DOM compliant */
96 var hint = hintSpan.cloneNode(false);
97 hint.setAttribute("id", "vimprobablehint" + hintCount);
98 hint.style.left = leftpos + "px";
99 hint.style.top = toppos + "px";
100 text = doc.createTextNode(hintCount + 1);
101 hint.appendChild(text);
103 hintContainer.appendChild(hint);
110 background: elem.style.background,
111 foreground: elem.style.color}
114 /* make the link black to ensure it's readable */
115 elem.style.color = config.elemColor;
116 elem.style.background = config.elemBackground;
119 doc.documentElement.appendChild(hintContainer);
121 /* recurse into any iframe or frame element */
122 var frameTags = ["frame","iframe"];
123 for (var f = 0; f < frameTags.length; ++f) {
124 var frames = doc.getElementsByTagName(frameTags[f]);
125 for (var i = 0, nframes = frames.length; i < nframes; ++i) {
127 rect = elem.getBoundingClientRect();
128 if (!elem.contentWindow || !rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY)
130 helper(elem.contentWindow, offsetX + rect.left, offsetY + rect.top);
135 helper(topwin, 0, 0);
139 if (hintCount == 1) {
140 /* just one hinted element - might as well follow it */
145 /* set focus on hint with given number */
146 this.focusHint = function(n)
148 /* reset previous focused hint */
149 var hint = _getHintByNumber(currentFocusNum);
151 hint.elem.className = hint.elem.className.replace(config.hintClassFocus, config.hintClass);
152 hint.elem.style.background = config.elemBackground;
157 /* mark new hint as focused */
158 var hint = _getHintByNumber(currentFocusNum);
160 hint.elem.className = hint.elem.className.replace(config.hintClass, config.hintClassFocus);
161 hint.elem.style.background = config.elemBackgroundFocus;
165 /* set focus to next avaiable hint */
166 this.focusNextHint = function()
168 var index = _getHintIdByNumber(currentFocusNum);
170 if (typeof(hints[index + 1]) != "undefined") {
171 this.focusHint(hints[index + 1].number);
173 this.focusHint(hints[0].number);
177 /* set focus to previous avaiable hint */
178 this.focusPreviousHint = function()
180 var index = _getHintIdByNumber(currentFocusNum);
181 if (index != 0 && typeof(hints[index - 1].number) != "undefined") {
182 this.focusHint(hints[index - 1].number);
184 this.focusHint(hints[hints.length - 1].number);
188 /* filters hints matching given number */
189 this.updateHints = function(n)
192 return this.createHints();
194 /* remove none matching hints */
196 for (var i = 0; i < hints.length; ++i) {
198 if (0 != hint.number.toString().indexOf(n.toString())) {
199 remove.push(hint.number);
203 for (var i = 0; i < remove.length; ++i) {
204 _removeHint(remove[i]);
207 if (hints.length === 1) {
208 return this.fire(hints[0].number);
210 return this.focusHint(n);
214 this.clearFocus = function()
216 if (document.activeElement && document.activeElement.blur) {
217 document.activeElement.blur();
221 /* remove all hints and set previous style to them */
222 this.clearHints = function()
224 if (hints.length == 0) {
227 for (var i = 0; i < hints.length; ++i) {
229 if (typeof(hint.elem) != "undefined") {
230 hint.elem.style.background = hint.background;
231 hint.elem.style.color = hint.foreground;
232 hint.span.parentNode.removeChild(hint.span);
236 hintContainer.parentNode.removeChild(hintContainer);
237 window.onkeyup = null;
240 /* fires the modeevent on hint with given number */
241 this.fire = function(n)
245 var n = currentFocusNum;
247 var hint = _getHintByNumber(n);
248 if (typeof(hint.elem) == "undefined")
252 var tag = el.nodeName.toLowerCase();
256 if (tag == "iframe" || tag == "frame" || tag == "textarea" || tag == "input" && (el.type == "text" || el.type == "password" || el.type == "checkbox" || el.type == "radio") || tag == "select") {
258 if (tag == "input" || tag == "textarea") {
266 case "f": result = _open(el); break;
267 case "F": result = _openNewWindow(el); break;
268 default: result = _getElemtSource(el);
274 this.focusInput = function()
276 if (document.getElementsByTagName("body")[0] === null || typeof(document.getElementsByTagName("body")[0]) != "object")
279 /* prefixing html: will result in namespace error */
280 var hinttags = "//input[@type='text'] | //input[@type='password'] | //textarea";
281 var r = document.evaluate(hinttags, document,
283 return "http://www.w3.org/1999/xhtml";
284 }, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
289 for (i = 0; i < r.snapshotLength; i++) {
290 var elem = r.snapshotItem(i);
292 if (elem.style.display != "none" && elem.style.visibility != "hidden") {
298 if (j == 1 && elem.style.display != "none" && elem.style.visibility != "hidden") {
300 var tag = elem.nodeName.toLowerCase();
301 if (tag == "textarea" || tag == "input") {
306 if (elem == document.activeElement) {
311 /* no appropriate field found focused - focus the first one */
312 if (j == 0 && first !== null) {
314 var tag = elem.nodeName.toLowerCase();
315 if (tag == "textarea" || tag == "input") {
321 /* retrieves text content fro given element */
322 function _getTextFromElement(el)
324 if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
326 } else if (el instanceof HTMLSelectElement) {
327 if (el.selectedIndex >= 0) {
328 text = el.item(el.selectedIndex).text;
333 text = el.textContent;
335 return text.toLowerCase();;
338 /* retrieves the hint for given hint number */
339 function _getHintByNumber(n)
341 var index = _getHintIdByNumber(n);
342 if (index !== null) {
348 /* retrieves the id of hint with given number */
349 function _getHintIdByNumber(n)
351 for (var i = 0; i < hints.length; ++i) {
353 if (hint.number === n) {
360 /* removes hint with given number from hints array */
361 function _removeHint(n)
363 var index = _getHintIdByNumber(n);
364 if (index === null) {
367 var hint = hints[index];
368 if (hint.number === n) {
369 hint.elem.style.background = hint.background;
370 hint.elem.style.color = hint.foreground;
371 hint.span.parentNode.removeChild(hint.span);
373 /* remove hints from all hints */
374 hints.splice(index, 1);
378 /* opens given element */
381 if (elem.target == "_blank") {
382 elem.removeAttribute("target");
388 /* opens given element into new window */
389 function _openNewWindow(elem)
391 var oldTarget = elem.target;
393 /* set target to open in new window */
394 elem.target = "_blank";
396 elem.target = oldTarget;
401 /* fire moudedown and click event on given element */
402 function _clickElement(elem)
404 doc = elem.ownerDocument;
405 view = elem.contentWindow;
407 var evObj = doc.createEvent("MouseEvents");
408 evObj.initMouseEvent("mousedown", true, true, view, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
409 elem.dispatchEvent(evObj);
411 var evObj = doc.createEvent("MouseEvents");
412 evObj.initMouseEvent("click", true, true, view, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
413 elem.dispatchEvent(evObj);
416 /* retrieves the url of given element */
417 function _getElemtSource(elem)
419 var url = elem.href || elem.src;