index.js
1 'use strict'; 2 3 var util = require('util'); 4 var union = require('arr-union'); 5 var define = require('define-property'); 6 var staticExtend = require('static-extend'); 7 var isObj = require('isobject'); 8 9 /** 10 * Expose class utils 11 */ 12 13 var cu = module.exports; 14 15 /** 16 * Expose class utils: `cu` 17 */ 18 19 cu.isObject = function isObject(val) { 20 return isObj(val) || typeof val === 'function'; 21 }; 22 23 /** 24 * Returns true if an array has any of the given elements, or an 25 * object has any of the give keys. 26 * 27 * ```js 28 * cu.has(['a', 'b', 'c'], 'c'); 29 * //=> true 30 * 31 * cu.has(['a', 'b', 'c'], ['c', 'z']); 32 * //=> true 33 * 34 * cu.has({a: 'b', c: 'd'}, ['c', 'z']); 35 * //=> true 36 * ``` 37 * @param {Object} `obj` 38 * @param {String|Array} `val` 39 * @return {Boolean} 40 * @api public 41 */ 42 43 cu.has = function has(obj, val) { 44 val = cu.arrayify(val); 45 var len = val.length; 46 47 if (cu.isObject(obj)) { 48 for (var key in obj) { 49 if (val.indexOf(key) > -1) { 50 return true; 51 } 52 } 53 54 var keys = cu.nativeKeys(obj); 55 return cu.has(keys, val); 56 } 57 58 if (Array.isArray(obj)) { 59 var arr = obj; 60 while (len--) { 61 if (arr.indexOf(val[len]) > -1) { 62 return true; 63 } 64 } 65 return false; 66 } 67 68 throw new TypeError('expected an array or object.'); 69 }; 70 71 /** 72 * Returns true if an array or object has all of the given values. 73 * 74 * ```js 75 * cu.hasAll(['a', 'b', 'c'], 'c'); 76 * //=> true 77 * 78 * cu.hasAll(['a', 'b', 'c'], ['c', 'z']); 79 * //=> false 80 * 81 * cu.hasAll({a: 'b', c: 'd'}, ['c', 'z']); 82 * //=> false 83 * ``` 84 * @param {Object|Array} `val` 85 * @param {String|Array} `values` 86 * @return {Boolean} 87 * @api public 88 */ 89 90 cu.hasAll = function hasAll(val, values) { 91 values = cu.arrayify(values); 92 var len = values.length; 93 while (len--) { 94 if (!cu.has(val, values[len])) { 95 return false; 96 } 97 } 98 return true; 99 }; 100 101 /** 102 * Cast the given value to an array. 103 * 104 * ```js 105 * cu.arrayify('foo'); 106 * //=> ['foo'] 107 * 108 * cu.arrayify(['foo']); 109 * //=> ['foo'] 110 * ``` 111 * 112 * @param {String|Array} `val` 113 * @return {Array} 114 * @api public 115 */ 116 117 cu.arrayify = function arrayify(val) { 118 return val ? (Array.isArray(val) ? val : [val]) : []; 119 }; 120 121 /** 122 * Noop 123 */ 124 125 cu.noop = function noop() { 126 return; 127 }; 128 129 /** 130 * Returns the first argument passed to the function. 131 */ 132 133 cu.identity = function identity(val) { 134 return val; 135 }; 136 137 /** 138 * Returns true if a value has a `contructor` 139 * 140 * ```js 141 * cu.hasConstructor({}); 142 * //=> true 143 * 144 * cu.hasConstructor(Object.create(null)); 145 * //=> false 146 * ``` 147 * @param {Object} `value` 148 * @return {Boolean} 149 * @api public 150 */ 151 152 cu.hasConstructor = function hasConstructor(val) { 153 return cu.isObject(val) && typeof val.constructor !== 'undefined'; 154 }; 155 156 /** 157 * Get the native `ownPropertyNames` from the constructor of the 158 * given `object`. An empty array is returned if the object does 159 * not have a constructor. 160 * 161 * ```js 162 * cu.nativeKeys({a: 'b', b: 'c', c: 'd'}) 163 * //=> ['a', 'b', 'c'] 164 * 165 * cu.nativeKeys(function(){}) 166 * //=> ['length', 'caller'] 167 * ``` 168 * 169 * @param {Object} `obj` Object that has a `constructor`. 170 * @return {Array} Array of keys. 171 * @api public 172 */ 173 174 cu.nativeKeys = function nativeKeys(val) { 175 if (!cu.hasConstructor(val)) return []; 176 var keys = Object.getOwnPropertyNames(val); 177 if ('caller' in val) keys.push('caller'); 178 return keys; 179 }; 180 181 /** 182 * Returns property descriptor `key` if it's an "own" property 183 * of the given object. 184 * 185 * ```js 186 * function App() {} 187 * Object.defineProperty(App.prototype, 'count', { 188 * get: function() { 189 * return Object.keys(this).length; 190 * } 191 * }); 192 * cu.getDescriptor(App.prototype, 'count'); 193 * // returns: 194 * // { 195 * // get: [Function], 196 * // set: undefined, 197 * // enumerable: false, 198 * // configurable: false 199 * // } 200 * ``` 201 * 202 * @param {Object} `obj` 203 * @param {String} `key` 204 * @return {Object} Returns descriptor `key` 205 * @api public 206 */ 207 208 cu.getDescriptor = function getDescriptor(obj, key) { 209 if (!cu.isObject(obj)) { 210 throw new TypeError('expected an object.'); 211 } 212 if (typeof key !== 'string') { 213 throw new TypeError('expected key to be a string.'); 214 } 215 return Object.getOwnPropertyDescriptor(obj, key); 216 }; 217 218 /** 219 * Copy a descriptor from one object to another. 220 * 221 * ```js 222 * function App() {} 223 * Object.defineProperty(App.prototype, 'count', { 224 * get: function() { 225 * return Object.keys(this).length; 226 * } 227 * }); 228 * var obj = {}; 229 * cu.copyDescriptor(obj, App.prototype, 'count'); 230 * ``` 231 * @param {Object} `receiver` 232 * @param {Object} `provider` 233 * @param {String} `name` 234 * @return {Object} 235 * @api public 236 */ 237 238 cu.copyDescriptor = function copyDescriptor(receiver, provider, name) { 239 if (!cu.isObject(receiver)) { 240 throw new TypeError('expected receiving object to be an object.'); 241 } 242 if (!cu.isObject(provider)) { 243 throw new TypeError('expected providing object to be an object.'); 244 } 245 if (typeof name !== 'string') { 246 throw new TypeError('expected name to be a string.'); 247 } 248 249 var val = cu.getDescriptor(provider, name); 250 if (val) Object.defineProperty(receiver, name, val); 251 }; 252 253 /** 254 * Copy static properties, prototype properties, and descriptors 255 * from one object to another. 256 * 257 * @param {Object} `receiver` 258 * @param {Object} `provider` 259 * @param {String|Array} `omit` One or more properties to omit 260 * @return {Object} 261 * @api public 262 */ 263 264 cu.copy = function copy(receiver, provider, omit) { 265 if (!cu.isObject(receiver)) { 266 throw new TypeError('expected receiving object to be an object.'); 267 } 268 if (!cu.isObject(provider)) { 269 throw new TypeError('expected providing object to be an object.'); 270 } 271 var props = Object.getOwnPropertyNames(provider); 272 var keys = Object.keys(provider); 273 var len = props.length, 274 key; 275 omit = cu.arrayify(omit); 276 277 while (len--) { 278 key = props[len]; 279 280 if (cu.has(keys, key)) { 281 define(receiver, key, provider[key]); 282 } else if (!(key in receiver) && !cu.has(omit, key)) { 283 cu.copyDescriptor(receiver, provider, key); 284 } 285 } 286 }; 287 288 /** 289 * Inherit the static properties, prototype properties, and descriptors 290 * from of an object. 291 * 292 * @param {Object} `receiver` 293 * @param {Object} `provider` 294 * @param {String|Array} `omit` One or more properties to omit 295 * @return {Object} 296 * @api public 297 */ 298 299 cu.inherit = function inherit(receiver, provider, omit) { 300 if (!cu.isObject(receiver)) { 301 throw new TypeError('expected receiving object to be an object.'); 302 } 303 if (!cu.isObject(provider)) { 304 throw new TypeError('expected providing object to be an object.'); 305 } 306 307 var keys = []; 308 for (var key in provider) { 309 keys.push(key); 310 receiver[key] = provider[key]; 311 } 312 313 keys = keys.concat(cu.arrayify(omit)); 314 315 var a = provider.prototype || provider; 316 var b = receiver.prototype || receiver; 317 cu.copy(b, a, keys); 318 }; 319 320 /** 321 * Returns a function for extending the static properties, 322 * prototype properties, and descriptors from the `Parent` 323 * constructor onto `Child` constructors. 324 * 325 * ```js 326 * var extend = cu.extend(Parent); 327 * Parent.extend(Child); 328 * 329 * // optional methods 330 * Parent.extend(Child, { 331 * foo: function() {}, 332 * bar: function() {} 333 * }); 334 * ``` 335 * @param {Function} `Parent` Parent ctor 336 * @param {Function} `extend` Optional extend function to handle custom extensions. Useful when updating methods that require a specific prototype. 337 * @param {Function} `Child` Child ctor 338 * @param {Object} `proto` Optionally pass additional prototype properties to inherit. 339 * @return {Object} 340 * @api public 341 */ 342 343 cu.extend = function() { 344 // keep it lazy, instead of assigning to `cu.extend` 345 return staticExtend.apply(null, arguments); 346 }; 347 348 /** 349 * Bubble up events emitted from static methods on the Parent ctor. 350 * 351 * @param {Object} `Parent` 352 * @param {Array} `events` Event names to bubble up 353 * @api public 354 */ 355 356 cu.bubble = function(Parent, events) { 357 events = events || []; 358 Parent.bubble = function(Child, arr) { 359 if (Array.isArray(arr)) { 360 events = union([], events, arr); 361 } 362 var len = events.length; 363 var idx = -1; 364 while (++idx < len) { 365 var name = events[idx]; 366 Parent.on(name, Child.emit.bind(Child, name)); 367 } 368 cu.bubble(Child, events); 369 }; 370 };