@kwiz/common
Version:
KWIZ common utilities and helpers for M365 platform
262 lines • 9.96 kB
JavaScript
import { makeUniqueArray } from "./collections.base";
import { jsonParse } from "./json";
import { isDate, isFunction, isNotEmptyArray, isNullOrEmptyString, isNullOrUndefined, isObject, isPrimitiveValue, isString } from "./typecheckers";
/** global window, safe for testing and environments without a browser */
export var $w = typeof window === "undefined" ? {
setTimeout: setTimeout,
clearTimeout: clearTimeout,
location: {
href: "", host: ""
}
} : window;
/** wrapper for hasOwnProperty that satisfies https://eslint.org/docs/latest/rules/no-prototype-builtins */
export function hasOwnProperty(obj, prop) {
if (!isNullOrUndefined(obj)) {
return Object.prototype.hasOwnProperty.call(obj, prop);
}
return false;
}
/** empty async function */
/* eslint-disable-next-line @typescript-eslint/no-empty-function */
export var noop = async () => { };
/** empty sync function */
/* eslint-disable-next-line @typescript-eslint/no-empty-function */
export var noops = () => { };
/** get or create kwizcom object from top window or current window, set allowFromTop if you want to try to get from window.top */
export function getKWizComGlobal(allowFromTop) {
if (allowFromTop) {
try {
$w.top["kwizcom"] = $w.top["kwizcom"] || {};
return $w.top["kwizcom"];
}
catch (ex) { }
}
$w["kwizcom"] = $w["kwizcom"] || {};
return $w["kwizcom"];
}
/** get or create kwizcom.globals dictionary from top window or current window. Add or return key:name initialize as defaults or blank object if does not already exist */
export function getGlobal(name, defaults, notFromTop) {
var kGlobal = getKWizComGlobal(notFromTop !== true);
kGlobal.globals = kGlobal.globals || {};
if (!kGlobal.globals[name]) {
if (isObject(defaults)) {
// eslint-disable-next-line @typescript-eslint/ban-types
kGlobal.globals[name] = { ...defaults };
}
else {
kGlobal.globals[name] = {};
}
}
return kGlobal.globals[name];
}
/**
* Automatically bind all functions of instance to instance
* Note: if you use knockout, you should skip ko.isObservable
* @param instance
*/
//eslint-disable-next-line @typescript-eslint/ban-types
export function autoBind(instance, skip) {
let funcitonNames = getAllFunctionNames(instance, 1);
funcitonNames.forEach(prop => {
const val = instance[prop];
if (!isFunction(skip) || !skip(prop, val))
instance[prop] = val.bind(instance);
});
}
/** Implements Object.assign which does not exist in IE
* Copies properties over from overrides into original object
* Merge default and override settings: var merged = assign({},defaults,props)
* Create deep copy of object by var copy = assign({},obj) */
export function assign(original, ...overrides) {
if (original === undefined || original === null) {
throw new TypeError('Cannot convert first argument to object');
}
var to = Object(original);
if (overrides && overrides.length > 0)
overrides.forEach(o => {
if (!isNullOrUndefined(o)) {
var keysArray = Object.keys(Object(o));
for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
var nextKey = keysArray[nextIndex];
var desc = Object.getOwnPropertyDescriptor(o, nextKey);
if (typeof (desc.value) === "undefined" || desc.value === null) {
to[nextKey] = desc.value;
}
else if (typeof (desc.value) === "object") {
//reference type, call assign recursively. fixed problem that object value types from overrides were modified when the returnd object was modified.
if (!Array.isArray(desc.value)) {
to[nextKey] = assign({}, desc.value);
}
else {
to[nextKey] = assign([], desc.value);
}
}
else {
if (desc !== undefined && desc.enumerable) {
to[nextKey] = desc.value; //this will make a shallow copy, altering the override object o[nextKey];
}
}
}
}
});
return to;
}
export function primitivesEqual(o1, o2) {
let normalize = (oo) => {
if (isNullOrUndefined(oo))
return null;
else if (isDate(oo))
return oo.getTime();
return oo;
};
o1 = normalize(o1);
o2 = normalize(o2);
return o1 === o2;
}
export function objectsEqual(o1, o2, ignoreKeys) {
let isPrimitive1 = isPrimitiveValue(o1);
let isPrimitive2 = isPrimitiveValue(o2);
if (isPrimitive1 !== isPrimitive2)
return false; //one primitive other not?
if (isPrimitive1)
return primitivesEqual(o1, o2);
if (isFunction(o1) || isFunction(o2)) {
try {
return o1.toString() === o2.toString();
}
catch (e) {
return false;
}
}
ignoreKeys = ignoreKeys || [];
let allKeys = makeUniqueArray(Object.keys(o1).concat(Object.keys(o2))).filter(key => !ignoreKeys.includes(key));
for (let i = 0; i < allKeys.length; i++) {
let key = allKeys[i];
if (!objectsEqual(o1[key], o2[key], ignoreKeys))
return false;
}
return true;
}
export function jsonClone(obj) {
//todo: check if assign utility method is faster
if (isNullOrUndefined(obj))
return null;
let result = obj;
try {
result = jsonParse(JSON.stringify(obj));
}
catch (e) {
if (isNotEmptyArray(obj))
result = obj.slice();
else
result = obj;
}
//clone date objects
try {
cloneDatesOnObjectRecursivily(obj, result);
}
catch (e) { }
return result;
}
function cloneDatesOnObjectRecursivily(obj1, obj2) {
Object.keys(obj1).forEach(key => {
let v = obj1[key];
if (v === null)
obj2[key] = null;
else if (isDate(v))
obj2[key] = new Date(v.getTime());
else if (isObject(v) && Object.keys(v).length) {
let v2 = obj2[key];
if (isObject(v2))
cloneDatesOnObjectRecursivily(v, v2);
}
});
}
/** if an object in this path doesnt exist under parent - creates it.*/
export function ensureObjectPath(objectPath, defaultValue = {}, parent = $w) {
if (isNullOrEmptyString(objectPath))
return;
let parts = objectPath.split('.');
for (let i = 0; i < parts.length; i++) {
let partName = parts[i];
if (i === parts.length - 1) //last
{
if (isNullOrUndefined(parent[partName]))
parent[partName] = defaultValue;
}
else {
if (isNullOrUndefined(parent[partName]))
parent[partName] = {};
parent = parent[partName];
}
}
}
/** If o has propb and not propa - will copy propb into propa and remove propb */
export function keepOne(o, propa, propb) {
/* using the unkown type doesn't work in modern apps project */
if (isObject(o)) {
if (!hasOwnProperty(o, propa) && hasOwnProperty(o, propb)) {
o[propa] = o[propb];
delete o[propb];
}
}
}
/**return all members and functions of an object, including inherited ones from its base class, excluding the constructor
* send prototypeLevels to limit the number of prototype climbs to get functions from. 0 means unlimited.
*/
export function getAllMemberNames(instance, prototypeLevels) {
let props = [];
let obj = instance;
let level = 0;
let unlimitedLevels = prototypeLevels < 0;
do {
props.push(...Object.getOwnPropertyNames(obj));
obj = Object.getPrototypeOf(obj);
level++;
} while (unlimitedLevels ? !!obj : !!obj && level <= prototypeLevels);
return makeUniqueArray(props.filter(p => p !== 'constructor' && p !== 'dispose'));
}
/**return all functions of an object, including inherited ones from its base class, excluding the constructor
* send prototypeLevels to limit the number of prototype climbs to get functions from. 0 means unlimited.
*/
export function getAllFunctionNames(instance, prototypeLevels) {
return getAllMemberNames(instance, prototypeLevels).filter(p => isFunction(instance[p]));
}
/** generic implementation of Object.values */
export function objectValues(obj) {
return Object.keys(obj).map((key) => {
return obj[key];
});
}
export class DefaultProp {
set value(newValue) { this._value = newValue; }
get value() {
if (!this.isValid(this._value)) {
this._value = isFunction(this._defaultValue)
? this._defaultValue()
: this._defaultValue;
}
return this._value;
}
constructor(defaultValue, initialValue, isValid) {
this._defaultValue = defaultValue;
this._value = initialValue;
this.isValid = isFunction(isValid) ? isValid : v => !isNullOrUndefined(v);
}
}
/** creates a safe property, if the value is null/undefined or empty string - it will return the default value. */
export function GetDefaultProp(defaultValue, initialValue, isValid) {
return new DefaultProp(defaultValue, initialValue, isValid);
}
/** Get string error message from an error object */
export function GetError(error, defaultError = "Unknown error") {
const err = isNullOrUndefined(error)
? defaultError
: isString(error)
? error
: isString(error.message)
? error.message
: defaultError;
return err.length > 0 ? err : defaultError;
}
//# sourceMappingURL=objects.js.map