2 (c) 2009 by Leon Winter
3 (c) 2009, 2010 by Hannes Schueller
4 (c) 2010 by Hans-Peter Deifel
5 (c) 2011 by Daniel Carl
11 hintCss
: "z-index:100000;font-family:monospace;font-size:10px;"
12 + "font-weight:bold;color:white;background-color:red;"
13 + "padding:0px 1px;position:absolute;",
14 hintClass
: "hinting_mode_hint",
15 hintClassFocus
: "hinting_mode_hint_focus",
16 elemBackground
: "#ff0",
17 elemBackgroundFocus
: "#8f0",
22 var currentFocusNum
= 1;
26 this.createHints = function(inputText
, hintMode
)
33 var top_height
= topwin
.innerHeight
;
34 var top_width
= topwin
.innerWidth
;
40 function helper (win
, offsetX
, offsetY
) {
41 var doc
= win
.document
;
43 var win_height
= win
.height
;
44 var win_width
= win
.width
;
47 var minX
= offsetX
< 0 ? -offsetX
: 0;
48 var minY
= offsetY
< 0 ? -offsetY
: 0;
49 var maxX
= offsetX
+ win_width
> top_width
? top_width
- offsetX
: top_width
;
50 var maxY
= offsetY
+ win_height
> top_height
? top_height
- offsetY
: top_height
;
52 var scrollX
= win
.scrollX
;
53 var scrollY
= win
.scrollY
;
55 hintContainer
= doc
.createElement("div");
56 hintContainer
.id
= "hint_container";
58 if (typeof(inputText
) == "undefined" || inputText
== "") {
59 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";
61 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
+ "')]";
64 var res
= doc
.evaluate(xpath_expr
, doc
,
66 return "http://www.w3.org/1999/xhtml";
67 }, XPathResult
.ORDERED_NODE_SNAPSHOT_TYPE
, null);
69 /* generate basic hint element which will be cloned and updated later */
70 var hintSpan
= doc
.createElement("span");
71 hintSpan
.setAttribute("class", config
.hintClass
);
72 hintSpan
.style
.cssText
= config
.hintCss
;
74 /* due to the different XPath result type, we will need two counter variables */
75 var rect
, elem
, text
, node
, show_text
;
76 for (var i
= 0; i
< res
.snapshotLength
; i
++)
78 if (hintCount
>= config
.maxAllowedHints
)
81 elem
= res
.snapshotItem(i
);
82 rect
= elem
.getBoundingClientRect();
83 if (!rect
|| rect
.left
> maxX
|| rect
.right
< minX
|| rect
.top
> maxY
|| rect
.bottom
< minY
)
86 var style
= topwin
.getComputedStyle(elem
, "");
87 if (style
.display
== "none" || style
.visibility
!= "visible")
90 var leftpos
= Math
.max((rect
.left
+ scrollX
), scrollX
);
91 var toppos
= Math
.max((rect
.top
+ scrollY
), scrollY
);
93 /* making this block DOM compliant */
94 var hint
= hintSpan
.cloneNode(false);
95 hint
.setAttribute("id", "vimprobablehint" + hintCount
);
96 hint
.style
.left
= leftpos
+ "px";
97 hint
.style
.top
= toppos
+ "px";
98 text
= doc
.createTextNode(hintCount
+ 1);
99 hint
.appendChild(text
);
101 hintContainer
.appendChild(hint
);
107 background
: elem
.style
.background
,
108 foreground
: elem
.style
.color
}
111 /* make the link black to ensure it's readable */
112 elem
.style
.color
= config
.elemColor
;
113 elem
.style
.background
= config
.elemBackground
;
116 doc
.documentElement
.appendChild(hintContainer
);
118 /* recurse into any iframe or frame element */
119 var frameTags
= ["frame","iframe"];
120 for (var f
= 0; f
< frameTags
.length
; ++f
) {
121 var frames
= doc
.getElementsByTagName(frameTags
[f
]);
122 for (var i
= 0, nframes
= frames
.length
; i
< nframes
; ++i
) {
124 rect
= elem
.getBoundingClientRect();
125 if (!elem
.contentWindow
|| !rect
|| rect
.left
> maxX
|| rect
.right
< minX
|| rect
.top
> maxY
|| rect
.bottom
< minY
)
127 helper(elem
.contentWindow
, offsetX
+ rect
.left
, offsetY
+ rect
.top
);
132 helper(topwin
, 0, 0);
136 if (hintCount
== 1) {
137 /* just one hinted element - might as well follow it */
142 /* set focus on hint with given number */
143 this.focusHint = function(n
)
145 /* reset previous focused hint */
146 var hint
= _getHintByNumber(currentFocusNum
);
148 hint
.elem
.className
= hint
.elem
.className
.replace(config
.hintClassFocus
, config
.hintClass
);
149 hint
.elem
.style
.background
= config
.elemBackground
;
154 /* mark new hint as focused */
155 var hint
= _getHintByNumber(currentFocusNum
);
157 hint
.elem
.className
= hint
.elem
.className
.replace(config
.hintClass
, config
.hintClassFocus
);
158 hint
.elem
.style
.background
= config
.elemBackgroundFocus
;
162 /* set focus to next avaiable hint */
163 this.focusNextHint = function()
165 var index
= _getHintIdByNumber(currentFocusNum
);
167 if (typeof(hints
[index
+ 1]) != "undefined") {
168 this.focusHint(hints
[index
+ 1].number
);
170 this.focusHint(hints
[0].number
);
174 /* set focus to previous avaiable hint */
175 this.focusPreviousHint = function()
177 var index
= _getHintIdByNumber(currentFocusNum
);
178 if (index
!= 0 && typeof(hints
[index
- 1].number
) != "undefined") {
179 this.focusHint(hints
[index
- 1].number
);
181 this.focusHint(hints
[hints
.length
- 1].number
);
185 /* filters hints matching given number */
186 this.updateHints = function(n
)
189 return this.createHints();
191 /* remove none matching hints */
193 for (var i
= 0; i
< hints
.length
; ++i
) {
195 if (0 != hint
.number
.toString().indexOf(n
.toString())) {
196 remove
.push(hint
.number
);
200 for (var i
= 0; i
< remove
.length
; ++i
) {
201 _removeHint(remove
[i
]);
204 if (hints
.length
=== 1) {
205 return this.fire(hints
[0].number
);
207 return this.focusHint(n
);
211 this.clearFocus = function()
213 if (document
.activeElement
&& document
.activeElement
.blur
) {
214 document
.activeElement
.blur();
218 /* remove all hints and set previous style to them */
219 this.clearHints = function()
221 if (hints
.length
== 0) {
224 for (var i
= 0; i
< hints
.length
; ++i
) {
226 if (typeof(hint
.elem
) != "undefined") {
227 hint
.elem
.style
.background
= hint
.background
;
228 hint
.elem
.style
.color
= hint
.foreground
;
229 hint
.span
.parentNode
.removeChild(hint
.span
);
233 hintContainer
.parentNode
.removeChild(hintContainer
);
234 window
.onkeyup
= null;
237 /* fires the modeevent on hint with given number */
238 this.fire = function(n
)
242 var n
= currentFocusNum
;
244 var hint
= _getHintByNumber(n
);
245 if (typeof(hint
.elem
) == "undefined")
249 var tag
= el
.nodeName
.toLowerCase();
253 if (tag
== "iframe" || tag
== "frame" || tag
== "textarea" || tag
== "input" && (el
.type
== "text" || el
.type
== "password" || el
.type
== "checkbox" || el
.type
== "radio") || tag
== "select") {
255 if (tag
== "input" || tag
== "textarea") {
263 case "f": result
= _open(el
); break;
264 case "F": result
= _openNewWindow(el
); break;
265 default: result
= _getElemtSource(el
);
271 this.focusInput = function()
273 if (document
.getElementsByTagName("body")[0] === null || typeof(document
.getElementsByTagName("body")[0]) != "object")
276 /* prefixing html: will result in namespace error */
277 var hinttags
= "//input[@type='text'] | //input[@type='password'] | //textarea";
278 var r
= document
.evaluate(hinttags
, document
,
280 return "http://www.w3.org/1999/xhtml";
281 }, XPathResult
.ORDERED_NODE_SNAPSHOT_TYPE
, null);
286 for (i
= 0; i
< r
.snapshotLength
; i
++) {
287 var elem
= r
.snapshotItem(i
);
289 if (elem
.style
.display
!= "none" && elem
.style
.visibility
!= "hidden") {
295 if (j
== 1 && elem
.style
.display
!= "none" && elem
.style
.visibility
!= "hidden") {
297 var tag
= elem
.nodeName
.toLowerCase();
298 if (tag
== "textarea" || tag
== "input") {
303 if (elem
== document
.activeElement
) {
308 /* no appropriate field found focused - focus the first one */
309 if (j
== 0 && first
!== null) {
311 var tag
= elem
.nodeName
.toLowerCase();
312 if (tag
== "textarea" || tag
== "input") {
318 /* retrieves text content fro given element */
319 function _getTextFromElement(el
)
321 if (el
instanceof HTMLInputElement
|| el
instanceof HTMLTextAreaElement
) {
323 } else if (el
instanceof HTMLSelectElement
) {
324 if (el
.selectedIndex
>= 0) {
325 text
= el
.item(el
.selectedIndex
).text
;
330 text
= el
.textContent
;
332 return text
.toLowerCase();;
335 /* retrieves the hint for given hint number */
336 function _getHintByNumber(n
)
338 var index
= _getHintIdByNumber(n
);
339 if (index
!== null) {
345 /* retrieves the id of hint with given number */
346 function _getHintIdByNumber(n
)
348 for (var i
= 0; i
< hints
.length
; ++i
) {
350 if (hint
.number
=== n
) {
357 /* removes hint with given number from hints array */
358 function _removeHint(n
)
360 var index
= _getHintIdByNumber(n
);
361 if (index
=== null) {
364 var hint
= hints
[index
];
365 if (hint
.number
=== n
) {
366 hint
.elem
.style
.background
= hint
.background
;
367 hint
.elem
.style
.color
= hint
.foreground
;
368 hint
.span
.parentNode
.removeChild(hint
.span
);
370 /* remove hints from all hints */
371 hints
.splice(index
, 1);
375 /* opens given element */
378 if (elem
.target
== "_blank") {
379 elem
.removeAttribute("target");
385 /* opens given element into new window */
386 function _openNewWindow(elem
)
388 var oldTarget
= elem
.target
;
390 /* set target to open in new window */
391 elem
.target
= "_blank";
393 elem
.target
= oldTarget
;
398 /* fire moudedown and click event on given element */
399 function _clickElement(elem
)
401 doc
= elem
.ownerDocument
;
402 view
= elem
.contentWindow
;
404 var evObj
= doc
.createEvent("MouseEvents");
405 evObj
.initMouseEvent("mousedown", true, true, view
, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
406 elem
.dispatchEvent(evObj
);
408 var evObj
= doc
.createEvent("MouseEvents");
409 evObj
.initMouseEvent("click", true, true, view
, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
410 elem
.dispatchEvent(evObj
);
413 /* retrieves the url of given element */
414 function _getElemtSource(elem
)
416 var url
= elem
.href
|| elem
.src
;