No images in this repository’s iceberg at this time
Download raw (9.5 KB)
<public:attach event="ondocumentready" onevent="CSSHover()" /> <script> /** * Whatever:hover - V3.11 * ------------------------------------------------------------ * Author - Peter Nederlof, http://www.xs4all.nl/~peterned * License - http://creativecommons.org/licenses/LGPL/2.1 * * Special thanks to Sergiu Dumitriu, http://purl.org/net/sergiu, * for fixing the expression loop. * * Whatever:hover is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * Whatever:hover is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * howto: body { behavior:url("csshover3.htc"); } * ------------------------------------------------------------ */ window.CSSHover = (function(){ // regular expressions, used and explained later on. var REG_INTERACTIVE = /(^|\s)((([^a]([^ ]+)?)|(a([^#.][^ ]+)+)):(hover|active|focus))/i; var REG_AFFECTED = /(.*?)\:(hover|active|focus)/i; var REG_PSEUDO = /[^:]+:([a-z\-]+).*/i; var REG_SELECT = /(\.([a-z0-9_\-]+):[a-z]+)|(:[a-z]+)/gi; var REG_CLASS = /\.([a-z0-9_\-]*on(hover|active|focus))/i; var REG_MSIE = /msie (5|6|7)/i; var REG_COMPAT = /backcompat/i; // property mapping, real css properties must be used in order to clear expressions later on... // Uses obscure css properties that no-one is likely to use. The properties are borrowed to // set an expression, and are then restored to the most likely correct value. var Properties = { index: 0, list: ['text-kashida', 'text-kashida-space', 'text-justify'], get: function() { return this.list[(this.index++)%this.list.length]; } }; // camelize is used to convert css properties from (eg) text-kashida to textKashida var camelize = function(str) { return str.replace(/-(.)/mg, function(result, match){ return match.toUpperCase(); }); }; /** * Local CSSHover object * -------------------------- */ var CSSHover = { // array of CSSHoverElements, used to unload created events elements: [], // buffer used for checking on duplicate expressions callbacks: {}, // init, called once ondomcontentready via the exposed window.CSSHover function init:function() { // don't run in IE8 standards; expressions don't work in standards mode anyway, // and the stuff we're trying to fix should already work properly if(!REG_MSIE.test(navigator.userAgent) && !REG_COMPAT.test(window.document.compatMode)) { return; } // start parsing the existing stylesheets var sheets = window.document.styleSheets, l = sheets.length; for(var i=0; i<l; i++) { this.parseStylesheet(sheets[i]); } }, // called from init, parses individual stylesheets parseStylesheet:function(sheet) { // check sheet imports and parse those recursively if(sheet.imports) { try { var imports = sheet.imports; var l = imports.length; for(var i=0; i<l; i++) { this.parseStylesheet(sheet.imports[i]); } } catch(securityException){ // trycatch for various possible errors } } // interate the sheet's rules and send them to the parser try { var rules = sheet.rules; var r = rules.length; for(var j=0; j<r; j++) { this.parseCSSRule(rules[j], sheet); } } catch(someException){ // trycatch for various errors, most likely accessing the sheet's rules. } }, // magic starts here ... parseCSSRule:function(rule, sheet) { // The sheet is used to insert new rules into, this must be the same sheet the rule // came from, to ensure that relative paths keep pointing to the right location. // only parse a rule if it contains an interactive pseudo. var select = rule.selectorText; if(REG_INTERACTIVE.test(select)) { var style = rule.style.cssText; // affected elements are found by truncating the selector after the interactive pseudo, // eg: "div li:hover" >> "div li" var affected = REG_AFFECTED.exec(select)[1]; // that pseudo is needed for a classname, and defines the type of interaction (focus, hover, active) // eg: "li:hover" >> "onhover" var pseudo = select.replace(REG_PSEUDO, 'on$1'); // the new selector is going to use that classname in a new css rule, // since IE6 doesn't support multiple classnames, this is merged into one classname // eg: "li:hover" >> "li.onhover", "li.folder:hover" >> "li.folderonhover" var newSelect = select.replace(REG_SELECT, '.$2' + pseudo); // the classname is needed for the events that are going to be set on affected nodes // eg: "li.folder:hover" >> "folderonhover" var className = REG_CLASS.exec(newSelect)[1]; // no need to set the same callback more than once when the same selector uses the same classname var hash = affected + className; if(!this.callbacks[hash]) { // affected elements are given an expression under a borrowed css property, because fake properties // can't have their expressions cleared. Different properties are used per pseudo, to avoid // expressions from overwriting eachother. The expression does a callback to CSSHover.patch, // rerouted via the exposed window.CSSHover function. var property = Properties.get(); var atRuntime = camelize(property); // because the expression is added to the stylesheet, and styles are always applied to html that is // dynamically added to the dom, the expression will also trigger for those new elements (provided // they are selected by the affected selector). sheet.addRule(affected, property + ':expression(CSSHover(this, "'+pseudo+'", "'+className+'", "'+atRuntime+'"))'); // hash it, so an identical selector/class combo does not duplicate the expression this.callbacks[hash] = true; } // duplicate expressions need not be set, but the style could differ sheet.addRule(newSelect, style); } }, // called via the expression, patches individual nodes patch:function(node, type, className, property) { // restores the borrowed css property to the value of its immediate parent, clearing // the expression so that it's not repeatedly called. try { var value = node.parentNode.currentStyle[property]; node.style[property] = value; } catch(e) { // the above reset should never fail, but just in case, clear the runtimeStyle if it does. // this will also stop the expression. node.runtimeStyle[property] = ''; } // just to make sure, also keep track of patched classnames locally on the node if(!node.csshover) { node.csshover = []; } // and check for it to prevent duplicate events with the same classname from being set if(!node.csshover[className]) { node.csshover[className] = true; // create an instance for the given type and class var element = new CSSHoverElement(node, type, className); // and store that instance for unloading later on this.elements.push(element); } // returns a dummy value to the expression return type; }, // unload stuff onbeforeunload unload:function() { try { // remove events var l = this.elements.length; for(var i=0; i<l; i++) { this.elements[i].unload(); } // and set properties to null this.elements = []; this.callbacks = {}; } catch (e) { } } }; /** * CSSHoverElement * -------------------------- */ // the event types associated with the interactive pseudos var CSSEvents = { onhover: { activator: 'onmouseenter', deactivator: 'onmouseleave' }, onactive: { activator: 'onmousedown', deactivator: 'onmouseup' }, onfocus: { activator: 'onfocus', deactivator: 'onblur' } }; // CSSHoverElement constructor, called via CSSHover.patch function CSSHoverElement(node, type, className) { // the CSSHoverElement patches individual nodes by manually applying the events that should // have fired by the css pseudoclasses, eg mouseenter and mouseleave for :hover. this.node = node; this.type = type; var replacer = new RegExp('(^|\\s)'+className+'(\\s|$)', 'g'); // store event handlers for removal onunload this.activator = function(){ node.className += ' ' + className; }; this.deactivator = function(){ node.className = node.className.replace(replacer, ' '); }; // add the events node.attachEvent(CSSEvents[type].activator, this.activator); node.attachEvent(CSSEvents[type].deactivator, this.deactivator); } CSSHoverElement.prototype = { // onbeforeunload, called via CSSHover.unload unload:function() { // remove events this.node.detachEvent(CSSEvents[this.type].activator, this.activator); this.node.detachEvent(CSSEvents[this.type].deactivator, this.deactivator); // and set properties to null this.activator = null; this.deactivator = null; this.node = null; this.type = null; } }; // add the unload to the onbeforeunload event window.attachEvent('onbeforeunload', function(){ CSSHover.unload(); }); /** * Public hook * -------------------------- */ return function(node, type, className, property) { if(node) { // called via the css expression; patches individual nodes return CSSHover.patch(node, type, className, property); } else { // called ondomcontentready via the public:attach node CSSHover.init(); } }; })(); </script>