Merge "Whitelist the <wbr> element."
[mediawiki.git] / skins / vector / csshover.htc
bloba13ea68da566b481c82c04f50da2309e529a7e61
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  */
26 window.CSSHover = (function(){
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;
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         };
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                         }
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                 },
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                 },
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.
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];
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);
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                 },
163                 // called via the expression, patches individual nodes
164                 patch:function(node, type, className, property) {
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                         }
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;
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                         }
193                         // returns a dummy value to the expression
194                         return type;
195                 },
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                                 }
207                                 // and set properties to null 
208                                 this.elements = [];
209                                 this.callbacks = {};
211                         } catch (e) {
212                         }
213                 }
214         };
216         /**
217          *      CSSHoverElement
218          *      --------------------------
219          */
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) {
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. 
234                 this.node = node;
235                 this.type = type;
236                 var replacer = new RegExp('(^|\\s)'+className+'(\\s|$)', 'g');
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() {
251                         // remove events 
252                         this.node.detachEvent(CSSEvents[this.type].activator, this.activator);
253                         this.node.detachEvent(CSSEvents[this.type].deactivator, this.deactivator);
255                         // and set properties to null 
256                         this.activator = null;
257                         this.deactivator = null;
258                         this.node = null;
259                         this.type = null;
260                 }
261         };
263         // add the unload to the onbeforeunload event
264         window.attachEvent('onbeforeunload', function(){
265                 CSSHover.unload();
266         });
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         };
283 })();
284 </script>