1 /** 2 * @private Returns a prototype object suitable for extending the given class 3 * <tt>f</tt>. Rather than constructing a new instance of <tt>f</tt> to serve as 4 * the prototype (which unnecessarily runs the constructor on the created 5 * prototype object, potentially polluting it), an anonymous function is 6 * generated internally that shares the same prototype: 7 * 8 * <pre>function g() {} 9 * g.prototype = f.prototype; 10 * return new g();</pre> 11 * 12 * For more details, see Douglas Crockford's essay on prototypal inheritance. 13 * 14 * @param {function} f a constructor. 15 * @returns a suitable prototype object. 16 * @see Douglas Crockford's essay on <a 17 * href="http://javascript.crockford.com/prototypal.html">prototypal 18 * inheritance</a>. 19 */ 20 pv.extend = function(f) { 21 function g() {} 22 g.prototype = f.prototype || f; 23 return new g(); 24 }; 25 26 try { 27 eval("pv.parse = function(x) x;"); // native support 28 } catch (e) { 29 30 /** 31 * @private Parses a Protovis specification, which may use JavaScript 1.8 32 * function expresses, replacing those function expressions with proper 33 * functions such that the code can be run by a JavaScript 1.6 interpreter. This 34 * hack only supports function expressions (using clumsy regular expressions, no 35 * less), and not other JavaScript 1.8 features such as let expressions. 36 * 37 * @param {string} s a Protovis specification (i.e., a string of JavaScript 1.8 38 * source code). 39 * @returns {string} a conformant JavaScript 1.6 source code. 40 */ 41 pv.parse = function(js) { // hacky regex support 42 var re = new RegExp("function\\s*(\\b\\w+)?\\s*\\([^)]*\\)\\s*", "mg"), m, d, i = 0, s = ""; 43 while (m = re.exec(js)) { 44 var j = m.index + m[0].length; 45 if (js.charAt(j) != '{') { 46 s += js.substring(i, j) + "{return "; 47 i = j; 48 for (var p = 0; p >= 0 && j < js.length; j++) { 49 var c = js.charAt(j); 50 switch (c) { 51 case '"': case '\'': { 52 while (++j < js.length && (d = js.charAt(j)) != c) { 53 if (d == '\\') j++; 54 } 55 break; 56 } 57 case '[': case '(': p++; break; 58 case ']': case ')': p--; break; 59 case ';': 60 case ',': if (p == 0) p--; break; 61 } 62 } 63 s += pv.parse(js.substring(i, --j)) + ";}"; 64 i = j; 65 } 66 re.lastIndex = j; 67 } 68 s += js.substring(i); 69 return s; 70 }; 71 } 72 73 /** 74 * @private Computes the value of the specified CSS property <tt>p</tt> on the 75 * specified element <tt>e</tt>. 76 * 77 * @param {string} p the name of the CSS property. 78 * @param e the element on which to compute the CSS property. 79 */ 80 pv.css = function(e, p) { 81 return window.getComputedStyle 82 ? window.getComputedStyle(e, null).getPropertyValue(p) 83 : e.currentStyle[p]; 84 }; 85 86 /** 87 * @private Reports the specified error to the JavaScript console. Mozilla only 88 * allows logging to the console for privileged code; if the console is 89 * unavailable, the alert dialog box is used instead. 90 * 91 * @param e the exception that triggered the error. 92 */ 93 pv.error = function(e) { 94 (typeof console == "undefined") ? alert(e) : console.error(e); 95 }; 96 97 /** 98 * @private Registers the specified listener for events of the specified type on 99 * the specified target. For standards-compliant browsers, this method uses 100 * <tt>addEventListener</tt>; for Internet Explorer, <tt>attachEvent</tt>. 101 * 102 * @param target a DOM element. 103 * @param {string} type the type of event, such as "click". 104 * @param {function} the event handler callback. 105 */ 106 pv.listen = function(target, type, listener) { 107 listener = pv.listener(listener); 108 return target.addEventListener 109 ? target.addEventListener(type, listener, false) 110 : target.attachEvent("on" + type, listener); 111 }; 112 113 /** 114 * @private Returns a wrapper for the specified listener function such that the 115 * {@link pv.event} is set for the duration of the listener's invocation. The 116 * wrapper is cached on the returned function, such that duplicate registrations 117 * of the wrapped event handler are ignored. 118 * 119 * @param {function} f an event handler. 120 * @returns {function} the wrapped event handler. 121 */ 122 pv.listener = function(f) { 123 return f.$listener || (f.$listener = function(e) { 124 try { 125 pv.event = e; 126 return f.call(this, e); 127 } finally { 128 delete pv.event; 129 } 130 }); 131 }; 132 133 /** 134 * @private Returns true iff <i>a</i> is an ancestor of <i>e</i>. This is useful 135 * for ignoring mouseout and mouseover events that are contained within the 136 * target element. 137 */ 138 pv.ancestor = function(a, e) { 139 while (e) { 140 if (e == a) return true; 141 e = e.parentNode; 142 } 143 return false; 144 }; 145 146 /** @private Returns a locally-unique positive id. */ 147 pv.id = function() { 148 var id = 1; return function() { return id++; }; 149 }(); 150 151 /** @private Returns a function wrapping the specified constant. */ 152 pv.functor = function(v) { 153 return typeof v == "function" ? v : function() { return v; }; 154 }; 155