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>