1 /** 2 * Emily.js - http://flams.github.com/emily/ 3 * Copyright(c) 2012-2013 Olivier Scherrer <pode.fr@gmail.com> 4 * MIT Licensed 5 */ 6 "use strict"; 7 8 /** 9 * Get the closest number in an array 10 * @param {Number} item the base number 11 * @param {Array} array the array to search into 12 * @param {Function} getDiff returns the difference between the base number and 13 * and the currently read item in the array. The item which returned the smallest difference wins. 14 * @private 15 */ 16 function _getClosest(item, array, getDiff) { 17 var closest, 18 diff; 19 20 if (!array) { 21 return; 22 } 23 24 array.forEach(function (comparedItem, comparedItemIndex) { 25 var thisDiff = getDiff(comparedItem, item); 26 27 if (thisDiff >= 0 && (typeof diff == "undefined" || thisDiff < diff)) { 28 diff = thisDiff; 29 closest = comparedItemIndex; 30 } 31 }); 32 33 return closest; 34 } 35 36 /** 37 * @class 38 * Tools is a collection of tools 39 */ 40 module.exports = { 41 /** 42 * For applications that don't run in a browser, window is not the global object. 43 * This function returns the global object wherever the application runs. 44 * @returns {Object} the global object 45 */ 46 getGlobal: function getGlobal() { 47 return Function('return this')(); 48 }, 49 50 /** 51 * Mixes an object into another 52 * @param {Object} source object to get values from 53 * @param {Object} destination object to mix values into 54 * @param {Boolean} optional, set to true to prevent overriding 55 * @returns {Object} the destination object 56 */ 57 mixin: function mixin(source, destination, dontOverride) { 58 this.loop(source, function (value, idx) { 59 if (!destination[idx] || !dontOverride) { 60 destination[idx] = source[idx]; 61 } 62 }); 63 return destination; 64 }, 65 66 /** 67 * Count the number of properties in an object 68 * It doesn't look up in the prototype chain 69 * @param {Object} object the object to count 70 * @returns {Number} 71 */ 72 count: function count(object) { 73 var nbItems = 0; 74 this.loop(object, function () { 75 nbItems++; 76 }); 77 78 return nbItems; 79 }, 80 81 /** 82 * Compares the properties of two objects and returns true if they're the same 83 * It's doesn't do it recursively 84 * @param {Object} first object 85 * @param {Object} second object 86 * @returns {Boolean} true if the two objets have the same properties 87 */ 88 compareObjects: function compareObjects(object1, object2) { 89 var getOwnProperties = function (object) { 90 return Object.getOwnPropertyNames(object).sort().join(""); 91 }; 92 return getOwnProperties(object1) == getOwnProperties(object2); 93 }, 94 95 /** 96 * Compares two numbers and tells if the first one is bigger (1), smaller (-1) or equal (0) 97 * @param {Number} number1 the first number 98 * @param {Number} number2 the second number 99 * @returns 1 if number1>number2, -1 if number2>number1, 0 if equal 100 */ 101 compareNumbers: function compareNumbers(number1, number2) { 102 if (number1>number2) { 103 return 1; 104 } else if (number1<number2) { 105 return -1; 106 } else { 107 return 0; 108 } 109 }, 110 111 /** 112 * Transform array-like objects to array, such as nodeLists or arguments 113 * @param {Array-like object} 114 * @returns {Array} 115 */ 116 toArray: function toArray(array) { 117 return [].slice.call(array); 118 }, 119 120 /** 121 * Small adapter for looping over objects and arrays 122 * Warning: it's not meant to be used with nodeList 123 * To use with nodeList, convert to array first 124 * @param {Array/Object} iterated the array or object to loop through 125 * @param {Function} callback the function to execute for each iteration 126 * @param {Object} scope the scope in which to execute the callback 127 * @returns {Boolean} true if executed 128 */ 129 loop: function loop(iterated, callback, scope) { 130 var i, 131 length; 132 133 if (iterated instanceof Object && callback instanceof Function) { 134 if (iterated instanceof Array) { 135 for (i=0; i<iterated.length; i++) { 136 callback.call(scope, iterated[i], i, iterated); 137 } 138 } else { 139 for (i in iterated) { 140 if (iterated.hasOwnProperty(i)) { 141 callback.call(scope, iterated[i], i, iterated); 142 } 143 } 144 } 145 return true; 146 } else { 147 return false; 148 } 149 }, 150 151 /** 152 * Make a diff between two objects 153 * @param {Array/Object} before is the object as it was before 154 * @param {Array/Object} after is what it is now 155 * @example: 156 * With objects: 157 * 158 * before = {a:1, b:2, c:3, d:4, f:6} 159 * after = {a:1, b:20, d: 4, e: 5} 160 * will return : 161 * { 162 * unchanged: ["a", "d"], 163 * updated: ["b"], 164 * deleted: ["f"], 165 * added: ["e"] 166 * } 167 * 168 * It also works with Arrays: 169 * 170 * before = [10, 20, 30] 171 * after = [15, 20] 172 * will return : 173 * { 174 * unchanged: [1], 175 * updated: [0], 176 * deleted: [2], 177 * added: [] 178 * } 179 * 180 * @returns object 181 */ 182 objectsDiffs : function objectsDiffs(before, after) { 183 if (before instanceof Object && after instanceof Object) { 184 var unchanged = [], 185 updated = [], 186 deleted = [], 187 added = []; 188 189 // Look through the after object 190 this.loop(after, function (value, idx) { 191 192 // To get the added 193 if (typeof before[idx] == "undefined") { 194 added.push(idx); 195 196 // The updated 197 } else if (value !== before[idx]) { 198 updated.push(idx); 199 200 // And the unchanged 201 } else if (value === before[idx]) { 202 unchanged.push(idx); 203 } 204 205 }); 206 207 // Loop through the before object 208 this.loop(before, function (value, idx) { 209 210 // To get the deleted 211 if (typeof after[idx] == "undefined") { 212 deleted.push(idx); 213 } 214 }); 215 216 return { 217 updated: updated, 218 unchanged: unchanged, 219 added: added, 220 deleted: deleted 221 }; 222 223 } else { 224 return false; 225 } 226 }, 227 228 /** 229 * Transforms Arrays and Objects into valid JSON 230 * @param {Object/Array} object the object to JSONify 231 * @returns the JSONified object or false if failed 232 */ 233 jsonify: function jsonify(object) { 234 if (object instanceof Object) { 235 return JSON.parse(JSON.stringify(object)); 236 } else { 237 return false; 238 } 239 }, 240 241 /** 242 * Clone an Array or an Object 243 * @param {Array/Object} object the object to clone 244 * @returns {Array/Object} the cloned object 245 */ 246 clone: function clone(object) { 247 if (object instanceof Array) { 248 return object.slice(0); 249 } else if (typeof object == "object" && object !== null && !(object instanceof RegExp)) { 250 return this.mixin(object, {}); 251 } else { 252 return false; 253 } 254 }, 255 256 257 /** 258 * 259 * 260 * 261 * 262 * Refactoring needed for the following 263 * 264 * 265 * 266 * 267 * 268 */ 269 270 /** 271 * Get the property of an object nested in one or more objects 272 * given an object such as a.b.c.d = 5, getNestedProperty(a, "b.c.d") will return 5. 273 * @param {Object} object the object to get the property from 274 * @param {String} property the path to the property as a string 275 * @returns the object or the the property value if found 276 */ 277 getNestedProperty: function getNestedProperty(object, property) { 278 if (object && object instanceof Object) { 279 if (typeof property == "string" && property !== "") { 280 var split = property.split("."); 281 return split.reduce(function (obj, prop) { 282 return obj && obj[prop]; 283 }, object); 284 } else if (typeof property == "number") { 285 return object[property]; 286 } else { 287 return object; 288 } 289 } else { 290 return object; 291 } 292 }, 293 294 /** 295 * Set the property of an object nested in one or more objects 296 * If the property doesn't exist, it gets created. 297 * @param {Object} object 298 * @param {String} property 299 * @param value the value to set 300 * @returns object if no assignment was made or the value if the assignment was made 301 */ 302 setNestedProperty: function setNestedProperty(object, property, value) { 303 if (object && object instanceof Object) { 304 if (typeof property == "string" && property !== "") { 305 var split = property.split("."); 306 return split.reduce(function (obj, prop, idx) { 307 obj[prop] = obj[prop] || {}; 308 if (split.length == (idx + 1)) { 309 obj[prop] = value; 310 } 311 return obj[prop]; 312 }, object); 313 } else if (typeof property == "number") { 314 object[property] = value; 315 return object[property]; 316 } else { 317 return object; 318 } 319 } else { 320 return object; 321 } 322 }, 323 324 /** 325 * Get the closest number in an array given a base number 326 * Example: closest(30, [20, 0, 50, 29]) will return 3 as 29 is the closest item 327 * @param {Number} item the base number 328 * @param {Array} array the array of numbers to search into 329 * @returns {Number} the index of the closest item in the array 330 */ 331 closest: function closest(item, array) { 332 return _getClosest(item, array, function (comparedItem, item) { 333 return Math.abs(comparedItem - item); 334 }); 335 }, 336 337 /** 338 * Get the closest greater number in an array given a base number 339 * Example: closest(30, [20, 0, 50, 29]) will return 2 as 50 is the closest greater item 340 * @param {Number} item the base number 341 * @param {Array} array the array of numbers to search into 342 * @returns {Number} the index of the closest item in the array 343 */ 344 closestGreater: function closestGreater(item, array) { 345 return _getClosest(item, array, function (comparedItem, item) { 346 return comparedItem - item; 347 }); 348 }, 349 350 /** 351 * Get the closest lower number in an array given a base number 352 * Example: closest(30, [20, 0, 50, 29]) will return 0 as 20 is the closest lower item 353 * @param {Number} item the base number 354 * @param {Array} array the array of numbers to search into 355 * @returns {Number} the index of the closest item in the array 356 */ 357 closestLower: function closestLower(item, array) { 358 return _getClosest(item, array, function (comparedItem, item) { 359 return item - comparedItem; 360 }); 361 } 362 };