KozUtils.js

"use strict";

/**
 * @namespace KozUtils
 * @desc Some useful function to make working with parameters more reliable and less painful in JavaScript.
 * @author Kozalo <kozalo@yandex.ru>
 * @copyright Kozalo.Ru, 2016
 */
let KozUtils = {
    /**
    * Copies given options into the defaultOptions object and returns the result.
    * @memberof KozUtils
    * @param {Object} options Object with user settings.
    * @param {Object} defaultOptions Specify a list of default parameters here.
    * @returns {Object} The members of the options object overrides ones of the defaultOptions to get the result.
    */
	initOptions: function(options, defaultOptions) {
        if (options === undefined)
            return defaultOptions;

        for (var optionName in options)
            defaultOptions[optionName] = options[optionName];

        return defaultOptions;
    },


    /**
    * Checks if all required parameters have been passed into the function. Throws an exception if any of them are missing.
    * @memberof KozUtils
    * @param {object[]|object} parameters Either an array of variables or a variable to check.
    * @param {string=|function=} funcName The name of the function. Used to make the description of an exception more detailed. If you pass a function, extracts its name automatically.
    * @param {Array<string>=|string=} paramNames Either an array of strings or a string. Pass it in the same order as _parameters_! Used to make the description of the exception more detailed as well.
    * @throws KozExceptions.missingArgument or a string exception.
    * @example
    * function foo(bar) {
    *     // KozUtils.checkMissingParameters(bar);
    *     // KozUtils.checkMissingParameters([bar], "foo");
    *     // KozUtils.checkMissingParameters([bar], "foo", ['bar']);
    *     KozUtils.checkMissingParameters(bar, "foo", "bar");
    *     document.writeln(bar);
    * }
    */
    checkMissingParameters: function(parameters, funcName, paramNames) {
    	if (! (parameters instanceof Array) )
    		parameters = [parameters];
    	if (! (paramNames instanceof Array) )
    		paramNames = [paramNames];
        if (typeof(funcName) === "function")
            funcName = funcName.name;

    	parameters.forEach(function (opt, index) {
    		if (opt === undefined) {
    			let errorObj = {
    				function: funcName,
    				parameter: {
    					index: index + 1,
    					name: paramNames[index]
    				}
    			};

                if (KozExceptions != undefined)
                    throw new KozExceptions.missingArgument(errorObj.parameter.name, JSON.stringify(errorObj))
    			else
                    throw "Missing required parameter: " + JSON.stringify(errorObj);
    		}
    	});
    },


    /**
    * Not only checks if all required parameters have been passed into the function, but also checks their types. Throws exceptions if any of them are missing or invalid.
    * @memberof KozUtils
    * @param {object[]|object} parameters Either an array of variables or a variable to check.
    * @param {string[]|string} types String representations of expected types for typeof-comparison.
    * @param {string=|function=} funcName The name of the function. Used to make the description of an exception more detailed. If you pass a function, extracts its name automatically.
    * @param {Array<string>=|string=} paramNames Either an array of strings or a string. Pass it in the same order as _parameters_! Used to make the description of the exception more detailed as well.
    * @throws KozExceptions.missingArgument, KozExceptions.invalidArgument or their equivalents.
    * @example
    * function foo(bar) {
    *     // KozUtils.checkParameters(bar, "string");
    *     // KozUtils.checkParameters([bar], "number", "foo");
    *     // KozUtils.checkParameters([bar], "string", "foo", ['bar']);
    *     KozUtils.checkParameters(bar, "number", "foo", "bar");
    *     document.writeln(bar);
    * }
    */
    checkParameters: function(parameters, types, funcName, paramNames) {
        KozUtils.checkMissingParameters([parameters, types], funcName, [paramNames, types]);

        if (! (parameters instanceof Array) )
            parameters = [parameters];
        if (! (types instanceof Array) )
            types = [types];
        if (typeof(funcName) === "function")
            funcName = funcName.name;

        if (paramNames == undefined)
            paramNames = new Array(parameters.length);
        else if (! (paramNames instanceof Array) )
            paramNames = [paramNames];


        for (let i in parameters) {
            if (typeof(parameters[i]) !== types[i])
            {
                let invalidArgumentMessage;
                if (paramNames[i] != undefined && funcName != undefined)
                    invalidArgumentMessage = funcName + ': type of "' + paramNames[i] + '" must be: "' + types[i] + '"!';
                else if (paramNames[i] != undefined && funcName == undefined)
                    invalidArgumentMessage = 'Type of "' + paramNames[i] + '" must be: "' + types[i] + '"!';
                else if (paramNames[i] == undefined && funcName != undefined)
                    invalidArgumentMessage = funcName + ': ' + types[i] + ' is expecting!';
                else
                    invalidArgumentMessage = types[i][0].toUpperCase() + types[i].substr(1) + ' is expecting!';

                if (typeof(KozExceptions) != undefined)
                    throw new KozExceptions.invalidArgument(paramNames[i], invalidArgumentMessage);
                else
                    throw invalidArgumentMessage;
            }
        }
    },


    /**
    * Gets the actual current size of an HTMLElement.
    * @memberof KozUtils
    * @param {HTMLElement} element Any HTMLElement.
    * @throws KozExceptions.invalidArgument or a string exception.
    * @returns {Object} The object has 2 fields: 'width' and 'height'.
    */
    getActualSize: function(element) {
        KozUtils.checkParameters(element, "HTMLElement", "getActualSize", "element");

        let rect = element.getBoundingClientRect();
        let width = rect.width;
        let height = rect.height;
        
        let styles = window.getComputedStyle(element);

        let marginTop = parseFloat(styles.marginTop);
        let marginBottom = parseFloat(styles.marginBottom);
        let paddingTop = parseFloat(styles.paddingTop);
        let paddingBottom = parseFloat(styles.paddingBottom);

        let marginLeft = parseFloat(styles.marginLeft);
        let marginRight = parseFloat(styles.marginRight);
        let paddingLeft = parseFloat(styles.paddingLeft);
        let paddingRight = parseFloat(styles.paddingRight);

        let actualWidth = (width - marginLeft - marginRight - paddingLeft - paddingRight) + "px";
        let actualHeight = (height - marginTop - marginBottom - paddingTop - paddingBottom) + "px";

        return {
            width: actualWidth,
            height: actualHeight
        };
    }
};