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