cordis/archive/q-module.ts

199 lines
6.0 KiB
TypeScript
Raw Normal View History

/* eslint-disable @typescript-eslint/no-explicit-any */
// Fake jQuery because jQuery is for faggots
export class SelectorError extends Error {
constructor(...args: any[]) {
super(...args);
this.name = 'SelectorError';
}
}
export default class Q {
private document: Document;
constructor(document: Document) {
this.document = document;
}
2021-12-06 01:42:14 +00:00
public $<T = HTMLElement>(queryString: string): T {
const element = this.document.querySelector<HTMLElement>(queryString);
if (element === null) {
throw new SelectorError(`unable to find [${queryString}] in document`);
}
2021-12-06 01:42:14 +00:00
return element as unknown as T; // dangerous but convenient
}
public $$(queryString: string): HTMLElement[] {
return Array.from(this.document.querySelectorAll<HTMLElement>(queryString));
}
2021-12-06 03:01:17 +00:00
public $$$<T = HTMLElement>(baseElement: Element, queryString: string): T {
const element = baseElement.querySelector<HTMLElement>(queryString);
if (element === null) {
throw new SelectorError(`unable to find [${queryString}] in document`);
}
2021-12-06 01:42:14 +00:00
return element as unknown as T; // dangerous but convenient
}
2021-12-06 03:01:17 +00:00
public $$$$(baseElement: Element, queryString: string): HTMLElement[] {
return Array.from(baseElement.querySelectorAll<HTMLElement>(queryString));
}
public $_(queryString: string): HTMLElement | null {
try {
return this.$(queryString);
} catch (e) {
return null;
}
}
2021-12-06 03:01:17 +00:00
public $$$_(baseElement: Element, queryString: string): HTMLElement | null {
try {
return this.$$$(baseElement, queryString);
} catch (e) {
return null;
}
}
/**
* @function $.zip
* @description Zip arrays together
* $.zip([2, 3, 4], [5, 6]) -> [ [2, 5], [3, 6], [4, undefined] ]
* $.zip([3, 4], [5, 6, 7]) -> [ [3, 5], [4, 6], [undefined, 7] ]
* @param {...any} arrays Lists to zip together
* @return The arrays zipped together
*/
public static zip(...arrays: any[]): any[] {
const n = 0;
const result: any[] = [];
for (const array of arrays) {
if (!Array.isArray(array)) throw new Error('all parameters must be arrays');
for (let i = 0; i < array.length; ++i) {
if (i == result.length) {
result.push([]);
for (let j = 0; j < n; ++j) {
result[i].push(undefined);
}
}
result[i].push(array[i]);
}
}
return result;
}
/**
2021-12-06 03:01:17 +00:00
* @description gets the previous Element in the DOM. Useful to skip any text nodes
* @param element Element to get the previous element of
* @returns the previous Element in the DOM or null if there are no Element before the specified element.
*/
2021-12-06 03:01:17 +00:00
public static previousElement(element: Element): Element | null {
let current: ChildNode | Element = element;
while (current.previousSibling) {
2021-12-06 03:01:17 +00:00
if (current.previousSibling instanceof Element) {
return current.previousSibling;
}
current = current.previousSibling;
}
return null;
}
/**
2021-12-06 03:01:17 +00:00
* @description gets the next Element in the DOM. Useful to skip any text nodes
* @param element Element to get the next element of
* @returns the next Element in the DOM or null if there are no Elements after the specified element.
*/
2021-12-06 03:01:17 +00:00
public static nextElement(element: Element): Element | null {
let current: ChildNode | Element = element;
while (current.nextSibling) {
2021-12-06 03:01:17 +00:00
if (current.nextSibling instanceof Element) {
return current.nextSibling;
}
current = current.nextSibling;
}
return null;
}
/**
* @function $.clearChildren
* @description removes all children from an element
2021-12-06 03:01:17 +00:00
* @param {Element} element The element to remove all children from
*/
2021-12-06 03:01:17 +00:00
public static clearChildren = function(element: Element): void {
while (element.firstChild) {
element.removeChild(element.firstChild);
}
}
/**
2021-12-06 03:01:17 +00:00
* @description Adds to the internal content of an Element
* @param {Element} element The element to add content to
* @param content The content to be added to the element
*/
2021-12-06 03:01:17 +00:00
public addContent(element: Element, content: any) {
content = Array.isArray(content) ? content : [ content ];
for (const e of content) {
element.appendChild(this.create(e));
}
}
/**
2021-12-06 03:01:17 +00:00
* Recursively creates an Element from the specified content
* @param obj An element initialization object { tag, id, class, content, ... }
* @var tag The HTML tag of the element (default: 'div')
* @var id The id of the elemnt
* @var class The class of the element (string or list of strings)
* @var content The internal content of the element. This is handled recursively.
2021-12-06 03:01:17 +00:00
* Can be a string, Element, obj, list of objs, or list of Elements
* @var ... All other attributes will be applied using element.setAttribute(key, value)
*/
2021-12-06 03:01:17 +00:00
public create(obj: Element | any | null): Node {
if (obj instanceof Element) {
return obj
} else if (typeof obj !== 'object') {
return this.document.createTextNode(obj.toString());
} else if (obj === null) {
return this.document.createTextNode('[null]');
} else if (obj === undefined) {
throw new Error('obj is undefined');
}
2021-12-06 03:01:17 +00:00
let element: Element;
if (obj.ns) {
element = document.createElementNS(obj.ns, obj.tag ?? 'div');
} else {
element = document.createElement(obj.tag ?? 'div');
}
if (obj.id) {
element.id = obj.id;
}
if (obj.class) {
if (Array.isArray(obj.class)) {
for (const c of obj.class) {
if (typeof c !== 'string') throw new Error(`Invalid obj.class type: ${typeof c}`);
element.classList.add(c);
}
} else if (typeof obj.class === 'string') {
element.className = obj.class;
} else {
throw new Error(`Invalid obj.class type: ${obj.class}`);
}
}
for (const key in obj) {
if (key !== 'tag' && key !== 'id' && key != 'class' && key != 'content') {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
element.setAttribute(key.toString(), obj[key].toString());
}
}
}
if (obj.content !== undefined) {
this.addContent(element, obj.content);
}
return element;
}
}