API Docs for: 0.0.1
Show:

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) || {}));