API Docs for: 0.0.1
Show:

File: lib/util.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/>.
 */

/*eslint complexity: [2, 15]*/
(function (niviz) {
  'use strict';

  var slice    = Array.prototype.slice;
  var concat   = Array.prototype.concat;
  var hasOwn   = Object.prototype.hasOwnProperty;
  var toString = Object.prototype.toString;


  /** @module niviz */

  /**
   * Common utilities.
   *
   * @class util
   * @static
   */
  var exports = niviz.util = {};

  exports.isUndefinedOrNull = function (obj) {
    if (obj === null || obj === undefined) return true;
    return false;
  };

  /**
   * nivizNode provides a few convenience functions regarding the
   * Node object passed as parameter, i. e. the height/width
   * getters and setters as well as the offset function -
   * an equivalent to jQuery's offset() function.
   *
   * @class nivizNode
   * @constructor
   *
   * @param {Node} node
   *
   * @return {nivizNode}
   */
  exports.nivizNode = function (node) {

    function nivizNode () {}

    function px (val) {
      return val + 'px';
    }

    nivizNode.height = function (val) {
      if (val === undefined || val === null)
        return node.getBoundingClientRect().height;

      node.style.height = px(val);
    };

    nivizNode.width = function (val) {
      if (val === undefined || val === null)
        return node.getBoundingClientRect().width;

      node.style.width = px(val);
    };

    nivizNode.empty = function () {
      node.innerHTML = '';
    };

    nivizNode.offset = function () {
      var rect = node.getBoundingClientRect();

      return {
        top: rect.top + document.body.scrollTop,
        left: rect.left + document.body.scrollLeft
      };
    };

    return nivizNode;
  };

  /**
   * Checks if variable is a function
   *
   * @method isFunction
   * @static
   *
   * @param {Object|Number|Function|Array} v The variable to check
   *
   * @return {Boolean} true if parameter is a function
   */
  exports.isFunction = function (v) {
    return v instanceof Function;
  };

  exports.isArray = function (arr) {
    if (typeof Array.isArray === 'function') {
      return Array.isArray(arr);
    }

    return toString.call(arr) === '[object Array]';
  };

  exports.isObject = function (obj) {
    if (!obj || toString.call(obj) !== '[object Object]')
      return false;

    var hasOwnConstructor = hasOwn.call(obj, 'constructor');
    var hasIsPrototypeOf = obj.constructor && obj.constructor.prototype
        && hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');

    // Not own constructor property must be Object
    if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf)
      return false;

    // Own properties are enumerated firstly, so to speed up,
    // if last one is own, then all properties are own.
    var key;
    for (key in obj) { /**/ }

    return typeof key === 'undefined' || hasOwn.call(obj, key);
  };

  /**
   * Extends an object with 1..n other objects.
   *
   * @method extend
   * @static
   *
   * @param {Object} obj The object to extend.
   * @param {Object} source* The source objects used to extend the object.
   *
   * @return {Object} The extended object
   */
  exports.extend = function extend() {
    var options, name, src, copy, copyIsArray, clone;
    var target = arguments[0];
    var i = 1;
    var length = arguments.length;
    var deep = false;

    // Handle a deep copy situation
    if (typeof target === 'boolean') {
      deep = target;
      target = arguments[1] || {};
      // skip the boolean and the target
      i = 2;
    }

    if (exports.isUndefinedOrNull(target)
        || (typeof target !== 'object' && typeof target !== 'function'))
      target = {};

    for (; i < length; ++i) {
      options = arguments[i];
      // Only deal with non-null/undefined values
      if (!exports.isUndefinedOrNull(options)) {
	// Extend the base object
	for (name in options) {
          src = target[name];
          copy = options[name];

          // Prevent never-ending loop
          if (target !== copy) {
            // Recurse if we're merging plain objects or arrays
            if (deep && copy && (exports.isObject(copy)
                                 || (copyIsArray = exports.isArray(copy)))) {
              if (copyIsArray) {
		copyIsArray = false;
		clone = src && exports.isArray(src) ? src : [];
              } else {
                clone = src && exports.isObject(src) ? src : {};
              }

              // Never move original objects, clone them
              target[name] = extend(deep, clone, copy);

              // Don't bring in undefined values
            } else if (typeof copy !== 'undefined') {
              target[name] = copy;
            }
          }
        }
      }
    }

    // Return the modified object
    return target;
  };

  /**
   * Format a string using sprintf-like syntax.
   *
   * @param {String} template The format string.
   * @param {String|Number|Object} parameters*
   */
  exports.format = function format(template) {
    var params;

    if (arguments.length === 1 && Array.isArray(template)) {
      params = template;
      template = params.shift();

    } else {
      params = slice.call(arguments, 1);
    }

    return template.replace(/%(s|d|j)/g, function () {
      return params.shift();
    });
  };

  /**
   * Converts CamelCase into snake_case.
   *
   * @param {String} string A CamelCased string.
   * @returns {String} The snake_cased string.
   */
  exports.underscore = function underscore(string) {
    return string
      .replace(/([a-z])([A-Z])/, '$1_$2')
      .replace(/[\s-]/, '_')
      .toLowerCase();
  };

  /**
   * Let constructor `C` inherit from `P`.
   *
   * @method inherits
   * @static
   *
   * @param {Function}  C The child constructor.
   * @param {Function}  P The parent constructor.
   *
   * @returns {Function} The extended child constructor.
   */
  exports.inherits = (function () {
    var F = function () {};

    return function inherits(C, P) {
      F.prototype = P.prototype;
      C.prototype = new F();
      C.uber = P;
      C.prototype.constructor = C;

      return C;
    };
  }());

  exports.borrow = function borrow(C, P, props) {
    exports.assert(C.prototype);
    exports.assert(P.prototype);

    for (var i = 0; i < props.length; ++i)
      C.prototype[props[i]] = P.prototype[props[i]];

    return C;
  };

  /**
   * Combines two or more arrays.
   *
   * @method zip
   * @static
   *
   * @param {Array*}
   */
  exports.zip = function zip() {
    var i, ii = 0, j, jj = arguments.length, zipped;

    for (i = 0; i < arguments.length; ++i)
      ii = Math.max(ii, arguments[i].length);

    zipped = new Array(ii);

    for (i = 0; i < ii; ++i) {
      zipped[i] = [];
      for (j = 0; j < jj; ++j)
        zipped[i].push(arguments[j][i]);
    }

    return zipped;
  };

  /**
   * Assert that `condition` holds.
   *
   * @method assert
   * @static
   *
   * @param {Boolean} condition
   * @param {String} [message]
   *
   * @throws {Error} If the condition is false.
   *
   * @returns {Boolean} The conditions value.
   */
  exports.assert = function assert(condition, message) {
    if (!condition)
      throw new Error(message || 'assertion failed');

    return true;
  };

  exports.literal = function literal(value) {
    if (value === '') return value;

    var num = Number(value);
    return isNaN(num) ? value : num;
  };

  exports.alias = function (obj, a, b) {
    Object.defineProperty(obj, a, {
      get: function () { return this[b]; }
    });

    return obj;
  };

  exports.noop = function noop() {};

  exports.delay = function delay(action, ms) {
    setTimeout(action, ms || 15);
  };

  /**
   * Picks the passed-in properties from an object.
   *
   * @method pick
   * @static
   *
   * @param {Object} obj
   * @param {String|Array<String>} keys* The keys to pick.
   *
   * @return {Object} A copy of `obj` containing only the
   *   given `keys`.
   */
  exports.pick = function pick(obj) {
    var i, ii, key, result = {}, keys = concat.apply([], slice.call(arguments, 1));

    for (i = 0, ii = keys.length; i < ii; ++i) {
      key = keys[i];
      if (key in obj) result[key] = obj[key];
    }

    return result;
  };

  /**
   * Rounds the passed in value with the specified precision.
   *
   * @method round
   * @static
   *
   * @param {Number} value
   * @param {Number} precision
   *
   * @return {Number} Rounded value
   */
  exports.round = function (value, precision) {
    exports.assert(!isNaN(Number(value)), 'Cannot round ' + value);

    var m = precision && precision > 0 ? Math.pow(10, precision) : 1;
    return Math.round(value * m) / m;
  };

  /**
   * Returns the base 10 logarithm of a number.
   *
   * @method log10
   * @static
   *
   * @param {Number} y
   * @return {Number}
   */
  exports.log10 = function (y) {
    return Math.log(y) / Math.LN10;
  };

  //Polyfills
  if (!(Object.assign instanceof Function)) {
    Object.assign = function(target) {

      if (exports.isUndefinedOrNull(target))
        throw new TypeError('Cannot convert undefined or null to object');

      target = Object(target);
      for (var index = 1; index < arguments.length; index++) {
        var source = arguments[index];
        if (!exports.isUndefinedOrNull(source)) {
          for (var key in source) {
            if (Object.prototype.hasOwnProperty.call(source, key)) {
              target[key] = source[key];
            }
          }
        }
      }
      return target;
    };
  }

  // Production steps of ECMA-262, Edition 5, 15.4.4.18
  // Reference: http://es5.github.io/#x15.4.4.18
  if (!Array.prototype.forEach) {

    Array.prototype.forEach = function(callback, thisArg) {

      var T, k;

      if (this === null) {
        throw new TypeError(' this is null or not defined');
      }

      // 1. Let O be the result of calling ToObject passing the |this| value as the argument.
      var O = Object(this);

      // 2. Let lenValue be the result of calling the Get internal method
      //    of O with the argument "length".
      // 3. Let len be ToUint32(lenValue).
      var len = O.length >>> 0;

      // 4. If IsCallable(callback) is false, throw a TypeError exception.
      // See: http://es5.github.com/#x9.11
      if (typeof callback !== 'function') {
        throw new TypeError(callback + ' is not a function');
      }

      // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
      if (arguments.length > 1) {
        T = thisArg;
      }

      // 6. Let k be 0
      k = 0;

      // 7. Repeat, while k < len
      while (k < len) {

        var kValue;

        // a. Let Pk be ToString(k).
        //    This is implicit for LHS operands of the in operator
        // b. Let kPresent be the result of calling the HasProperty internal
        //    method of O with argument Pk.
        //    This step can be combined with c
        // c. If kPresent is true, then
        if (k in O) {

          // i. Let kValue be the result of calling the Get internal method of
          //    O with argument Pk.
          kValue = O[k];

          // ii. Call the Call internal method of callback with T as the this value and
          // argument list containing kValue, k, and O.
          callback.call(T, kValue, k, O);
        }
        // d. Increase k by 1.
        k++;
      }
      // 8. return undefined
    };
  }

  // NodeList.forEach (not present in IE and Edge
  (function () {
    if (typeof NodeList.prototype.forEach === 'function') return false;
    NodeList.prototype.forEach = Array.prototype.forEach;
  })();

}(niviz));