[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 /** 2 * Javelin utility functions. 3 * 4 * @provides javelin-util 5 * 6 * @javelin-installs JX.$E 7 * @javelin-installs JX.$A 8 * @javelin-installs JX.$AX 9 * @javelin-installs JX.isArray 10 * @javelin-installs JX.copy 11 * @javelin-installs JX.bind 12 * @javelin-installs JX.bag 13 * @javelin-installs JX.keys 14 * @javelin-installs JX.log 15 * @javelin-installs JX.id 16 * @javelin-installs JX.now 17 * 18 * @javelin 19 */ 20 21 /** 22 * Throw an exception and attach the caller data in the exception. 23 * 24 * @param string Exception message. 25 */ 26 JX.$E = function(message) { 27 var e = new Error(message); 28 var caller_fn = JX.$E.caller; 29 if (caller_fn) { 30 e.caller_fn = caller_fn.caller; 31 } 32 throw e; 33 }; 34 35 36 /** 37 * Convert an array-like object (usually ##arguments##) into a real Array. An 38 * "array-like object" is something with a ##length## property and numerical 39 * keys. The most common use for this is to let you call Array functions on the 40 * magical ##arguments## object. 41 * 42 * JX.$A(arguments).slice(1); 43 * 44 * @param obj Array, or array-like object. 45 * @return Array Actual array. 46 */ 47 JX.$A = function(object) { 48 // IE8 throws "JScript object expected" when trying to call 49 // Array.prototype.slice on a NodeList, so just copy items one by one here. 50 var r = []; 51 for (var ii = 0; ii < object.length; ii++) { 52 r.push(object[ii]); 53 } 54 return r; 55 }; 56 57 58 /** 59 * Cast a value into an array, by wrapping scalars into singletons. If the 60 * argument is an array, it is returned unmodified. If it is a scalar, an array 61 * with a single element is returned. For example: 62 * 63 * JX.$AX([3]); // Returns [3]. 64 * JX.$AX(3); // Returns [3]. 65 * 66 * Note that this function uses a @{function:JX.isArray} check whether or not 67 * the argument is an array, so you may need to convert array-like objects (such 68 * as ##arguments##) into real arrays with @{function:JX.$A}. 69 * 70 * This function is mostly useful to create methods which accept either a 71 * value or a list of values. 72 * 73 * @param wild Scalar or Array. 74 * @return Array If the argument was a scalar, an Array with the argument as 75 * its only element. Otherwise, the original Array. 76 */ 77 JX.$AX = function(maybe_scalar) { 78 return JX.isArray(maybe_scalar) ? maybe_scalar : [maybe_scalar]; 79 }; 80 81 82 /** 83 * Checks whether a value is an array. 84 * 85 * JX.isArray(['an', 'array']); // Returns true. 86 * JX.isArray('Not an Array'); // Returns false. 87 * 88 * @param wild Any value. 89 * @return bool true if the argument is an array, false otherwise. 90 */ 91 JX.isArray = Array.isArray || function(maybe_array) { 92 return Object.prototype.toString.call(maybe_array) == '[object Array]'; 93 }; 94 95 96 /** 97 * Copy properties from one object to another. If properties already exist, they 98 * are overwritten. 99 * 100 * var cat = { 101 * ears: 'clean', 102 * paws: 'clean', 103 * nose: 'DIRTY OH NOES' 104 * }; 105 * var more = { 106 * nose: 'clean', 107 * tail: 'clean' 108 * }; 109 * 110 * JX.copy(cat, more); 111 * 112 * // cat is now: 113 * // { 114 * // ears: 'clean', 115 * // paws: 'clean', 116 * // nose: 'clean', 117 * // tail: 'clean' 118 * // } 119 * 120 * NOTE: This function does not copy the ##toString## property or anything else 121 * which isn't enumerable or is somehow magic or just doesn't work. But it's 122 * usually what you want. 123 * 124 * @param obj Destination object, which properties should be copied to. 125 * @param obj Source object, which properties should be copied from. 126 * @return obj Modified destination object. 127 */ 128 JX.copy = function(copy_dst, copy_src) { 129 for (var k in copy_src) { 130 copy_dst[k] = copy_src[k]; 131 } 132 return copy_dst; 133 }; 134 135 136 /** 137 * Create a function which invokes another function with a bound context and 138 * arguments (i.e., partial function application) when called; king of all 139 * functions. 140 * 141 * Bind performs context binding (letting you select what the value of ##this## 142 * will be when a function is invoked) and partial function application (letting 143 * you create some function which calls another one with bound arguments). 144 * 145 * = Context Binding = 146 * 147 * Normally, when you call ##obj.method()##, the magic ##this## object will be 148 * the ##obj## you invoked the method from. This can be undesirable when you 149 * need to pass a callback to another function. For instance: 150 * 151 * COUNTEREXAMPLE 152 * var dog = new JX.Dog(); 153 * dog.barkNow(); // Makes the dog bark. 154 * 155 * JX.Stratcom.listen('click', 'bark', dog.barkNow); // Does not work! 156 * 157 * This doesn't work because ##this## is ##window## when the function is 158 * later invoked; @{method:JX.Stratcom.listen} does not know about the context 159 * object ##dog##. The solution is to pass a function with a bound context 160 * object: 161 * 162 * var dog = new JX.Dog(); 163 * var bound_function = JX.bind(dog, dog.barkNow); 164 * 165 * JX.Stratcom.listen('click', 'bark', bound_function); 166 * 167 * ##bound_function## is a function with ##dog## bound as ##this##; ##this## 168 * will always be ##dog## when the function is called, no matter what 169 * property chain it is invoked from. 170 * 171 * You can also pass ##null## as the context argument to implicitly bind 172 * ##window##. 173 * 174 * = Partial Function Application = 175 * 176 * @{function:JX.bind} also performs partial function application, which allows 177 * you to bind one or more arguments to a function. For instance, if we have a 178 * simple function which adds two numbers: 179 * 180 * function add(a, b) { return a + b; } 181 * add(3, 4); // 7 182 * 183 * Suppose we want a new function, like this: 184 * 185 * function add3(b) { return 3 + b; } 186 * add3(4); // 7 187 * 188 * Instead of doing this, we can define ##add3()## in terms of ##add()## by 189 * binding the value ##3## to the ##a## argument: 190 * 191 * var add3_bound = JX.bind(null, add, 3); 192 * add3_bound(4); // 7 193 * 194 * Zero or more arguments may be bound in this way. This is particularly useful 195 * when using closures in a loop: 196 * 197 * COUNTEREXAMPLE 198 * for (var ii = 0; ii < button_list.length; ii++) { 199 * button_list[ii].onclick = function() { 200 * JX.log('You clicked button number '+ii+'!'); // Fails! 201 * }; 202 * } 203 * 204 * This doesn't work; all the buttons report the highest number when clicked. 205 * This is because the local ##ii## is captured by the closure. Instead, bind 206 * the current value of ##ii##: 207 * 208 * var func = function(button_num) { 209 * JX.log('You clicked button number '+button_num+'!'); 210 * } 211 * for (var ii = 0; ii < button_list.length; ii++) { 212 * button_list[ii].onclick = JX.bind(null, func, ii); 213 * } 214 * 215 * @param obj|null Context object to bind as ##this##. 216 * @param function Function to bind context and arguments to. 217 * @param ... Zero or more arguments to bind. 218 * @return function New function which invokes the original function with 219 * bound context and arguments when called. 220 */ 221 JX.bind = function(context, func, more) { 222 if (__DEV__) { 223 if (typeof func != 'function') { 224 JX.$E( 225 'JX.bind(context, <yuck>, ...): '+ 226 'Attempting to bind something that is not a function.'); 227 } 228 } 229 230 var bound = JX.$A(arguments).slice(2); 231 if (func.bind) { 232 return func.bind.apply(func, [context].concat(bound)); 233 } 234 235 return function() { 236 return func.apply(context || window, bound.concat(JX.$A(arguments))); 237 }; 238 }; 239 240 241 /** 242 * "Bag of holding"; function that does nothing. Primarily, it's used as a 243 * placeholder when you want something to be callable but don't want it to 244 * actually have an effect. 245 * 246 * @return void 247 */ 248 JX.bag = function() { 249 // \o\ \o/ /o/ woo dance party 250 }; 251 252 253 /** 254 * Convert an object's keys into a list. For example: 255 * 256 * JX.keys({sun: 1, moon: 1, stars: 1}); // Returns: ['sun', 'moon', 'stars'] 257 * 258 * @param obj Object to retrieve keys from. 259 * @return list List of keys. 260 */ 261 JX.keys = Object.keys || function(obj) { 262 var r = []; 263 for (var k in obj) { 264 r.push(k); 265 } 266 return r; 267 }; 268 269 270 /** 271 * Identity function; returns the argument unmodified. This is primarily useful 272 * as a placeholder for some callback which may transform its argument. 273 * 274 * @param wild Any value. 275 * @return wild The passed argument. 276 */ 277 JX.id = function(any) { 278 return any; 279 }; 280 281 282 if (!window.console || !window.console.log) { 283 if (window.opera && window.opera.postError) { 284 window.console = {log: function(m) { window.opera.postError(m); }}; 285 } else { 286 window.console = {log: function(m) { }}; 287 } 288 } 289 290 291 /** 292 * Print a message to the browser debugging console (like Firebug). 293 * 294 * @param string Message to print to the browser debugging console. 295 * @return void 296 */ 297 JX.log = function(message) { 298 window.console.log(message); 299 }; 300 301 302 if (__DEV__) { 303 window.alert = (function(native_alert) { 304 var recent_alerts = []; 305 var in_alert = false; 306 return function(msg) { 307 if (in_alert) { 308 JX.log( 309 'alert(...): '+ 310 'discarded reentrant alert.'); 311 return; 312 } 313 in_alert = true; 314 recent_alerts.push(JX.now()); 315 316 if (recent_alerts.length > 3) { 317 recent_alerts.splice(0, recent_alerts.length - 3); 318 } 319 320 if (recent_alerts.length >= 3 && 321 (recent_alerts[recent_alerts.length - 1] - recent_alerts[0]) < 5000) { 322 if (confirm(msg + "\n\nLots of alert()s recently. Kill them?")) { 323 window.alert = JX.bag; 324 } 325 } else { 326 // Note that we can't .apply() the IE6 version of this "function". 327 native_alert(msg); 328 } 329 in_alert = false; 330 }; 331 })(window.alert); 332 } 333 334 /** 335 * Date.now is the fastest timestamp function, but isn't supported by every 336 * browser. This gives the fastest version the environment can support. 337 * The wrapper function makes the getTime call even slower, but benchmarking 338 * shows it to be a marginal perf loss. Considering how small of a perf 339 * difference this makes overall, it's not really a big deal. The primary 340 * reason for this is to avoid hacky "just think of the byte savings" JS 341 * like +new Date() that has an unclear outcome for the unexposed. 342 * 343 * @return Int A Unix timestamp of the current time on the local machine 344 */ 345 JX.now = (Date.now || function() { return new Date().getTime(); });
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |