1 /* 2 * Dual-licensed under the MIT License & the Academic Free License v. 2.1. 3 * See the file LICENSE for more information. 4 * 5 * (c) 2007-2008 by Per Cederberg & Dynabyte AB. All rights reserved. 6 */ 7 8 // Check for loaded MochiKit 9 if (typeof(MochiKit) == "undefined") { 10 throw new ReferenceError("MochiKit must be loaded before loading this script"); 11 } 12 13 /** 14 * @name MochiKit.Base 15 * @namespace Provides functional programming and useful comparisons. 16 */ 17 // Check for loaded MochiKit.Base 18 if (typeof(MochiKit.Base) == "undefined") { 19 throw new ReferenceError("MochiKit.Base must be loaded before loading this script"); 20 } 21 22 /** 23 * Returns the first function argument that is not undefined. 24 * 25 * @param {Object} [...] the values to check 26 * 27 * @return {Object} the first non-undefined argument, or 28 * undefined if all arguments were undefined 29 */ 30 MochiKit.Base.defaultValue = function(/* ... */) { 31 for (var i = 0; i < arguments.length; i++) { 32 if (typeof(arguments[i]) != "undefined") { 33 return arguments[i]; 34 } 35 } 36 return undefined; 37 } 38 39 /** 40 * Creates a dictionary object from a list of keys and values. It 41 * can be used either as a reverse of items(), or as a reverse of 42 * keys() and values(). That is, either the function take a single 43 * list where each element contains both key and value, or it takes 44 * two separate lists, one with keys and the other with values. If 45 * a key is specified twice, only the last value will be used. 46 * 47 * @code dict([['a', 1], ['b', 2]]) --> { a: 1, b: 2 } 48 * dict(['a','b'], [1, 2]) --> { a: 1, b: 2 } 49 * 50 * @param {Array} itemsOrKeys the list of items or keys 51 * @param {Array} [values] the optional list of values 52 * 53 * @return {Object} a dictionary object with all the keys set to the 54 * corresponding value 55 */ 56 MochiKit.Base.dict = function(itemsOrKeys, values) { 57 var o = {}; 58 if (!MochiKit.Base.isArrayLike(itemsOrKeys)) { 59 throw new TypeError("First argument must be array-like"); 60 } 61 if (MochiKit.Base.isArrayLike(values) && itemsOrKeys.length !== values.length) { 62 throw new TypeError("Both arrays must be of same length"); 63 } 64 for (var i = 0; i < itemsOrKeys.length; i++) { 65 var k = itemsOrKeys[i]; 66 if (k === null || k === undefined) { 67 throw new TypeError("Key at index " + i + " is null or undefined"); 68 } else if (MochiKit.Base.isArrayLike(k)) { 69 o[k[0]] = k[1]; 70 } else if (MochiKit.Base.isArrayLike(values)) { 71 o[k] = values[i]; 72 } else { 73 o[k] = values; 74 } 75 } 76 return o; 77 } 78 79 /** 80 * Creates a new object by copying keys and values from another 81 * object. A list of key names (or an object whose property names 82 * will be used as keys) must be specified as an argument. The 83 * returned object will only contain properties that were defined in 84 * the source object, keeping the source object values. The source 85 * object will be left unmodified. 86 * 87 * @param {Object} src the source object to select values from 88 * @param {Array/Object} keys the list of keys to select, or an 89 * object with the keys to select 90 * 91 * @return {Object} a new object containing the matching keys and 92 * values found in the source object 93 */ 94 MochiKit.Base.select = function(src, keys) { 95 var res = {}; 96 if (!MochiKit.Base.isArrayLike(keys)) { 97 keys = MochiKit.Base.keys(keys); 98 } 99 for (var i = 0; i < keys.length; i++) { 100 var k = keys[i]; 101 if (k in src) { 102 res[k] = src[k]; 103 } 104 } 105 return res; 106 } 107 108 /** 109 * Filters an object by removing a list of keys. A list of key names 110 * (or an object whose property names will be used as keys) must be 111 * specified as an argument. A new object containing the source 112 * object values for the specified keys will be returned. The source 113 * object will be modified by removing all the specified keys. 114 * 115 * @param {Object} src the source object to select and modify 116 * @param {Array/Object} keys the list of keys to remove, or an 117 * object with the keys to remove 118 * 119 * @return {Object} a new object containing the matching keys and 120 * values found in the source object 121 */ 122 MochiKit.Base.mask = function(src, keys) { 123 var res = {}; 124 if (!MochiKit.Base.isArrayLike(keys)) { 125 keys = MochiKit.Base.keys(keys); 126 } 127 for (var i = 0; i < keys.length; i++) { 128 var k = keys[i]; 129 if (k in src) { 130 res[k] = src[k]; 131 delete src[k]; 132 } 133 } 134 return res; 135 } 136 137 /** 138 * Returns the name of a function. This is often useful for debugging 139 * or logging purposes. If the function is anonymous or the 140 * JavaScript environment doesn't provide function <code>name</code> 141 * properties, any registered function name or undefined will be 142 * returned. 143 * 144 * @param {Function} func the function to name 145 * 146 * @return {String} the function name, or undefined if not available 147 * 148 * @see MochiKit.Base.registerFunctionNames 149 */ 150 MochiKit.Base.functionName = function(func) { 151 if (func == null) { 152 return null; 153 } else if (func.name != null && func.name != "") { 154 return func.name; 155 } else { 156 return func.NAME; 157 } 158 } 159 160 /** 161 * Registers function names for debugging or logging. This is useful 162 * when using anonymous functions or inside JavaScript environments 163 * that do not provide function <code>name</code> properties. This 164 * function will add the specified name as a new <code>NAME</code> 165 * property to any function that doesn't already have a name. This 166 * function will also process any properties or prototype properties 167 * recursively adding names like <code>name.[property name]</code>. 168 * 169 * @param {Object} obj the function or object to register 170 * @param {String} name the function or object (class) name 171 * @param {Array} [stack] the object stack to avoid circular recursion 172 * 173 * @see MochiKit.Base.functionName 174 */ 175 MochiKit.Base.registerFunctionNames = function(obj, name, stack) { 176 if (typeof(obj) === "function" && 177 (obj.name == null || obj.name == "") && 178 typeof(obj.NAME) === "undefined") { 179 obj.NAME = name; 180 } 181 stack = stack || []; 182 if (obj != null && name != null && 183 (typeof(obj) === "object" || typeof(obj) === "function") && 184 obj !== Object.prototype && obj !== Function.prototype && 185 typeof(obj.nodeType) !== "number" && 186 MochiKit.Base.findIdentical(stack, obj) < 0) { 187 188 stack.push(obj); 189 for (var prop in obj) { 190 var str = name + "." + prop; 191 MochiKit.Base.registerFunctionNames(obj[prop], str, stack); 192 } 193 var str = name + ".prototype"; 194 MochiKit.Base.registerFunctionNames(obj.prototype, str, stack); 195 stack.pop(); 196 } 197 } 198 199 /** 200 * Returns the current execution stack trace. The stack trace is an 201 * array of function names with the innermost function at the lowest 202 * index (0). Due to limitations in the JavaScript API:s, the stack 203 * trace will be cut if recursion is detected. The stack trace will 204 * also be cut if the call depth exceeds the maximum depth or if any 205 * function in the chain has an injected stack trace. 206 * 207 * @param {Number} [maxDepth] the maximum call depth, defaults to 20 208 * 209 * @return {Array} the stack trace array of function names 210 * 211 * @see MochiKit.Base.functionName 212 * @see MochiKit.Base.injectStackTrace 213 */ 214 MochiKit.Base.stackTrace = function(maxDepth) { 215 var func = arguments.callee.caller; 216 var visited = []; 217 var res = []; 218 maxDepth = maxDepth || 20; 219 while (func != null) { 220 if (MochiKit.Base.findIdentical(visited, func) >= 0) { 221 res.push("...recursion..."); 222 break; 223 } 224 if (func.$stackTrace != null) { 225 res = res.concat(func.$stackTrace); 226 break; 227 } 228 var name = MochiKit.Base.functionName(func); 229 if (name === null) { 230 // Skip stack trace when null (but not when undefined) 231 } else { 232 res.push(name || "<anonymous>"); 233 } 234 visited.push(func); 235 if (visited.length >= maxDepth) { 236 res.push("..."); 237 break; 238 } 239 func = func.caller; 240 } 241 return res; 242 } 243 244 /** 245 * Injects a stack trace for a function. This method is useful for 246 * creating a fake stack trace in anonymous or callback functions. A 247 * null value can be used to clear any previously injected stack 248 * trace for the calling function. 249 * 250 * @param {Array} stackTrace the stack trace, or null to clear 251 * @param {Function} [func] the function to modify, or null for the 252 * currently executing function (i.e. the caller) 253 */ 254 MochiKit.Base.injectStackTrace = function(stackTrace, func) { 255 func = func || arguments.callee.caller; 256 if (func != null) { 257 if (stackTrace) { 258 func.$stackTrace = stackTrace; 259 } else { 260 delete func.$stackTrace; 261 } 262 } 263 } 264 265 /** 266 * Resolves a relative URI to an absolute URI. This function will 267 * return absolute URI:s directly and traverse any "../" directory 268 * paths in the specified URI. The base URI provided must be 269 * absolute. 270 * 271 * @param {String} uri the relative URI to resolve 272 * @param {String} base the absolute base URI 273 * 274 * @return {String} the resolved absolute URI 275 */ 276 MochiKit.Base.resolveURI = function (uri, base) { 277 if (uri.indexOf("://") > 0) { 278 return uri; 279 } else if (uri.indexOf("#") == 0) { 280 var pos = base.lastIndexOf("#"); 281 if (pos >= 0) { 282 base = base.substring(0, pos); 283 } 284 return base + uri; 285 } else if (uri.indexOf("/") == 0) { 286 var pos = base.indexOf("://"); 287 base = base.substring(0, pos + 2); 288 return base + uri; 289 } else if (uri.indexOf("../") == 0) { 290 var pos = base.lastIndexOf("/"); 291 base = base.substring(0, pos); 292 uri = uri.substring(3); 293 return MochiKit.Base.resolveURI(uri, base); 294 } else { 295 var pos = base.lastIndexOf("/"); 296 base = base.substring(0, pos + 1); 297 return base + uri; 298 } 299 } 300