csshover.htc 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  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. window.CSSHover = (function(){
  26. // regular expressions, used and explained later on.
  27. var REG_INTERACTIVE = /(^|\s)((([^a]([^ ]+)?)|(a([^#.][^ ]+)+)):(hover|active|focus))/i;
  28. var REG_AFFECTED = /(.*?)\:(hover|active|focus)/i;
  29. var REG_PSEUDO = /[^:]+:([a-z\-]+).*/i;
  30. var REG_SELECT = /(\.([a-z0-9_\-]+):[a-z]+)|(:[a-z]+)/gi;
  31. var REG_CLASS = /\.([a-z0-9_\-]*on(hover|active|focus))/i;
  32. var REG_MSIE = /msie (5|6|7)/i;
  33. var REG_COMPAT = /backcompat/i;
  34. // property mapping, real css properties must be used in order to clear expressions later on...
  35. // Uses obscure css properties that no-one is likely to use. The properties are borrowed to
  36. // set an expression, and are then restored to the most likely correct value.
  37. var Properties = {
  38. index: 0,
  39. list: ['text-kashida', 'text-kashida-space', 'text-justify'],
  40. get: function() {
  41. return this.list[(this.index++)%this.list.length];
  42. }
  43. };
  44. // camelize is used to convert css properties from (eg) text-kashida to textKashida
  45. var camelize = function(str) {
  46. return str.replace(/-(.)/mg, function(result, match){
  47. return match.toUpperCase();
  48. });
  49. };
  50. /**
  51. * Local CSSHover object
  52. * --------------------------
  53. */
  54. var CSSHover = {
  55. // array of CSSHoverElements, used to unload created events
  56. elements: [],
  57. // buffer used for checking on duplicate expressions
  58. callbacks: {},
  59. // init, called once ondomcontentready via the exposed window.CSSHover function
  60. init:function() {
  61. // don't run in IE8 standards; expressions don't work in standards mode anyway,
  62. // and the stuff we're trying to fix should already work properly
  63. if(!REG_MSIE.test(navigator.userAgent) && !REG_COMPAT.test(window.document.compatMode)) {
  64. return;
  65. }
  66. // start parsing the existing stylesheets
  67. var sheets = window.document.styleSheets, l = sheets.length;
  68. for(var i=0; i<l; i++) {
  69. this.parseStylesheet(sheets[i]);
  70. }
  71. },
  72. // called from init, parses individual stylesheets
  73. parseStylesheet:function(sheet) {
  74. // check sheet imports and parse those recursively
  75. if(sheet.imports) {
  76. try {
  77. var imports = sheet.imports;
  78. var l = imports.length;
  79. for(var i=0; i<l; i++) {
  80. this.parseStylesheet(sheet.imports[i]);
  81. }
  82. } catch(securityException){
  83. // trycatch for various possible errors
  84. }
  85. }
  86. // interate the sheet's rules and send them to the parser
  87. try {
  88. var rules = sheet.rules;
  89. var r = rules.length;
  90. for(var j=0; j<r; j++) {
  91. this.parseCSSRule(rules[j], sheet);
  92. }
  93. } catch(someException){
  94. // trycatch for various errors, most likely accessing the sheet's rules.
  95. }
  96. },
  97. // magic starts here ...
  98. parseCSSRule:function(rule, sheet) {
  99. // The sheet is used to insert new rules into, this must be the same sheet the rule
  100. // came from, to ensure that relative paths keep pointing to the right location.
  101. // only parse a rule if it contains an interactive pseudo.
  102. var select = rule.selectorText;
  103. if(REG_INTERACTIVE.test(select)) {
  104. var style = rule.style.cssText;
  105. // affected elements are found by truncating the selector after the interactive pseudo,
  106. // eg: "div li:hover" >> "div li"
  107. var affected = REG_AFFECTED.exec(select)[1];
  108. // that pseudo is needed for a classname, and defines the type of interaction (focus, hover, active)
  109. // eg: "li:hover" >> "onhover"
  110. var pseudo = select.replace(REG_PSEUDO, 'on$1');
  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. var newSelect = select.replace(REG_SELECT, '.$2' + pseudo);
  115. // the classname is needed for the events that are going to be set on affected nodes
  116. // eg: "li.folder:hover" >> "folderonhover"
  117. var className = REG_CLASS.exec(newSelect)[1];
  118. // no need to set the same callback more than once when the same selector uses the same classname
  119. var hash = affected + className;
  120. if(!this.callbacks[hash]) {
  121. // affected elements are given an expression under a borrowed css property, because fake properties
  122. // can't have their expressions cleared. Different properties are used per pseudo, to avoid
  123. // expressions from overwriting eachother. The expression does a callback to CSSHover.patch,
  124. // rerouted via the exposed window.CSSHover function.
  125. var property = Properties.get();
  126. var atRuntime = camelize(property);
  127. // because the expression is added to the stylesheet, and styles are always applied to html that is
  128. // dynamically added to the dom, the expression will also trigger for those new elements (provided
  129. // they are selected by the affected selector).
  130. sheet.addRule(affected, property + ':expression(CSSHover(this, "'+pseudo+'", "'+className+'", "'+atRuntime+'"))');
  131. // hash it, so an identical selector/class combo does not duplicate the expression
  132. this.callbacks[hash] = true;
  133. }
  134. // duplicate expressions need not be set, but the style could differ
  135. sheet.addRule(newSelect, style);
  136. }
  137. },
  138. // called via the expression, patches individual nodes
  139. patch:function(node, type, className, property) {
  140. // restores the borrowed css property to the value of its immediate parent, clearing
  141. // the expression so that it's not repeatedly called.
  142. try {
  143. var value = node.parentNode.currentStyle[property];
  144. node.style[property] = value;
  145. } catch(e) {
  146. // the above reset should never fail, but just in case, clear the runtimeStyle if it does.
  147. // this will also stop the expression.
  148. node.runtimeStyle[property] = '';
  149. }
  150. // just to make sure, also keep track of patched classnames locally on the node
  151. if(!node.csshover) {
  152. node.csshover = [];
  153. }
  154. // and check for it to prevent duplicate events with the same classname from being set
  155. if(!node.csshover[className]) {
  156. node.csshover[className] = true;
  157. // create an instance for the given type and class
  158. var element = new CSSHoverElement(node, type, className);
  159. // and store that instance for unloading later on
  160. this.elements.push(element);
  161. }
  162. // returns a dummy value to the expression
  163. return type;
  164. },
  165. // unload stuff onbeforeunload
  166. unload:function() {
  167. try {
  168. // remove events
  169. var l = this.elements.length;
  170. for(var i=0; i<l; i++) {
  171. this.elements[i].unload();
  172. }
  173. // and set properties to null
  174. this.elements = [];
  175. this.callbacks = {};
  176. } catch (e) {
  177. }
  178. }
  179. };
  180. /**
  181. * CSSHoverElement
  182. * --------------------------
  183. */
  184. // the event types associated with the interactive pseudos
  185. var CSSEvents = {
  186. onhover: { activator: 'onmouseenter', deactivator: 'onmouseleave' },
  187. onactive: { activator: 'onmousedown', deactivator: 'onmouseup' },
  188. onfocus: { activator: 'onfocus', deactivator: 'onblur' }
  189. };
  190. // CSSHoverElement constructor, called via CSSHover.patch
  191. function CSSHoverElement(node, type, className) {
  192. // the CSSHoverElement patches individual nodes by manually applying the events that should
  193. // have fired by the css pseudoclasses, eg mouseenter and mouseleave for :hover.
  194. this.node = node;
  195. this.type = type;
  196. var replacer = new RegExp('(^|\\s)'+className+'(\\s|$)', 'g');
  197. // store event handlers for removal onunload
  198. this.activator = function(){ node.className += ' ' + className; };
  199. this.deactivator = function(){ node.className = node.className.replace(replacer, ' '); };
  200. // add the events
  201. node.attachEvent(CSSEvents[type].activator, this.activator);
  202. node.attachEvent(CSSEvents[type].deactivator, this.deactivator);
  203. }
  204. CSSHoverElement.prototype = {
  205. // onbeforeunload, called via CSSHover.unload
  206. unload:function() {
  207. // remove events
  208. this.node.detachEvent(CSSEvents[this.type].activator, this.activator);
  209. this.node.detachEvent(CSSEvents[this.type].deactivator, this.deactivator);
  210. // and set properties to null
  211. this.activator = null;
  212. this.deactivator = null;
  213. this.node = null;
  214. this.type = null;
  215. }
  216. };
  217. // add the unload to the onbeforeunload event
  218. window.attachEvent('onbeforeunload', function(){
  219. CSSHover.unload();
  220. });
  221. /**
  222. * Public hook
  223. * --------------------------
  224. */
  225. return function(node, type, className, property) {
  226. if(node) {
  227. // called via the css expression; patches individual nodes
  228. return CSSHover.patch(node, type, className, property);
  229. } else {
  230. // called ondomcontentready via the public:attach node
  231. CSSHover.init();
  232. }
  233. };
  234. })();
  235. </script>