CDClientLib.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. /*!
  2. Client lib for manipulating on DOM elements.
  3. Author: CrazyDoctor (Oleg Karataev)
  4. */
  5. class CDElement {
  6. constructor(el) {
  7. this.element = el;
  8. el.cdelement = this;
  9. try {
  10. this.events = Object.keys(getEventListeners(el));
  11. } catch(e) {
  12. this.events = [];
  13. }
  14. }
  15. static get(el) {
  16. if(el == null)
  17. return null;
  18. if(el instanceof CDElement)
  19. return el;
  20. if(el.cdelement)
  21. return el.cdelement;
  22. if(el instanceof Element || el instanceof Window)
  23. return el.cdelement = new CDElement(el);
  24. throw 'CDElement.get() error';
  25. }
  26. get() {
  27. return this.element;
  28. }
  29. copy() {
  30. return new CDElement(this.get().cloneNode(true));
  31. }
  32. getFirstChild(selector) {
  33. const children = Array.from(this.get().children);
  34. if (children.length == 0)
  35. return null;
  36. if (CDUtils.isEmpty(selector))
  37. return CDElement.get(children[0]);
  38. const child = this.get().querySelector(selector);
  39. if(child)
  40. return CDElement.get(child);
  41. return null;
  42. }
  43. hasChildren() {
  44. return Array.from(this.get().children).length > 0;
  45. }
  46. getChildren(selector) {
  47. if (CDUtils.isEmpty(selector))
  48. return Array.from(this.get().children).map((element) => CDElement.get(element));
  49. return Array.from(this.get().querySelectorAll(selector)).map((element) => CDElement.get(element));
  50. }
  51. getChildrenRecursive() {
  52. let children = this.getChildren();
  53. for(const child of children) {
  54. if(this.hasChildren())
  55. children = children.concat(child.getChildrenRecursive());
  56. }
  57. return children;
  58. }
  59. getTag() {
  60. return this.get().tagName.toLowerCase();
  61. }
  62. getParent() {
  63. return CDElement.get(this.get().parentElement);
  64. }
  65. getValue() {
  66. return this.get().value || this.getInnerHTML();
  67. }
  68. setValue(value) {
  69. this.get().value = value;
  70. return this;
  71. }
  72. append(element) {
  73. this.get().append(element.get());
  74. }
  75. prepend(element) {
  76. this.get().prepend(element.get());
  77. }
  78. remove() {
  79. this.get().remove();
  80. }
  81. disable() {
  82. this.removeCssProperty('display');
  83. this.addClass('disabled');
  84. return this;
  85. }
  86. enable(display) {
  87. this.removeClass('disabled');
  88. this.get().style.display = CDUtils.isEmpty(display) ? 'block' : display;
  89. return this;
  90. }
  91. minimizeHeight() {
  92. this.get().style.height = '0px';
  93. return this;
  94. }
  95. minimizeWidth() {
  96. this.get().style.width = '0px';
  97. return this;
  98. }
  99. expandHeight(size) {
  100. if (CDUtils.isEmpty(size))
  101. return this;
  102. this.get().style.height = size + 'px';
  103. return this;
  104. }
  105. expandWidth(size){
  106. if (CDUtils.isEmpty(size))
  107. return this;
  108. this.get().style.width = size + 'px';
  109. return this;
  110. }
  111. isMinimized(el) {
  112. return CDUtils.isEmpty(this.get().style.height);
  113. }
  114. isDisabled(el) {
  115. return CDUtils.isEmpty(this.get().style.display);
  116. }
  117. setId(id) {
  118. if (CDUtils.isEmpty(id))
  119. return this;
  120. this.get().id = id;
  121. return id;
  122. }
  123. previousSibling() {
  124. return CDElement.get(this.get().previousElementSibling);
  125. }
  126. nextSibling() {
  127. return CDElement.get(this.get().nextElementSibling);
  128. }
  129. addClass(cls) {
  130. if (CDUtils.isEmpty(cls))
  131. return this;
  132. cls.split(' ').forEach((c) => {
  133. if(c.length > 0 && !this.hasClass(c))
  134. this.get().classList.add(c);
  135. });
  136. return this;
  137. }
  138. getClass() {
  139. if (CDUtils.isEmpty(this.get().classList))
  140. return '';
  141. let classList = '';
  142. this.get().classList.forEach((cls) => {
  143. classList += cls + " ";
  144. });
  145. return classList.trim();
  146. }
  147. removeClass(cls) {
  148. if (CDUtils.isEmpty(cls))
  149. return this;
  150. this.get().classList.remove(cls);
  151. return this;
  152. }
  153. hasClass(cls) {
  154. if (CDUtils.isEmpty(this.get().classList))
  155. return false;
  156. let has = false;
  157. this.get().classList.forEach((c) => {
  158. if (c === cls) has = true;
  159. });
  160. return has;
  161. }
  162. removeClass(cls) {
  163. if (CDUtils.isEmpty(cls))
  164. return this;
  165. this.get().classList.remove(cls);
  166. return this;
  167. }
  168. switchClass(cls, condition) {
  169. if(condition != null)
  170. return condition ? this.addClass(cls) : this.removeClass(cls);
  171. return this.hasClass(cls) ? this.removeClass(cls) : this.addClass(cls);
  172. }
  173. removeCssProperty(prop) {
  174. if (CDUtils.isEmpty(prop))
  175. return this;
  176. this.get().style.setProperty(prop, '');
  177. return this;
  178. }
  179. setAttribute(attr, value) {
  180. this.get().setAttribute(attr, CDUtils.isEmpty(value) ? '' : value);
  181. return this;
  182. }
  183. getAttribute(attr) {
  184. return this.get().getAttribute(attr);
  185. }
  186. setInnerHTML(value) {
  187. this.get().innerHTML = CDUtils.isEmpty(value) ? '' : value;
  188. return this;
  189. }
  190. getInnerHTML() {
  191. return this.get().innerHTML;
  192. }
  193. getInnerWidth() {
  194. return this.get().innerWidth;
  195. }
  196. getInnerHeight() {
  197. return this.get().innerHeight;
  198. }
  199. getOffsetTop() {
  200. return this.get().offsetTop;
  201. }
  202. scrollTo(selector) {
  203. const child = this.getFirstChild(selector);
  204. if(child)
  205. this.get().scrollTop = child.getOffsetTop() - this.getOffsetTop();
  206. }
  207. scrollIntoView() {
  208. this.get().scrollIntoView();
  209. return this;
  210. }
  211. style(property, value, priority) {
  212. this.get().style.setProperty(property, value, priority);
  213. return this;
  214. }
  215. focus() {
  216. this.get().focus();
  217. return this;
  218. }
  219. blur() {
  220. this.get().blur();
  221. return this;
  222. }
  223. click() {
  224. this.get().click();
  225. return this;
  226. }
  227. on(event, callback) {
  228. if (CDUtils.isEmpty(event) || CDUtils.isEmpty(callback))
  229. return this;
  230. this.get().addEventListener(event, callback);
  231. return this;
  232. }
  233. un(event, callback) {
  234. if (CDUtils.isEmpty(event))
  235. return this;
  236. this.get().removeEventListener(event, callback);
  237. return this;
  238. }
  239. }
  240. class Url {
  241. constructor(url) {
  242. this.url = new URL(url || location);
  243. this.urlSearch = new URLSearchParams(this.url.search);
  244. }
  245. static getHash() {
  246. return new Url().getHash();
  247. }
  248. static setHash(hash) {
  249. return new Url().setHash(hash);
  250. }
  251. static setPath(path) {
  252. return new Url().setPath(path);
  253. }
  254. static getOrigin() {
  255. return new Url().getOrigin();
  256. }
  257. static goTo(url, blank) {
  258. window.open(url, blank ? '_blank' : '_self');
  259. }
  260. static reload() {
  261. location.reload();
  262. }
  263. static getPath() {
  264. return new Url().getPath();
  265. }
  266. static getFullPath() {
  267. const url = new Url().url;
  268. return `${url.origin}${url.pathname}`;
  269. }
  270. setPath(path) {
  271. this.url.pathname = path;
  272. return this;
  273. }
  274. getHash() {
  275. const hash = this.url.hash.substring(1);
  276. return hash.length > 0 ? hash : null;
  277. }
  278. setHash(hash) {
  279. this.url.hash = !hash || hash.length == 0 ? '' : `#${hash}`;
  280. return this;
  281. }
  282. setSearchParams(params) {
  283. const paramsArr = [];
  284. for(const key of Object.keys(params)) {
  285. paramsArr.push(`${key}=${params[key]}`);
  286. }
  287. this.url.search = paramsArr.join('&');
  288. }
  289. getSearchParams() {
  290. const params = {};
  291. Array.from(this.url.searchParams).forEach((pair) => {
  292. params[pair[0]] = pair[1];
  293. });
  294. return params;
  295. }
  296. setPath(path) {
  297. this.url.pathname = path;
  298. return this;
  299. }
  300. getPath() {
  301. return this.url.pathname;
  302. }
  303. getOrigin() {
  304. return this.url.origin;
  305. }
  306. getProtocol() {
  307. return this.url.protocol;
  308. }
  309. setProtocol(protocol) {
  310. this.url.protocol = protocol;
  311. return this;
  312. }
  313. toString() {
  314. this.url.search = this.urlSearch.toString();
  315. return this.url.toString();
  316. }
  317. toLocalString() {
  318. this.url.search = this.urlSearch.toString();
  319. return this.toString().substring(this.url.origin.length);
  320. }
  321. updateLocation() {
  322. const hashChanged = Url.getHash() !== this.getHash();
  323. history.replaceState(null, null, this.toLocalString());
  324. hashChanged && window.dispatchEvent(new HashChangeEvent('hashchange'));
  325. }
  326. }
  327. class Style {
  328. static apply(element, styleStr) {
  329. element = element instanceof CDElement ? element : CDElement.get(element);
  330. const propertiesMap = this.getCssPropertiesMap(styleStr);
  331. for(const prop of Object.keys(propertiesMap))
  332. element.style(prop, propertiesMap[prop].value, propertiesMap[prop].priority);
  333. }
  334. static getCssPropertiesMap(styleStr) {
  335. const parts = styleStr.split(';');
  336. const map = {};
  337. for(let part of parts) {
  338. part = part.trim();
  339. if(part.length == 0)
  340. continue;
  341. const propVal = part.split(':');
  342. const property = propVal[0].trim();
  343. const value = propVal[1].trim().split('!');
  344. map[property] = { value: value[0], priority: value.length > 1 ? value[1] : '' };
  345. }
  346. return map;
  347. }
  348. }
  349. class DOM {
  350. static Events = {
  351. Click: 'click',
  352. Load: 'load',
  353. KeyDown: 'keydown',
  354. KeyUp: 'keyup',
  355. KeyPress: 'keypress',
  356. Change: 'change',
  357. Cut: 'cut',
  358. Drop: 'drop',
  359. Paste: 'paste',
  360. Input: 'input',
  361. HashChange: 'hashchange',
  362. MouseDown: 'mousedown',
  363. ContextMenu: 'contextmenu',
  364. Blur: 'blur'
  365. };
  366. static Tags = {
  367. A: 'a',
  368. Div: 'div',
  369. Span: 'span',
  370. H1: 'h1',
  371. H2: 'h2',
  372. H3: 'h3',
  373. P: 'p',
  374. Textarea: 'textarea',
  375. Input: 'input',
  376. Table: 'table',
  377. Tr: 'tr',
  378. Th: 'th',
  379. Tbody: 'tbody',
  380. Td: 'td',
  381. Select: 'select',
  382. Option: 'option'
  383. };
  384. static Keys = {
  385. Enter: 'Enter',
  386. Escape: 'Escape',
  387. Control: 'Control',
  388. Shift: 'Shift',
  389. Backspace: 'Backspace',
  390. Meta: 'Meta'
  391. };
  392. static MouseButtons = {
  393. Left: 1,
  394. Right: 2,
  395. Middle: 4
  396. };
  397. static get(selector) {
  398. if (CDUtils.isEmpty(selector))
  399. throw "DOM.get() invalid selector.";
  400. const element = document.querySelector(selector);
  401. if (CDUtils.isEmpty(element))
  402. return null;
  403. return CDElement.get(element);
  404. }
  405. static getAll(selector) {
  406. if (CDUtils.isEmpty(selector))
  407. throw "DOM.getAll() invalid selector.";
  408. const elements = document.querySelectorAll(selector);
  409. if(CDUtils.isEmpty(elements))
  410. return [];
  411. return Array.from(elements).map((element) => CDElement.get(element));
  412. }
  413. static create(config, container, prepend) {
  414. if (CDUtils.isEmpty(config) || CDUtils.isEmpty(config.tag))
  415. return;
  416. const element = CDElement.get(document.createElement(config.tag));
  417. if (!CDUtils.isEmpty(config.attr)) {
  418. Object.keys(config.attr).forEach((name) => {
  419. if(config.attr[name] !== undefined)
  420. element.setAttribute(name, config.attr[name]);
  421. });
  422. }
  423. if (!CDUtils.isEmpty(config.cls)) element.addClass(config.cls);
  424. if (!CDUtils.isEmpty(config.id)) element.setId(config.id);
  425. if (!CDUtils.isEmpty(config.style)) Style.apply(element, config.style);
  426. if (!CDUtils.isEmpty(config.cn)) {
  427. config.cn.forEach((el) => {
  428. if (el instanceof Element) {
  429. element.append(CDElement.get(el));
  430. } else if (el instanceof CDElement) {
  431. element.append(el);
  432. } else this.create(el, element);
  433. });
  434. }
  435. // innerHTML appends after cn
  436. if (!CDUtils.isEmpty(config.innerHTML)) element.setInnerHTML(element.getInnerHTML() + config.innerHTML);
  437. if (!CDUtils.isEmpty(container))
  438. (prepend === true ? container.prepend(element) : container.append(element));
  439. return element;
  440. }
  441. static append(container, element) {
  442. if (CDUtils.isEmpty(element) || CDUtils.isEmpty(container) ||
  443. (!(element instanceof Element) && !(element instanceof CDElement)) ||
  444. (!(container instanceof Element) && !(container instanceof CDElement)))
  445. return;
  446. (container instanceof CDElement ? container.get() : container).append((element instanceof CDElement ? element.get() : element));
  447. }
  448. static setTitle(title) {
  449. document.title = title;
  450. }
  451. static setCookie(name, value, hours) {
  452. document.cookie = name + "=" + JSON.stringify(value) + "; path=/; expires=" + (new Date(Date.now() + hours * 3600000).toGMTString());
  453. }
  454. static getCookie(name) {
  455. const cookies = {};
  456. document.cookie.split(';').forEach(function(el) {
  457. const [key, value] = el.split('=');
  458. cookies[key.trim()] = value;
  459. });
  460. const cookieValue = cookies[name];
  461. if(CDUtils.isEmpty(cookieValue))
  462. return null;
  463. if(CDUtils.isJsonString(cookieValue))
  464. return JSON.parse(cookieValue);
  465. else
  466. return cookieValue;
  467. }
  468. static getCookieProperty(name, property) {
  469. const cookie = DOM.getCookie(name);
  470. if(cookie && !(cookie instanceof Object))
  471. throw 'DOM.getCookieProperty(): cookie value is not a JSON';
  472. return cookie ? cookie[property] : null;
  473. }
  474. static setCookieProperty(name, property, value, hours) {
  475. const cookie = DOM.getCookie(name);
  476. if(cookie) {
  477. if(!(cookie instanceof Object))
  478. throw 'DOM.setCookieProperty(): initial cookie value is not a JSON';
  479. cookie[property] = value;
  480. DOM.setCookie(name, cookie, hours || 24);
  481. } else {
  482. DOM.setCookie(name, { [property]: value }, hours || 24);
  483. }
  484. }
  485. static documentOn(event, callback) {
  486. if (CDUtils.isEmpty(event) || CDUtils.isEmpty(callback))
  487. return;
  488. document.addEventListener(event, callback);
  489. }
  490. static copyToClipboard(str) {
  491. const body = DOM.get('body');
  492. const input = DOM.create({ tag: DOM.Tags.Input, style: 'display: none;' }, body);
  493. input.setValue(str);
  494. input.get().select();
  495. input.get().setSelectionRange(0, 99999);
  496. navigator.clipboard.writeText(input.get().value).then(() => {
  497. input.remove();
  498. });
  499. }
  500. }
  501. class CDUtils {
  502. static isEmpty(val) {
  503. return val === null || val === undefined ||
  504. val === '' || val.length === 0;
  505. }
  506. static isJsonString(str) {
  507. try {
  508. JSON.parse(str);
  509. } catch (e) {
  510. return false;
  511. }
  512. return true;
  513. }
  514. static nl2br(str) {
  515. return str.replaceAll(/(?:\r\n|\r|\n)/g, '<br/>');
  516. }
  517. static br2nl(str) {
  518. return str.replaceAll(/<br\/?>/g, '\n');
  519. }
  520. static async SHA256(input) {
  521. return crypto.subtle.digest('SHA-256', new TextEncoder('utf8').encode(input)).then(h => {
  522. const hexes = [], view = new DataView(h);
  523. for (let i = 0; i < view.byteLength; i += 4)
  524. hexes.push(('00000000' + view.getUint32(i).toString(16)).slice(-8));
  525. return hexes.join('');
  526. });
  527. }
  528. /**
  529. * Returns string representation of <b>x</b> with leading zeros.<br/>
  530. * Length of the resulting string will be equal to <b>length</b> or <b>2</b> if <b>length</b> was not specified.<br/>
  531. * If length of the initial number is greater or equal to <b>length</b> parameter, nothing will be changed.
  532. * <br/><br/>
  533. * Examples:<br/>CDUtils.pad(5) -> "05"<br/>CDUtils.pad(21) -> "21"<br/>CDUtils.pad(21, 3) -> "021"
  534. * @param {number|String} x
  535. * @param {number} [length]
  536. * @return string
  537. */
  538. static pad(x, length) {
  539. const count = length != null && length >= 2 ? length : 2;
  540. const str = x.toString();
  541. const diff = count - str.length;
  542. return `${"0".repeat(diff < 0 ? 0 : diff)}${str}`;
  543. }
  544. /**
  545. * Returns formatted date. <br/>
  546. * H - hours<br/>I - minutes<br/>S - seconds<br/>s - milliseconds<br/>D - day<br/>M - month<br/>Y - year (0000)<br/>y - year (00)
  547. * @param {number} timestamp
  548. * @param {string} format
  549. * @return string
  550. */
  551. static dateFormat(timestamp, format) {
  552. const date = new Date(timestamp);
  553. const day = date.getDate();
  554. const month = date.getMonth()+1;
  555. const year = date.getFullYear();
  556. const hours = date.getHours();
  557. const minutes = date.getMinutes();
  558. const seconds = date.getSeconds();
  559. const mseconds = date.getMilliseconds();
  560. return format.replaceAll('H', CDUtils.pad(hours.toString()))
  561. .replaceAll('I', CDUtils.pad(minutes.toString()))
  562. .replaceAll('S', CDUtils.pad(seconds.toString()))
  563. .replaceAll('s', CDUtils.pad(mseconds.toString()))
  564. .replaceAll('D', CDUtils.pad(day.toString()))
  565. .replaceAll('M', CDUtils.pad(month.toString()))
  566. .replaceAll('Y', CDUtils.pad(year.toString(), 4))
  567. .replaceAll('y', CDUtils.pad((year % 100).toString()));
  568. }
  569. static dateFormatUTC(date, timeZoneOffsetHours, format) {
  570. date = date instanceof Date ? date : new Date(date);
  571. date.setUTCHours(date.getUTCHours() + timeZoneOffsetHours);
  572. var year = date.getUTCFullYear();
  573. var month = date.getUTCMonth() + 1;
  574. var day = date.getUTCDate();
  575. var hours = date.getUTCHours();
  576. var minutes = date.getUTCMinutes();
  577. var seconds = date.getUTCSeconds();
  578. return format.replaceAll('H', CDUtils.pad(hours.toString()))
  579. .replaceAll('I', CDUtils.pad(minutes.toString()))
  580. .replaceAll('S', CDUtils.pad(seconds.toString()))
  581. .replaceAll('D', CDUtils.pad(day.toString()))
  582. .replaceAll('M', CDUtils.pad(month.toString()))
  583. .replaceAll('Y', CDUtils.pad(year.toString(), 4))
  584. .replaceAll('y', CDUtils.pad((year % 100).toString())) + ` UTC${timeZoneOffsetHours > 0 ? '+' : '-'}${timeZoneOffsetHours}`;
  585. }
  586. static repeat(str, n) {
  587. let res = '';
  588. for(let i = 0; i < n; i++)
  589. res += str;
  590. return res;
  591. }
  592. static randInt(min, max) {
  593. const minCeiled = Math.ceil(min);
  594. const maxFloored = Math.floor(max);
  595. return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled);
  596. }
  597. static arrayShuffle(unshuffled) {
  598. return unshuffled
  599. .map(value => ({ value, sort: Math.random() }))
  600. .sort((a, b) => a.sort - b.sort)
  601. .map(({ value }) => value);
  602. }
  603. }
  604. const window_ = CDElement.get(window);