cordis/client/webapp/q-module.ts

198 lines
5.9 KiB
TypeScript

// 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;
}
public $(queryString: string): HTMLElement {
let element = this.document.querySelector<HTMLElement>(queryString);
if (element === null) {
throw new SelectorError(`unable to find [${queryString}] in document`);
}
return element;
}
public $$(queryString: string): HTMLElement[] {
return Array.from(this.document.querySelectorAll<HTMLElement>(queryString));
}
public $$$(baseElement: HTMLElement, queryString: string): HTMLElement {
let element = baseElement.querySelector<HTMLElement>(queryString);
if (element === null) {
throw new SelectorError(`unable to find [${queryString}] in document`);
}
return element;
}
public $$$$(baseElement: HTMLElement, queryString: string): HTMLElement[] {
return Array.from(baseElement.querySelectorAll<HTMLElement>(queryString));
}
public $_(queryString: string): HTMLElement | null {
try {
return this.$(queryString);
} catch (e) {
return null;
}
}
public $$$_(baseElement: HTMLElement, 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[] {
let n = 0;
let result: any[] = [];
for (let 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;
}
/**
* @description gets the previous HTMLElement in the DOM. Useful to skip any text nodes
* @param element HTMLElement to get the previous element of
* @returns the previous HTMLElement in the DOM or null if there are no HTMLElements before the specified element.
*/
public static previousElement(element: HTMLElement): HTMLElement | null {
let current: ChildNode | HTMLElement = element;
while (current.previousSibling) {
if (current.previousSibling instanceof HTMLElement) {
return current.previousSibling;
}
current = current.previousSibling;
}
return null;
}
/**
* @description gets the next HTMLElement in the DOM. Useful to skip any text nodes
* @param element HTMLElement to get the next element of
* @returns the next HTMLElement in the DOM or null if there are no HTMLElements after the specified element.
*/
public static nextElement(element: HTMLElement): HTMLElement | null {
let current: ChildNode | HTMLElement = element;
while (current.nextSibling) {
if (current.nextSibling instanceof HTMLElement) {
return current.nextSibling;
}
current = current.nextSibling;
}
return null;
}
/**
* @function $.clearChildren
* @description removes all children from an element
* @param {HTMLElement} element The element to remove all children from
*/
public static clearChildren = function(element: HTMLElement): void {
while (element.firstChild) {
element.removeChild(element.firstChild);
}
}
/**
* @description Adds to the internal content of an HTMLElement
* @param {HTMLElement} element The element to add content to
* @param content The content to be added to the element
*/
public addContent(element: HTMLElement, content: any) {
content = Array.isArray(content) ? content : [ content ];
for (let e of content) {
element.appendChild(this.create(e));
}
}
/**
* Recursively creates an HTMLElement 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.
* Can be a string, HTMLElement, obj, list of objs, or list of HTMLElements
* @var ... All other attributes will be applied using element.setAttribute(key, value)
*/
public create(obj: HTMLElement | any | null): Node {
if (obj instanceof HTMLElement) {
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');
}
let element: HTMLElement;
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 (let 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 (let key in obj) {
if (key !== 'tag' && key !== 'id' && key != 'class' && key != 'content') {
if (obj.hasOwnProperty(key)) {
element.setAttribute(key.toString(), obj[key].toString());
}
}
}
if (obj.content !== undefined) {
this.addContent(element, obj.content);
}
return element;
}
}