UNPKG

@kwiz/common

Version:

KWIZ common utilities and helpers for M365 platform

372 lines 14 kB
import { getFromFullName, isBoolean, isDate, isFunction, isNotEmptyArray, isNullOrEmptyArray, isNullOrEmptyString, isNullOrUndefined, isNumber, isString, isTypeofFullNameFunction } from "./typecheckers"; /** Finds an object in array based on a filter and moves it to the start of the array */ export function moveToStart(arr, filter) { let index = firstIndexOf(arr, filter); if (index > 0) { let obj = arr[index]; arr.splice(index, 1); arr.unshift(obj); } } /** Finds an object in array based on a filter and moves it to the end of the array */ export function moveToEnd(arr, filter) { let index = firstIndexOf(arr, filter); if (index !== -1 && index !== arr.length - 1) { let obj = arr[index]; arr.splice(index, 1); arr.push(obj); } } /** Get the first index of an object of an array, or -1 if the array is empty / null */ // export function firstIndexOf<T extends Element>(arr: HTMLCollectionOf<T>, filter?: (item: T, index?: number) => boolean, startFrom?: number): number; // export function firstIndexOf<T>(arr: T[], filter?: (item: T, index?: number) => boolean, startFrom?: number): number; export function firstIndexOf(arr, filter, startFrom) { if (!isNullOrUndefined(arr) && arr.length > 0) { if (isFunction(filter)) { //use for loop so we can stop when it is found for (let i = startFrom > 0 ? startFrom : 0; i < arr.length; i++) if (filter(arr[i], i) === true) return i; } else return 0; } return -1; } /** Get the first object of an array, or null if the array is empty / null * If you pass a filter, it will find the first element that matches the filter and return it, stopping the loop when it is found * */ export function firstOrNull(arr, filter) { let index = firstIndexOf(arr, filter); return index < 0 ? null : arr[index]; } /** Get the last index of an object of an array, or -1 if the array is empty / null */ export function lastIndexOf(arr, filter) { if (!isNullOrUndefined(arr) && arr.length > 0) { if (isFunction(filter)) { //use for loop so we can stop when it is found for (let i = arr.length - 1; i >= 0; i--) if (filter(arr[i]) === true) return i; } else return arr.length - 1; } return -1; } /** get the last element or null */ export function lastOrNull(arr, filter) { let index = lastIndexOf(arr, filter); return index < 0 ? null : arr[index]; } /** Get the first index of an object of an array, or -1 if the array is empty / null */ export async function firstIndexOfAsync(arr, filter, startFrom) { if (!isNullOrUndefined(arr) && arr.length > 0) { if (isFunction(filter)) { //use for loop so we can stop when it is found for (let i = startFrom > 0 ? startFrom : 0; i < arr.length; i++) if ((await filter(arr[i], i)) === true) return i; } else return 0; } return -1; } /** Get the first object of an array, or null if the array is empty / null * If you pass a filter, it will find the first element that matches the filter and return it, stopping the loop when it is found * */ export async function firstOrNullAsync(arr, filter) { let index = await firstIndexOfAsync(arr, filter); return index < 0 ? null : arr[index]; } /** Get the last index of an object of an array, or -1 if the array is empty / null */ export async function lastIndexOfAsync(arr, filter) { if (!isNullOrUndefined(arr) && arr.length > 0) { if (isFunction(filter)) { //use for loop so we can stop when it is found for (let i = arr.length - 1; i >= 0; i--) if ((await filter(arr[i])) === true) return i; } else return arr.length - 1; } return -1; } /** get the last element or null */ export async function lastOrNullAsync(arr, filter) { let index = await lastIndexOfAsync(arr, filter); return index < 0 ? null : arr[index]; } /** Sorts an array of complex objects, use defaultPrimitiveGetValue for default functionality */ export function sortArray(arr, getValue) { if (!isNullOrEmptyArray(arr)) { if (isTypeofFullNameFunction("Intl.Collator")) { //todo: should probably use the SharePoint locale isntead of 'undefined' let collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); arr.sort((a, b) => { let va = getValue(a); let vb = getValue(b); return collator.compare(va, vb); }); } else { arr.sort((a, b) => { let va = getValue(a); if (isString(va)) va = va.toLowerCase(); let vb = getValue(b); if (isString(vb)) vb = vb.toLowerCase(); return va === vb ? 0 : va > vb ? 1 : -1; }); } } return arr; } /** removes null, undefined or "" elements from the array */ export function filterEmptyEntries(arr) { return arr.filter(val => !isNullOrEmptyString(val)); } export function sortNumberArrayAsc(a, b) { return a - b; } export function sortNumberArray(a, b) { return b - a; } /** call a foreach on an object or an array, with an option to break when returning false */ export function forEach(obj, func, args) { if (obj && func && isFunction(func)) { if (Array.isArray(obj) || obj.constructor && getFromFullName("constructor.name", obj) === "Array") { for (let i = 0; i < obj.length; i++) { let property = i; let value = obj[property]; let result = func(property.toString(10), value, args); if (result === false) { break; } } } else { let keys = Object.keys(obj); for (let i = 0; i < keys.length; i++) { let property = keys[i]; let value = obj[property]; let result = func(property, value, args); if (result === false) { break; } } } } } export async function forEachAsync(arr, handler, options) { if (!isNullOrUndefined(arr) && Object.keys(arr).length > 0) { let keys = Object.keys(arr); if (options && options.parallel) { let promises = []; keys.forEach((key, i) => { promises.push(handler(arr[key], i)); }); return Promise.all(promises); } else { let results = []; for (let i = 0; i < keys.length; i++) { results.push(await handler(arr[keys[i]], i)); } return results; } } else return []; } export function sizeOf(obj) { if (Array.isArray(obj)) return obj.length; return Object.keys(obj).length; } export function chunkArray(array, chunkSize) { let chunkedArray = []; for (var i = 0; i < array.length; i += chunkSize) { chunkedArray.push(array.slice(i, i + chunkSize)); } return chunkedArray; } /** Takes an array and transforms it into a hash. this will assign 1 item per key, assumig getKey will be unique per item. */ export function toHash(arr, getKey, filter, transformValue) { let hash = {}; if (!isFunction(transformValue)) transformValue = v => v; if (isNotEmptyArray(arr)) arr.forEach(i => { if (!isFunction(filter) || filter(i)) hash[getKey(i)] = transformValue(i); }); return hash; } /** Returns an array from the values of the dictionary. */ export function toArray(hash, filter, transform) { let arr = []; if (!isFunction(transform)) transform = (key, element) => element; if (!isNullOrUndefined(hash)) Object.keys(hash).forEach(key => { if (!isFunction(filter) || filter(hash[key])) arr.push(transform(key, hash[key])); }); return arr; } /** returns a new dictionary, converting each entry in source using the transform function */ export function convertDictionary(source, transform) { let result = {}; forEach(source, (key, value) => { result[key] = transform(value); }); return result; } export function flattenArray(array) { return array.reduce((acc, val) => acc.concat(val), []); } /** careful, does not work for date/complex objects. Use GetUniqueArrayInfo if you suspect you might have Date/complex objects. */ export function makeUniqueArray(arr) { return arr.filter((v, i, a) => a.indexOf(v) === i); } /** return an array of unique values, and the first index they were found, use defaultPrimitiveGetValue for default functionality */ export function GetUniqueArrayInfo(arr, getValue) { var uniqueValues = []; var uniqueArray = []; var foundValues = []; var hasDuplicates = false; var duplicateIndexes = []; if (isNotEmptyArray(arr)) { arr.forEach((item, index) => { let value = getValue(item); if (foundValues.includes(value)) { hasDuplicates = true; duplicateIndexes.push(index); } else { foundValues.push(value); uniqueValues.push({ item: item, value: value, firstIndex: index }); uniqueArray.push(item); } }); } return { /** true if duplicate values found */ hasDuplicates: hasDuplicates, /** all duplicate item indexes */ duplicateIndexes: duplicateIndexes, /** unique values and their info */ uniqueValues: uniqueValues, /** the unique version of this array */ uniqueArray: uniqueArray }; } /** returns true if the element is a group of items */ export function IsMultiLevelGroup(groupOrItem) { let asGroup = groupOrItem; return !isNullOrUndefined(asGroup.subGroups) && Array.isArray(asGroup.groupItems) && isNumber(asGroup.index) && isNumber(asGroup.depth); } /** returns a flat array of groups>items ordered by groups */ export function FlattenGroupItems(groups) { let flatItems = []; Object.keys(groups).forEach(groupName => { let group = groups[groupName]; if (!isNullOrEmptyString(groupName)) flatItems.push(group); let subGroups = Object.keys(group.subGroups); if (isNotEmptyArray(subGroups)) { flatItems.push(...FlattenGroupItems(group.subGroups)); } else flatItems.push(...group.groupItems); }); return flatItems; } /** split a collection by page size and return the info */ export function GetPagedCollectionInfo(collection, pageSize, currentPage) { let pagedItems = []; if (pageSize < 1) { pagedItems = [collection.slice()]; } else { let copy = collection.slice(); while (isNotEmptyArray(copy)) { pagedItems.push(copy.splice(0, pageSize)); } } currentPage = isNumber(currentPage) && currentPage >= 0 && currentPage < pagedItems.length ? currentPage : 0; return { /** nubmer of pages */ pages: pagedItems.length, /** page items, per page (Array of pages, each has an array of the page items) */ pagedItems: pagedItems, /** the current page */ currentPage: currentPage, /** the current page items */ currentPageItems: pagedItems[currentPage] || [], /** has more than 1 page */ hasPages: pagedItems.length > 1, allowPrev: currentPage > 0, allowNext: currentPage < pagedItems.length - 1 }; } /** use with sortArray or get unique array to handle premitive types or dates, with a JSON.stringify to all other values */ export function defaultPrimitiveGetValue(item) { return isNullOrUndefined(item) ? "" : isDate(item) ? item.getTime() : isBoolean(item) ? item === true ? 1 : 0 : isNumber(item) || isString(item) ? item : JSON.stringify(item); } export function RemoveItemFromArr(arr, item) { let idx = arr.indexOf(item); if (idx >= 0) arr.splice(idx, 1); } export function PushNoDuplicate(arr, item) { if (!arr.includes(item)) arr.push(item); } /** fills an array with a value. Array.fill isn't available on SPFx. */ export function ArrayFill(arr, value, onlyEmpty) { for (let i = 0; i < arr.length; i++) { if (onlyEmpty !== true || isNullOrUndefined(arr[i])) arr[i] = value; } return arr; } /** give a name and a collection, and it will return a unique name availalbe, suffixing a _# to the name * example: file * return file, file_2, file_9 etc... whichever is availalbe first. */ export function FindNextAvailableName(name, usedNames, options) { let nameForTest = name; if (options && options.caseSensitive !== true) { usedNames = usedNames.map(n => n.toLowerCase()); nameForTest = name.toLowerCase(); } let nameSuffix = options && options.suffix || ""; let suffixIdx = 0; let suffixStr = ""; while (usedNames.indexOf(`${nameForTest}${suffixStr}${nameSuffix}`) >= 0) { suffixIdx++; suffixStr = "_" + suffixIdx; } return `${name}${suffixStr}${nameSuffix}`; } //** returns an array of numbers from 0,1,2... */ export function numbersArray(length, startFrom = 0) { //dvp build will fail without any type if (isNullOrUndefined(length) || length < 0) length = 0; let arr = Array.from(Array(length).keys()); return startFrom > 0 ? arr.map(i => i + startFrom) : arr; } //# sourceMappingURL=collections.base.js.map