1+ <public:attach event="ondocumentready" onevent="CSSHover()" />
2+ <script>
3+ /**
4+ * Whatever:hover - V3.11
5+ * ------------------------------------------------------------
6+ * Author - Peter Nederlof, http://www.xs4all.nl/~peterned
7+ * License - http://creativecommons.org/licenses/LGPL/2.1
8+ *
9+ * Special thanks to Sergiu Dumitriu, http://purl.org/net/sergiu,
10+ * for fixing the expression loop.
11+ *
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.
16+ *
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.
21+ *
22+ * howto: body { behavior:url("csshover3.htc"); }
23+ * ------------------------------------------------------------
24+ */
25+
26+ window.CSSHover = (function(){
27+
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;
36+
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.
40+ var Properties = {
41+ index: 0,
42+ list: ['text-kashida', 'text-kashida-space', 'text-justify'],
43+ get: function() {
44+ return this.list[(this.index++)%this.list.length];
45+ }
46+ };
47+
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();
52+ });
53+ };
54+
55+ /**
56+ * Local CSSHover object
57+ * --------------------------
58+ */
59+
60+ var CSSHover = {
61+
62+ // array of CSSHoverElements, used to unload created events
63+ elements: [],
64+
65+ // buffer used for checking on duplicate expressions
66+ callbacks: {},
67+
68+ // init, called once ondomcontentready via the exposed window.CSSHover function
69+ init: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)) {
73+ return;
74+ }
75+
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]);
80+ }
81+ },
82+
83+ // called from init, parses individual stylesheets
84+ parseStylesheet:function(sheet) {
85+ // check sheet imports and parse those recursively
86+ if(sheet.imports) {
87+ try {
88+ var imports = sheet.imports;
89+ var l = imports.length;
90+ for(var i=0; i<l; i++) {
91+ this.parseStylesheet(sheet.imports[i]);
92+ }
93+ } catch(securityException){
94+ // trycatch for various possible errors
95+ }
96+ }
97+
98+ // interate the sheet's rules and send them to the parser
99+ try {
100+ var rules = sheet.rules;
101+ var r = rules.length;
102+ for(var j=0; j<r; j++) {
103+ this.parseCSSRule(rules[j], sheet);
104+ }
105+ } catch(someException){
106+ // trycatch for various errors, most likely accessing the sheet's rules.
107+ }
108+ },
109+
110+ // magic starts here ...
111+ parseCSSRule:function(rule, sheet) {
112+
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.
115+
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;
120+
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];
124+
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');
128+
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);
133+
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];
137+
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]) {
141+
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);
148+
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+'"))');
153+
154+ // hash it, so an identical selector/class combo does not duplicate the expression
155+ this.callbacks[hash] = true;
156+ }
157+
158+ // duplicate expressions need not be set, but the style could differ
159+ sheet.addRule(newSelect, style);
160+ }
161+ },
162+
163+ // called via the expression, patches individual nodes
164+ patch:function(node, type, className, property) {
165+
166+ // restores the borrowed css property to the value of its immediate parent, clearing
167+ // the expression so that it's not repeatedly called.
168+ try {
169+ var value = node.parentNode.currentStyle[property];
170+ node.style[property] = value;
171+ } catch(e) {
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] = '';
175+ }
176+
177+ // just to make sure, also keep track of patched classnames locally on the node
178+ if(!node.csshover) {
179+ node.csshover = [];
180+ }
181+
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;
185+
186+ // create an instance for the given type and class
187+ var element = new CSSHoverElement(node, type, className);
188+
189+ // and store that instance for unloading later on
190+ this.elements.push(element);
191+ }
192+
193+ // returns a dummy value to the expression
194+ return type;
195+ },
196+
197+ // unload stuff onbeforeunload
198+ unload:function() {
199+ try {
200+
201+ // remove events
202+ var l = this.elements.length;
203+ for(var i=0; i<l; i++) {
204+ this.elements[i].unload();
205+ }
206+
207+ // and set properties to null
208+ this.elements = [];
209+ this.callbacks = {};
210+
211+ } catch (e) {
212+ }
213+ }
214+ };
215+
216+ /**
217+ * CSSHoverElement
218+ * --------------------------
219+ */
220+
221+ // the event types associated with the interactive pseudos
222+ var CSSEvents = {
223+ onhover: { activator: 'onmouseenter', deactivator: 'onmouseleave' },
224+ onactive: { activator: 'onmousedown', deactivator: 'onmouseup' },
225+ onfocus: { activator: 'onfocus', deactivator: 'onblur' }
226+ };
227+
228+ // CSSHoverElement constructor, called via CSSHover.patch
229+ function CSSHoverElement(node, type, className) {
230+
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.
233+
234+ this.node = node;
235+ this.type = type;
236+ var replacer = new RegExp('(^|\\s)'+className+'(\\s|$)', 'g');
237+
238+ // store event handlers for removal onunload
239+ this.activator = function(){ node.className += ' ' + className; };
240+ this.deactivator = function(){ node.className = node.className.replace(replacer, ' '); };
241+
242+ // add the events
243+ node.attachEvent(CSSEvents[type].activator, this.activator);
244+ node.attachEvent(CSSEvents[type].deactivator, this.deactivator);
245+ }
246+
247+ CSSHoverElement.prototype = {
248+ // onbeforeunload, called via CSSHover.unload
249+ unload:function() {
250+
251+ // remove events
252+ this.node.detachEvent(CSSEvents[this.type].activator, this.activator);
253+ this.node.detachEvent(CSSEvents[this.type].deactivator, this.deactivator);
254+
255+ // and set properties to null
256+ this.activator = null;
257+ this.deactivator = null;
258+ this.node = null;
259+ this.type = null;
260+ }
261+ };
262+
263+ // add the unload to the onbeforeunload event
264+ window.attachEvent('onbeforeunload', function(){
265+ CSSHover.unload();
266+ });
267+
268+ /**
269+ * Public hook
270+ * --------------------------
271+ */
272+
273+ return function(node, type, className, property) {
274+ if(node) {
275+ // called via the css expression; patches individual nodes
276+ return CSSHover.patch(node, type, className, property);
277+ } else {
278+ // called ondomcontentready via the public:attach node
279+ CSSHover.init();
280+ }
281+ };
282+
283+ })();
284+ </script>
0 commit comments