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  };