1 <public:attach event="ondocumentready" onevent="CSSHover()" />
4 * Whatever:hover - V3.11
5 * ------------------------------------------------------------
6 * Author - Peter Nederlof, http://www.xs4all.nl/~peterned
7 * License - http://creativecommons.org/licenses/LGPL/2.1
9 * Special thanks to Sergiu Dumitriu, http://purl.org/net/sergiu,
10 * for fixing the expression loop.
12 * Whatever:hover is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public
14 * License as published by the Free Software Foundation; either
15 * version 2.1 of the License, or (at your option) any later version.
17 * Whatever:hover is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * Lesser General Public License for more details.
22 * howto: body { behavior:url("csshover3.htc"); }
23 * ------------------------------------------------------------
26 window.CSSHover = (function(){
28 // regular expressions, used and explained later on.
29 var REG_INTERACTIVE = /(^|\s)((([^a]([^ ]+)?)|(a([^#.][^ ]+)+)):(hover|active|focus))/i;
30 var REG_AFFECTED = /(.*?)\:(hover|active|focus)/i;
31 var REG_PSEUDO = /[^:]+:([a-z\-]+).*/i;
32 var REG_SELECT = /(\.([a-z0-9_\-]+):[a-z]+)|(:[a-z]+)/gi;
33 var REG_CLASS = /\.([a-z0-9_\-]*on(hover|active|focus))/i;
34 var REG_MSIE = /msie (5|6|7)/i;
35 var REG_COMPAT = /backcompat/i;
37 // property mapping, real css properties must be used in order to clear expressions later on...
38 // Uses obscure css properties that no-one is likely to use. The properties are borrowed to
39 // set an expression, and are then restored to the most likely correct value.
42 list: ['text-kashida', 'text-kashida-space', 'text-justify'],
44 return this.list[(this.index++)%this.list.length];
48 // camelize is used to convert css properties from (eg) text-kashida to textKashida
49 var camelize = function(str) {
50 return str.replace(/-(.)/mg, function(result, match){
51 return match.toUpperCase();
56 * Local CSSHover object
57 * --------------------------
62 // array of CSSHoverElements, used to unload created events
65 // buffer used for checking on duplicate expressions
68 // init, called once ondomcontentready via the exposed window.CSSHover function
70 // don't run in IE8 standards; expressions don't work in standards mode anyway,
71 // and the stuff we're trying to fix should already work properly
72 if(!REG_MSIE.test(navigator.userAgent) && !REG_COMPAT.test(window.document.compatMode)) {
76 // start parsing the existing stylesheets
77 var sheets = window.document.styleSheets, l = sheets.length;
78 for(var i=0; i<l; i++) {
79 this.parseStylesheet(sheets[i]);
83 // called from init, parses individual stylesheets
84 parseStylesheet:function(sheet) {
85 // check sheet imports and parse those recursively
88 var imports = sheet.imports;
89 var l = imports.length;
90 for(var i=0; i<l; i++) {
91 this.parseStylesheet(sheet.imports[i]);
93 } catch(securityException){
94 // trycatch for various possible errors
98 // interate the sheet's rules and send them to the parser
100 var rules = sheet.rules;
101 var r = rules.length;
102 for(var j=0; j<r; j++) {
103 this.parseCSSRule(rules[j], sheet);
105 } catch(someException){
106 // trycatch for various errors, most likely accessing the sheet's rules.
110 // magic starts here ...
111 parseCSSRule:function(rule, sheet) {
113 // The sheet is used to insert new rules into, this must be the same sheet the rule
114 // came from, to ensure that relative paths keep pointing to the right location.
116 // only parse a rule if it contains an interactive pseudo.
117 var select = rule.selectorText;
118 if(REG_INTERACTIVE.test(select)) {
119 var style = rule.style.cssText;
121 // affected elements are found by truncating the selector after the interactive pseudo,
122 // eg: "div li:hover" >> "div li"
123 var affected = REG_AFFECTED.exec(select)[1];
125 // that pseudo is needed for a classname, and defines the type of interaction (focus, hover, active)
126 // eg: "li:hover" >> "onhover"
127 var pseudo = select.replace(REG_PSEUDO, 'on$1');
129 // the new selector is going to use that classname in a new css rule,
130 // since IE6 doesn't support multiple classnames, this is merged into one classname
131 // eg: "li:hover" >> "li.onhover", "li.folder:hover" >> "li.folderonhover"
132 var newSelect = select.replace(REG_SELECT, '.$2' + pseudo);
134 // the classname is needed for the events that are going to be set on affected nodes
135 // eg: "li.folder:hover" >> "folderonhover"
136 var className = REG_CLASS.exec(newSelect)[1];
138 // no need to set the same callback more than once when the same selector uses the same classname
139 var hash = affected + className;
140 if(!this.callbacks[hash]) {
142 // affected elements are given an expression under a borrowed css property, because fake properties
143 // can't have their expressions cleared. Different properties are used per pseudo, to avoid
144 // expressions from overwriting eachother. The expression does a callback to CSSHover.patch,
145 // rerouted via the exposed window.CSSHover function.
146 var property = Properties.get();
147 var atRuntime = camelize(property);
149 // because the expression is added to the stylesheet, and styles are always applied to html that is
150 // dynamically added to the dom, the expression will also trigger for those new elements (provided
151 // they are selected by the affected selector).
152 sheet.addRule(affected, property + ':expression(CSSHover(this, "'+pseudo+'", "'+className+'", "'+atRuntime+'"))');
154 // hash it, so an identical selector/class combo does not duplicate the expression
155 this.callbacks[hash] = true;
158 // duplicate expressions need not be set, but the style could differ
159 sheet.addRule(newSelect, style);
163 // called via the expression, patches individual nodes
164 patch:function(node, type, className, property) {
166 // restores the borrowed css property to the value of its immediate parent, clearing
167 // the expression so that it's not repeatedly called.
169 var value = node.parentNode.currentStyle[property];
170 node.style[property] = value;
172 // the above reset should never fail, but just in case, clear the runtimeStyle if it does.
173 // this will also stop the expression.
174 node.runtimeStyle[property] = '';
177 // just to make sure, also keep track of patched classnames locally on the node
182 // and check for it to prevent duplicate events with the same classname from being set
183 if(!node.csshover[className]) {
184 node.csshover[className] = true;
186 // create an instance for the given type and class
187 var element = new CSSHoverElement(node, type, className);
189 // and store that instance for unloading later on
190 this.elements.push(element);
193 // returns a dummy value to the expression
197 // unload stuff onbeforeunload
202 var l = this.elements.length;
203 for(var i=0; i<l; i++) {
204 this.elements[i].unload();
207 // and set properties to null
218 * --------------------------
221 // the event types associated with the interactive pseudos
223 onhover: { activator: 'onmouseenter', deactivator: 'onmouseleave' },
224 onactive: { activator: 'onmousedown', deactivator: 'onmouseup' },
225 onfocus: { activator: 'onfocus', deactivator: 'onblur' }
228 // CSSHoverElement constructor, called via CSSHover.patch
229 function CSSHoverElement(node, type, className) {
231 // the CSSHoverElement patches individual nodes by manually applying the events that should
232 // have fired by the css pseudoclasses, eg mouseenter and mouseleave for :hover.
236 var replacer = new RegExp('(^|\\s)'+className+'(\\s|$)', 'g');
238 // store event handlers for removal onunload
239 this.activator = function(){ node.className += ' ' + className; };
240 this.deactivator = function(){ node.className = node.className.replace(replacer, ' '); };
243 node.attachEvent(CSSEvents[type].activator, this.activator);
244 node.attachEvent(CSSEvents[type].deactivator, this.deactivator);
247 CSSHoverElement.prototype = {
248 // onbeforeunload, called via CSSHover.unload
252 this.node.detachEvent(CSSEvents[this.type].activator, this.activator);
253 this.node.detachEvent(CSSEvents[this.type].deactivator, this.deactivator);
255 // and set properties to null
256 this.activator = null;
257 this.deactivator = null;
263 // add the unload to the onbeforeunload event
264 window.attachEvent('onbeforeunload', function(){
270 * --------------------------
273 return function(node, type, className, property) {
275 // called via the css expression; patches individual nodes
276 return CSSHover.patch(node, type, className, property);
278 // called ondomcontentready via the public:attach node