cui-light
Version:
cUI light framework for the web
558 lines (557 loc) • 17.2 kB
JavaScript
import { ArgumentError, RegisterElementError } from "../models/errors";
import { COMPONENTS_COUNTER, CUID_ATTRIBUTE } from "./statics";
/**
* Checks if value is defined an is not null
* Additionally with type check it can check value if it is not empty string or collection or object
*
* @param val - value
* @param typecheck - default true - additional check whether value is not empty (string, collection, object)
* @returns whether value passed all conditions
*/
export function is(val, typecheck = true) {
if (typeof val !== 'undefined' && val !== null) {
if (!typecheck) {
return true;
}
else {
return !isEmpty(val);
}
}
return false;
}
/**
* Checks if value is empty string, array or object
*
*
* @param val - value
* @returns whether value passed all conditions
*/
export function isEmpty(val) {
if (typeof val === "string") {
return val.length === 0;
}
if (typeof val === "boolean") {
return val;
}
else if (Array.isArray(val)) {
return val.length === 0;
}
return false;
}
export function getName(prefix, name) {
if (!is(prefix) || !is(name)) {
throw new ArgumentError("Missing argument value");
}
return `${prefix}-${name}`;
}
export function sleep(timeout) {
return new Promise(resolve => setTimeout(() => {
resolve(true);
}, timeout));
}
/**
* Creates proper html element from given string
* @param htmlString - string containing html
*/
export function createElementFromString(htmlString) {
if (!is(htmlString)) {
return null;
}
let template = document.createElement('template');
template.innerHTML = htmlString;
return template.content.children.length > 0 ? template.content.children[0] : null;
}
export function getMatchingAttribute(element, attributes) {
let attr = null;
let len = attributes.length;
for (let i = 0; i < len; i++) {
let attribute = attributes[i];
if (element.hasAttribute(attribute)) {
attr = attribute;
break;
}
}
return attr;
}
export function getMatchingAttributes(element, attributes) {
if (!element || !is(element.hasAttribute)) {
return [];
}
return attributes.filter(a => {
return element.hasAttribute(a);
});
}
export function getRangeValue(value, min, max) {
if (value < min) {
return min;
}
else if (value > max) {
return max;
}
return value;
}
export function getRangeValueOrDefault(value, min, max, def) {
if (value === null || typeof value === 'undefined' || isNaN(value)) {
return def;
}
return getRangeValue(value, min, max);
}
export function increaseValue(value, amount) {
return amount < 0 || amount > 1 ? 0 : value + (value * amount);
}
export function decreaseValue(value, amount) {
return amount < 0 || amount > 1 ? 0 : value - (value * amount);
}
export function clone(object) {
if (!is(object)) {
return null;
}
return Object.assign({}, object);
}
export function getSingleColorRatio(first, second, max) {
return Math.abs(((first - second) / max) * 100);
}
/**
* Creates string containg css selector for array of attributes
* @param attributes - attributes values
*/
export function joinAttributesForQuery(attributes) {
if (!is(attributes)) {
return "";
}
return `[${attributes.join('],[')}]`;
}
/**
* Checks light system light mode
* @returns Light Mode - dark/light
*/
export function getSystemLightMode() {
return window.matchMedia &&
window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
/**
* Check print mode in the browser window
* @returns true if window is displayed in print mode
*/
export function getSystemPrintMode() {
return window.matchMedia &&
window.matchMedia('print').matches;
}
/**
* Verifies whether attributes exist and have some values
* @param attributes attributes list
*/
export function are(...attributes) {
if (!is(attributes)) {
return false;
}
let c = attributes.length;
for (let i = 0; i < c; i++) {
if (!is(attributes[i])) {
return false;
}
}
return true;
}
export function calcWindowSize(width) {
if (width <= 640) {
return 'small';
}
else if (width > 640 && width <= 960) {
return "medium";
}
else if (width > 960 && width <= 1200) {
return "large";
}
return 'xlarge';
}
export function calcWindowSize2(width) {
let size = "none";
if (width >= 640) {
size = 'small';
}
if (width >= 960) {
size = "medium";
}
if (width >= 1200) {
size = "large";
}
if (width >= 1600) {
size = 'xlarge';
}
return size;
}
/**
* Creates object from string.
* Supported syntaxes are:
* - JSON
* - single value
* - key:value,value;key=value
*
* @param attribute - attribute value
*/
export function parseAttributeString(attribute) {
let ret = {};
if (!is(attribute)) {
return ret;
}
//@ts-ignore - null already checked
ret = parseJsonString(attribute.trim());
if (!is(ret)) {
//@ts-ignore - null already checked
if (!attribute.includes(';') && !attribute.includes(':')) {
ret = attribute;
}
else {
ret = {};
//@ts-ignore - null already checked
attribute.split(';').forEach(val => {
let sp = splitColon(val);
//let sp = val.split(':')
let len = sp.length;
let tag = undefined;
let value = "";
if (len < 2) {
return;
}
tag = sp[0].trim();
value = sp[1].trim();
if (tag)
ret[tag] = value.replace('U+0003B', ';');
});
}
}
return ret;
}
/**
* Creates object from JSON string
* String must start with { and end with }
*
* @param attribute - attribute value
* @returns object if string passes test, null otherwise
*/
export function parseJsonString(attribute) {
let out = null;
if (is(attribute) && attribute.startsWith('{') && attribute.endsWith('}')) {
try {
out = jsonify(attribute);
}
catch (e) {
console.error(prepLogString("An exception occured", 'Functions', 'parseJsonString'), e);
}
return out;
}
return null;
}
/**
* Creates object from JSON string
* @param attribute - JSON string
*/
export function jsonify(attribute) {
return attribute && attribute.length > 0 ? JSON.parse(attribute) : {};
}
export function getOffsetTop(element) {
if (!is(element)) {
return -1;
}
let val = element.offsetTop - parseInt(getStyleValue(element, 'margin-top')) - parseInt(getStyleValue(element, 'padding-top')) - parseInt(getStyleValue(element, 'border-top-width'));
return val < 0 ? element.offsetTop : val;
}
export function getOffsetLeft(element) {
if (!is(element)) {
return -1;
}
let val = element.offsetLeft - parseInt(getStyleValue(element, 'margin-left')) - parseInt(getStyleValue(element, 'padding-left')) - parseInt(getStyleValue(element, 'border-left-width'));
return val < 0 ? element.offsetLeft : val;
}
export function getIntOrDefault(value, def) {
let integer = parseInt(value);
return isNaN(integer) ? def : integer;
}
export function getStyleValue(target, property) {
if (!is(target) || !is(property)) {
return null;
}
if (target.currentStyle) {
return target.currentStyle[property];
}
else if (window.getComputedStyle) {
return window.getComputedStyle(target, null).getPropertyValue(property);
}
return null;
}
export function prepLogString(message, component, functionName) {
return `[${new Date().toLocaleString()}][${component !== null && component !== void 0 ? component : "-"}][${functionName !== null && functionName !== void 0 ? functionName : '-'}][${message}]`;
}
export function isInRange(value, min, max) {
return is(value) && value >= min && value <= max;
}
export function getActiveClass(prefix) {
return `${prefix !== null && prefix !== void 0 ? prefix : ""}-active`;
}
export function parseAttribute(element, attribute) {
return are(element, attribute) ? parseAttributeString(element.getAttribute(attribute)) : null;
}
/**
* Checks whether string value contains synonym of true
* @param value - string to check
*/
export function isStringTrue(value) {
return is(value) ? ['y', 't', 'true', 'yes'].includes(value.toLowerCase()) : false;
}
export function boolStringOrDefault(prop, def) {
return is(prop) ? isStringTrue(prop) : def;
}
export function getStringOrDefault(value, def) {
return is(value) ? value.toLowerCase() : def;
}
/**
* Checks whether device supports touch
*/
export function isTouchSupported() {
return ('ontouchstart' in window) ||
(navigator.maxTouchPoints > 0) ||
(navigator.msMaxTouchPoints > 0);
}
/**
* Checks whether value is type of string
* @param {any} value - value to be checked
*/
export function isString(value) {
return typeof value === 'string';
}
/**
* Replaces mask {prefix} with actual value in the string
* @param {string} value - text containg a mask
* @param {string} prefix - value to be put in place of mask
*/
export function replacePrefix(value, prefix) {
return !is(value) ? value : value.replace("{prefix}", prefix !== null && prefix !== void 0 ? prefix : "");
}
/**
* Generates identifier for Cui element
*
* @param element - Cui element selector
* @returns generated identifier
*/
export function generateCUID(element) {
let starter = is(element) ? element : "cui-element";
return `${starter}-${COMPONENTS_COUNTER.next().value}`;
}
/**
* Generates random string
*/
export function generateRandomString() {
let rand = getRandomInt(1, 1000);
let rand2 = getRandomInt(1, 100);
return `${Math.floor(Math.random() * rand2)}-${Math.floor(rand)}`;
}
/**
* Generates random integer
* @param min - min number
* @param max - max number
* @returns random integer
*/
export function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
/**
* Registers Element as Cui element, initializes handlers, sets component style to document header and sets $cuid
* @param {any} node - document node
* @param {ICuiComponent[]} components -supported components array
* @param {string[]} attributes - supported attributes array
* @param {CuiUtils} utils - Cui Utils instance
*/
export function registerCuiElement(node, components, attributes, utils) {
let element = node;
element.$handlers = {};
let matching = getMatchingAttributes(node, attributes);
if (is(matching)) {
element.$cuid = node.hasAttribute(CUID_ATTRIBUTE) ? node.getAttribute(CUID_ATTRIBUTE) : generateCUID(node.tagName);
node.setAttribute(CUID_ATTRIBUTE, element.$cuid);
matching.forEach(match => {
let component = components.find(c => { return c.attribute === match; });
if (!is(component)) {
return;
}
try {
//@ts-ignore - component already checked agains undefined
utils.styleAppender.append(component.getStyle());
//@ts-ignore - component already checked agains undefined
let handler = component.get(node, utils);
//@ts-ignore - component already checked agains undefined
element.$handlers[component.attribute] = handler;
//@ts-ignore - component already checked agains undefined
element.$handlers[component.attribute].handle(parseAttribute(node, component.attribute));
}
catch (e) {
let attr = matching ? matching.join(", ") : "";
throw new RegisterElementError(`An error occured during [${attr}] initialization: ${e.message}`);
}
});
}
}
export function addCuiArgument(element, cuiArg, args) {
if (!are(cuiArg, args, element)) {
return false;
}
if (element.hasAttribute(cuiArg)) {
return false;
}
let argArr = [];
enumerateObject(args, (arg, value) => {
argArr.push(`${arg}: ${value}`);
});
element.setAttribute(cuiArg, argArr.join("; "));
return true;
}
export function* counter() {
let idx = 0;
while (true) {
let reset = yield idx++;
if (reset || idx > 200000) {
idx = 0;
}
}
}
export function getHandlerExtendingOrNull(target, fName) {
if (!is(target.$handlers)) {
return null;
}
for (let handler in target.$handlers) {
if (target.$handlers.hasOwnProperty(handler)) {
let h = target.$handlers[handler];
if (hasFunction(h, fName))
return h;
}
}
return null;
}
/**
* Checks whether property exists on the object and it is a function
* @param obj - object
* @param fName - property name
*/
export function hasFunction(obj, fName) {
return is(obj[fName]) && typeof obj[fName] === 'function';
}
/**
* Gets closest parent element which is a cUI element
* @param element
*/
export function getParentCuiElement(element) {
if (!is(element)) {
return undefined;
}
let parent = element.parentElement;
return is(parent) && is(parent.$cuid) ? parent : getParentCuiElement(parent);
}
/**
* Calculates element height by calculating childerns heights
* @param element
*/
export function getChildrenHeight(element) {
let height = 0;
if (!element) {
return -1;
}
Array.from(element.children).forEach((child) => {
height += child.getBoundingClientRect().height;
});
return height > 0 ? height + 4 : height;
}
export function enumerateObject(object, callback) {
if (!are(object, callback)) {
return;
}
for (let prop in object) {
if (object.hasOwnProperty(prop)) {
callback(prop, object[prop]);
}
}
}
export function round(value, decimalPlaces) {
const multiplier = Math.pow(10, decimalPlaces);
return Math.floor(value) / multiplier;
}
export function calculateNextIndex(val, currentIndex, totalLength) {
let idx = -1;
switch (val) {
case 'prev':
idx = currentIndex <= 0 ? totalLength - 1 : currentIndex - 1;
break;
case 'next':
idx = currentIndex < totalLength - 1 ? currentIndex + 1 : 0;
break;
case 'first':
idx = 0;
break;
case 'last':
idx = totalLength - 1;
default:
idx = getIntOrDefault(val, -1);
break;
}
return idx;
}
export function getFirstMatching(array, callback) {
let count = array.length;
if (!array || count === 0) {
return undefined;
}
for (let idx = 0; idx < count; idx++) {
if (callback(array[idx], idx)) {
return array[idx];
}
}
return undefined;
}
export function mapObject(input, callback) {
return callback(input);
}
export function mapObjectArray(input, callback) {
return input.map((item) => {
return mapObject(item, callback);
});
}
/**
* Delays callback execution by specific time. Callback cannot be called again until previous execution finishes or was cancelled
* @param callback - callback to execute
* @param delayTime - time in ms that execution shall be delayed by
* @returns Cancel callback
*/
export function delay(callback, delayTime) {
if (!are(callback, delayTime)) {
throw new Error("[delay]: Input arguments are not correct");
}
let id = null;
return function (...args) {
if (id === null) {
id = setTimeout(() => {
callback(...args);
id = null;
}, delayTime);
}
return function () {
if (id !== null) {
clearTimeout(id);
id = null;
}
};
};
}
export function splitColon(text) {
let ret = [];
if (!is(text)) {
return ret;
}
let split = text.split(":");
if (split.length === 1) {
return split;
}
let tag = split.shift();
// @ts-ignore tag is always defined
return [tag, split.join(":")];
}