/* Copyright © 2015-2016 David Valdman */
define(function(require, exports, module) {
var OptionsManager = require('./OptionsManager');
var EventHandler = require('../events/EventHandler');
var SimpleStream = require('../streams/SimpleStream');
/**
* A utility class which can be extended by custom classes. These classes will then
* include event input and output streams, a optionsManager for handling optional
* parameters with defaults, and take an event dictionary.
*
* Specifically, instantiations will have an `options` dictionary property,
* `input`, `output` stream properties, and
* `on`, `off`, `emit`, `trigger`, `subscribe`, `unsubscribe` methods.
*
* @example
*
* var MyClass = Controller.extend({
* defaults : {
* defaultOption1 : value1,
* defaultOption2 : value2
* },
* events : {
* 'change' : myUpdateOptionsFunction
* },
* initialize : function(options){
* // this method called on instantiation
* // options are passed in after being patched by the specified defaults
*
* this.input.on('test', function(){
* console.log('test fired');
* });
* }
* });
*
* var myInstance = new MyClass({
* defaultOption1 : value3
* });
*
* // myInstance.options = {
* // defaultOption1 : value3,
* // defaultOption2 : value2
* // }
*
* myInstance.subscribe(anotherStream);
*
* anotherStream.emit('test'); // "test fired" in console
*
* @class Controller
* @private
* @constructor
* @namespace Core
* @uses Core.OptionsManager
* @param options {Object} Instance options
*/
function Controller(options) {
this.options = _clone(this.constructor.DEFAULT_OPTIONS || Controller.DEFAULT_OPTIONS);
this._optionsManager = new OptionsManager(this.options);
if (options) this.setOptions(options);
this.input = new SimpleStream();
this.output = new SimpleStream();
EventHandler.setInputHandler(this, this.input);
EventHandler.setOutputHandler(this, this.output);
EventHandler.setInputEvents(this, this.constructor.EVENTS || Controller.EVENTS, this.input);
this.input.bindThis(this);
this.input.subscribe(this._optionsManager);
if (this.initialize) this.initialize(this.options);
}
/**
* Overwrite the DEFAULT_OPTIONS dictionary on the constructor of the class you wish to extend
* with the Controller to patch any options that are not prescribed on instantiation.
*
* @attribute DEFAULT_OPTIONS
* @readOnly
*/
Controller.DEFAULT_OPTIONS = {};
/**
* Overwrite the EVENTS dictionary on the constructor of the class you wish to extend
* with the Controller to include events in {key : value} pairs where the keys are
* event channel names and the values are functions to be executed.
*
* @attribute EVENTS
* @readOnly
*/
Controller.EVENTS = {};
/**
* Options getter.
*
* @method getOptions
* @param key {string} Key
* @return object {Object} Options value for the key
*/
Controller.prototype.getOptions = function getOptions(key) {
return OptionsManager.prototype.getOptions.apply(this._optionsManager, arguments);
};
/**
* Options setter.
*
* @method setOptions
* @param options {Object} Options
*/
Controller.prototype.setOptions = function setOptions() {
OptionsManager.prototype.setOptions.apply(this._optionsManager, arguments);
};
var RESERVED_KEYS = {
DEFAULTS : 'defaults',
EVENTS : 'events'
};
function _clone(obj) {
var copy;
if (typeof obj === 'object') {
copy = (obj instanceof Array) ? [] : {};
for (var key in obj) {
var value = obj[key];
if (typeof value === 'object' && value !== null) {
if (value instanceof Array) {
copy[key] = [];
for (var i = 0; i < value.length; i++)
copy[key][i] = _clone(value[i]);
}
else copy[key] = _clone(value);
}
else copy[key] = value;
}
}
else copy = obj;
return copy;
}
function extend(protoObj, constants){
var parent = this;
var child = (protoObj.hasOwnProperty('constructor'))
? function(){ protoObj.constructor.apply(this, arguments); }
: function(){ parent.apply(this, arguments); };
child.extend = extend;
child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;
for (var key in protoObj){
var value = protoObj[key];
switch (key) {
case RESERVED_KEYS.DEFAULTS:
child.DEFAULT_OPTIONS = value;
break;
case RESERVED_KEYS.EVENTS:
if (!child.EVENTS) child.EVENTS = value;
else
for (var key in value)
child.EVENTS[key] = value[key];
break;
default:
child.prototype[key] = value;
}
}
for (var key in constants)
child[key] = constants[key];
return child;
}
/**
* Allows a class to extend Controller.
* Note: this is a method defined on the Controller constructor
*
* @method extend
* @param protoObj {Object} Prototype properties of the extended class
* @param constants {Object} Constants to be added to the extended class's constructor
*/
Controller.extend = extend;
module.exports = Controller;
});