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 24window.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>