| 1 |
<public:attach event="ondocumentready" onevent="CSSHover()" />
|
| 2 |
<script>
|
| 3 |
// <?)|(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> |