// Fake jQuery because jQuery is for faggots let document: Document | null = null; export class SelectorError extends Error { constructor(...args: any[]) { super(...args); this.name = 'SelectorError'; } } export const $ = function(queryString: string): HTMLElement { if (document === null) { throw new SelectorError('document not specified'); } let element = document.querySelector(queryString); if (element === null) { throw new SelectorError(`unable to find [${queryString}] in document`); } return element; } export const $_ = function(queryString: string): HTMLElement | null { try { return $(queryString); } catch (e) { return null; } } export const $$ = function(queryString: string): HTMLElement[] { // Because ElementList Objects are cancerous if (document === null) { throw new SelectorError('document not specified'); } return Array.from(document.querySelectorAll(queryString)); } export const $$$ = function(element: HTMLElement, queryString: string): HTMLElement { let foundElement = element.querySelector(queryString); if (foundElement === null) { throw new SelectorError(`unable to find [${queryString}] in element`); } return foundElement; } export const $$$$ = function(element: HTMLElement, queryString: string): HTMLElement[] { return Array.from(element.querySelectorAll(queryString)); } // Set the document for all q.js functions to use $.setDocument = function(doc: Document) { document = doc; } // General javascript functions /** * @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 */ $.zip = function(...arrays: any[]): any[] { let n = 0; let result: any[] = []; for (let array of arrays) { $.assert(Array.isArray(array), '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; } /** * @function $.previousElement * @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. */ $.previousElement = function(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; } /** * @function $.nextElement * @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. */ $.nextElement = function(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; } // From elipzer.com q.js, custom HTMLElement generation /** * @function $.assert * @description Asserts that the value is true, throwing an error with specified * message otherwise * @param value The value of the assertion * @param message The message to be put in the error message if the value is falsy */ $.assert = function(value: boolean, message: string): void { if (!value) { throw new Error(message); } } /** * @function $.addContent * @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 */ $.addContent = function(element: HTMLElement, content: any) { content = Array.isArray(content) ? content : [ content ]; for (let e of content) { element.appendChild($.create(e)); } } /** * @function $.clearChildren * @description removes all children from an element * @param {HTMLElement} element The element to remove all children from */ $.clearChildren = function(element: HTMLElement): void { while (element.firstChild) { element.removeChild(element.firstChild); } } /** * @function $.create * 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) */ $.create = function(obj: HTMLElement | any | null): Node { if (document === null) { throw new Error('document not specified'); } if (obj instanceof HTMLElement) { return obj } else if (typeof obj !== 'object') { return document.createTextNode(obj.toString()); } else if (obj == null) { return document.createTextNode('[null]'); } $.assert(obj !== undefined, '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) { $.assert(typeof c === 'string', `Invalid obj.class[i] 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) { $.addContent(element, obj.content); } return element; }