@cocreate/utils
Version:
A simple utils component in vanilla javascript. Easily configured using HTML5 attributes and/or JavaScript API.
1,273 lines (1,160 loc) • 37.8 kB
JavaScript
(function (root, factory) {
if (typeof define === "function" && define.amd) {
define([], function () {
return factory(true);
});
} else if (typeof module === "object" && module.exports) {
module.exports = factory(false);
} else {
root.returnExports = factory(true);
}
})(typeof self !== "undefined" ? self : this, function (isBrowser) {
/*globals DOMParser*/
function clickedElement() {
document.addEventListener("click", (e) => {
document.clickedElement = e.target;
});
try {
let frames = document.querySelectorAll("iframe");
for (let frame of frames) {
try {
let frameDocument = frame.contentDocument;
if (!frameDocument.clickedElementListenerAdded) {
frameDocument.addEventListener("click", (e) => {
frameDocument.clickedElement = e.target;
});
// Mark the document to avoid adding duplicate listeners
frameDocument.clickedElementListenerAdded = true;
}
} catch (iframeError) {
console.log(
`Cross-origin frame handling failed for: ${frame}`,
iframeError
);
}
}
} catch (e) {
console.log("Top-level frame document handling failed:", e);
}
}
/**
* Generates an ObjectId
*/
let counter = 0;
function ObjectId(inputId) {
if (inputId && /^[0-9a-fA-F]{24}$/.test(inputId)) {
// If a valid ObjectId is provided, return it as a custom ObjectId object
return {
timestamp: inputId.substring(0, 8),
processId: inputId.substring(8, 20),
counter: inputId.substring(20),
toString: function () {
return this.timestamp + this.processId + this.counter;
}
};
} else if (inputId) {
throw new Error("Invalid ObjectId provided.");
}
// Generate a new custom ObjectId
const timestampHex = Math.floor(
new Date(new Date().toISOString()).getTime() / 1000
)
.toString(16)
.padStart(8, "0");
const processIdHex = Math.floor(Math.random() * 0x100000000000)
.toString(16)
.padStart(12, "0");
counter = (counter + 1) % 10000;
if (counter < 2) {
counter = Math.floor(Math.random() * (5000 - 100 + 1)) + 100;
}
const counterHex = counter.toString(16).padStart(4, "0");
// Return the custom ObjectId object with a toString() method
return {
timestamp: timestampHex,
processId: processIdHex,
counter: counterHex,
toString: function () {
return this.timestamp + this.processId + this.counter;
}
};
}
function checkValue(value) {
if (/{{\s*([\w\W]+)\s*}}/g.test(value)) return false;
else return true;
}
function isValidDate(value) {
if (
typeof value === "string" &&
value.length >= 20 &&
value.length <= 24
) {
// if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/i.test(value))
if (
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?([-+]\d{2}:\d{2}|Z)?$/i.test(
value
)
) {
return true;
}
}
return false;
}
function dotNotationToObject(data, obj = {}) {
try {
let arrayGroup = {}; // Track groups by key paths (e.g., 'messages[a]')
for (const key of Object.keys(data)) {
let value = data[key];
let newObject = obj;
let oldObject = new Object(obj);
let keys = key.split(".");
let length = keys.length - 1;
for (let i = 0; i < keys.length; i++) {
// Check if the key ends with ']', indicating an array or grouping operation
if (keys[i].endsWith("]")) {
// Handle array push (e.g., messages[] -> push value)
if (keys[i].endsWith("[]")) {
let baseKey = keys[i].slice(0, -2); // Remove '[]'
// Initialize newObject[baseKey] as an array if not an array or doesn't exist
if (!Array.isArray(newObject[baseKey])) {
newObject[baseKey] = [];
}
if (length == i) {
// If value is an array, spread the array values into newObject[baseKey]
if (Array.isArray(value)) {
newObject[baseKey].push(...value);
} else {
// If value is not an array, just push the single value
newObject[baseKey].push(value);
}
}
}
// Check for array index (e.g., messages[0])
else if (/\[([0-9]+)\]/g.test(keys[i])) {
let [k, index] = keys[i].split("[");
index = index.slice(0, -1); // Get the index
// Initialize newObject[k] as an array if it doesn't exist or is not an array
if (!Array.isArray(newObject[k])) {
newObject[k] = [];
}
if (length == i) {
if (value === undefined) {
newObject[k].splice(index, 1); // Remove element if value is undefined
} else {
newObject[k][index] = value; // Replace value at specified index
}
} else {
newObject[k][index] = oldObject[k][index] || {}; // Initialize inner object
newObject = newObject[k][index];
oldObject = oldObject[k][index];
}
}
// Handle letter-based groupings (e.g., messages[a].role)
else if (/\[\w\]/g.test(keys[i])) {
let [k, group] = keys[i].split("[");
group = group.slice(0, -1); // Get the letter inside []
// Initialize newObject[k] as an array if not an array or doesn't exist
if (!Array.isArray(newObject[k])) {
newObject[k] = [];
}
// If there's no object at this group index yet, push a new object
let index;
if (arrayGroup[keys.slice(0, i + 1).join(".")]) {
// Reuse the existing index for the group
index =
arrayGroup[keys.slice(0, i + 1).join(".")];
} else {
// Create a new group and track the index
index = newObject[k].length;
arrayGroup[keys.slice(0, i + 1).join(".")] =
index;
newObject[k][index] = {};
}
// Move into the newly created or existing object for the group
if (length == i) {
newObject[k][index] = value; // Set value in the group
} else {
newObject = newObject[k][index]; // Continue with the group object
}
}
}
// Handle regular object keys (non-array keys)
else {
if (length == i) {
if (value === undefined) {
delete newObject[keys[i]]; // Delete key if value is undefined
} else {
newObject[keys[i]] = value; // Set value
}
} else {
newObject[keys[i]] = oldObject[keys[i]] || {}; // Initialize inner object
newObject = newObject[keys[i]];
oldObject = oldObject[keys[i]];
}
}
}
}
return obj;
} catch (error) {
console.log("Error converting dot notation to object", error);
return false;
}
}
/**
* Flattens a deeply nested object or array into a single-level object
* where keys are dot/bracket notation paths and values are the corresponding
* primitive values (string, number, boolean, null, undefined) from the
* original structure.
*
* @param {object|array} input The object or array to flatten.
* @returns {object} A flat object with dot/bracket notation keys mapped to their primitive values.
*/
function objectToDotNotation(input) {
const results = {}; // Initialize an empty OBJECT to store key-value pairs
// Helper function for recursion
function traverse(currentValue, path) {
// Base Case: Primitive values (or null/undefined)
// We consider anything that's not an object or is null as a primitive endpoint.
if (typeof currentValue !== "object" || currentValue === null) {
// Only add if a path exists (handles the edge case where the initial input itself is primitive)
if (path !== undefined && path !== null && path !== "") {
results[path] = currentValue; // Assign the primitive value to the constructed path key
}
return; // Stop recursion for this branch
}
// Recursive Step: Array
if (Array.isArray(currentValue)) {
// Only traverse non-empty arrays if we're looking for primitive values
// If you wanted empty arrays represented (e.g., 'notes': []), you'd add logic here.
if (currentValue.length > 0) {
currentValue.forEach((item, index) => {
// Build the next path segment using bracket notation for arrays
const nextPath = `${path}[${index}]`;
traverse(item, nextPath);
});
} else if (path) {
// Optional: represent empty arrays explicitly if needed
// results[path] = []; // Uncomment this line if you want empty arrays included
}
}
// Recursive Step: Object (and not null, not an array)
else {
const keys = Object.keys(currentValue);
// Only traverse non-empty objects if we're looking for primitive values
// If you wanted empty objects represented (e.g., 'metadata': {}), you'd add logic here.
if (keys.length > 0) {
keys.forEach((key) => {
// Build the next path segment:
// - Use dot notation if the current path is not empty.
// - Just use the key if it's the first level.
const nextPath = path ? `${path}.${key}` : key;
traverse(currentValue[key], nextPath);
});
} else if (path) {
// Optional: represent empty objects explicitly if needed
// results[path] = {}; // Uncomment this line if you want empty objects included
}
}
}
// Start the traversal with the initial input and an empty path
// Using an empty string '' ensures the first level keys don't start with '.'
traverse(input, "");
return results; // Return the populated results object
}
function getValueFromObject(object = {}, path = "", throwError = false) {
try {
if (!Object.keys(object).length || !path) {
if (throwError)
throw new Error("Invalid input to getValueFromObject");
return;
}
path = path.replace(/\[(\d+)\]/g, ".$1");
let data = object,
subpath = path.split(".");
for (let i = 0; i < subpath.length; i++) {
if (throwError && !(subpath[i] in data))
throw new Error("Key not found in object: " + subpath[i]);
data = data[subpath[i]];
if (!data) break;
}
return data;
} catch (error) {
// console.error("Error in getValueFromObject:", error);
if (throwError) throw error;
}
}
function createUpdate(update, data, globalOpertors) {
let operatorKeys = {};
if (globalOpertors) operatorKeys = { ...globalOpertors };
Object.keys(data).forEach((key) => {
if (key.startsWith("$")) {
if (!key.includes("."))
for (let oldkey of Object.keys(data[key]))
operatorKeys[key + "." + oldkey] = data[key][oldkey];
else operatorKeys[key] = data[key];
} else if (
typeof data[key] === "string" &&
data[key].startsWith("$")
) {
operatorKeys[data[key] + "." + key] = data[key];
} else if (key.endsWith("]")) {
const regex = /^(.*(?:\[\d+\].*?)?)\[(.*?)\](?:\[\])?$/;
const match = key.match(regex);
if (match[2] === "")
operatorKeys["$push." + match[1]] = data[key];
else {
let index = parseInt(match[2], 10);
if (index === NaN)
operatorKeys[match[2] + "." + match[1]] = data[key];
else operatorKeys[key] = data[key];
}
} else if (key.includes(".")) {
operatorKeys[key] = data[key];
} else if (data[key] === undefined) {
delete update[key];
} else update[key] = data[key];
});
return dotNotationToObjectUpdate(operatorKeys, update);
}
function dotNotationToObjectUpdate(data, object = {}) {
try {
for (const key of Object.keys(data)) {
let newObject = object;
let oldObject = new Object(newObject);
let keys = key
.replace(/\[(\d+)\]/g, ".$1")
.split(".")
.map((k) => (isNaN(k) ? k : Number(k)));
let value = data[key];
let operator;
if (keys[0].startsWith("$")) operator = keys.shift();
let length = keys.length - 1;
for (let i = 0; i < keys.length; i++) {
// if (/^\d+$/.test(keys[i])) keys[i] = parseInt(keys[i]);
if (length == i) {
if (operator) {
let operators = [
"$rename",
"$inc",
"$push",
"$each",
"$splice",
"$unset",
"$delete",
"$slice",
"$pop",
"$shift",
"$addToSet",
"$pull"
];
if (!operators.includes(operator)) continue;
if (operator === "$rename") {
newObject[value] = newObject[keys[i]];
delete newObject[keys[i]];
} else if (
operator === "$delete" ||
operator === "$unset" ||
operator === "$slice"
) {
if (typeof keys[i] === "number") {
newObject.splice(keys[i], 1);
} else {
delete newObject[keys[i]];
}
} else if (operator === "$shift") {
newObject[keys[i]].shift();
} else if (operator === "$pop") {
newObject[keys[i]].pop();
} else if (operator === "$addToSet") {
if (!newObject[keys[i]])
newObject[keys[i]] = [];
let exists;
if (Array.isArray(value)) {
exists = newObject[keys[i]].some(
(item) =>
Array.isArray(item) &&
isEqualArray(item, value)
);
} else if (
typeof value === "object" &&
value !== null
) {
exists = newObject[keys[i]].some(
(item) =>
typeof item === "object" &&
isEqualObject(item, value)
);
} else {
exists = newObject[keys[i]].includes(value);
}
if (!exists) newObject[keys[i]].push(value);
} else if (operator === "$pull") {
if (!newObject[keys[i]]) continue;
if (Array.isArray(value)) {
newObject[keys[i]] = newObject[
keys[i]
].filter(
(item) =>
!Array.isArray(item) ||
!isEqualArray(item, value)
);
} else if (
typeof value === "object" &&
value !== null
) {
newObject[keys[i]] = newObject[
keys[i]
].filter(
(item) =>
typeof item !== "object" ||
!isEqualObject(item, value)
);
} else {
newObject[keys[i]] = newObject[
keys[i]
].filter((item) => item !== value);
}
} else if (
operator === "$push" ||
operator === "$splice"
) {
if (
typeof keys[i] === "number" &&
newObject.length >= keys[i]
)
newObject.splice(keys[i], 0, value);
else if (newObject[keys[i]])
newObject[keys[i]].push(value);
else newObject[keys[i]] = [value];
} else if (operator === "$each") {
if (!Array.isArray(value)) value = [value];
if (typeof keys[i] === "number")
newObject.splice(keys[i], 0, ...value);
else newObject[keys[i]].push(...value);
} else if (operator === "$inc") {
if (
!newObject[keys[i]] ||
typeof newObject[keys[i]] !== "number"
)
newObject[keys[i]] = value;
else newObject[keys[i]] += value;
}
} else if (value === undefined) {
if (typeof keys[i] === "number")
newObject.splice(keys[i], 1);
else delete newObject[keys[i]];
} else if (typeof keys[i] === "number") {
newObject.splice(keys[i], 0, value);
} else {
newObject[keys[i]] = value;
}
} else if (
typeof keys[i + 1] === "number" &&
!Array.isArray(newObject[keys[i]])
) {
newObject[keys[i]] = [];
} else {
newObject[keys[i]] = newObject[keys[i]] || {};
// newObject[keys[i]] = oldObject[keys[i]] || {};
// oldObject = oldObject[keys[i]];
}
newObject = newObject[keys[i]];
}
}
return object;
} catch (error) {
console.log("Error converting dot notation to object", error);
return false;
}
}
/**
* Converts a JavaScript object into a URL-encoded query string using
* the standard URLSearchParams API (works in Node.js and modern browsers).
* - Uses repeated keys for arrays.
* - Skips null/undefined values.
* - Converts other values to strings.
*
* @param {object | null | undefined} paramsObj The object to convert.
* @returns {string} A URL-encoded query string starting with '?'
* if params exist, otherwise an empty string.
*/
function objectToSearchParams(paramsObj) {
if (
!paramsObj ||
typeof paramsObj !== "object" ||
Array.isArray(paramsObj)
) {
return "";
}
// Filter out null/undefined values
const filteredObj = {};
for (const key in paramsObj) {
if (Object.hasOwn(paramsObj, key)) {
const value = paramsObj[key];
if (value !== null && value !== undefined) {
filteredObj[key] = value;
}
}
}
if (Object.keys(filteredObj).length === 0) {
return "";
}
// --- CORE LOGIC ---
// Create URLSearchParams directly from the filtered object
// This works identically in modern Node.js and browsers.
const searchParams = new URLSearchParams(filteredObj);
const queryString = searchParams.toString();
// --- END CORE LOGIC ---
return queryString ? `?${queryString}` : "";
}
function domParser(str) {
try {
var mainTag = str.match(/\<(?<tag>[a-z0-9]+)(.*?)?\>/).groups.tag;
} catch (e) {
// console.log(e, 'find position: can not find the main tag');
}
let doc;
switch (mainTag) {
case "html":
doc = new DOMParser().parseFromString(str, "text/html");
return doc.documentElement;
case "body":
doc = new DOMParser().parseFromString(str, "text/html");
return doc.body;
case "head":
doc = new DOMParser().parseFromString(str, "text/html");
return doc.head;
default:
let con = document.createElement("dom-parser");
con.innerHTML = str;
return con;
}
}
function parseTextToHtml(text) {
let doc = new DOMParser().parseFromString(text, "text/html");
if (doc.head.children[0]) return doc.head.children[0];
else return doc.body.children[0];
}
function escapeHtml(html) {
return html
.replaceAll("&", "&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll("'", "'")
.replaceAll('"', """);
}
function cssPath(node, container) {
let pathSplits = [];
do {
if (!node || !node.tagName) return false;
let pathSplit = node.tagName.toLowerCase();
if (node.id) {
pathSplit += "#" + node.id;
node = "";
} else {
let eid = node.getAttribute("eid");
if (eid && !/{{\s*([\w\W]+)\s*}}/g.test(eid)) {
pathSplit += `[eid="${eid}"]`;
node = "";
}
// if (node.classList.length) {
// node.classList.forEach((item) => {
// if (item.indexOf(":") === -1) pathSplit += "." + item;
// });
// }
else if (
node.parentNode &&
node.parentNode.children.length > 1
) {
// TODO: improve array logic so ignores javascript generated html??
let children = [];
for (let child of node.parentNode.children) {
if (child.tagName == node.tagName) children.push(child);
}
let index = Array.prototype.indexOf.call(children, node);
pathSplit += `:nth-of-type(${index + 1})`;
}
node = node.parentNode;
if (
node == null ||
node.tagName == "HTML" ||
node.tagName == "BODY" ||
node.tagName == "DOM-PARSER" ||
node.nodeName == "#document" ||
node.hasAttribute("contenteditable")
)
node = "";
}
pathSplits.unshift(pathSplit);
} while (node);
let path = pathSplits.join(" > ");
if (path && path.includes("<")) {
let index = path.lastIndexOf(" >");
if (index != -1) path = path.slice(0, index);
else {
index = path.lastIndexOf("<");
path = path.slice(0, index);
}
}
return path;
}
// Define a list of query types that describe relationships or contexts in the DOM.
const queryTypes = [
"$closest", // Selects the closest ancestor matching the selector
"$parent", // Selects the direct parent element
"$next", // Selects the next sibling element
"$previous", // Selects the previous sibling element
"$document", // Selects the document containing the element
"$frame", // Selects the frame or iframe containing the element
"$top" // Selects the top-level document or window
];
// Construct a regular expression pattern to match any of the query types.
// Each query type begins with a dollar sign, which is escaped in the regex.
const regexPatternString = `(?:${queryTypes
.map((type) => type.replace("$", "\\$")) // Escape $ character for regex
.join("|")})`;
// Compile the regular expression pattern into a RegExp object.
// This regex will be used to find the first occurrence of any query type within a string.
const queryTypesRegex = new RegExp(regexPatternString);
/**
* Function to query DOM elements based on specified criteria.
* @param {Object} params - Object containing parameters for querying elements.
* @param {Element|Document} params.element - The root element or document to start the query from. Defaults to the entire document.
* @param {string} params.prefix - Optional prefix used to construct the query.
* @param {string} params.selector - The CSS selector or query string to use.
* @returns {Array} - An array of elements that match the query.
*/
function queryElements({ element = document, prefix, selector }) {
// Initialize a Set to store unique elements.
let elements = new Set();
// If no selector is provided and the element is an element node.
if (!selector && element.nodeType === 1) {
// If no prefix is provided, derive one from the element's attributes.
if (!prefix) {
for (let attr of element.attributes) {
// If an attribute with "-query" suffix is found, extract prefix.
if (attr.name.endsWith("-query")) {
prefix = attr.name.slice(0, -6);
}
}
// If no valid prefix is found, exit the function.
if (!prefix) return false;
}
// Get the selector using the derived prefix.
selector = element.getAttribute(prefix + "-" + "query");
if (!selector) return false; // Exit if no selector is found.
}
// Split complex selectors into individual ones, handling nested structures.
let selectors = selector.split(/,(?![^()\[\]]*[)\]])/g);
for (let i = 0; i < selectors.length; i++) {
if (!selectors[i]) continue; // Skip empty selectors.
let queriedElement = element; // Start query from the current element.
// If media queries are included, verify and filter the selector accordingly.
if (selectors[i].includes("@")) {
selectors[i] = checkMediaQueries(selectors[i]);
if (selectors[i] === false) continue; // Skip if media query is not matched.
}
let remainingSelector = selectors[i].trim(); // Trim any whitespace.
let match;
// Process each part of the selector that corresponds to specific query types/operators.
while ((match = queryTypesRegex.exec(remainingSelector)) !== null) {
const matchIndex = match.index;
const operator = match[0];
// Process the part before the operator (if any).
const part = remainingSelector
.substring(0, matchIndex)
.trim()
.replace(/,$/, "");
if (part) {
queriedElement = querySelector(queriedElement, part);
if (!queriedElement) break; // Exit loop if no element is found.
}
// Remove the processed part and operator from the remaining selector.
remainingSelector = remainingSelector
.substring(matchIndex + operator.length)
.trim();
// Handle the $closest operator specifically.
if (operator === "$closest") {
let [closest, remaining = ""] = remainingSelector.split(
/\s+/,
2
);
queriedElement = queriedElement.closest(closest);
remainingSelector = remaining.trim();
} else {
// Process other operators using the queryType function.
queriedElement = queryType(queriedElement, operator);
}
if (!queriedElement) break; // Exit loop if no element is found.
}
if (!queriedElement) continue; // Skip if no element is found.
// Process the remaining part after the last operator (if any).
if (remainingSelector) {
queriedElement = querySelector(
queriedElement,
remainingSelector
);
}
// Add elements to the set.
if (
Array.isArray(queriedElement) ||
queriedElement instanceof HTMLCollection ||
queriedElement instanceof NodeList
) {
for (let el of queriedElement) {
if (el instanceof Element) {
elements.add(el);
}
}
} else if (queriedElement instanceof Element) {
elements.add(queriedElement);
}
}
return Array.from(elements); // Convert Set to Array and return found elements.
}
function queryType(element, type) {
if (!element) return null;
switch (type) {
case "$top":
return window.top.document;
case "$frame":
// If element is a document, return the iframe element containing it
if (element.nodeType === 9) return window.frameElement;
// If element is an iframe, return it as is
return element;
case "$document":
// If element is a document, return itself, else return `ownerDocument`
return element.nodeType === 9 ? element : element.ownerDocument;
case "$closest":
// If closest find the first selector seperated by space
return element.nodeType === 9 ? element : element.ownerDocument;
case "$parent":
// If it's a document, return the parent document (if inside an iframe)
if (element.nodeType === 9) {
return element.defaultView !== window.top
? element.defaultView.parent.document
: null;
}
// Otherwise, return parent element
return element.parentElement;
case "$next":
return element.nextElementSibling;
case "$previous":
return element.previousElementSibling;
default:
return null;
}
}
function querySelector(element, selector) {
if (!element) return null;
return selector.endsWith("[]")
? element.querySelectorAll(selector.slice(0, -2))
: element.querySelector(selector);
}
const mediaRanges = {
xs: [0, 575],
sm: [576, 768],
md: [769, 992],
lg: [993, 1200],
xl: [1201, 0]
};
function checkMediaQueries(selector) {
if (selector && selector.includes("@")) {
const viewportWidth = window.innerWidth;
let mediaViewport = false;
let screenSizes = selector.split("@");
selector = screenSizes.shift();
for (let screenSize of screenSizes) {
// Check if screenSize is a valid range in the 'ranges' object
if (mediaRanges.hasOwnProperty(screenSize)) {
const [minWidth, maxWidth] = mediaRanges[screenSize];
if (
viewportWidth >= minWidth &&
viewportWidth <= maxWidth
) {
mediaViewport = true;
break;
}
}
}
if (!mediaViewport) return false;
}
return selector;
}
function queryData(data, query) {
if (query.$and) {
for (let i = 0; i < query.$and.length; i++) {
if (!queryData(data, query.$and[i])) return false;
}
}
if (query.$nor) {
for (let i = 0; i < query.$nor.length; i++) {
if (queryData(data, query.$nor[i])) return false;
}
}
for (let key of Object.keys(query)) {
if (key === "$and" || key === "$or") continue;
if (!queryMatch(data, { [key]: query[key] })) return false;
}
if (query.$or) {
for (let i = 0; i < query.$or.length; i++) {
if (queryData(data, query.$or[i])) return true;
}
}
return true;
}
function queryMatch(data, query) {
for (let key of Object.keys(query)) {
// if (!data.hasOwnProperty(key))
// return false
let dataValue;
try {
dataValue = getValueFromObject(data, key, true);
} catch (error) {
return false;
}
if (
typeof query[key] === "string" ||
typeof query[key] === "number" ||
typeof query[key] === "boolean"
) {
if (Array.isArray(dataValue))
return dataValue.includes(query[key]);
else return dataValue === query[key];
} else if (Array.isArray(query[key])) {
if (Array.isArray(dataValue)) {
return isEqualArray(dataValue, query[key]);
} else {
return false;
}
} else {
for (let property of Object.keys(query[key])) {
if (property === "$options") continue;
if (!property.startsWith("$")) {
if (typeof dataValue !== "object") {
return false;
} else
return queryMatch(
{
[property]: getValueFromObject(
dataValue,
property
)
},
{ [property]: query[key][property] }
);
} else {
let queryValue = query[key][property];
if (isValidDate(queryValue) && isValidDate(dataValue)) {
queryValue = new Date(queryValue);
dataValue = new Date(dataValue);
}
let queryStatus = false;
switch (property) {
case "$eq":
if (
Array.isArray(dataValue) &&
Array.isArray(queryValue)
) {
queryStatus = isEqualArray(
dataValue,
queryValue
);
} else {
queryStatus = dataValue === queryValue;
}
break;
case "$ne":
if (
Array.isArray(dataValue) &&
Array.isArray(queryValue)
) {
queryStatus = !isEqualArray(
dataValue,
queryValue
);
} else {
queryStatus = dataValue !== queryValue;
}
break;
case "$not":
queryStatus = !queryMatch(data, {
[key]: query[key]["$not"]
});
break;
case "$lt":
queryStatus = dataValue < queryValue;
break;
case "$lte":
queryStatus = dataValue <= queryValue;
break;
case "$gt":
queryStatus = dataValue > queryValue;
break;
case "$gte":
queryStatus = dataValue >= queryValue;
break;
case "$in":
if (Array.isArray(dataValue)) {
queryStatus = dataValue.some((element) =>
queryValue.includes(element)
);
} else {
queryStatus =
queryValue.includes(dataValue);
}
break;
case "$nin":
if (Array.isArray(dataValue)) {
queryStatus = !dataValue.some((element) =>
queryValue.includes(element)
);
} else {
queryStatus =
!queryValue.includes(dataValue);
}
break;
case "$all":
if (
Array.isArray(dataValue) &&
Array.isArray(queryValue)
) {
queryStatus = queryValue.every((element) =>
dataValue.includes(element)
);
}
break;
case "$elemMatch":
if (Array.isArray(data[key])) {
queryStatus = data[key].some((element) =>
queryMatch(
element,
query[key][property]
)
);
}
break;
case "$size":
if (Array.isArray(dataValue)) {
queryStatus =
dataValue.length === queryValue;
}
break;
case "$exists":
queryStatus = queryValue
? data.hasOwnProperty(key)
: !data.hasOwnProperty(key);
break;
case "$regex":
if (typeof dataValue === "string") {
let regexFlag =
query[key]["$options"] || "";
let regex = new RegExp(
queryValue,
regexFlag
);
queryStatus = regex.test(dataValue);
}
break;
case "$type":
let dataType = typeof dataValue;
if (Array.isArray(dataValue)) {
dataType = "array";
}
queryStatus = dataType === queryValue;
break;
case "$mod":
if (
typeof dataValue === "number" &&
Array.isArray(queryValue) &&
queryValue.length === 2
) {
const [divisor, remainder] = queryValue;
queryStatus =
dataValue % divisor === remainder;
}
break;
case "$where":
if (typeof queryValue === "function") {
try {
// queryStatus = queryValue.call(data);
} catch (error) {
console.error(
"Error in queryData $where function:",
error
);
}
}
break;
default:
console.log("unknown operator");
break;
}
if (!queryStatus) return false;
}
}
return true;
}
}
}
function isEqualArray(arr1, arr2) {
if (arr1.length !== arr2.length) {
return false;
}
for (let i = 0; i < arr1.length; i++) {
if (!isEqualObject(arr1[i], arr2[i])) {
return false;
}
}
return true;
}
function isEqualObject(obj1, obj2) {
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) {
return false;
}
for (const key of keys1) {
if (obj1[key] !== obj2[key]) {
return false;
}
}
return true;
}
function searchData(data, search) {
if (!search) return true;
if (!Array.isArray(search)) search = [search];
for (let i = 0; i < search.length; i++) {
let searchValue = search[i].value;
if (!Array.isArray(searchValue)) searchValue = [searchValue];
for (let key in data) {
let value = data[key];
let status = false;
switch (typeof value) {
case "number":
value = value.toString();
break;
case "object":
value = JSON.stringify(value);
break;
case "function":
value = value.toString();
break;
}
if (
search[i].caseSensitive != "true" ||
search[i].caseSensitive != true
)
value = value.toLowerCase();
for (let i = 0; i < searchValue.length; i++) {
let searchString = searchValue[i];
if (
search[i].caseSensitive != "true" ||
search[i].caseSensitive != true
)
searchString = searchString.toLowerCase();
if (searchString === "" && search[i].operator === "and") {
if (value !== "") return false;
}
if (value.indexOf(searchString) > -1) status = true;
if (status) return true;
else if (search[i].operator == "and") return false;
}
}
if (search[i].value.length && search[i].operator == "or")
return false;
}
return true;
}
function sortData(data, sort) {
return data.sort((a, b) => {
for (let i = 0; i < sort.length; i++) {
let key = sort[i].key;
if (a[key] == null && b[key] == null) continue;
if (a[key] == null)
return sort[i].direction === "desc" ? -1 : 1;
if (b[key] == null)
return sort[i].direction === "desc" ? 1 : -1;
if (typeof a[key] !== typeof b[key]) {
return typeof a[key] < typeof b[key] ? -1 : 1;
}
if (a[key] !== b[key]) {
if (typeof a[key] === "string") {
return sort[i].direction === "desc"
? b[key].localeCompare(a[key])
: a[key].localeCompare(b[key]);
} else {
// Assuming numeric or other comparable types
return sort[i].direction === "desc"
? b[key] - a[key]
: a[key] - b[key];
}
}
}
return 0;
});
}
function getAttributes(el) {
if (!el) return;
let attributes = window.CoCreateConfig.attributes;
let object = {};
for (let attribute of el.attributes) {
let variable = attributes[attribute.name];
if (variable) {
object[variable] = el.getAttribute(attribute.name);
}
}
return object;
}
function getAttributeNames(variables) {
let reversedObject = {};
for (const key of Object.keys(CoCreateConfig.attributes)) {
reversedObject[CoCreateConfig.attributes[key]] = key;
}
let attributes = [];
for (const variable of variables) {
let attribute = reversedObject[variable];
if (attribute) attributes.push(attribute);
}
return attributes;
}
function setAttributeNames(attributes, overWrite) {
let reversedObject = {};
for (const key of Object.keys(CoCreateConfig.attributes)) {
reversedObject[CoCreateConfig.attributes[key]] = key;
}
for (const attribute of Object.keys(attributes)) {
const variable = attributes[attribute];
if (!reversedObject[variable] || overWrite != false)
reversedObject[variable] = attribute;
}
let revertObject = {};
for (const key of Object.keys(reversedObject)) {
revertObject[reversedObject[key]] = key;
}
CoCreateConfig.attributes = revertObject;
}
if (isBrowser) clickedElement();
return {
ObjectId,
checkValue,
isValidDate,
dotNotationToObject,
objectToDotNotation,
getValueFromObject,
objectToSearchParams,
domParser,
parseTextToHtml,
escapeHtml,
cssPath,
queryElements,
checkMediaQueries,
queryData,
searchData,
sortData,
createUpdate,
getAttributes,
setAttributeNames,
getAttributeNames
};
});