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