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 | */ |
---|