Compression fix
[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 /* Original reg expression  /^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/ 
169     was replaced by new RegExp() cuz compressor fault */
170     if (token.match(new RegExp('^(\\w*)\\[(\\w+)([=~\\|\\^\\$\\*]?)=?"?([^\\]"]*)"?\\]$'))) {
171       var tagName = RegExp.$1;
172       var attrName = RegExp.$2;
173       var attrOperator = RegExp.$3;
174       var attrValue = RegExp.$4;
175       if (!tagName) {
176         tagName = '*';
177       }
178       // Grab all of the tagName elements within current context
179       var found = new Array;
180       var foundCount = 0;
181       for (var h = 0; h < currentContext.length; h++) {
182         var elements;
183         if (tagName == '*') {
184             elements = getAllChildren(currentContext[h]);
185         } else {
186             elements = currentContext[h].getElementsByTagName(tagName);
187         }
188         for (var j = 0; j < elements.length; j++) {
189           found[foundCount++] = elements[j];
190         }
191       }
192       currentContext = new Array;
193       var currentContextIndex = 0;
194       var checkFunction; // This function will be used to filter the elements
195       switch (attrOperator) {
196         case '=': // Equality
197           checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); };
198           break;
199         case '~': // Match one of space seperated words 
200           checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); };
201           break;
202         case '|': // Match start with value followed by optional hyphen
203           checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); };
204           break;
205         case '^': // Match starts with value
206           checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); };
207           break;
208         case '$': // Match ends with value - fails with "Warning" in Opera 7
209           checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); };
210           break;
211         case '*': // Match ends with value
212           checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); };
213           break;
214         default :
215           // Just test for existence of attribute
216           checkFunction = function(e) { return e.getAttribute(attrName); };
217       }
218       currentContext = new Array;
219       var currentContextIndex = 0;
220       for (var k = 0; k < found.length; k++) {
221         if (checkFunction(found[k])) {
222           currentContext[currentContextIndex++] = found[k];
223         }
224       }
225       // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue);
226       continue; // Skip to next token
227     }
228     
229     if (!currentContext[0]){
230         return;
231     }
232     
233     // If we get here, token is JUST an element (not a class or ID selector)
234     tagName = token;
235     var found = new Array;
236     var foundCount = 0;
237     for (var h = 0; h < currentContext.length; h++) {
238       var elements = currentContext[h].getElementsByTagName(tagName);
239       for (var j = 0; j < elements.length; j++) {
240         found[foundCount++] = elements[j];
241       }
242     }
243     currentContext = found;
244   }
245   return currentContext;
246 }
247
248 /* That revolting regular expression explained 
249 /^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/
250   \---/  \---/\-------------/    \-------/
251     |      |         |               |
252     |      |         |           The value
253     |      |    ~,|,^,$,* or =
254     |   Attribute 
255    Tag
256 */