/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* @license MPL 2.0
* @copyright Famous Industries, Inc. 2014
*/
/* Modified work copyright © 2015-2016 David Valdman */
define(function(require, exports, module) {
var EventHandler = require('../events/EventHandler');
/**
* A utility for setting options in a class that enables patching options
* with prescribed defaults and emitting `change` events when options are changed.
* Recursively defined for nested options objects.
*
* Note: only JSONable objects are allowed, so no functions.
*
* @class OptionsManager
* @namespace Core
* @constructor
* @private
* @uses Core.EventHandler
* @param value {Object} Options object literal
*/
function OptionsManager(value) {
this._value = value;
this._eventHandler = null;
}
/**
* Constructor method. Create OptionsManager from source dictionary with arguments overriden by patch dictionary.
*
* @method OptionsManager.patch
* @param options {Object} Options to be patched
* @param patch {...Object} Options to overwrite
* @return source {Object}
*/
OptionsManager.patch = function patch(options, patch) {
var manager = new OptionsManager(options);
for (var i = 1; i < arguments.length; i++) manager.patch(arguments[i]);
return options;
};
/**
* Constructor method. Convenience method to set options with defaults on an object instance.
*
* @method OptionsManager.patch
* @param options {Object} Options to be patched
* @param overrides {...Object} Options to overwrite
* @return source {Object}
*/
OptionsManager.setOptions = function(instance, options, defaults){
defaults = defaults || _clone(instance.constructor.DEFAULT_OPTIONS) || {};
var optionsManager = new OptionsManager(defaults);
instance.setOptions = OptionsManager.prototype.setOptions.bind(optionsManager);
instance.getOptions = OptionsManager.prototype.getOptions.bind(optionsManager);
if (options) instance.setOptions(options);
return optionsManager.get();
};
function _createEventHandler() {
if (!this._eventHandler) this._eventHandler = new EventHandler();
}
/**
* Patch options with provided patches. Triggers `change` event on the object.
*
* @method patch
* @param options {Object} Patch options
* @return this {OptionsManager}
*/
OptionsManager.prototype.patch = function patch(options, clone) {
var myState = this._value;
for (var k in options) {
if ((k in myState) && (options[k] && options[k].constructor === Object) && (myState[k] && myState[k].constructor === Object)) {
if (!myState.hasOwnProperty(k)) myState[k] = Object.create(myState[k]);
this.key(k).patch(options[k]);
if (this._eventHandler) this._eventHandler.emit('change', {key: k, value: this.key(k).value()});
}
else this.set(k, options[k]);
}
return this;
};
/**
* Alias for patch
*
* @method setOptions
*/
OptionsManager.prototype.setOptions = OptionsManager.prototype.patch;
/**
* Return OptionsManager based on sub-object retrieved by `key`.
*
* @method key
* @param key {string} Key
* @return {OptionsManager} Value
*/
OptionsManager.prototype.key = function key(key) {
var result = new OptionsManager(this._value[key]);
if (!(result._value instanceof Object) || result._value instanceof Array) result._value = {};
return result;
};
/**
* Look up options value by key or get the full options hash.
*
* @method get
* @param key {string} Key
* @return {Object} Associated object or full options hash
*/
OptionsManager.prototype.get = function get(key) {
return key ? this._value[key] : this._value;
};
/**
* Alias for get
*
* @method getOptions
*/
OptionsManager.prototype.getOptions = OptionsManager.prototype.get;
/**
* Set key to value. Outputs `change` event if a value is overwritten.
*
* @method set
* @param key {string} Key
* @param value {Object} Value
* @return {OptionsManager} Updated OptionsManager
*/
OptionsManager.prototype.set = function set(key, value) {
var originalValue = this.get(key);
this._value[key] = value;
if (this._eventHandler && value !== originalValue) this._eventHandler.emit('change', {key: key, value: value});
return this;
};
/**
* Adds a handler to the `type` channel which will be executed on `emit`.
*
* @method "on"
* @param type {String} Channel name
* @param handler {Function} Callback
*/
OptionsManager.prototype.on = function on(type, handler) {
_createEventHandler.call(this);
EventHandler.prototype.on.apply(this._eventHandler, arguments);
};
/**
* Removes the `handler` from the `type` channel.
* This undoes the work of `on`.
*
* @method off
* @param type {String} Channel name
* @param handler {Function} Callback
*/
OptionsManager.prototype.off = function off(type, handler) {
_createEventHandler.call(this);
EventHandler.prototype.off.apply(this._eventHandler, arguments);
};
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;
}
module.exports = OptionsManager;
});