import $ from "jquery";
/*
Last edit date: 05.06.2017
*/
/**
* A class with static methods used across all Shark components.
* @constructor
* @version 0.1.2.36 - 2017.06.05
*/
class Utils {
constructor() {
this.__id = "Utils";
}
/**
* Return a random number between 0 and a max value ora between two values.
* @static
* @version 0.1.0.1
* @param {number} minOrMax - If this is the only passed parameter thant it will be used as "max", and the return number will be between 0 and this number. If the max parameter is passed, then this parameter will be as lower limit.
* @param {number} [max] - The higher limit for the generate number.
* @returns {number} - The random number generated.
*/
static betterRandom (minOrMax, max) {
let min = parseFloat(minOrMax);
let returnValue = Math.random();
max = parseFloat(max);
if (!max && minOrMax) {
min = 0;
max = parseFloat(minOrMax);
}
if (!min) {
min = 0;
}
if (!max) {
max = 1;
}
returnValue *= (max - min);
returnValue += min;
return returnValue;
}
/**
* Return a random integer number between 0 and a max value ora between two values. If not integer numbers are passed in, the result will be anyway an integer.
* @static
* @version 0.1.0.1
* @param {number} minOrMax - If this is the only passed parameter thant it will be used as "max", and the return number will be between 0 and this number. If the max parameter is passed, then this parameter will be as lower limit.
* @param {number} [max] - The higher limit for the generate number.
*/
static betterRandomInt (minOrMax, max) {
return Math.round(Utils.betterRandom(minOrMax, max));
}
/**
* Convert a string with camel-cased text to a dash separated words. Each uppercase character will be considered as the word starter and lower-cased.
* @static
* @version 0.1.0.1
* @param {string} inputString - The camelCased string.
* @returns {string} - The dash-converted string.
*/
static camelToDashed (inputString) {
let newString = "";
inputString.split("").forEach((currLetter, index) => {
if (currLetter.toLowerCase() !== currLetter) {
if (index > 0) {
newString += "-";
}
currLetter = currLetter.toLowerCase();
}
newString += currLetter;
});
return newString;
}
//Valutare questa:
// async getHash (m) {
// const msgUint8 = new TextEncoder().encode(m);
// const hashBuffer = await crypto.subtle.digest('SHA-256', msgUint8);
// const hashArray = Array.from(new Uint8Array(hashBuffer));
// const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
// return hashHex;
// }
static computeHash (value) {
const stringified = Utils.safeStringify(value) || "";
return stringified.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0);
}
/**
* Convert a string with dash as word separator to a camel-cased text. The first letter after each dash is upper-cased and joined with previous word.
* @static
* @version 0.1.0.1
* @param {string} inputString - The camelCased string.
* @returns {string} - The camel-cased string.
*/
static dashedToCamel (inputString) {
let newString = inputString;
let dashPos = 0;
while (dashPos > -1) {
dashPos = newString.indexOf("-");
if (dashPos > -1) {
newString = newString.substr(0, dashPos) + newString.substr(dashPos + 1, 1).toUpperCase() + newString.substr(dashPos + 2);
}
}
return newString;
}
static extractDifferentProperties (firstObject, secondObject) {
const returnObject = {};
for (let prop in firstObject) {
if (firstObject[prop] && secondObject[prop]) {
if (firstObject[prop] !== secondObject[prop]) {
returnObject[prop] = secondObject[prop];
}
}
}
return returnObject;
}
static flatten (objectToFlatten) {
const result = {}; //Object.create(objectToFlatten);
for (var key in objectToFlatten) {
//console.log(key, objectToFlatten[key], typeof objectToFlatten[key]);
if (typeof objectToFlatten[key] !== 'function') {
if (typeof objectToFlatten[key] === "object") {
result[key] = Utils.flatten(objectToFlatten[key]);
}
else {
result[key] = objectToFlatten[key];
}
}
}
//console.warn("Utils.flatten:", result);
return result;
}
/**
* Generate a random GUID
* @static
* @version 0.1.0.1
* @param {string} [prefix] -
* @param {string} [suffix] -
*/
static generateGUID(prefix = "", suffix = "") {
var guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
return prefix + guid.toUpperCase() + suffix;
}
/**
* Get a property value from an object. The property can be a sub-property of any property of the object. If the path identificates an unexisting property, this function returns undefined.
* @static
* @version 0.1.0.1
* @param {object} object - The object to read from the property value.
* @param {string} path - The dot-separated path that identify the route to follow to get to the property to read the value.
* @returns {boolean|object|string|array|number|null|undefined} - The value of the property.
*/
static getDeepProperty (object, path) {
if (object) {
const pathParts = path.split(".");
// var object = object;
//Uso "i" più avanti, quindi lo lascio come "var": andrebbe sistemato
for (var i = 0; i < pathParts.length - 1; i++) {
var squarePos = pathParts[i].indexOf("[");
var modelArrayIndex = -1;
if (squarePos > -1) {
modelArrayIndex = parseInt(pathParts[i].substring(squarePos + 1, pathParts[i].indexOf("]")));
pathParts[i] = pathParts[i].substr(0, squarePos);
}
if (modelArrayIndex === -1) {
if (!object[pathParts[i]]) {
break;
}
object = object[pathParts[i]];
}
else {
if (!object[pathParts[i]][modelArrayIndex]) {
break;
}
object = object[pathParts[i]][modelArrayIndex];
}
}
var squarePos = pathParts[i].indexOf("[");
var modelArrayIndex = -1;
if (squarePos > -1) {
modelArrayIndex = parseInt(pathParts[i].substring(squarePos + 1, pathParts[i].indexOf("]")));
pathParts[i] = pathParts[i].substr(0, squarePos);
}
if (modelArrayIndex === -1) {
return object[pathParts[i]];
}
else {
return object[pathParts[i]][modelArrayIndex];
}
}
else {
return undefined;
}
// Secondo qui: https://jsperf.com/dereference-object-property-path-from-string
// Questa sotto potrebbe essere una strada migliore
// function deref(obj, s) {
// var i = 0;
// s = s.split('.');
// while (obj && i < s.length)
// obj = obj[s[i++]];
// }
}
static matchLists (sourceList = [], matchLists = {}, options = {}) {
//Dave.Info: molto grezza ma fa il suo lavoro.
sourceList.forEach(currItem => {
for (const currProp in currItem) {
if (currProp.substr(-2) === "Guid") {
const matchListName = currProp.substr(0, currProp.length - 2);
const matchList = matchLists[matchListName];
if (matchList) {
currItem[`__${matchListName}`] = Utils.safp(matchList, "guid", currItem[currProp]);
}
}
}
});
}
/**
* Replicate the property from the sourceObject into targetObject, with various options.
* @static
* @version 0.1.2.36 - 2017.06.05
*
* @param {object} targetObject - The object that the properties will be copied onto.
* @param {object} sourceObject - The object that cointains properties and values to be copied.
* @param {object} [options] - An object containing the options.
* @param {boolean} [options.createMissingProperties = false] - If true properties that are in the soruceObject but not in the targetObject are created in the targetObject and assigned the value they have in the sourceObject; if false, only properties alredy in the targetObject takes the value they have in the sourceObject.
* @param {boolean} [options.matchEmptyParameters = true] - If true a property that is empty in the sourceObject are copied into the targetObject, even if the value in the targetObject is not empty; if false, an empty property in sourceObject will not overwrite any content present in the targetObject.
*/
static matchProperties(targetObject, sourceObject, options) {
let settings = {
createMissingProperties: false,
exclude: [],
// excludeTargetMissingProperties: false,
matchEmptyParameters: true,
matchUndefinedParameters: false
};
Object.assign(settings, options);
// $.extend(true, settings, options);
if (!Array.isArray(settings.exclude)) {
settings.exclude = [];
}
for (let prop in sourceObject) {
// Dave.ToDo: Qui bisognerà capire se riesco a migliorare la gestione degli Object (magari dopo averli parsati), sostituendo l'extend di jQuery con Object.assign().
// Dave.ToDo: qui si può fare un sacco di lavoro di miglioramento
const targetPropCanBeWritten = (Object.prototype.hasOwnProperty.call(targetObject, prop) && Object.getOwnPropertyDescriptor(targetObject, prop).writable) || (!Object.prototype.hasOwnProperty.call(targetObject, prop) && settings.createMissingProperties);
if (targetPropCanBeWritten) {
if (settings.exclude.length === 0 || settings.exclude.indexOf(prop) === -1) {
if ((typeof targetObject[prop] !== "undefined" || settings.createMissingProperties) && ((sourceObject[prop] && sourceObject[prop] !== "") || settings.matchEmptyParameters) && ((sourceObject[prop] !== undefined) || settings.matchUndefinedParameters)) {
// if (sourceObject[prop] && typeof sourceObject[prop].splice === "function") {
if (Array.isArray(sourceObject[prop])) {
targetObject[prop] = [...sourceObject[prop]];
// targetObject[prop] = sourceObject[prop].map(item => item);
}
else if ((typeof sourceObject[prop] === "object" || typeof targetObject[prop] === "object") && (sourceObject[prop] !== null && targetObject[prop] !== null)) {
if (targetObject[prop] && targetObject[prop].constructor.name === "Object" && sourceObject[prop].constructor.name === "Object" && Object.keys(targetObject[prop]).length > 0) {
Utils.matchProperties(targetObject[prop], sourceObject[prop], options);
}
else {
if (targetObject[prop] && targetObject[prop].constructor.name === "Object" && typeof sourceObject[prop] === "string") {
targetObject[prop] = $.extend(true, targetObject[prop], Utils.parseJSON(sourceObject[prop], "object"));
}
else if (targetObject[prop] && targetObject[prop].constructor.name === "Array" && typeof sourceObject[prop] === "string") {
targetObject[prop] = Utils.parseJSON(sourceObject[prop], "array");
}
else {
// Per rimpiazzare jQuery serve un deep clone/merge
// targetObject[prop] = Object.assign({}, sourceObject[prop]);
targetObject[prop] = $.extend(true, {}, sourceObject[prop]);
}
}
}
else {
targetObject[prop] = sourceObject[prop];
}
}
}
}
else {
// Dave questo console.arn rompe le scatole, devo usare il trace
// console.warn(`Property ${prop} of object "targetObject" is not writable.`, targetObject);
// Shark.trace(`Property ${prop} of object "targetObject" is not writable.`, targetObject, S);
}
}
/* for (var prop in sourceObject) {
if ((typeof targetObject[prop] !== "undefined" || settings.createMissingProperties) && ((sourceObject[prop] && sourceObject[prop] !== "") || settings.matchEmptyParameters)) {
targetObject[prop] = sourceObject[prop];
}
}
*/
/* for (var prop in sourceObject) {
if ((targetObject[prop] || settings.createMissingProperties) && (sourceObject[prop] !== "" || settings.matchEmptyParameters)) {
//if (typeof sourceObject[prop] === "object") {
// var newObject = {};
// Utils.matchProperties(newObject, sourceObject[prop], options);
// targetObject[prop] = newObject;
//}
//else {
targetObject[prop] = sourceObject[prop];
//}
}
}*/
}
static moveCaretToEnd(target) {
setTimeout(function () {
if (typeof target.selectionStart === "number") {
target.selectionStart = target.selectionEnd = target.value.length;
}
else if (typeof target.createTextRange !== "undefined") {
target.focus();
var range = target.createTextRange();
range.collapse(false);
range.select();
}
}, 10);
}
static parseJSON (data, expectedType) {
if (typeof data === "string" && data.toLowerCase() === "array") {
return [];
}
else if (data === "" || data === "\"\"") {
switch (expectedType) {
case "array":
return [];
default:
return {};
}
}
else {
if (typeof data === "string") {
let parsedData;
try {
parsedData = JSON.parse(data);
}
catch (error) {
console.error("Utisl.parseJSON", {error, data, expectedType});
parsedData = null;
}
if (parsedData === null || parsedData === "") {
switch (expectedType) {
case "array":
return [];
case "object":
return {};
default:
return parsedData;
}
}
else {
switch (expectedType) {
case "array":
if (typeof parsedData.splice === "function") {
return parsedData;
}
else {
return [];
}
// case "object":
// return {};
// break;
default:
return parsedData;
}
}
}
else if (typeof data === "undefined") {
switch (expectedType) {
case "array":
return [];
default:
return {};
}
}
else {
return data;
}
}
}
static replaceNullValue (objectToParse, replaceVal) {
if (Array.isArray(objectToParse)) {
objectToParse.forEach(item => Utils.replaceNullValue(item, replaceVal));
}
else {
for (let currProp in objectToParse) {
if (typeof objectToParse[currProp] === "undefined" || objectToParse[currProp] === null) {
objectToParse[currProp] = replaceVal;
}
}
}
}
static safeStringify (sourceValue) {
return JSON.stringify(sourceValue, (key, value) => {
if (value instanceof Map) {
value = Array.from(value);
}
return (key !== "__shguid" && key.substr(0, 2) === "__") ? "" : value
});
}
/**
* Shorthand for the [searchArrayForProperty method]{@link Utils.searchArrayForProperty}.
*
* @borrows searchArrayForProperty as safp
* @see Utils.searchArrayForProperty
*
*/
static safp (array, property, value, searchType) {
return Utils.searchArrayForProperty(array, property, value, searchType);
}
/**
* Search an array for an object that has the requested property with the given value. By default if a result is found it stops the loop and returns that item.
*<br />If no object matches the requested filter this method returns null.
*<br />Since the version 2.0.6 this method uses the faster Array.prototype.find method if is available and the parameters are compatible.
*
* @returns {object|array|number|null}
* @static
* @version 2.0.6, 2018.05.31
*
* @param {array} list - The array of objects to search into.
* @param {string} property - The name of the property to search in each object of the array. If a dot separated list (eg. "childs.name") is passed, the value param is compared to the last property of the <i>path</i>; for example if the "property" param is "childs.name", then the value param will compared to the "name" property of the "childs" property of each element in the array.
* @param {object} value - The value that will be used to check the given property. It must have same value and same type, since a comparison of type === is made.
* @param {boolean|string} [searchType = "first"] - This can be one of "first", "all" or "index".
* <br />For compatibilty with previous version of this method, a boolean can be passed and if the value is true the method stops and return on the first occourence and therefore return a single object (like it was "first"), if is false, the method will return an array of object that match the filter (like it was "all").
* <br />If searchType is set to "all" or false, the method will return an array even if it matches only one object.
*
*/
static searchArrayForProperty (list, property, value, searchType) {
if (!list) {
console.warn("Error, cant search on a null list.");
return;
}
switch (typeof searchType) {
case "undefined":
searchType = "first";
break;
case "boolean":
searchType = searchType ? "first" : "all";
break;
}
var propList;
var returnList = [];
if (property.indexOf(".") > -1) {
propList = property.split(".");
}
else {
propList = [property];
}
var propListLength = propList.length;
//Dave.ToDo: Qui, nei casi permessi (solo find first) provo ad usare il Array.find, è molto più veloce. Da capire se va e in quali altri casi posso usarlo.
//Non è vero, è più lento...
// if (Array.prototype.find && propListLength === 1) {
// console.log("******** Utils.SAFP sta usando il find");
// var result = list.find(function (item) {return item[property] === value});
// return typeof result === "undefined" ? null : result;
// }
var listLength = list.length;
for (var i = 0; i < listLength; i++) {
var currValue = list[i][propList[0]];
if (currValue) {
for (var p = 1; p < propListLength; p++) {
if (!currValue[propList[p]]) {
break;
}
currValue = currValue[propList[p]];
//console.info(currValue);
}
}
//Dave.ToDo: Should implement something like "indexAll" to have all indexes of found items.
if (currValue === value) {
switch (searchType) {
case "all":
returnList.push(list[i]);
break;
case "first":
return list[i];
case "index":
return i;
}
}
}
//Dave.ToDo: Potremmo valutare che se la property richiesta è senza "." faccio il giro diverso... devo fare un controllo sui tempi con tipo 1.000.000 di righe
/* return null;
for (var i = 0; i < array.length; i++) {
if (array[i][property] === value) {
if (returnFirstOnly) {
return array[i];
}
else {
returnList.push(array[i]);
}
}
}*/
if (returnList.length > 0) {
return returnList;
}
else {
return null;
}
}
/**
* Assign a property to an object. The property can be a sub-property of any property of the object. If the path parameters indicates a sub-property of a property that does not exists, the property and the sub-property will be both created.
* @static
* @version 0.1.0.1
* @param {object} object - The object on whom the property will be created or assigned a value.
* @param {string} path -
*/
static setDeepProperty (object, path, value, depth = 0) {
const pathParts = path.split(".");
// depth = depth || 0;
if (!object[pathParts[depth]]) {
object[pathParts[depth]] = {};
}
if (depth < pathParts.length - 1) {
Utils.setDeepProperty(object[pathParts[depth]], path, value, depth + 1);
}
else {
//Dave.Warn: Qui sarebbe il caso di fare attenzione che se il tipo che mi viene passato è diverso da quello presente dovrei dare un warn (se imposto a "true" un oggetto complesso rompo tutto)
object[pathParts[depth]] = value;
}
return object;
}
static trace (content) {
try {
if (AppData.consoleDebugActive) {
console.debug(content);
//console.debug(arguments.callee.caller);
}
}
catch (err) {
alert(content);
}
}
static toCamelCase (value) {
return value.slice(0, 1).toLowerCase() + value.slice(1);
}
static toTitleCase (value, forceLowerCase = true) {
return value.slice(0, 1).toUpperCase() + (forceLowerCase ? value.slice(1).toLowerCase() : value.substr(1));
}
/**
* Return a string of <code>length</code> characters representing the given number with leading zeros.
* @static
* @version 1.0.2 - 2019.04.23
* @param {number} number -
* @param {number} length -
* @returns {string} - The string representing the passed number with leading zeros.
*/
static zeroFill (number, length) {
let numberAsString = number.toString();
if (numberAsString.length < length) {
return numberAsString.padStart(length, "0")
}
return numberAsString;
}
}
export { Utils };