/*!
Client lib for manipulating on DOM elements.
Author: CrazyDoctor (Oleg Karataev)
*/
class CDElement {
constructor(el) {
this.element = el;
el.cdelement = this;
try {
this.events = Object.keys(getEventListeners(el));
} catch(e) {
this.events = [];
}
}
static get(el) {
if(el == null)
return null;
if(el instanceof CDElement)
return el;
if(el.cdelement)
return el.cdelement;
if(el instanceof Element || el instanceof Window)
return el.cdelement = new CDElement(el);
throw 'CDElement.get() error';
}
get() {
return this.element;
}
copy() {
return new CDElement(this.get().cloneNode(true));
}
getFirstChild(selector) {
const children = Array.from(this.get().children);
if (children.length == 0)
return null;
if (CDUtils.isEmpty(selector))
return CDElement.get(children[0]);
const child = this.get().querySelector(selector);
if(child)
return CDElement.get(child);
return null;
}
hasChildren() {
return Array.from(this.get().children).length > 0;
}
getChildren(selector) {
if (CDUtils.isEmpty(selector))
return Array.from(this.get().children).map((element) => CDElement.get(element));
return Array.from(this.get().querySelectorAll(selector)).map((element) => CDElement.get(element));
}
getChildrenRecursive() {
let children = this.getChildren();
for(const child of children) {
if(this.hasChildren())
children = children.concat(child.getChildrenRecursive());
}
return children;
}
getTag() {
return this.get().tagName.toLowerCase();
}
getParent() {
return CDElement.get(this.get().parentElement);
}
getValue() {
return this.get().value || this.getInnerHTML();
}
setValue(value) {
this.get().value = value;
return this;
}
append(element) {
this.get().append(element.get());
}
prepend(element) {
this.get().prepend(element.get());
}
remove() {
this.get().remove();
}
disable() {
this.removeCssProperty('display');
this.addClass('disabled');
return this;
}
enable(display) {
this.removeClass('disabled');
this.get().style.display = CDUtils.isEmpty(display) ? 'block' : display;
return this;
}
minimizeHeight() {
this.get().style.height = '0px';
return this;
}
minimizeWidth() {
this.get().style.width = '0px';
return this;
}
expandHeight(size) {
if (CDUtils.isEmpty(size))
return this;
this.get().style.height = size + 'px';
return this;
}
expandWidth(size){
if (CDUtils.isEmpty(size))
return this;
this.get().style.width = size + 'px';
return this;
}
isMinimized(el) {
return CDUtils.isEmpty(this.get().style.height);
}
isDisabled(el) {
return CDUtils.isEmpty(this.get().style.display);
}
setId(id) {
if (CDUtils.isEmpty(id))
return this;
this.get().id = id;
return id;
}
previousSibling() {
return CDElement.get(this.get().previousElementSibling);
}
nextSibling() {
return CDElement.get(this.get().nextElementSibling);
}
addClass(cls) {
if (CDUtils.isEmpty(cls))
return this;
cls.split(' ').forEach((c) => {
if(c.length > 0 && !this.hasClass(c))
this.get().classList.add(c);
});
return this;
}
getClass() {
if (CDUtils.isEmpty(this.get().classList))
return '';
let classList = '';
this.get().classList.forEach((cls) => {
classList += cls + " ";
});
return classList.trim();
}
removeClass(cls) {
if (CDUtils.isEmpty(cls))
return this;
this.get().classList.remove(cls);
return this;
}
hasClass(cls) {
if (CDUtils.isEmpty(this.get().classList))
return false;
let has = false;
this.get().classList.forEach((c) => {
if (c === cls) has = true;
});
return has;
}
removeClass(cls) {
if (CDUtils.isEmpty(cls))
return this;
this.get().classList.remove(cls);
return this;
}
switchClass(cls, condition) {
if(condition != null)
return condition ? this.addClass(cls) : this.removeClass(cls);
return this.hasClass(cls) ? this.removeClass(cls) : this.addClass(cls);
}
removeCssProperty(prop) {
if (CDUtils.isEmpty(prop))
return this;
this.get().style.setProperty(prop, '');
return this;
}
setAttribute(attr, value) {
this.get().setAttribute(attr, CDUtils.isEmpty(value) ? '' : value);
return this;
}
getAttribute(attr) {
return this.get().getAttribute(attr);
}
setInnerHTML(value) {
this.get().innerHTML = CDUtils.isEmpty(value) ? '' : value;
return this;
}
getInnerHTML() {
return this.get().innerHTML;
}
getInnerWidth() {
return this.get().innerWidth;
}
getInnerHeight() {
return this.get().innerHeight;
}
getOffsetTop() {
return this.get().offsetTop;
}
scrollTo(selector) {
const child = this.getFirstChild(selector);
if(child)
this.get().scrollTop = child.getOffsetTop() - this.getOffsetTop();
}
scrollIntoView() {
this.get().scrollIntoView();
return this;
}
style(property, value, priority) {
this.get().style.setProperty(property, value, priority);
return this;
}
focus() {
this.get().focus();
return this;
}
blur() {
this.get().blur();
return this;
}
click() {
this.get().click();
return this;
}
on(event, callback) {
if (CDUtils.isEmpty(event) || CDUtils.isEmpty(callback))
return this;
this.get().addEventListener(event, callback);
return this;
}
un(event, callback) {
if (CDUtils.isEmpty(event))
return this;
this.get().removeEventListener(event, callback);
return this;
}
}
class Url {
constructor(url) {
this.url = new URL(url || location);
this.urlSearch = new URLSearchParams(this.url.search);
}
static getHash() {
return new Url().getHash();
}
static setHash(hash) {
return new Url().setHash(hash);
}
static setPath(path) {
return new Url().setPath(path);
}
static getOrigin() {
return new Url().getOrigin();
}
static goTo(url, blank) {
window.open(url, blank ? '_blank' : '_self');
}
static reload() {
location.reload();
}
static getPath() {
return new Url().getPath();
}
static getFullPath() {
const url = new Url().url;
return `${url.origin}${url.pathname}`;
}
setPath(path) {
this.url.pathname = path;
return this;
}
getHash() {
const hash = this.url.hash.substring(1);
return hash.length > 0 ? hash : null;
}
setHash(hash) {
this.url.hash = !hash || hash.length == 0 ? '' : `#${hash}`;
return this;
}
setSearchParams(params) {
const paramsArr = [];
for(const key of Object.keys(params)) {
paramsArr.push(`${key}=${params[key]}`);
}
this.url.search = paramsArr.join('&');
}
getSearchParams() {
const params = {};
Array.from(this.url.searchParams).forEach((pair) => {
params[pair[0]] = pair[1];
});
return params;
}
setPath(path) {
this.url.pathname = path;
return this;
}
getPath() {
return this.url.pathname;
}
getOrigin() {
return this.url.origin;
}
getProtocol() {
return this.url.protocol;
}
setProtocol(protocol) {
this.url.protocol = protocol;
return this;
}
toString() {
this.url.search = this.urlSearch.toString();
return this.url.toString();
}
toLocalString() {
this.url.search = this.urlSearch.toString();
return this.toString().substring(this.url.origin.length);
}
updateLocation() {
const hashChanged = Url.getHash() !== this.getHash();
history.replaceState(null, null, this.toLocalString());
hashChanged && window.dispatchEvent(new HashChangeEvent('hashchange'));
}
}
class Style {
static apply(element, styleStr) {
element = element instanceof CDElement ? element : CDElement.get(element);
const propertiesMap = this.getCssPropertiesMap(styleStr);
for(const prop of Object.keys(propertiesMap))
element.style(prop, propertiesMap[prop].value, propertiesMap[prop].priority);
}
static getCssPropertiesMap(styleStr) {
const parts = styleStr.split(';');
const map = {};
for(let part of parts) {
part = part.trim();
if(part.length == 0)
continue;
const propVal = part.split(':');
const property = propVal[0].trim();
const value = propVal[1].trim().split('!');
map[property] = { value: value[0], priority: value.length > 1 ? value[1] : '' };
}
return map;
}
}
class DOM {
static Events = {
Click: 'click',
Load: 'load',
KeyDown: 'keydown',
KeyUp: 'keyup',
KeyPress: 'keypress',
Change: 'change',
Cut: 'cut',
Drop: 'drop',
Paste: 'paste',
Input: 'input',
HashChange: 'hashchange',
MouseDown: 'mousedown',
ContextMenu: 'contextmenu',
Blur: 'blur'
};
static Tags = {
A: 'a',
Div: 'div',
Span: 'span',
H1: 'h1',
H2: 'h2',
H3: 'h3',
P: 'p',
Textarea: 'textarea',
Input: 'input',
Table: 'table',
Tr: 'tr',
Th: 'th',
Tbody: 'tbody',
Td: 'td',
Select: 'select',
Option: 'option'
};
static Keys = {
Enter: 'Enter',
Escape: 'Escape',
Control: 'Control',
Shift: 'Shift',
Backspace: 'Backspace',
Meta: 'Meta'
};
static MouseButtons = {
Left: 1,
Right: 2,
Middle: 4
};
static get(selector) {
if (CDUtils.isEmpty(selector))
throw "DOM.get() invalid selector.";
const element = document.querySelector(selector);
if (CDUtils.isEmpty(element))
return null;
return CDElement.get(element);
}
static getAll(selector) {
if (CDUtils.isEmpty(selector))
throw "DOM.getAll() invalid selector.";
const elements = document.querySelectorAll(selector);
if(CDUtils.isEmpty(elements))
return [];
return Array.from(elements).map((element) => CDElement.get(element));
}
static create(config, container, prepend) {
if (CDUtils.isEmpty(config) || CDUtils.isEmpty(config.tag))
return;
const element = CDElement.get(document.createElement(config.tag));
if (!CDUtils.isEmpty(config.attr)) {
Object.keys(config.attr).forEach((name) => {
if(config.attr[name] !== undefined)
element.setAttribute(name, config.attr[name]);
});
}
if (!CDUtils.isEmpty(config.cls)) element.addClass(config.cls);
if (!CDUtils.isEmpty(config.id)) element.setId(config.id);
if (!CDUtils.isEmpty(config.style)) Style.apply(element, config.style);
if (!CDUtils.isEmpty(config.cn)) {
config.cn.forEach((el) => {
if (el instanceof Element) {
element.append(CDElement.get(el));
} else if (el instanceof CDElement) {
element.append(el);
} else this.create(el, element);
});
}
// innerHTML appends after cn
if (!CDUtils.isEmpty(config.innerHTML)) element.setInnerHTML(element.getInnerHTML() + config.innerHTML);
if (!CDUtils.isEmpty(container))
(prepend === true ? container.prepend(element) : container.append(element));
return element;
}
static append(container, element) {
if (CDUtils.isEmpty(element) || CDUtils.isEmpty(container) ||
(!(element instanceof Element) && !(element instanceof CDElement)) ||
(!(container instanceof Element) && !(container instanceof CDElement)))
return;
(container instanceof CDElement ? container.get() : container).append((element instanceof CDElement ? element.get() : element));
}
static setTitle(title) {
document.title = title;
}
static setCookie(name, value, hours) {
document.cookie = name + "=" + JSON.stringify(value) + "; path=/; expires=" + (new Date(Date.now() + hours * 3600000).toGMTString());
}
static getCookie(name) {
const cookies = {};
document.cookie.split(';').forEach(function(el) {
const [key, value] = el.split('=');
cookies[key.trim()] = value;
});
const cookieValue = cookies[name];
if(CDUtils.isEmpty(cookieValue))
return null;
if(CDUtils.isJsonString(cookieValue))
return JSON.parse(cookieValue);
else
return cookieValue;
}
static getCookieProperty(name, property) {
const cookie = DOM.getCookie(name);
if(cookie && !(cookie instanceof Object))
throw 'DOM.getCookieProperty(): cookie value is not a JSON';
return cookie ? cookie[property] : null;
}
static setCookieProperty(name, property, value, hours) {
const cookie = DOM.getCookie(name);
if(cookie) {
if(!(cookie instanceof Object))
throw 'DOM.setCookieProperty(): initial cookie value is not a JSON';
cookie[property] = value;
DOM.setCookie(name, cookie, hours || 24);
} else {
DOM.setCookie(name, { [property]: value }, hours || 24);
}
}
static documentOn(event, callback) {
if (CDUtils.isEmpty(event) || CDUtils.isEmpty(callback))
return;
document.addEventListener(event, callback);
}
static copyToClipboard(str) {
const body = DOM.get('body');
const input = DOM.create({ tag: DOM.Tags.Input, style: 'display: none;' }, body);
input.setValue(str);
input.get().select();
input.get().setSelectionRange(0, 99999);
navigator.clipboard.writeText(input.get().value).then(() => {
input.remove();
});
}
}
class CDUtils {
static isEmpty(val) {
return val === null || val === undefined ||
val === '' || val.length === 0;
}
static isJsonString(str) {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
}
static nl2br(str) {
return str.replaceAll(/(?:\r\n|\r|\n)/g, '
');
}
static br2nl(str) {
return str.replaceAll(/
/g, '\n');
}
static async SHA256(input) {
return crypto.subtle.digest('SHA-256', new TextEncoder('utf8').encode(input)).then(h => {
const hexes = [], view = new DataView(h);
for (let i = 0; i < view.byteLength; i += 4)
hexes.push(('00000000' + view.getUint32(i).toString(16)).slice(-8));
return hexes.join('');
});
}
/**
* Returns string representation of x with leading zeros.
* Length of the resulting string will be equal to length or 2 if length was not specified.
* If length of the initial number is greater or equal to length parameter, nothing will be changed.
*
* Examples:
CDUtils.pad(5) -> "05"
CDUtils.pad(21) -> "21"
CDUtils.pad(21, 3) -> "021"
* @param {number|String} x
* @param {number} [length]
* @return string
*/
static pad(x, length) {
const count = length != null && length >= 2 ? length : 2;
const str = x.toString();
const diff = count - str.length;
return `${"0".repeat(diff < 0 ? 0 : diff)}${str}`;
}
/**
* Returns formatted date.
* H - hours
I - minutes
S - seconds
s - milliseconds
D - day
M - month
Y - year (0000)
y - year (00)
* @param {number} timestamp
* @param {string} format
* @return string
*/
static dateFormat(timestamp, format) {
const date = new Date(timestamp);
const day = date.getDate();
const month = date.getMonth()+1;
const year = date.getFullYear();
const hours = date.getHours();
const minutes = date.getMinutes();
const seconds = date.getSeconds();
const mseconds = date.getMilliseconds();
return format.replaceAll('H', CDUtils.pad(hours.toString()))
.replaceAll('I', CDUtils.pad(minutes.toString()))
.replaceAll('S', CDUtils.pad(seconds.toString()))
.replaceAll('s', CDUtils.pad(mseconds.toString()))
.replaceAll('D', CDUtils.pad(day.toString()))
.replaceAll('M', CDUtils.pad(month.toString()))
.replaceAll('Y', CDUtils.pad(year.toString(), 4))
.replaceAll('y', CDUtils.pad((year % 100).toString()));
}
static dateFormatUTC(date, timeZoneOffsetHours, format) {
date = date instanceof Date ? date : new Date(date);
date.setUTCHours(date.getUTCHours() + timeZoneOffsetHours);
var year = date.getUTCFullYear();
var month = date.getUTCMonth() + 1;
var day = date.getUTCDate();
var hours = date.getUTCHours();
var minutes = date.getUTCMinutes();
var seconds = date.getUTCSeconds();
return format.replaceAll('H', CDUtils.pad(hours.toString()))
.replaceAll('I', CDUtils.pad(minutes.toString()))
.replaceAll('S', CDUtils.pad(seconds.toString()))
.replaceAll('D', CDUtils.pad(day.toString()))
.replaceAll('M', CDUtils.pad(month.toString()))
.replaceAll('Y', CDUtils.pad(year.toString(), 4))
.replaceAll('y', CDUtils.pad((year % 100).toString())) + ` UTC${timeZoneOffsetHours > 0 ? '+' : '-'}${timeZoneOffsetHours}`;
}
static repeat(str, n) {
let res = '';
for(let i = 0; i < n; i++)
res += str;
return res;
}
static randInt(min, max) {
const minCeiled = Math.ceil(min);
const maxFloored = Math.floor(max);
return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled);
}
static arrayShuffle(unshuffled) {
return unshuffled
.map(value => ({ value, sort: Math.random() }))
.sort((a, b) => a.sort - b.sort)
.map(({ value }) => value);
}
}
const window_ = CDElement.get(window);