index.js
  1  'use strict';
  2  
  3  var util = require('util');
  4  var define = require('define-property');
  5  var CacheBase = require('cache-base');
  6  var Emitter = require('component-emitter');
  7  var isObject = require('isobject');
  8  var merge = require('mixin-deep');
  9  var pascal = require('pascalcase');
 10  var cu = require('class-utils');
 11  
 12  /**
 13   * Optionally define a custom `cache` namespace to use.
 14   */
 15  
 16  function namespace(name) {
 17    var Cache = name ? CacheBase.namespace(name) : CacheBase;
 18    var fns = [];
 19  
 20    /**
 21     * Create an instance of `Base` with the given `config` and `options`.
 22     *
 23     * ```js
 24     * // initialize with `config` and `options`
 25     * var app = new Base({isApp: true}, {abc: true});
 26     * app.set('foo', 'bar');
 27     *
 28     * // values defined with the given `config` object will be on the root of the instance
 29     * console.log(app.baz); //=> undefined
 30     * console.log(app.foo); //=> 'bar'
 31     * // or use `.get`
 32     * console.log(app.get('isApp')); //=> true
 33     * console.log(app.get('foo')); //=> 'bar'
 34     *
 35     * // values defined with the given `options` object will be on `app.options
 36     * console.log(app.options.abc); //=> true
 37     * ```
 38     *
 39     * @param {Object} `config` If supplied, this object is passed to [cache-base][] to merge onto the the instance upon instantiation.
 40     * @param {Object} `options` If supplied, this object is used to initialize the `base.options` object.
 41     * @api public
 42     */
 43  
 44    function Base(config, options) {
 45      if (!(this instanceof Base)) {
 46        return new Base(config, options);
 47      }
 48      Cache.call(this, config);
 49      this.is('base');
 50      this.initBase(config, options);
 51    }
 52  
 53    /**
 54     * Inherit cache-base
 55     */
 56  
 57    util.inherits(Base, Cache);
 58  
 59    /**
 60     * Add static emitter methods
 61     */
 62  
 63    Emitter(Base);
 64  
 65    /**
 66     * Initialize `Base` defaults with the given `config` object
 67     */
 68  
 69    Base.prototype.initBase = function(config, options) {
 70      this.options = merge({}, this.options, options);
 71      this.cache = this.cache || {};
 72      this.define('registered', {});
 73      if (name) this[name] = {};
 74  
 75      // make `app._callbacks` non-enumerable
 76      this.define('_callbacks', this._callbacks);
 77      if (isObject(config)) {
 78        this.visit('set', config);
 79      }
 80      Base.run(this, 'use', fns);
 81    };
 82  
 83    /**
 84     * Set the given `name` on `app._name` and `app.is*` properties. Used for doing
 85     * lookups in plugins.
 86     *
 87     * ```js
 88     * app.is('foo');
 89     * console.log(app._name);
 90     * //=> 'foo'
 91     * console.log(app.isFoo);
 92     * //=> true
 93     * app.is('bar');
 94     * console.log(app.isFoo);
 95     * //=> true
 96     * console.log(app.isBar);
 97     * //=> true
 98     * console.log(app._name);
 99     * //=> 'bar'
100     * ```
101     * @name .is
102     * @param {String} `name`
103     * @return {Boolean}
104     * @api public
105     */
106  
107    Base.prototype.is = function(name) {
108      if (typeof name !== 'string') {
109        throw new TypeError('expected name to be a string');
110      }
111      this.define('is' + pascal(name), true);
112      this.define('_name', name);
113      this.define('_appname', name);
114      return this;
115    };
116  
117    /**
118     * Returns true if a plugin has already been registered on an instance.
119     *
120     * Plugin implementors are encouraged to use this first thing in a plugin
121     * to prevent the plugin from being called more than once on the same
122     * instance.
123     *
124     * ```js
125     * var base = new Base();
126     * base.use(function(app) {
127     *   if (app.isRegistered('myPlugin')) return;
128     *   // do stuff to `app`
129     * });
130     *
131     * // to also record the plugin as being registered
132     * base.use(function(app) {
133     *   if (app.isRegistered('myPlugin', true)) return;
134     *   // do stuff to `app`
135     * });
136     * ```
137     * @name .isRegistered
138     * @emits `plugin` Emits the name of the plugin being registered. Useful for unit tests, to ensure plugins are only registered once.
139     * @param {String} `name` The plugin name.
140     * @param {Boolean} `register` If the plugin if not already registered, to record it as being registered pass `true` as the second argument.
141     * @return {Boolean} Returns true if a plugin is already registered.
142     * @api public
143     */
144  
145    Base.prototype.isRegistered = function(name, register) {
146      if (this.registered.hasOwnProperty(name)) {
147        return true;
148      }
149      if (register !== false) {
150        this.registered[name] = true;
151        this.emit('plugin', name);
152      }
153      return false;
154    };
155  
156    /**
157     * Define a plugin function to be called immediately upon init. Plugins are chainable
158     * and expose the following arguments to the plugin function:
159     *
160     * - `app`: the current instance of `Base`
161     * - `base`: the [first ancestor instance](#base) of `Base`
162     *
163     * ```js
164     * var app = new Base()
165     *   .use(foo)
166     *   .use(bar)
167     *   .use(baz)
168     * ```
169     * @name .use
170     * @param {Function} `fn` plugin function to call
171     * @return {Object} Returns the item instance for chaining.
172     * @api public
173     */
174  
175    Base.prototype.use = function(fn) {
176      fn.call(this, this);
177      return this;
178    };
179  
180    /**
181     * The `.define` method is used for adding non-enumerable property on the instance.
182     * Dot-notation is **not supported** with `define`.
183     *
184     * ```js
185     * // arbitrary `render` function using lodash `template`
186     * app.define('render', function(str, locals) {
187     *   return _.template(str)(locals);
188     * });
189     * ```
190     * @name .define
191     * @param {String} `key` The name of the property to define.
192     * @param {any} `value`
193     * @return {Object} Returns the instance for chaining.
194     * @api public
195     */
196  
197    Base.prototype.define = function(key, val) {
198      if (isObject(key)) {
199        return this.visit('define', key);
200      }
201      define(this, key, val);
202      return this;
203    };
204  
205    /**
206     * Mix property `key` onto the Base prototype. If base is inherited using
207     * `Base.extend` this method will be overridden by a new `mixin` method that will
208     * only add properties to the prototype of the inheriting application.
209     *
210     * ```js
211     * app.mixin('foo', function() {
212     *   // do stuff
213     * });
214     * ```
215     * @name .mixin
216     * @param {String} `key`
217     * @param {Object|Array} `val`
218     * @return {Object} Returns the `base` instance for chaining.
219     * @api public
220     */
221  
222    Base.prototype.mixin = function(key, val) {
223      Base.prototype[key] = val;
224      return this;
225    };
226  
227    /**
228     * Non-enumberable mixin array, used by the static [Base.mixin]() method.
229     */
230  
231    Base.prototype.mixins = Base.prototype.mixins || [];
232  
233    /**
234     * Getter/setter used when creating nested instances of `Base`, for storing a reference
235     * to the first ancestor instance. This works by setting an instance of `Base` on the `parent`
236     * property of a "child" instance. The `base` property defaults to the current instance if
237     * no `parent` property is defined.
238     *
239     * ```js
240     * // create an instance of `Base`, this is our first ("base") instance
241     * var first = new Base();
242     * first.foo = 'bar'; // arbitrary property, to make it easier to see what's happening later
243     *
244     * // create another instance
245     * var second = new Base();
246     * // create a reference to the first instance (`first`)
247     * second.parent = first;
248     *
249     * // create another instance
250     * var third = new Base();
251     * // create a reference to the previous instance (`second`)
252     * // repeat this pattern every time a "child" instance is created
253     * third.parent = second;
254     *
255     * // we can always access the first instance using the `base` property
256     * console.log(first.base.foo);
257     * //=> 'bar'
258     * console.log(second.base.foo);
259     * //=> 'bar'
260     * console.log(third.base.foo);
261     * //=> 'bar'
262     * // and now you know how to get to third base ;)
263     * ```
264     * @name .base
265     * @api public
266     */
267  
268    Object.defineProperty(Base.prototype, 'base', {
269      configurable: true,
270      get: function() {
271        return this.parent ? this.parent.base : this;
272      }
273    });
274  
275    /**
276     * Static method for adding global plugin functions that will
277     * be added to an instance when created.
278     *
279     * ```js
280     * Base.use(function(app) {
281     *   app.foo = 'bar';
282     * });
283     * var app = new Base();
284     * console.log(app.foo);
285     * //=> 'bar'
286     * ```
287     * @name #use
288     * @param {Function} `fn` Plugin function to use on each instance.
289     * @return {Object} Returns the `Base` constructor for chaining
290     * @api public
291     */
292  
293    define(Base, 'use', function(fn) {
294      fns.push(fn);
295      return Base;
296    });
297  
298    /**
299     * Run an array of functions by passing each function
300     * to a method on the given object specified by the given property.
301     *
302     * @param  {Object} `obj` Object containing method to use.
303     * @param  {String} `prop` Name of the method on the object to use.
304     * @param  {Array} `arr` Array of functions to pass to the method.
305     */
306  
307    define(Base, 'run', function(obj, prop, arr) {
308      var len = arr.length, i = 0;
309      while (len--) {
310        obj[prop](arr[i++]);
311      }
312      return Base;
313    });
314  
315    /**
316     * Static method for inheriting the prototype and static methods of the `Base` class.
317     * This method greatly simplifies the process of creating inheritance-based applications.
318     * See [static-extend][] for more details.
319     *
320     * ```js
321     * var extend = cu.extend(Parent);
322     * Parent.extend(Child);
323     *
324     * // optional methods
325     * Parent.extend(Child, {
326     *   foo: function() {},
327     *   bar: function() {}
328     * });
329     * ```
330     * @name #extend
331     * @param {Function} `Ctor` constructor to extend
332     * @param {Object} `methods` Optional prototype properties to mix in.
333     * @return {Object} Returns the `Base` constructor for chaining
334     * @api public
335     */
336  
337    define(Base, 'extend', cu.extend(Base, function(Ctor, Parent) {
338      Ctor.prototype.mixins = Ctor.prototype.mixins || [];
339  
340      define(Ctor, 'mixin', function(fn) {
341        var mixin = fn(Ctor.prototype, Ctor);
342        if (typeof mixin === 'function') {
343          Ctor.prototype.mixins.push(mixin);
344        }
345        return Ctor;
346      });
347  
348      define(Ctor, 'mixins', function(Child) {
349        Base.run(Child, 'mixin', Ctor.prototype.mixins);
350        return Ctor;
351      });
352  
353      Ctor.prototype.mixin = function(key, value) {
354        Ctor.prototype[key] = value;
355        return this;
356      };
357      return Base;
358    }));
359  
360    /**
361     * Used for adding methods to the `Base` prototype, and/or to the prototype of child instances.
362     * When a mixin function returns a function, the returned function is pushed onto the `.mixins`
363     * array, making it available to be used on inheriting classes whenever `Base.mixins()` is
364     * called (e.g. `Base.mixins(Child)`).
365     *
366     * ```js
367     * Base.mixin(function(proto) {
368     *   proto.foo = function(msg) {
369     *     return 'foo ' + msg;
370     *   };
371     * });
372     * ```
373     * @name #mixin
374     * @param {Function} `fn` Function to call
375     * @return {Object} Returns the `Base` constructor for chaining
376     * @api public
377     */
378  
379    define(Base, 'mixin', function(fn) {
380      var mixin = fn(Base.prototype, Base);
381      if (typeof mixin === 'function') {
382        Base.prototype.mixins.push(mixin);
383      }
384      return Base;
385    });
386  
387    /**
388     * Static method for running global mixin functions against a child constructor.
389     * Mixins must be registered before calling this method.
390     *
391     * ```js
392     * Base.extend(Child);
393     * Base.mixins(Child);
394     * ```
395     * @name #mixins
396     * @param {Function} `Child` Constructor function of a child class
397     * @return {Object} Returns the `Base` constructor for chaining
398     * @api public
399     */
400  
401    define(Base, 'mixins', function(Child) {
402      Base.run(Child, 'mixin', Base.prototype.mixins);
403      return Base;
404    });
405  
406    /**
407     * Similar to `util.inherit`, but copies all static properties, prototype properties, and
408     * getters/setters from `Provider` to `Receiver`. See [class-utils][]{#inherit} for more details.
409     *
410     * ```js
411     * Base.inherit(Foo, Bar);
412     * ```
413     * @name #inherit
414     * @param {Function} `Receiver` Receiving (child) constructor
415     * @param {Function} `Provider` Providing (parent) constructor
416     * @return {Object} Returns the `Base` constructor for chaining
417     * @api public
418     */
419  
420    define(Base, 'inherit', cu.inherit);
421    define(Base, 'bubble', cu.bubble);
422    return Base;
423  }
424  
425  /**
426   * Expose `Base` with default settings
427   */
428  
429  module.exports = namespace();
430  
431  /**
432   * Allow users to define a namespace
433   */
434  
435  module.exports.namespace = namespace;