File: lib/config.js
/*
* niViz -- snow profiles visualization
* Copyright (C) 2015 WSL/SLF - Fluelastrasse 11 - 7260 Davos Dorf - Switzerland.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
(function (niviz, storage) {
'use strict';
// --- Module Dependencies ---
var property = Object.defineProperty;
var properties = Object.defineProperties;
var util = niviz.util;
var assert = util.assert;
var underscore = util.underscore;
/** @module niviz */
/**
* A serializable JSON configuration that may be persisted in the local storage.
* A Config object is a collection of Setting objects.
*
* @class Config
* @constructor
*
* @param {String} [name=''] The configuration's name.
* @param {Array} [settings] The initial list of settings.
* @param {String} [display_name=''] The configurations display name.
*/
function Config(name, settings, display_name) {
this.name = name;
this.displayName = display_name || '';
this.settings = {
order: [], names: {}
};
if (settings) { this.add(settings); }
}
/**
* The namespace for niviz configurations within the local storage.
*
* @property namespace
* @type String
* @static
*/
Config.namespace = 'niviz';
properties(Config.prototype, {
/**
* The config id to use for the local storage.
* @property id
* @type String
*/
id: {
get: function () {
return [Config.namespace, underscore(this.name)].join('$');
}
},
/**
* The config values as an Object with a property for each setting.
* @property values
* @type Object
*/
values: {
get: function () {
return this.settings.order.reduce(by_value, {});
}
}
});
/**
* Add settings to the configuration.
*
* @method add
* @chainable
* @param {Array<Object>} settings
*/
Config.prototype.add = function (settings) {
assert(Array.isArray(settings));
settings.forEach(function (setting) {
setting = new Setting(setting);
assert(this[setting.name] === undefined, 'setting name must be unique!');
this.settings.order.push(setting);
this.settings.names[setting.name] = setting;
property(this, setting.name, {
get: this.get.bind(this, setting.name),
set: this.set.bind(this, setting.name)
});
}.bind(this));
return this;
};
/**
* Retrieve sepecific setting from config.
*
* @method get
* @param {String} name The name of the setting to retrieve
* @return {Object|String|Array<Object>} Return specific setting
*/
Config.prototype.get = function (name) {
var setting = this.settings.names[name];
return setting && setting.get();
};
/**
* Set a specific setting in this config.
*
* @method set
* @chainable
* @param {String} name The name of the setting to set
* @param {Object|String|Array<Object>} value The value of the setting
*/
Config.prototype.set = function (name, value) {
var setting = this.settings.names[name];
// Silently ignores unknown settings!
if (setting) { setting.set(value); }
return this;
};
/**
* Store the config in the local storage.
*
* @method store
* @chainable
*/
Config.prototype.store = function () {
storage[this.id] = JSON.stringify(this.values);
return this;
};
/**
* Load the config from the local storage.
*
* @method load
* @chainable
*/
Config.prototype.load = function () {
var stored = storage[this.id];
if (stored) {
this.merge(JSON.parse(stored));
}
return this;
};
/**
* Take a JSON object and merge its properties into the config.
*
* @method merge
* @chainable
* @param {Object} values
*/
Config.prototype.merge = function (values) {
for (var name in values) {
if (values.hasOwnProperty(name)) {
this.set(name, values[name]);
}
}
return this;
};
/**
* A configuration setting template.
*
* @class Setting
* @constructor
*
* @param {Object} obj The setting template.
*/
function Setting(obj) {
assert(obj && obj.name, 'Settings must have a name!');
this.name = obj.name;
this.type = obj.type || 'string';
this.value = obj.value;
this.values = obj.values;
this.hint = obj.hint;
this.default = obj.default;
this.required = obj.required || false;
}
/**
* A normalization for objects and arrays. Strips any AngularJS hashKeys
* from the objects.
*
* @method passthrough
* @static
* @param {Object|Array<Object>} object
* @return {Object|Array<Object>}
*/
Setting.passthrough = function (object) {
if (object && object.forEach) // HACK for angular
object.forEach(function(el) {
if (el.$$hashKey) delete el.$$hashKey;
});
return object;
};
/**
* The normalization schemes for different types of settings.
*
* @property normalize
* @type Object
* @static
*/
Setting.normalize = {
string: String, color: String, number: Number, boolean: Boolean,
select2: Setting.passthrough, meteo: Setting.passthrough,
pro: Setting.passthrough, barparams: Setting.passthrough,
timelineparams: Setting.passthrough, meteogroup: Setting.passthrough
};
properties(Setting.prototype, {
/**
* Test whether the setting has a value.
* @property empty
* @type Boolean
*/
empty: {
get: function () { return this.value === undefined; }
},
/**
* The type of setting.
* @property element
* @type String
*/
element: {
get: function () {
if (this.type === 'color') return 'colorpicker';
if (this.type === 'boolean') return 'checkbox';
if (this.type === 'select2') return 'select2';
if (this.type === 'meteo') return 'meteo';
if (this.type === 'meteogroup') return 'meteogroup';
if (this.type === 'pro') return 'pro';
if (this.type === 'barparams') return 'barparams';
if (this.type === 'timelineparams') return 'timelineparams';
if (this.values && this.values !== null) return 'select';
return 'input';
}
}
});
/**
* Get the current value of the setting or the default value.
*
* @method get
* @return {Object}
*/
Setting.prototype.get = function () {
return (this.empty) ? this.default : this.value;
};
/**
* Set the value of the setting.
*
* @method set
* @chainable
* @param {Object} value
*/
Setting.prototype.set = function (value) {
if (value === undefined || value === null || value === '') { return this.clear(); }
value = this.normalize(value);
this.value = value;
return this;
};
/**
* Normalize the value according to its type,
* the exact scheme is specified in Setting.normalize.
*
* @method normalize
* @param {Object} value
*/
Setting.prototype.normalize = function (value) {
return Setting.normalize[this.type](value);
};
/**
* Delete the value of the setting.
*
* @method clear
* @chainable
*/
Setting.prototype.clear = function () {
delete this.value;
return this;
};
// --- Helper ---
function by_value(values, setting) {
values[setting.name] = setting.get();
if (Array.isArray(values[setting.name])) {
values[setting.name] = JSON.parse(JSON.stringify(values[setting.name]));
}
return values;
}
// --- Module Export ---
Config.Setting = Setting;
Config.storage = storage;
niviz.Config = Config;
niviz.config = {};
}(niviz, ((navigator.userAgent !== 'PhantomJS') && window.localStorage) || {}));