Added javascript add-on for unobtrusive apply handlers to html inputs
[fa-stable.git] / js / behaviour.js
1 /*
2    Behaviour v1.1 by Ben Nolan, June 2005. Based largely on the work
3    of Simon Willison (see comments by Simon below).
4
5    Description:
6         
7         Uses css selectors to apply javascript behaviours to enable
8         unobtrusive javascript in html documents.
9         
10    Usage:   
11    
12         var myrules = {
13                 'b.someclass' : function(element){
14                         element.onclick = function(){
15                                 alert(this.innerHTML);
16                         }
17                 },
18                 '#someid u' : function(element){
19                         element.onmouseover = function(){
20                                 this.innerHTML = "BLAH!";
21                         }
22                 }
23         };
24         
25         Behaviour.register(myrules);
26         
27         // Call Behaviour.apply() to re-apply the rules (if you
28         // update the dom, etc).
29
30    License:
31    
32         This file is entirely BSD licensed.
33         
34    More information:
35         
36         http://ripcord.co.nz/behaviour/
37    
38 */   
39
40 var Behaviour = {
41         list : new Array,
42         
43         register : function(sheet){
44                 Behaviour.list.push(sheet);
45         },
46         
47         start : function(){
48                 Behaviour.addLoadEvent(function(){
49                         Behaviour.apply();
50                 });
51         },
52         
53         apply : function(){
54                 for (h=0;sheet=Behaviour.list[h];h++){
55                         for (selector in sheet){
56                                 list = document.getElementsBySelector(selector);
57                                 
58                                 if (!list){
59                                         continue;
60                                 }
61
62                                 for (i=0;element=list[i];i++){
63                                         sheet[selector](element);
64                                 }
65                         }
66                 }
67         },
68         
69         addLoadEvent : function(func){
70                 var oldonload = window.onload;
71                 
72                 if (typeof window.onload != 'function') {
73                         window.onload = func;
74                 } else {
75                         window.onload = function() {
76                                 oldonload();
77                                 func();
78                         }
79                 }
80         }
81 }
82
83 Behaviour.start();
84
85 /*
86    The following code is Copyright (C) Simon Willison 2004.
87
88    document.getElementsBySelector(selector)
89    - returns an array of element objects from the current document
90      matching the CSS selector. Selectors can contain element names, 
91      class names and ids and can be nested. For example:
92      
93        elements = document.getElementsBySelect('div#main p a.external')
94      
95      Will return an array of all 'a' elements with 'external' in their 
96      class attribute that are contained inside 'p' elements that are 
97      contained inside the 'div' element which has id="main"
98
99    New in version 0.4: Support for CSS2 and CSS3 attribute selectors:
100    See http://www.w3.org/TR/css3-selectors/#attribute-selectors
101
102    Version 0.4 - Simon Willison, March 25th 2003
103    -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows
104    -- Opera 7 fails 
105 */
106
107 function getAllChildren(e) {
108   // Returns all children of element. Workaround required for IE5/Windows. Ugh.
109   return e.all ? e.all : e.getElementsByTagName('*');
110 }
111
112 document.getElementsBySelector = function(selector) {
113   // Attempt to fail gracefully in lesser browsers
114   if (!document.getElementsByTagName) {
115     return new Array();
116   }
117   // Split selector in to tokens
118   var tokens = selector.split(' ');
119   var currentContext = new Array(document);
120   for (var i = 0; i < tokens.length; i++) {
121     token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');;
122     if (token.indexOf('#') > -1) {
123       // Token is an ID selector
124       var bits = token.split('#');
125       var tagName = bits[0];
126       var id = bits[1];
127       var element = document.getElementById(id);
128       if (tagName && element.nodeName.toLowerCase() != tagName) {
129         // tag with that ID not found, return false
130         return new Array();
131       }
132       // Set currentContext to contain just this element
133       currentContext = new Array(element);
134       continue; // Skip to next token
135     }
136     if (token.indexOf('.') > -1) {
137       // Token contains a class selector
138       var bits = token.split('.');
139       var tagName = bits[0];
140       var className = bits[1];
141       if (!tagName) {
142         tagName = '*';
143       }
144       // Get elements matching tag, filter them for class selector
145       var found = new Array;
146       var foundCount = 0;
147       for (var h = 0; h < currentContext.length; h++) {
148         var elements;
149         if (tagName == '*') {
150             elements = getAllChildren(currentContext[h]);
151         } else {
152             elements = currentContext[h].getElementsByTagName(tagName);
153         }
154         for (var j = 0; j < elements.length; j++) {
155           found[foundCount++] = elements[j];
156         }
157       }
158       currentContext = new Array;
159       var currentContextIndex = 0;
160       for (var k = 0; k < found.length; k++) {
161         if (found[k].className && found[k].className.match(new RegExp('\\b'+className+'\\b'))) {
162           currentContext[currentContextIndex++] = found[k];
163         }
164       }
165       continue; // Skip to next token
166     }
167     // Code to deal with attribute selectors
168     if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) {
169       var tagName = RegExp.$1;
170       var attrName = RegExp.$2;
171       var attrOperator = RegExp.$3;
172       var attrValue = RegExp.$4;
173       if (!tagName) {
174         tagName = '*';
175       }
176       // Grab all of the tagName elements within current context
177       var found = new Array;
178       var foundCount = 0;
179       for (var h = 0; h < currentContext.length; h++) {
180         var elements;
181         if (tagName == '*') {
182             elements = getAllChildren(currentContext[h]);
183         } else {
184             elements = currentContext[h].getElementsByTagName(tagName);
185         }
186         for (var j = 0; j < elements.length; j++) {
187           found[foundCount++] = elements[j];
188         }
189       }
190       currentContext = new Array;
191       var currentContextIndex = 0;
192       var checkFunction; // This function will be used to filter the elements
193       switch (attrOperator) {
194         case '=': // Equality
195           checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); };
196           break;
197         case '~': // Match one of space seperated words 
198           checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); };
199           break;
200         case '|': // Match start with value followed by optional hyphen
201           checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); };
202           break;
203         case '^': // Match starts with value
204           checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); };
205           break;
206         case '$': // Match ends with value - fails with "Warning" in Opera 7
207           checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); };
208           break;
209         case '*': // Match ends with value
210           checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); };
211           break;
212         default :
213           // Just test for existence of attribute
214           checkFunction = function(e) { return e.getAttribute(attrName); };
215       }
216       currentContext = new Array;
217       var currentContextIndex = 0;
218       for (var k = 0; k < found.length; k++) {
219         if (checkFunction(found[k])) {
220           currentContext[currentContextIndex++] = found[k];
221         }
222       }
223       // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue);
224       continue; // Skip to next token
225     }
226     
227     if (!currentContext[0]){
228         return;
229     }
230     
231     // If we get here, token is JUST an element (not a class or ID selector)
232     tagName = token;
233     var found = new Array;
234     var foundCount = 0;
235     for (var h = 0; h < currentContext.length; h++) {
236       var elements = currentContext[h].getElementsByTagName(tagName);
237       for (var j = 0; j < elements.length; j++) {
238         found[foundCount++] = elements[j];
239       }
240     }
241     currentContext = found;
242   }
243   return currentContext;
244 }
245
246 /* That revolting regular expression explained 
247 /^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/
248   \---/  \---/\-------------/    \-------/
249     |      |         |               |
250     |      |         |           The value
251     |      |    ~,|,^,$,* or =
252     |   Attribute 
253    Tag
254 */