Updates to experimental color scheme a
[lhc/web/wiklou.git] / skins / vector / csshover.htc
1 <public:attach event="ondocumentready" onevent="CSSHover()" />
2 <script>
3 // <![CDATA[
4 /**
5 * Whatever:hover - V3.00.081222
6 * ------------------------------------------------------------
7 * Author - Peter Nederlof, http://www.xs4all.nl/~peterned
8 * License - http://creativecommons.org/licenses/LGPL/2.1
9 *
10 * Whatever:hover is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
14 *
15 * Whatever:hover is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
19 *
20 * howto: body { behavior:url("csshover3.htc"); }
21 * ------------------------------------------------------------
22 */
23
24 window.CSSHover = (function(){
25
26 // regular expressions, used and explained later on.
27 var REG_INTERACTIVE = /(^|\s)((([^a]([^ ]+)?)|(a([^#.][^ ]+)+)):(hover|active|focus))/i,
28 REG_AFFECTED = /(.*?)\:(hover|active|focus)/i,
29 REG_PSEUDO = /[^:]+:([a-z-]+).*/i,
30 REG_SELECT = /(\.([a-z0-9_-]+):[a-z]+)|(:[a-z]+)/gi,
31 REG_CLASS = /\.([a-z0-9_-]*on(hover|active|focus))/i,
32 REG_MSIE = /msie (5|6|7)/i,
33 REG_COMPAT = /backcompat/i;
34
35 // css prefix, a leading dash would be nice (spec), but IE6 doesn't like that.
36 var CSSHOVER_PREFIX = 'csh-';
37
38 /**
39 * Local CSSHover object
40 * --------------------------
41 */
42
43 var CSSHover = {
44
45 // array of CSSHoverElements, used to unload created events
46 elements: [],
47
48 // buffer used for checking on duplicate expressions
49 callbacks: {},
50
51 // init, called once ondomcontentready via the exposed window.CSSHover function
52 init:function() {
53 // don't run in IE8 standards; expressions don't work in standards mode anyway,
54 // and the stuff we're trying to fix should already work properly
55 if(!REG_MSIE.test(navigator.userAgent) && !REG_COMPAT.test(window.document.compatMode)) return;
56
57 // start parsing the existing stylesheets
58 var sheets = window.document.styleSheets, l = sheets.length;
59 for(var i=0; i<l; i++) {
60 this.parseStylesheet(sheets[i]);
61 }
62 },
63
64 // called from init, parses individual stylesheets
65 parseStylesheet:function(sheet) {
66 // check sheet imports and parse those recursively
67 if(sheet.imports) {
68 try {
69 var imports = sheet.imports, l = imports.length;
70 for(var i=0; i<l; i++) {
71 this.parseStylesheet(sheet.imports[i]);
72 }
73 } catch(securityException){
74 // trycatch for various possible errors,
75 // todo; might need to be placed inside the for loop, since an error
76 // on an import stops following imports from being processed.
77 }
78 }
79
80 // interate the sheet's rules and send them to the parser
81 try {
82 var rules = sheet.rules, l = rules.length;
83 for(var j=0; j<l; j++) {
84 this.parseCSSRule(rules[j], sheet);
85 }
86 } catch(securityException){
87 // trycatch for various errors, most likely accessing the sheet's rules,
88 // don't see how individual rules would throw errors, but you never know.
89 }
90 },
91
92 // magic starts here ...
93 parseCSSRule:function(rule, sheet) {
94
95 // The sheet is used to insert new rules into, this must be the same sheet the rule
96 // came from, to ensure that relative paths keep pointing to the right location.
97
98 // only parse a rule if it contains an interactive pseudo.
99 var select = rule.selectorText;
100 if(REG_INTERACTIVE.test(select)) {
101 var style = rule.style.cssText,
102
103 // affected elements are found by truncating the selector after the interactive pseudo,
104 // eg: "div li:hover" >> "div li"
105 affected = REG_AFFECTED.exec(select)[1],
106
107 // that pseudo is needed for a classname, and defines the type of interaction (focus, hover, active)
108 // eg: "li:hover" >> "onhover"
109 pseudo = select.replace(REG_PSEUDO, 'on$1'),
110
111 // the new selector is going to use that classname in a new css rule,
112 // since IE6 doesn't support multiple classnames, this is merged into one classname
113 // eg: "li:hover" >> "li.onhover", "li.folder:hover" >> "li.folderonhover"
114 newSelect = select.replace(REG_SELECT, '.$2' + pseudo),
115
116 // the classname is needed for the events that are going to be set on affected nodes
117 // eg: "li.folder:hover" >> "folderonhover"
118 className = REG_CLASS.exec(newSelect)[1];
119
120 // no need to set the same callback more than once when the same selector uses the same classname
121 var hash = affected + className;
122 if(!this.callbacks[hash]) {
123
124 // affected elements are given an expression under a fake css property, the classname is used
125 // because a unique name (eg "behavior:") would be overruled (in IE6, not 7) by a following rule
126 // selecting the same element. The expression does a callback to CSSHover.patch, rerouted via the
127 // exposed window.CSSHover function.
128
129 // because the expression is added to the stylesheet, and styles are always applied to html that is
130 // dynamically added to the dom, the expression will also trigger for those new elements (provided
131 // they are selected by the affected selector).
132
133 sheet.addRule(affected, CSSHOVER_PREFIX + className + ':expression(CSSHover(this, "'+pseudo+'", "'+className+'"))');
134
135 // hash it, so an identical selector/class combo does not duplicate the expression
136 this.callbacks[hash] = true;
137 }
138
139 // duplicate expressions need not be set, but the style could differ
140 sheet.addRule(newSelect, style);
141 }
142 },
143
144 // called via the expression, patches individual nodes
145 patch:function(node, type, className) {
146
147 // the patch's type is returned to the expression. That way the expression property
148 // can be found and removed, to stop it from calling patch over and over.
149 // The if will fail the first time, since the expression has not yet received a value.
150 var property = CSSHOVER_PREFIX + className;
151 if(node.style[property]) {
152 node.style[property] = null;
153 }
154
155 // just to make sure, also keep track of patched classnames locally on the node
156 if(!node.csshover) node.csshover = [];
157
158 // and check for it to prevent duplicate events with the same classname from being set
159 if(!node.csshover[className]) {
160 node.csshover[className] = true;
161
162 // create an instance for the given type and class
163 var element = new CSSHoverElement(node, type, className);
164
165 // and store that instance for unloading later on
166 this.elements.push(element);
167 }
168
169 // returns a dummy value to the expression
170 return type;
171 },
172
173 // unload stuff onbeforeunload
174 unload:function() {
175 try {
176
177 // remove events
178 var l = this.elements.length;
179 for(var i=0; i<l; i++) {
180 this.elements[i].unload();
181 }
182
183 // and set properties to null
184 this.elements = [];
185 this.callbacks = {};
186
187 } catch (e) {
188 }
189 }
190 };
191
192 // add the unload to the onbeforeunload event
193 window.attachEvent('onbeforeunload', function(){
194 CSSHover.unload();
195 });
196
197 /**
198 * CSSHoverElement
199 * --------------------------
200 */
201
202 // the event types associated with the interactive pseudos
203 var CSSEvents = {
204 onhover: { activator: 'onmouseenter', deactivator: 'onmouseleave' },
205 onactive: { activator: 'onmousedown', deactivator: 'onmouseup' },
206 onfocus: { activator: 'onfocus', deactivator: 'onblur' }
207 };
208
209 // CSSHoverElement constructor, called via CSSHover.patch
210 function CSSHoverElement(node, type, className) {
211
212 // the CSSHoverElement patches individual nodes by manually applying the events that should
213 // have fired by the css pseudoclasses, eg mouseenter and mouseleave for :hover.
214
215 this.node = node;
216 this.type = type;
217 var replacer = new RegExp('(^|\\s)'+className+'(\\s|$)', 'g');
218
219 // store event handlers for removal onunload
220 this.activator = function(){ node.className += ' ' + className; };
221 this.deactivator = function(){ node.className = node.className.replace(replacer, ' '); };
222
223 // add the events
224 node.attachEvent(CSSEvents[type].activator, this.activator);
225 node.attachEvent(CSSEvents[type].deactivator, this.deactivator);
226 }
227
228 CSSHoverElement.prototype = {
229 // onbeforeunload, called via CSSHover.unload
230 unload:function() {
231
232 // remove events
233 this.node.detachEvent(CSSEvents[this.type].activator, this.activator);
234 this.node.detachEvent(CSSEvents[this.type].deactivator, this.deactivator);
235
236 // and set properties to null
237 this.activator = null;
238 this.deactivator = null;
239 this.node = null;
240 this.type = null;
241 }
242 };
243
244 /**
245 * Public hook
246 * --------------------------
247 */
248
249 return function(node, type, className) {
250 if(node) {
251 // called via the css expression; patches individual nodes
252 return CSSHover.patch(node, type, className);
253 } else {
254 // called ondomcontentready via the public:attach node
255 CSSHover.init();
256 }
257 };
258
259 })();
260
261 // ]]>
262 </script>