/** @module react-aiot/util */
import { handleSortableTreeInit, handleSortableTree, handleSortableTreeWillUpdate } from './injectSortable';
import { handleSearch, handleSearchBlur, handleSearchClose, handleSearchKeyDown } from './injectSearchable';
export { handleSortableTreeInit, handleSortableTree, handleSortableTreeWillUpdate };
export { handleSearch, handleSearchBlur, handleSearchClose, handleSearchKeyDown };
/**
* Create a unique id.
*
* @returns {string}
*/
export function uuid() {
function s4() {
return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
}
return "aiot-" + s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
}
/**
* Helper function to add a class to a DOMElement.
*
* @param {DOMElement} el The element
* @param {string} className The class names
*/
export function addClass(el, className) {
if (el.classList)
el.classList.add(className);
else
el.className += ' ' + className;
}
/**
* Helper function to remove a class to a DOMElement.
*
* @param {DOMElement} el The element
* @param {string} className The class names
*/
export function removeClass(el, className) {
if (el.classList)
el.classList.remove(className);
else
el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
}
/**
* Get parent of a element.
*
* @see https://gist.github.com/ziggi/2f15832b57398649ee9b
*/
export function parents(elem, selector) {
var elements = [];
var ishaveselector = selector !== undefined;
while ((elem = elem.parentElement) !== null) {
if (elem.nodeType !== window.Node.ELEMENT_NODE) {
continue;
}
if (!ishaveselector || elem.matches(selector)) {
elements.push(elem);
}
}
return elements;
}
/**
* Create a helper class to store compact JSON data in local storage if supported.
*
* @param {int} id Unique id for the storage key. Prefixed with "AIOT-"
*/
export class Storage {
constructor(id) {
this.id = id;
this.cache = undefined;
}
/**
* Set the item for a JSON path for this local storage object.
*
* @param {string} path The path
* @param {*} value The stringify-able value
*/
setItem(path, value) {
let collection = this.getItem();
this.setObjectPath(collection, path, value);
window.localStorage.setItem("AIOT-" + this.id, JSON.stringify(collection));
this.cache = undefined;
}
/**
* Get the item for a JSON path for this local storage object.
*
* @param {string} path The path
* @returns {*}
*/
getItem(path) {
if (!this.cache) {
this.cache = JSON.parse(window.localStorage.getItem('AIOT-' + this.id) || '{}');
}
return path ? this.getObjectPath(this.cache, path) : this.cache;
}
/**
* Get the item for a JSON path for the passed object.
*
* @param {object} object The object
* @param {string} path The path
* @returns {*}
*/
getObjectPath(object, path) {
let names = (path || '').split('.');
for (let i = 0; object && i < names.length; i++) {
object = object[names[i]];
}
return object;
}
/**
* Set the item for a JSON path for the passed object.
*
* @param {object} object The object
* @param {string} path The path
* @param {*} value The stringify-able value
*/
setObjectPath(object, path, value) {
let l, names = (path || '').split('.');
if ( ( l = names.length ) > 0 ) {
for (var i = 0; object && i < l - 1; i++) {
if (!object[names[i]] ) {
object[names[i]] = {};
}
object = object[names[i]];
}
object[names[l - 1]] = value;
}
}
}
/**
* If true the current browser supports local storage.
*
* @type boolean
*/
export const SUPPORTS_LOCAL_STORAGE = (function() {
var test = 'test';
try {
var _localStorage = window["localStorage"];
_localStorage.setItem(test, test);
_localStorage.removeItem(test);
return true;
} catch(e) {
return false;
}
})();
/**
* An object with the touchable event names for mouseup, mousedown, mousemove.
*
* @type object
*/
export const touchable = 'ontouchstart' in window || window.navigator.maxTouchPoints
? { touch: true, up: "touchend", down: "touchstart", move: "touchmove" }
: { touch: false, up: "mouseup", down: "mousedown", move: "mousemove" };
/**
* Allows to inject a virtual CSS style sheet.
*
* @param {string} id The unique id for the style. If already available it gets overriden by the new style rules
* @param {string} style The style rules
* @returns {DOMElement|boolean} If successful it returns the script-DOMElement and false if failure
*/
export function injectStyle(id, style) {
try {
// Remove old element
const script = document.getElementById(id);
script && script.remove();
// Create element
const elem = document.createElement('div');
elem.id = id;
elem.style.display = 'none';
elem.innerHTML = '­<style>' + style + '</style>';
document.body.appendChild(elem);
return elem;
} catch (e) {
return false;
}
}
/**
* Resizes a given container with calc(100% - width).
*
* @param {string} containerId The container ID
* @param {string} oppositeId The opposite ID of container
* @param {int} width Width in px
*/
export function resizeOpposite(containerId, oppositeId, width) {
return injectStyle(containerId + '-styleOpposite',
'#' + oppositeId + '{ width: -webkit-calc(100% - ' + width + 'px);width: -moz-calc(100% - ' + width + 'px);width: calc(100% - ' + width + 'px); }');
}
/**
* Allows to update a tree node item by id.
*
* @param {string|id} id The id of the tree node
* @param {object[]} tree The {@link module:react-aiot/components/TreeNode|TreeNode} properties to modify
* @param {module:react-aiot/util#updateTreeItemByIdCallback} callback
* @param {module:react-aiot/util#updateTreeItemByIdSetState} setState
*/
export function updateTreeItemById(id, tree, callback, setState) {
const newTree = tree;
for (let i = 0; i < newTree.length; i++) {
if (newTree[i].id === id) {
/**
* This function is called when the tree node is found in updateTreeItemById.
* You can then modify the node.
*
* @callback module:react-aiot/util#updateTreeItemByIdCallback
* @param {object} node {@link module:react-aiot/components/TreeNode|TreeNode}
*/
callback(newTree[i]);
break;
}else if (newTree[i].childNodes) {
updateTreeItemById(id, newTree[i].childNodes, callback);
}
}
/**
* This function is called when the tree node is successfully modified by your
* callback and can no be setted as state in your component.
*
* @callback module:react-aiot/util#updateTreeItemByIdSetState
* @param {object[]} tree The new {@link module:react-aiot/components/TreeNode|TreeNode} properties
*/
setState && newTree[0] && setState(newTree);
}
function _findTreeItemById(id, tree = [], callback, parentId = 0) {
for (let i = 0; i < tree.length; i++) {
if (tree[i].id === id) {
callback(tree[i], parentId);
break;
}else if (tree[i].childNodes) {
_findTreeItemById(id, tree[i].childNodes, callback, tree[i].id);
}
}
}
/**
* Get the parent id of a tree node.
*
* @param {string|int} id The id which you want to fetch the parent of
* @param {object[]} tree {@link module:react-aiot/components/TreeNode|TreeNode} properties
* @param {string|int} [parentId=0] If the item has no parent this id is returned
* @returns {string|int} Can also return undefined if not found
*/
export function getTreeParentById(id, tree, parentId = 0) {
let found;
_findTreeItemById(id, tree, (node, parentId) => (found = parentId), parentId);
return found;
}
/**
* Get the tree node by id.
*
* @param {string|int} id The id
* @param {object[]} tree {@link module:react-aiot/components/TreeNode|TreeNode} properties
* @returns {object}
*/
export function getTreeItemById(id, tree) {
let found;
_findTreeItemById(id, tree, node => (found = node));
return found;
}
/**
* Builds an ordered parent pairs array.
*
* @param {object[]} [tree=[]] The tree
* @paremt {string|int} [rootId=0] The root id
* @returns {object[]} With fid (node id) and the given pid (parent id)
*/
export function buildOrderedParentPairs(tree = [], rootId = 0) {
const pairs = [], mapper = function(node) {
pairs.push({ fid: node.id, pid: this });
node.childNodes && node.childNodes.forEach(mapper.bind(node.id));
};
tree.forEach(mapper.bind(rootId));
return pairs;
}
/**
* Checks if two flat arrays are equal.
*/
export function flatArrayEqual(arr1, arr2) {
if(arr1.length !== arr2.length)
return false;
for(let i = arr1.length; i--;) {
if(arr1[i] !== arr2[i])
return false;
}
return true;
}
/**
* Shim layer with setTimeout fallback
*
* @see https://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
*/
(function() {
var lastTime = 0;
var vendors = ['webkit', 'moz'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame =
window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());