var VbHint = (function(){
'use strict';
- var hConts = [], /* holds the hintcontainers of the different documents */
- hints = [], /* holds all hint data (hinted element, label, number) */
- ixdFocus = 0, /* index of current focused hint */
- cId = "_hintContainer", /* id of the conteiner holding the hint lables */
- lClass = "_hintLabel", /* class used on the hint labels with the hint numbers */
- hClass = "_hintElem", /* marks hinted elements */
- fClass = "_hintFocus", /* marks focused element and focued hint */
- config;
-
- function create(inputText) {
- clear();
+ var hints = [], /* holds all hint data (hinted element, label, number) in view port */
+ docs = [], /* hold the affected document with the start and end index of the hints */
+ validHints = [], /* holds the valid hinted elements matching the filter condition */
+ activeHint = 1, /* number of current focused hint in valid hints array */
+ filterText = "", /* holds the typed filter text */
+ filterNum = 0, /* holds the numeric filter */
+ cId = "_hintContainer", /* id of the conteiner holding the hint lables */
+ lClass = "_hintLabel", /* class used on the hint labels with the hint numbers */
+ hClass = "_hintElem", /* marks hinted elements */
+ fClass = "_hintFocus", /* marks focused element and focued hint */
+ config,
+ style = "." + lClass + "{" +
+ "-webkit-transform:translate(-4px,-4px);" +
+ "position:absolute;" +
+ "z-index:100000;" +
+ "font-family:monospace;" +
+ "font-weight:bold;" +
+ "font-size:10px;" +
+ "color:#000;" +
+ "background-color:#fff;" +
+ "margin:0;" +
+ "padding:0px 1px;" +
+ "border:1px solid #444;" +
+ "opacity:0.7" +
+ "}" +
+ "." + hClass + "{" +
+ "background-color:#ff0 !important;" +
+ "color:#000 !important" +
+ "}" +
+ "." + hClass + "." + fClass + "{" +
+ "background-color:#8f0 !important" +
+ "}" +
+ "." + lClass + "." + fClass + "{" +
+ "opacity:1" +
+ "}";
+
+ function clear() {
+ var i, j, hint, doc, e;
+ for (i = 0; i < docs.length; i++) {
+ doc = docs[i];
+ /* find all hinted elements vimbhint 'hint' */
+ var res = xpath(doc.doc, "//*[contains(@vimbhint, 'hint')]");
+ for (j = 0; j < res.snapshotLength; j++) {
+ e = res.snapshotItem(j);
+ e.removeAttribute("vimbhint");
+ e.classList.remove(fClass);
+ e.classList.remove(hClass);
+ }
+ doc.div.parentNode.removeChild(doc.div);
+ }
+ docs = [];
+ hints = [];
+ validHints = [];
+ filterText = "";
+ filterNum = 0;
+ activeHint = 1;
+ }
+ function create() {
var count = 0;
function helper(win, offsets) {
return s.display !== "none" && s.visibility == "visible";
}
- var doc = win.document,
- xpath = getXpath(inputText),
- res = doc.evaluate(
- xpath, doc,
- function (p) {return "http://www.w3.org/1999/xhtml";},
- XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
- ),
+ var doc = win.document,
+ res = xpath(doc, getXpath()),
/* generate basic hint element which will be cloned and updated later */
labelTmpl = doc.createElement("span"),
e, i;
labelTmpl.className = lClass;
+ labelTmpl.setAttribute("vimbhint", "label");
var containerOffsets = getOffsets(doc),
offsetX = containerOffsets[0],
offsetY = containerOffsets[1],
fragment = doc.createDocumentFragment(),
- rect, label, text;
+ rect, label, text, showText, start = hints.length;
/* collect all visible elements in hints array */
for (i = 0; i < res.snapshotLength; i++) {
label = labelTmpl.cloneNode(false);
label.style.left = Math.max((rect.left + offsetX), offsetX) + "px";
label.style.top = Math.max((rect.top + offsetY), offsetY) + "px";
- label.innerText = count;
+ label.style.display = "none";
/* if hinted element is an image - show title or alt of the image in hint label */
/* this allows to see how to filter for the image */
text = "";
+ showText = false;
if (e instanceof HTMLImageElement) {
- text = e.alt || e.title;
+ text = e.title || e.alt;
+ showText = true;
} else if (e.firstElementChild instanceof HTMLImageElement && /^\s*$/.test(e.textContent)) {
- text = e.firstElementChild.title || e.firstElementChild.alt;
- }
- if (text) {
- label.innerText += ": " + text.substr(0, 20);
+ text = e.firstElementChild.title || e.firstElementChild.alt;
+ showText = true;
+ } else if (e instanceof HTMLInputElement) {
+ var type = e.type;
+ if (type === "image") {
+ text = e.alt || "";
+ } else if (e.value && type !== "password") {
+ text = e.value;
+ showText = (type === "radio" || type === "checkbox");
+ }
+ } else if (e instanceof HTMLSelectElement) {
+ if (e.selectedIndex >= 0) {
+ text = e.item(e.selectedIndex).text;
+ }
+ } else {
+ text = e.textContent;
}
-
/* add the hint class to the hinted element */
- e.classList.add(hClass);
fragment.appendChild(label);
+ e.setAttribute("vimbhint", "hint");
hints.push({
- e: e,
- num: count,
- label: label
+ e: e,
+ label: label,
+ text: text,
+ showText: showText
});
if (count >= config.maxHints) {
var hDiv = doc.createElement("div");
hDiv.id = cId;
hDiv.appendChild(fragment);
- doc.documentElement.appendChild(hDiv);
+ hDiv.setAttribute("vimbhint", "container");
+ if (doc.body) {
+ doc.body.appendChild(hDiv);
+ }
/* create the default style sheet */
createStyle(doc);
- hConts.push(hDiv);
+ docs.push({
+ doc: doc,
+ start: start,
+ end: hints.length - 1,
+ div: hDiv
+ });
/* recurse into any iframe or frame element */
for (var f in win.frames) {
}
helper(window);
+ }
+
+ function show() {
+ var i, hint, doc, num = 1, activeHint = filterNum || 1,
+ matcher = getMatcher(filterText);
+ /* clear the array of valid hints */
+ validHints = [];
+ for (i = 0; i < hints.length; i++) {
+ hint = hints[i];
+ /* collect only hints matching the filter text */
+ if (matcher(hint.text)) {
+ /* assigne the new hint number to the hint */
+ hint.num = num++;
+ setFocus(hint, activeHint === hint.num);
+ /* check for number filter */
+ if (!filterNum || 0 === hint.num.toString().indexOf(filterNum.toString())) {
+ hint.label.style.display = "";
+ hint.e.classList.add(hClass);
+
+ /* create the label with the hint number */
+ hint.label.innerText = hint.num;
+ if (hint.showText && hint.text) {
+ /* use \x20 instead of ' ' to keep this space during */
+ /* js2h.sh processing */
+ hint.label.innerText += ":\x20" + hint.text.substr(0, 20);
+ }
+ validHints.push(hint);
+ continue;
+ }
+ }
+ /* remove hint labels from no more visible hints */
+ hint.label.style.display = "none";
+ hint.label.classList.remove(fClass);
+ hint.e.classList.remove(fClass);
+ hint.e.classList.remove(hClass);
+ }
- if (count <= 1) {
- return fire(0);
+ if (validHints.length === 1) {
+ return fire();
}
- return focusHint(0);
+ return focusHint(1, activeHint);
+ }
+
+ /* Retruns a vlidator method to check if the hint elemens text matches */
+ /* the given filter text. */
+ function getMatcher(text) {
+ var tokens = text.toLowerCase().split(/\s+/);
+ return function (itemText) {
+ itemText = itemText.toLowerCase();
+ return tokens.every(function (token) {
+ return 0 <= itemText.indexOf(token);
+ });
+ };
}
function getOffsets(doc) {
return;
}
var e = doc.createElement("style");
- e.innerHTML += "." + lClass + "{" +
- "-webkit-transform:translate(-4px,-4px);" +
- "position:absolute;" +
- "z-index:100000;" +
- "font-family:monospace;" +
- "font-weight:bold;" +
- "font-size:10px;" +
- "color:#000;" +
- "background-color:#fff;" +
- "margin:0;" +
- "padding:0px 1px;" +
- "border:1px solid #444;" +
- "opacity:0.7" +
- "}" +
- "." + hClass + "{" +
- "background-color:#ff0 !important;" +
- "color:#000 !important" +
- "}" +
- "." + hClass + "." + fClass + "{" +
- "background-color:#8f0 !important" +
- "}" +
- "." + lClass + "." + fClass + "{" +
- "opacity:1" +
- "}";
-
+ e.innerHTML = style;
doc.head.appendChild(e);
/* prevent us from adding the style multiple times */
doc.hasStyle = true;
}
function focus(back) {
- var n, i = ixdFocus;
+ var old = activeHint;
if (back) {
- n = (i >= 1) ? i - 1 : hints.length - 1;
- } else {
- n = (i + 1 < hints.length) ? i + 1 : 0;
- }
- return focusHint(n);
- }
-
- function update(n) {
- var remove = [], idx, r, i, hint;
- if (n === 0) {
- return create();
- }
- /* remove none matching hints */
- for (i = 0; i < hints.length; ++i) {
- hint = hints[i];
- /* collect the hints to be removed */
- if (0 !== hint.num.toString().indexOf(n.toString())) {
- remove.push(hint);
- }
- }
-
- /* now remove the hints */
- for (i = 0; i < remove.length; ++i) {
- r = remove[i];
- r.e.classList.remove(fClass);
- r.e.classList.remove(hClass);
- r.label.parentNode.removeChild(r.label);
-
- /* remove hints from all hints */
- if ((idx = hints.indexOf(r)) !== -1) {
- hints.splice(idx, 1);
+ if (--activeHint < 1) {
+ activeHint = validHints.length;
}
- }
-
-
- if (hints.length === 1) {
- return fire(0);
- }
- return focusHint(0);
- }
-
- function clear() {
- var i, hint;
- if (hints.length === 0) {
- return;
- }
- for (i = 0; i < hints.length; ++i) {
- hint = hints[i];
- if (hint.e) {
- hint.e.classList.remove(fClass);
- hint.e.classList.remove(hClass);
- hint.label.parentNode.removeChild(hint.label);
+ } else {
+ if (++activeHint > validHints.length) {
+ activeHint = 1;
}
}
- hints = [];
- for (i = 0; i < hConts.length; ++i) {
- hConts[i].parentNode.removeChild(hConts[i]);
- }
- hConts = [];
+ return focusHint(activeHint, old);
}
- function fire(i) {
- var hint = getHint(i || ixdFocus);
+ function fire(num) {
+ var hint = validHints[(num || activeHint) - 1];
if (!hint) {
return "DONE:";
}
}
/* set focus on hint with given number */
- function focusHint(i) {
+ function focusHint(newNum, oldNum) {
/* reset previous focused hint */
var hint;
- if ((hint = getHint(ixdFocus))) {
- hint.e.classList.remove(fClass);
- hint.label.classList.remove(fClass);
+ if ((hint = validHints[oldNum - 1])) {
+ setFocus(hint, false);
mouseEvent(hint.e, "mouseout");
}
/* mark new hint as focused */
- ixdFocus = i;
- if ((hint = getHint(i))) {
- hint.e.classList.add(fClass);
- hint.label.classList.add(fClass);
+ activeHint = newNum;
+ if ((hint = validHints[newNum - 1])) {
+ setFocus(hint, true);
mouseEvent(hint.e, "mouseover");
}
}
- /* retrieves the hint for given hint number */
- function getHint(i) {
- return hints[i] || null;
+ /* Toggles the focus highlight of a hint */
+ function setFocus(hint, active) {
+ if (active) {
+ /* TODO place and remove the active value independent from 'hint' */
+ hint.e.setAttribute("vimbhint", "hint active");
+ hint.e.classList.add(fClass);
+ hint.label.setAttribute("vimbhint", "label active");
+ hint.label.classList.add(fClass);
+ } else {
+ hint.e.setAttribute("vimbhint", "hint");
+ hint.e.classList.remove(fClass);
+ hint.label.setAttribute("vimbhint", "label");
+ hint.label.classList.remove(fClass);
+ }
}
function click(e) {
+ /* for sites that are interested in mouseover before click */
mouseEvent(e, "mouseover");
+ /* this is the w3.org definition for DOM-Level 2 events */
mouseEvent(e, "mousedown");
mouseEvent(e, "mouseup");
mouseEvent(e, "click");
}
/* retrieves the xpath expression according to mode */
- function getXpath(s) {
- if (s === undefined) {
- s = "";
- }
- /* replace $WHAT in xpath to contains(translate(WHAT, 'SEARCH', 'search'), 'search') */
- function buildQuery(what, x, s) {
- var l, i, parts;
- l = s.toLowerCase();
- parts = [
- "contains(translate(",
- ",'"+s.toUpperCase()+"','"+l+"'),'"+l+"')"
- ];
- for (i = 0; i < what.length; ++i) {
- x = x.split("$" + what[i]).join(parts.join(what[i]));
- }
- return x;
- }
-
+ function getXpath() {
switch (config.mode) {
case "l":
- if (!s) {
- return "//*[@href] | //*[@onclick or @tabindex or @class='lk' or @role='link' or @role='button'] | //input[not(@type='hidden' or @disabled or @readonly)] | //textarea[not(@disabled or @readonly)] | //button | //select";
- }
- return buildQuery(
- ["@value", ".", "@placeholder", "@title", "@alt"],
- "//*[@href and ($. or child::img[$@title or $@alt])] | //*[(@onclick or @class='lk' or @role='link' or role='button') and $.] | //input[not(@type='hidden' or @disabled or @readonly) and ($@value or $@placeholder)] | //textarea[not(@disabled or @readonly) and $.] | //button[$.] | //select[$.]",
- s
- );
+ return "//*[@href] | //*[@onclick or @tabindex or @class='lk' or @role='link' or @role='button'] | //input[not(@type='hidden' or @disabled or @readonly)] | //textarea[not(@disabled or @readonly)] | //button | //select";
case "e":
- if (!s) {
- return "//input[not(@type) or @type='text'] | //textarea";
- }
- return buildQuery(
- ["@value", ".", "@placeholder"],
- "//input[(not(@type) or @type='text') and ($@value or $@placeholder)] | //textarea[$.]",
- s
- );
+ return "//input[not(@type) or @type='text'] | //textarea";
case "i":
- if (!s) {
- return "//img[@src]";
- }
- return buildQuery(["@title", "@alt"], "//img[@src and ($@title or $@alt)]", s);
+ return "//img[@src]";
}
}
+ function xpath(doc, expr) {
+ return doc.evaluate(
+ expr, doc, function (p) {return "http://www.w3.org/1999/xhtml";},
+ XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null
+ );
+ }
+
/* follow the count last link on pagematching the given pattern */
function followLink(rel, pattern, count) {
/* returns array of matching elements */
config.mode = map[prefix][0];
config.usage = map[prefix][1];
}
+ create();
+ return show();
+ },
+ filter: function filter(text) {
+ filterText = text || "";
+ return show();
+ },
+ update: function update(n) {
+ filterNum = n;
+ return show();
},
- create: create,
- update: update,
clear: clear,
fire: fire,
focus: focus,