@kwiz/common
Version:
KWIZ common utilities and helpers for M365 platform
372 lines • 14 kB
JavaScript
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