1 /** 2 * @author Gillis Haasnoot <gillis.haasnoot@gmail.com> 3 * @package Banana.Util 4 * @summary Various utils 5 */ 6 7 /** 8 @class Banana.Util 9 @name Banana.Util 10 */ 11 12 goog.provide('Banana.Util.Utils'); 13 14 /** 15 * serializes a mixed object 16 * 17 * @param {object} data 18 */ 19 Banana.Util.serialize = function(data) 20 { 21 return JSON._stringify(data); 22 }; 23 24 /** 25 * unserializes a serialized string 26 * 27 * @param {object} data 28 */ 29 Banana.Util.unserialize = function(data) 30 { 31 return JSON.parse(data); 32 }; 33 34 /** 35 * @returns Function based on namespace 36 */ 37 Banana.Util.NamespaceToFunction = function(ns) 38 { 39 var s = ns.split('.'); 40 41 var fn = window; 42 43 for (var i = 0, len = s.length; i < len; i++) 44 { 45 fn = fn[s[i]]; 46 if (!fn) 47 { 48 return null; 49 } 50 } 51 return fn; 52 }; 53 54 /** 55 * @returns data by path name 56 * 57 */ 58 Banana.Util.getDataByPath = function(data,path) 59 { 60 var path = path.split('.'); 61 62 var d = data; 63 64 for(var i=0,len = path.length; i < len; i++) 65 { 66 d = d[path[i]]; 67 if (d == undefined) 68 { 69 return null; 70 } 71 } 72 73 return d; 74 }; 75 76 // Use the OWL library for the cloning 77 goog.require('Banana.thirdParty.OWLClone'); 78 /** 79 * Clone an object. Standard it wil do a shallow copy, when a deep clone is requested 80 * it will copy everything by value 81 * 82 * @param {mixed} data The data to clone 83 * @param {Boolean} deep Perform a deep clone 84 * 85 * @return {mixed} The cloned data 86 * @constructor 87 */ 88 Banana.Util.Clone = function(data, deep) { 89 90 if (deep) 91 { 92 return owl.deepCopy(data); 93 } 94 return owl.clone(data); 95 }; 96 97 /** 98 * Find an object by a field value 99 * 100 * @param {Array} data Dataset to search 101 * @param {mixed} field Field to search 102 * @param {mixed} value Value to find 103 * 104 * @return Object|null Object found 105 * @constructor 106 */ 107 Banana.Util.FindByField = function(data, field, value) 108 { 109 for (var x = 0, dataSize = data.length; x < dataSize; x++) { 110 if (data[x][field] == value) { 111 return data[x]; 112 } 113 } 114 return null; 115 }; 116 117 /** 118 * Combine an array collection by a field 119 * 120 * @param Array arr Array to combine 121 * @param String field Field to combine on 122 * 123 * @returns Array containing arrays with the items 124 * @constructor 125 */ 126 Banana.Util.CombineArrayByField = function(arr, field) 127 { 128 if (!(arr instanceof Array)) 129 { 130 log.error('Banana.Util.CombineArrayByField - List should be of type Array'); 131 return arr; 132 } 133 else if (!field) 134 { 135 log.error('Banana.Util.CombineArrayByField - No field given'); 136 return arr; 137 } 138 139 var list = {}; 140 141 for (var x = 0; x < arr.length; x++) 142 { 143 if (!list[arr[x][field]]) 144 { 145 list[arr[x][field]] = []; 146 } 147 list[arr[x][field]].push(arr[x]); 148 } 149 150 var result = []; 151 for (var i in list) 152 result.push(list[i]); 153 154 return result; 155 }; 156 157 /** 158 * Copies all new properties from newdata to olddata 159 * All references in olddata stay intact 160 * 161 * NOTE: arrays should contain objects with an identifier, or objects 162 * identified by their position in the array. Mixing them will lead to 163 * problems. 164 * 165 * NOTE2: olddata will be identical to newdata in the end. Properties 166 * of olddata that are not in newdata will be removed. 167 * 168 * example 169 * old [a,b,c] 170 * new [a,x,d] with x having identifier 171 * result [a,b,d] cause x is overwritten by d 172 * 173 * 174 * @param {Object} newdata 175 * @param {Object} olddata 176 * @param {String} identifier 177 * @param {Object} reference to parent object. used by framework. usefull to keep references intact 178 * @return {Object} instance of olddata with newdata recursively copied inside 179 * 180 * @constructor 181 */ 182 Banana.Util.CopyTo = function(newdata,olddata,identifier,refObj) 183 { 184 if (newdata instanceof Array && olddata instanceof Array) 185 { 186 // Empty existing items and back them up 187 var backup = olddata.splice(0, olddata.length); 188 189 for (var i = 0, len = newdata.length; i < len; i++) 190 { 191 var valueA = newdata[i]; 192 if (typeof(valueA) == 'object') //arrays and objects 193 { 194 var objectAIdentifier = valueA[identifier]; 195 196 //if our object A has an identifier 197 if (objectAIdentifier) 198 { 199 //check if we can find objectt with same id in the backup of B 200 var match = false; 201 for (var j = 0, olen = backup.length; j < olen; j++) 202 { 203 var valueB = backup[j]; 204 205 var objectBIdentifier = valueB[identifier]; 206 if (objectBIdentifier && objectBIdentifier == objectAIdentifier) 207 { 208 match = true; 209 // Let's restore from backup 210 olddata.push(valueB); 211 212 Banana.Util.CopyTo(valueA,valueB,identifier,{ref:olddata,prop:i}); 213 break; 214 } 215 } 216 217 if (!match) 218 { 219 // Item not found in backup, we'll use new one 220 olddata.push(valueA); 221 } 222 } 223 else 224 { 225 // Items is doesn't have and ID, we'll use new one 226 olddata.push(valueA); 227 } 228 } 229 else // not array/object: primitive value 230 { 231 olddata[i] = valueA; 232 } 233 } 234 } 235 236 // seems that only new data contains the array. we just copy the array to b. 237 else if (newdata instanceof Array) 238 { 239 if (refObj) 240 { 241 refObj.ref[refObj.prop] = newdata; 242 } 243 else 244 { 245 throw "Unable to assign property to object (missing reference)"; 246 } 247 } 248 249 // if its an object we also loop over it. now keys are our matches 250 else if (typeof(newdata)== 'object') 251 { 252 var deadkeys = []; 253 254 for(var prop in olddata) 255 { 256 if (typeof(olddata[prop]) == 'function') continue; 257 258 deadkeys.push(prop); 259 } 260 261 for(var prop in newdata) 262 { 263 var valueA = newdata[prop]; 264 265 if (typeof(valueA) == 'function') continue; 266 267 // Remove from dead keys array 268 var index = deadkeys.indexOf(prop); 269 if (index >= 0) 270 { 271 deadkeys.splice(index,1); 272 } 273 274 if (typeof(newdata[prop]) == 'object' && olddata[prop]) 275 { 276 Banana.Util.CopyTo(newdata[prop],olddata[prop],identifier,{ref:olddata,prop:prop}) 277 } 278 else 279 { 280 olddata[prop] = valueA; 281 } 282 } 283 284 for (var i=0;i<deadkeys.length;i++) 285 { 286 delete olddata[deadkeys[i]]; 287 } 288 } 289 290 return olddata; 291 } 292 293 /** 294 * Compares 2 objects. returns true when equal. 295 * Compares recursively 296 * 297 * @param {Object} A 298 * @param {Object} B 299 * @param {Array} list of property names which should not be compared. i.e timestamps 300 * 301 * @return {boolean} true when equal 302 * @constructor 303 */ 304 Banana.Util.ObjectsAreEqual = function(a,b,ignores) 305 { 306 return Banana.Util.ObjectPropsSameTo(a,b,ignores) && Banana.Util.ObjectPropsSameTo(b,a,ignores); 307 } 308 309 /** 310 * checks if A's properties are the same on B 311 * 312 * @param {object} a 313 * @param {object} b 314 * @param {Array} list of property names which should not be compared. i.e timestamps 315 * @return {boolean} true when equal 316 * @constructor 317 */ 318 Banana.Util.ObjectPropsSameTo = function(a,b,ignores) 319 { 320 //i we dont give either a a or b then not equal 321 if (!b || !a) 322 { 323 return false; 324 } 325 326 if (!ignores) 327 { 328 ignores = []; 329 } 330 331 //we are going to check all property in A 332 for (var prop in a) 333 { 334 if (typeof(a[prop]) == 'function') continue; 335 336 if (typeof(a[prop]) == 'object' && b[prop] !== null) 337 { 338 if (typeof b[prop] == 'object') 339 { 340 //both A and B have an object assigned to their property. we need to check if those objects are the same 341 //if not objects not the same 342 if (!Banana.Util.ObjectsAreEqual(a[prop],b[prop],ignores)) 343 { 344 return false; 345 } 346 } 347 else 348 { 349 ///b doesnt have an object in this property 350 return false; 351 } 352 } 353 354 //compare string, number and bools 355 //dont compare when prop is in the ignore list, for example datestamps 356 else if (typeof(a[prop]) == 'string' || typeof(a[prop]) == 'number' || typeof(a[prop]) == 'boolean') 357 { 358 if (ignores.indexOf(prop) == -1 && b[prop] != a[prop]) 359 { 360 return false; 361 } 362 } 363 } 364 return true; 365 }; 366 367 /** 368 * Sorts objects by key 369 * 370 * @param {Object} obj members 371 * @param {Function} sortFunc custom sort 372 * @return {Object} newObj members 373 * @constructor 374 */ 375 Banana.Util.sortObjectKeys = function(obj,sortFunc) 376 { 377 var formArray =[]; 378 379 for (var key in obj) 380 { 381 formArray.push(key); 382 } 383 384 if (sortFunc) 385 { 386 formArray.sort(sortFunc); 387 } 388 else 389 { 390 formArray.sort(); 391 } 392 393 var newObj = {}; 394 395 for (var i = 0; i < formArray.length; i++) 396 { 397 var key = formArray[i]; 398 newObj[key] = obj[key]; 399 } 400 401 return newObj; 402 }; 403 404 /** 405 * Gives a random string. Useful for UIDs 406 * @return {String} 407 * @constructor 408 */ 409 Banana.Util.generateUniqueId = function() 410 { 411 return (((1+Math.random())*0x1000000000)|0).toString(16).substring(1); 412 } 413 414 415 /** 416 * Flattens tree structure 417 * @Param {Array} array 418 * @param {String} childkey the key holding the children 419 * @constructor 420 */ 421 Banana.Util.flattenTreeDepthFirst = function(object,childKey,reversed) 422 { 423 if (!reversed) 424 { 425 reversed = []; 426 } 427 428 if (!object) 429 { 430 return reversed; 431 } 432 433 if (object[childKey]) 434 { 435 var i,len 436 for (i =0,len=object[childKey].length; i < len; i++) 437 { 438 Banana.Util.flattenTreeDepthFirst(object[childKey][i],childKey,reversed); 439 } 440 } 441 442 reversed.push(object); 443 return reversed; 444 }; 445 446 /** 447 * Iterates over array asynchronized 448 * iteration is devided into chunks where after each chunk the cpu gets time to do other tasks 449 * 450 * TODO: let the user specify the chunksize 451 * @param {Array} array to iterate over 452 * @param {Function} cb callback called every iteration 453 * @param {int} timeout between chunks, default 0 454 * @param {Function} completeCb callback. Invoked when iteration is complete 455 * @param {Function} complete chunk callback. Invoked after each chunk completion 456 * @constructor 457 */ 458 Banana.Util.arrayInteratorAsync = function(array,cb,timeout,completeCb,completeChunkCb) 459 { 460 if (typeof(cb) != 'function') 461 { 462 return log.error("No callback specified for async iterator"); 463 } 464 465 timeout = timeout || 0; 466 var chunkSize = array.length > 10 ? array.length/10 : 1; 467 468 function executeWait(loop) 469 { 470 setTimeout(function(){ 471 472 loop(); 473 474 },timeout) 475 } 476 477 var i = -1; 478 var alength = array.length; 479 480 function loop() 481 { 482 i++; 483 484 if (alength > i) 485 { 486 for (var j =0; j <= chunkSize;j++) 487 { 488 i++; 489 490 if (i > alength) 491 { 492 return completeCb(); 493 } 494 495 cb(array[i],i); 496 } 497 498 if (completeChunkCb) 499 { 500 completeChunkCb(); 501 } 502 503 executeWait(loop) 504 } 505 else if (completeCb) 506 { 507 return completeCb(); 508 } 509 }; 510 511 loop(); 512 };