Projekt

Obecné

Profil

Stáhnout (11.8 KB) Statistiky
| Větev: | Revize:
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;
(3-3/4)