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 xpath_expr
= _getXpathXpression(inputText
);
60 var res
= doc
.evaluate(xpath_expr
, doc
,
62 return "http://www.w3.org/1999/xhtml";
63 }, XPathResult
.ORDERED_NODE_SNAPSHOT_TYPE
, null);
65 /* generate basic hint element which will be cloned and updated later */
66 var hintSpan
= doc
.createElement("span");
67 hintSpan
.setAttribute("class", config
.hintClass
);
68 hintSpan
.style
.cssText
= config
.hintCss
;
70 /* due to the different XPath result type, we will need two counter variables */
71 var rect
, elem
, text
, node
, show_text
;
72 for (var i
= 0; i
< res
.snapshotLength
; i
++)
74 if (hintCount
>= config
.maxAllowedHints
)
77 elem
= res
.snapshotItem(i
);
78 rect
= elem
.getBoundingClientRect();
79 if (!rect
|| rect
.left
> maxX
|| rect
.right
< minX
|| rect
.top
> maxY
|| rect
.bottom
< minY
)
82 var style
= topwin
.getComputedStyle(elem
, "");
83 if (style
.display
== "none" || style
.visibility
!= "visible")
86 var leftpos
= Math
.max((rect
.left
+ scrollX
), scrollX
);
87 var toppos
= Math
.max((rect
.top
+ scrollY
), scrollY
);
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
);
103 background
: elem
.style
.background
,
104 foreground
: elem
.style
.color
}
107 /* make the link black to ensure it's readable */
108 elem
.style
.color
= config
.elemColor
;
109 elem
.style
.background
= config
.elemBackground
;
112 doc
.documentElement
.appendChild(hintContainer
);
114 /* recurse into any iframe or frame element */
115 var frameTags
= ["frame","iframe"];
116 for (var f
= 0; f
< frameTags
.length
; ++f
) {
117 var frames
= doc
.getElementsByTagName(frameTags
[f
]);
118 for (var i
= 0, nframes
= frames
.length
; i
< nframes
; ++i
) {
120 rect
= elem
.getBoundingClientRect();
121 if (!elem
.contentWindow
|| !rect
|| rect
.left
> maxX
|| rect
.right
< minX
|| rect
.top
> maxY
|| rect
.bottom
< minY
)
123 helper(elem
.contentWindow
, offsetX
+ rect
.left
, offsetY
+ rect
.top
);
128 helper(topwin
, 0, 0);
132 if (hintCount
== 1) {
133 /* just one hinted element - might as well follow it */
138 /* set focus on hint with given number */
139 this.focusHint = function(n
)
141 /* reset previous focused hint */
142 var hint
= _getHintByNumber(currentFocusNum
);
144 hint
.elem
.className
= hint
.elem
.className
.replace(config
.hintClassFocus
, config
.hintClass
);
145 hint
.elem
.style
.background
= config
.elemBackground
;
150 /* mark new hint as focused */
151 var hint
= _getHintByNumber(currentFocusNum
);
153 hint
.elem
.className
= hint
.elem
.className
.replace(config
.hintClass
, config
.hintClassFocus
);
154 hint
.elem
.style
.background
= config
.elemBackgroundFocus
;
158 /* set focus to next avaiable hint */
159 this.focusNextHint = function()
161 var index
= _getHintIdByNumber(currentFocusNum
);
163 if (typeof(hints
[index
+ 1]) != "undefined") {
164 this.focusHint(hints
[index
+ 1].number
);
166 this.focusHint(hints
[0].number
);
170 /* set focus to previous avaiable hint */
171 this.focusPreviousHint = function()
173 var index
= _getHintIdByNumber(currentFocusNum
);
174 if (index
!= 0 && typeof(hints
[index
- 1].number
) != "undefined") {
175 this.focusHint(hints
[index
- 1].number
);
177 this.focusHint(hints
[hints
.length
- 1].number
);
181 /* filters hints matching given number */
182 this.updateHints = function(n
)
185 return this.createHints();
187 /* remove none matching hints */
189 for (var i
= 0; i
< hints
.length
; ++i
) {
191 if (0 != hint
.number
.toString().indexOf(n
.toString())) {
192 remove
.push(hint
.number
);
196 for (var i
= 0; i
< remove
.length
; ++i
) {
197 _removeHint(remove
[i
]);
200 if (hints
.length
=== 1) {
201 return this.fire(hints
[0].number
);
203 return this.focusHint(n
);
207 this.clearFocus = function()
209 if (document
.activeElement
&& document
.activeElement
.blur
) {
210 document
.activeElement
.blur();
214 /* remove all hints and set previous style to them */
215 this.clearHints = function()
217 if (hints
.length
== 0) {
220 for (var i
= 0; i
< hints
.length
; ++i
) {
222 if (typeof(hint
.elem
) != "undefined") {
223 hint
.elem
.style
.background
= hint
.background
;
224 hint
.elem
.style
.color
= hint
.foreground
;
225 hint
.span
.parentNode
.removeChild(hint
.span
);
229 hintContainer
.parentNode
.removeChild(hintContainer
);
230 window
.onkeyup
= null;
233 /* fires the modeevent on hint with given number */
234 this.fire = function(n
)
238 var n
= currentFocusNum
;
240 var hint
= _getHintByNumber(n
);
241 if (typeof(hint
.elem
) == "undefined")
245 var tag
= el
.nodeName
.toLowerCase();
249 if (tag
== "iframe" || tag
== "frame" || tag
== "textarea" || tag
== "input" && (el
.type
== "text" || el
.type
== "password" || el
.type
== "checkbox" || el
.type
== "radio") || tag
== "select") {
251 if (tag
== "input" || tag
== "textarea") {
259 case "f": result
= _open(el
); break;
260 case "F": result
= _openNewWindow(el
); break;
261 case "s": result
= "save;" + _getElemtSource(el
); break;
262 case "y": result
= "yank;" + _getElemtSource(el
); break;
263 case "O": "colon;" + _getElemtSource(el
); break;
264 default: result
= _getElemtSource(el
);
270 this.focusInput = function()
272 if (document
.getElementsByTagName("body")[0] === null || typeof(document
.getElementsByTagName("body")[0]) != "object")
275 /* prefixing html: will result in namespace error */
276 var hinttags
= "//input[@type='text'] | //input[@type='password'] | //textarea";
277 var r
= document
.evaluate(hinttags
, document
,
279 return "http://www.w3.org/1999/xhtml";
280 }, XPathResult
.ORDERED_NODE_SNAPSHOT_TYPE
, null);
285 for (i
= 0; i
< r
.snapshotLength
; i
++) {
286 var elem
= r
.snapshotItem(i
);
288 if (elem
.style
.display
!= "none" && elem
.style
.visibility
!= "hidden") {
294 if (j
== 1 && elem
.style
.display
!= "none" && elem
.style
.visibility
!= "hidden") {
296 var tag
= elem
.nodeName
.toLowerCase();
297 if (tag
== "textarea" || tag
== "input") {
302 if (elem
== document
.activeElement
) {
307 /* no appropriate field found focused - focus the first one */
308 if (j
== 0 && first
!== null) {
310 var tag
= elem
.nodeName
.toLowerCase();
311 if (tag
== "textarea" || tag
== "input") {
317 /* retrieves text content fro given element */
318 function _getTextFromElement(el
)
320 if (el
instanceof HTMLInputElement
|| el
instanceof HTMLTextAreaElement
) {
322 } else if (el
instanceof HTMLSelectElement
) {
323 if (el
.selectedIndex
>= 0) {
324 text
= el
.item(el
.selectedIndex
).text
;
329 text
= el
.textContent
;
331 return text
.toLowerCase();;
334 /* retrieves the hint for given hint number */
335 function _getHintByNumber(n
)
337 var index
= _getHintIdByNumber(n
);
338 if (index
!== null) {
344 /* retrieves the id of hint with given number */
345 function _getHintIdByNumber(n
)
347 for (var i
= 0; i
< hints
.length
; ++i
) {
349 if (hint
.number
=== n
) {
356 /* removes hint with given number from hints array */
357 function _removeHint(n
)
359 var index
= _getHintIdByNumber(n
);
360 if (index
=== null) {
363 var hint
= hints
[index
];
364 if (hint
.number
=== n
) {
365 hint
.elem
.style
.background
= hint
.background
;
366 hint
.elem
.style
.color
= hint
.foreground
;
367 hint
.span
.parentNode
.removeChild(hint
.span
);
369 /* remove hints from all hints */
370 hints
.splice(index
, 1);
374 /* opens given element */
377 if (elem
.target
== "_blank") {
378 elem
.removeAttribute("target");
384 /* opens given element into new window */
385 function _openNewWindow(elem
)
387 var oldTarget
= elem
.target
;
389 /* set target to open in new window */
390 elem
.target
= "_blank";
392 elem
.target
= oldTarget
;
397 /* fire moudedown and click event on given element */
398 function _clickElement(elem
)
400 doc
= elem
.ownerDocument
;
401 view
= elem
.contentWindow
;
403 var evObj
= doc
.createEvent("MouseEvents");
404 evObj
.initMouseEvent("mousedown", true, true, view
, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
405 elem
.dispatchEvent(evObj
);
407 var evObj
= doc
.createEvent("MouseEvents");
408 evObj
.initMouseEvent("click", true, true, view
, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, null);
409 elem
.dispatchEvent(evObj
);
412 /* retrieves the url of given element */
413 function _getElemtSource(elem
)
415 var url
= elem
.href
|| elem
.src
;
419 /* retrieves the xpath expression according to mode */
420 function _getXpathXpression(text
)
423 if (typeof(text
) == "undefined") {
426 if (mode
== "f" || mode
== "F") {
428 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";
430 expr
= "//*[(@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @role='link' or @href) and contains(., '" + text
+ "')] | //input[not(@type='hidden') and contains(., '" + text
+ "')] | //a[@href and contains(., '" + text
+ "')] | //area[contains(., '" + text
+ "')] | //textarea[contains(., '" + text
+ "')] | //button[contains(@value, '" + text
+ "')] | //select[contains(., '" + text
+ "')]";
434 expr
= "//*[@role='link' or @href] | //a[href] | //area | //img[not(ancestor::a)]";
436 expr
= "//*[(@role='link' or @href) and contains(., '" + text
+ "')] | //a[@href and contains(., '" + text
+ "')] | //area[contains(., '" + text
+ "')] | //img[not(ancestor::a) and contains(., '" + text
+ "')]";