the hint styles are moved into ./examples/data/style.css
[uzbl-mobile] / examples / scripts / linkfollow.js
1 // link follower for uzbl
2 // requires http://github.com/DuClare/uzbl/commit/6c11777067bdb8aac09bba78d54caea04f85e059
3 //
4 // first, it needs to be loaded before every time it is used.
5 // One way would be to use something like load_start_handler to send
6 // "act script /usr/share/examples/scripts/linkfollow.js"
7 // (currently, it is recommended to use load_finish_handler since the JS code seems to get
8 // flushed. Using a load_start_handler with a 1s delay works but not always)
9 //
10 // when script is loaded, it can be invoked with
11 // bind f* = js hints.set("%s")
12 // bind f_ = js hints.follow("%s")
13 //
14 // At the moment, it may be useful to have way of forcing uzbl to load the script
15 // bind :lf = script /usr/share/examples/scripts/linkfollow.js
16 //
17 // To enable hint highlighting, add:
18 // set stylesheet_uri = /usr/share/uzbl/examples/data/style.css
19 //
20 // based on follow_Numbers.js
21 //
22 // TODO: set CSS styles (done, but not working properly)
23 // TODO: load the script as soon as the DOM is ready
24
25
26
27 function Hints(){
28         var uzblid = 'uzbl_hint';
29         var uzblclass = 'uzbl_highlight';
30         var uzblclassfirst = 'uzbl_h_first';
31         var doc = document;
32         this.set = setHints;
33         this.follow = followHint;
34         this.keyPressHandler = keyPressHandler;
35
36         function hasClass(ele,cls) {
37                         return ele.className.split(/ /).some(function (n) { return n == cls });
38         }
39          
40         function addClass(ele,cls) {
41                         if (!hasClass(ele,cls)) ele.className += " "+cls;
42         }
43          
44         function removeClass(ele,cls) {
45                 ele.className = ele.className.split(/ /).filter(function (n) { n != cls}).join(" ");
46         }
47
48         function elementPosition(el) {
49                         var up = el.offsetTop;
50                         var left = el.offsetLeft; var width = el.offsetWidth;
51                         var height = el.offsetHeight;
52
53                         while (el.offsetParent) {
54                                         el = el.offsetParent;
55                                         up += el.offsetTop;
56                                         left += el.offsetLeft;
57                         }
58                         return [up, left, width, height];
59         }
60
61         function generateHint(pos, label) {
62                         var hint = doc.createElement('div');
63                         hint.setAttribute('name', uzblid);
64                         hint.innerText = label;
65       //the css is set with ./examples/data/style.css
66                         hint.style.left = pos[1] + 'px';
67                         hint.style.top = pos[0] + 'px';
68                         //var img = el.getElementsByTagName('img');
69                         //if (img.length > 0) {
70                         //    hint.style.left = pos[1] + img[0].width / 2 + 'px';
71                         //}
72                         return hint;
73         }
74
75         function elementInViewport(offset) {
76                         var up = offset[0];
77                         var left = offset[1];
78                         var width = offset[2];
79                         var height = offset[3];
80                         return (up < window.pageYOffset + window.innerHeight && 
81                                         left < window.pageXOffset + window.innerWidth 
82                                         && (up + height) > window.pageYOffset 
83                                         && (left + width) > window.pageXOffset);
84         }
85
86         function isVisible(el) {
87                                 if (el == doc) { return true; }
88                                 if (!el) { return false; }
89                                 if (!el.parentNode) { return false; }
90                                 if (el.style) {
91                                                 if (el.style.display == 'none') {
92                                                                 return false;
93                                                 }
94                                                 if (el.style.visibility == 'hidden') {
95                                                                 return false;
96                                                 }
97                                 }
98                                 return isVisible(el.parentNode);
99         }
100
101         var hintable = "//a[@href] | //img | //input";
102
103         function Matcher(str){
104                 var numbers = str.replace(/[^\d]/g,"");
105                 var words = str.replace(/\d/g,"").split(/\s+/).map(function (n) { return new RegExp(n,"i")});
106                 this.test = test;
107                 this.toString = toString;
108                 this.numbers = numbers;
109                 function test(element) {
110                         // test all the regexp
111                         return words.every(function (regex) { return element.textContent.match(regex)});
112                 }
113                 function toString(){
114                         return "{"+numbers+"},{"+words+"}";
115                 }
116         }
117
118
119         function setHints(r){
120                 if(doc.body) doc.body.onkeyup = this.keyPressHandler;
121                 var re = new Matcher(r);
122                 clearHints();
123                 var hintdiv = doc.createElement('div');
124                 hintdiv.setAttribute('id', uzblid);
125                 var c = 1;
126                 var items = doc.evaluate(hintable,doc,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);
127                 for (var i = 0; i < items.snapshotLength;i++){
128                         var item = items.snapshotItem(i);
129                         var pos = elementPosition(item);
130                         if(re.test(item) && isVisible(item) && elementInViewport(pos)){
131                                 var h = generateHint(pos,c);
132                                 h.href = function () {return item};
133                                 hintdiv.appendChild(h);
134                                 if(c==1){
135                                         addClass(item,uzblclassfirst);
136                 //                      addClass(item,uzblclass);
137                                 } else {
138                                         addClass(item,uzblclass);
139                                 }
140                                 c++;
141                         }
142                 }
143                 if (document.body) {
144                                 document.body.insertBefore(hintdiv,document.body.firstChild);
145                 }
146         }
147
148         function clearHints(){
149                 var hintdiv = doc.getElementById(uzblid);
150                 if(hintdiv){
151                         hintdiv.parentNode.removeChild(hintdiv);
152                 }
153                 var first = doc.getElementsByClassName(uzblclassfirst)[0];
154                 if(first){
155                         removeClass(first,uzblclassfirst);
156                 }
157                 // TODO: not all class attributes get removed
158                 var items = doc.getElementsByClassName(uzblclass);
159                 for (var i = 0; i<items.length; i++){
160                         removeClass(items[i],uzblclass);
161                 };
162                 
163         }
164
165         function keyPressHandler(e) {
166                         var kC = window.event ? event.keyCode: e.keyCode;
167                         var Esc = window.event ? 27 : e.DOM_VK_ESCAPE;
168                         if (kC == Esc) {
169                                         clearHints();
170                                         doc.body.removeAttribute("onkeyup");
171                         }
172         }
173         function followHint(follow){
174                 var m = new Matcher(follow);
175                 var elements = doc.getElementsByClassName(uzblclass);
176                 // filter
177                 var matched = [];
178                 matched.push(doc.getElementsByClassName(uzblclassfirst)[0]);
179                 for (var i = 0; i < elements.length;i++){
180                         if(m.test(elements[i])){
181                                 matched.push(elements[i]);
182                         }
183                 }
184                 clearHints();
185                 var n = parseInt(m.numbers,10);
186                 if(n){
187                         var item = matched[n-1];
188                 } else {
189                         var item = matched[0];
190                 }
191                 if (item) {
192                         item.style.borderStyle = "dotted";
193                         item.style.borderWidth = "thin";
194
195       var name = item.tagName;
196       if (name == 'A') {
197           if(item.click) {item.click()};
198           window.location = item.href;
199       } else if (name == 'INPUT') {
200           var type = item.getAttribute('type').toUpperCase();
201           if (type == 'TEXT' || type == 'FILE' || type == 'PASSWORD') {
202               item.focus();
203               item.select();
204           } else {
205               item.click();
206           }
207       } else if (name == 'TEXTAREA' || name == 'SELECT') {
208           item.focus();
209           item.select();
210       } else {
211           item.click();
212           window.location = item.href;
213       }
214                 }
215         }
216 }
217 var hints;
218 //document.addEventListener("DOMContentLoaded",function () { hints = new Hints()},false);
219
220 var hints = new Hints();
221
222 // vim:set et tw=2:
223
224