2 (c) 2009 by Leon Winter
3 (c) 2009, 2010 by Hannes Schueller
4 (c) 2010 by Hans-Peter Deifel
8 const maxAllowedHints
= 500;
11 var currentFocusNum
= 1;
15 this.createHints = function(inputText
, hintMode
)
22 var top_height
= topwin
.innerHeight
;
23 var top_width
= topwin
.innerWidth
;
29 function helper (win
, offsetX
, offsetY
) {
30 var doc
= win
.document
;
32 var win_height
= win
.height
;
33 var win_width
= win
.width
;
36 var minX
= offsetX
< 0 ? -offsetX
: 0;
37 var minY
= offsetY
< 0 ? -offsetY
: 0;
38 var maxX
= offsetX
+ win_width
> top_width
? top_width
- offsetX
: top_width
;
39 var maxY
= offsetY
+ win_height
> top_height
? top_height
- offsetY
: top_height
;
41 var scrollX
= win
.scrollX
;
42 var scrollY
= win
.scrollY
;
44 hintContainer
= doc
.createElement("div");
45 hintContainer
.id
= "hint_container";
47 if (typeof(inputText
) == "undefined" || inputText
== "") {
48 xpath_expr
= "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href] | //input[not(@type='hidden')] | //a | //area | //textarea | //button | //select";
50 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[contains(., '" + inputText
+ "')] | //area[contains(., '" + inputText
+ "')] | //textarea[contains(., '" + inputText
+ "')] | //button[contains(@value, '" + inputText
+ "')] | //select[contains(., '" + inputText
+ "')]";
53 var res
= doc
.evaluate(xpath_expr
, doc
,
55 return "http://www.w3.org/1999/xhtml";
56 }, XPathResult
.ORDERED_NODE_SNAPSHOT_TYPE
, null);
58 /* generate basic hint element which will be cloned and updated later */
59 var hintSpan
= doc
.createElement("span");
60 hintSpan
.setAttribute("class", "hinting_mode_hint");
61 hintSpan
.style
.position
= "absolute";
62 hintSpan
.style
.background
= "red";
63 hintSpan
.style
.color
= "#fff";
64 hintSpan
.style
.font
= "bold 10px monospace";
65 hintSpan
.style
.zIndex
= "10000000";
67 /* due to the different XPath result type, we will need two counter variables */
68 var rect
, elem
, text
, node
, show_text
;
69 for (var i
= 0; i
< res
.snapshotLength
; i
++)
71 if (hintCount
>= maxAllowedHints
)
74 elem
= res
.snapshotItem(i
);
75 rect
= elem
.getBoundingClientRect();
76 if (!rect
|| rect
.left
> maxX
|| rect
.right
< minX
|| rect
.top
> maxY
|| rect
.bottom
< minY
)
79 var style
= topwin
.getComputedStyle(elem
, "");
80 if (style
.display
== "none" || style
.visibility
!= "visible")
83 var leftpos
= Math
.max((rect
.left
+ scrollX
), scrollX
);
84 var toppos
= Math
.max((rect
.top
+ scrollY
), scrollY
);
86 /* process elements text */
87 text
= _getTextFromElement(elem
);
89 /* making this block DOM compliant */
90 var hint
= hintSpan
.cloneNode(false);
91 hint
.setAttribute("id", "vimprobablehint" + hintCount
);
92 hint
.style
.left
= leftpos
+ "px";
93 hint
.style
.top
= toppos
+ "px";
94 text
= doc
.createTextNode(hintCount
+ 1);
95 hint
.appendChild(text
);
97 hintContainer
.appendChild(hint
);
104 background
: elem
.style
.background
,
105 foreground
: elem
.style
.color
}
108 /* make the link black to ensure it's readable */
109 elem
.style
.color
= "#000";
110 elem
.style
.background
= "#ff0";
113 doc
.documentElement
.appendChild(hintContainer
);
115 /* recurse into any iframe or frame element */
116 var frameTags
= ["frame","iframe"];
117 for (var i
in frameTags
) {
118 var frames
= doc
.getElementsByTagName(frameTags
[i
]);
119 for (var i
= 0, nframes
= frames
.length
; i
< nframes
; ++i
) {
121 rect
= elem
.getBoundingClientRect();
122 if (!elem
.contentWindow
|| !rect
|| rect
.left
> maxX
|| rect
.right
< minX
|| rect
.top
> maxY
|| rect
.bottom
< minY
)
124 helper(elem
.contentWindow
, offsetX
+ rect
.left
, offsetY
+ rect
.top
);
129 helper(topwin
, 0, 0);
133 if (hintCount
== 1) {
134 /* just one hinted element - might as well follow it */
139 /* set focus on hint with given number */
140 this.focusHint = function(n
)
142 /* reset previous focused hint */
143 var hint
= _getHintByNumber(currentFocusNum
);
145 hint
.elem
.className
= hint
.elem
.className
.replace("hinting_mode_hint_focus", "hinting_mode_hint");
146 hint
.elem
.style
.background
= "#ff0";
151 /* mark new hint as focused */
152 var hint
= _getHintByNumber(currentFocusNum
);
154 hint
.elem
.className
= hint
.elem
.className
.replace("hinting_mode_hint", "hinting_mode_hint_focus");
155 hint
.elem
.style
.background
= "#8f0";
159 /* set focus to next avaiable hint */
160 this.focusNextHint = function()
162 var index
= _getHintIdByNumber(currentFocusNum
);
164 if (typeof(hints
[index
+ 1]) != "undefined") {
165 this.focusHint(hints
[index
+ 1].number
);
167 this.focusHint(hints
[0].number
);
171 /* set focus to previous avaiable hint */
172 this.focusPreviousHint = function()
174 var index
= _getHintIdByNumber(currentFocusNum
);
175 if (index
!= 0 && typeof(hints
[index
- 1].number
) != "undefined") {
176 this.focusHint(hints
[index
- 1].number
);
178 this.focusHint(hints
[hints
.length
- 1].number
);
182 /* filters hints matching given number */
183 this.updateHints = function(n
)
189 /* remove none matching hints */
193 if (0 != hint
.number
.toString().indexOf(n
.toString())) {
194 remove
.push(hint
.number
);
198 for (var i
= 0; i
< remove
.length
; ++i
) {
199 _removeHint(remove
[i
]);
202 if (hints
.length
=== 1) {
203 this.fire(hints
[0].number
);
209 this.clearFocus = function()
211 if (document
.activeElement
&& document
.activeElement
.blur
) {
212 document
.activeElement
.blur();
216 /* remove all hints and set previous style to them */
217 this.clearHints = function()
219 if (hints
.length
== 0) {
224 if (typeof(hint
.elem
) != "undefined") {
225 hint
.elem
.style
.background
= hint
.background
;
226 hint
.elem
.style
.color
= hint
.foreground
;
227 hint
.span
.parentNode
.removeChild(hint
.span
);
231 hintContainer
.parentNode
.removeChild(hintContainer
);
232 window
.onkeyup
= null;
235 /* fires the modeevent on hint with given number */
236 this.fire = function(n
)
240 var n
= currentFocusNum
;
242 var hint
= _getHintByNumber(n
);
243 if (typeof(hint
.elem
) == "undefined")
247 var tag
= el
.nodeName
.toLowerCase();
251 if (tag
== "iframe" || tag
== "frame" || tag
== "textarea" || tag
== "input" && (el
.type
== "text" || el
.type
== "password" || el
.type
== "checkbox" || el
.type
== "radio") || tag
== "select") {
258 case "f": result
= _open(el
); break;
259 case "F": result
= _openNewWindow(el
); break;
260 default: result
= _getElemtSource(el
);
266 this.focusInput = function()
268 if (document
.getElementsByTagName("body")[0] === null || typeof(document
.getElementsByTagName("body")[0]) != "object")
271 /* prefixing html: will result in namespace error */
272 var hinttags
= "//input[@type='text'] | //input[@type='password'] | //textarea";
273 var r
= document
.evaluate(hinttags
, document
,
275 return "http://www.w3.org/1999/xhtml";
276 }, XPathResult
.ORDERED_NODE_SNAPSHOT_TYPE
, null);
281 for (i
= 0; i
< r
.snapshotLength
; i
++) {
282 var elem
= r
.snapshotItem(i
);
284 if (elem
.style
.display
!= "none" && elem
.style
.visibility
!= "hidden") {
290 if (j
== 1 && elem
.style
.display
!= "none" && elem
.style
.visibility
!= "hidden") {
294 if (elem
== document
.activeElement
) {
299 /* no appropriate field found focused - focus the first one */
300 if (j
== 0 && first
!== null)
304 /* retrieves text content fro given element */
305 function _getTextFromElement(el
)
307 if (el
instanceof HTMLInputElement
|| el
instanceof HTMLTextAreaElement
) {
309 } else if (el
instanceof HTMLSelectElement
) {
310 if (el
.selectedIndex
>= 0) {
311 text
= el
.item(el
.selectedIndex
).text
;
316 text
= el
.textContent
;
318 return text
.toLowerCase();;
321 /* retrieves the hint for given hint number */
322 function _getHintByNumber(n
)
324 var index
= _getHintIdByNumber(n
);
325 if (index
!== null) {
331 /* retrieves the id of hint with given number */
332 function _getHintIdByNumber(n
)
334 for (var i
= 0; i
< hints
.length
; ++i
) {
336 if (hint
.number
=== n
) {
343 /* removes hint with given number from hints array */
344 function _removeHint(n
)
346 var index
= _getHintIdByNumber(n
);
347 if (index
=== null) {
350 var hint
= hints
[index
];
351 if (hint
.number
=== n
) {
352 hint
.elem
.style
.background
= hint
.background
;
353 hint
.elem
.style
.color
= hint
.foreground
;
354 hint
.span
.parentNode
.removeChild(hint
.span
);
356 /* remove hints from all hints */
357 hints
.splice(index
, 1);
361 /* opens given element */
368 /* opens given element into new window */
369 function _openNewWindow(elem
)
371 var oldTarget
= elem
.target
;
373 /* set target to open in new window */
374 elem
.target
= "_blank";
376 elem
.target
= oldTarget
;
381 /* fire moudedown and click event on given element */
382 function _clickElement(elem
)
384 doc
= elem
.ownerDocument
;
385 view
= elem
.contentWindow
;
387 var evObj
= doc
.createEvent("MouseEvents");
388 evObj
.initMouseEvent("mousedown", true, true, view
, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
389 elem
.dispatchEvent(evObj
);
391 var evObj
= doc
.createEvent("MouseEvents");
392 evObj
.initMouseEvent("click", true, true, view
, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
393 elem
.dispatchEvent(evObj
);
396 /* retrieves the url of given element */
397 function _getElemtSource(elem
)
399 var url
= elem
.href
|| elem
.src
;