diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..073c789 Binary files /dev/null and b/.DS_Store differ diff --git a/JDOM.js b/JDOM.js index f0cd1e0..4b0130f 100644 --- a/JDOM.js +++ b/JDOM.js @@ -1,18 +1,41 @@ +import './html-typedefs.js' + +/** + * @typedef Animation + * @property {CSSPropertiesConfiguration} css + * @property {Number} duration + */ + +/** + * @typedef JDOMCustomHTMLElement + * @extends HTMLElement + * @property {function(style: string)} addStyle + * @property {...any} args + * @property {Node} el + */ + class JDOM { + /** + * @param {Node|JDOM|NodeList|string} element + * @param {Node} parent + */ constructor(element, parent = undefined) { if (typeof parent === 'undefined') parent = document; + /** + * @type {Node[]} + */ this.elem = [] if (element instanceof NodeList) { this.elem = element - } else if (element instanceof HTMLElement || element === document || element === window) { + } else if (element instanceof Node || element === document || element === window) { this.elem = [element] } else if (element instanceof JDOM) { this.elem = element.elem } else { - this.elem = parent.querySelectorAll(element); + this.elem = [...parent.querySelectorAll(element)]; } this.$ = selector => { @@ -22,70 +45,147 @@ class JDOM { } } + /** + * @param {function(JDOM)} callable + * @return {JDOM} + */ each(callable) { + for (const el of this.elem) { + callable.call(el, new JDOM(el)) + } + return this + } + + /** + * @param {function(JDOM)} callable + * @return {JDOM} + */ + forEach(callable) { + return this.each(callable) + } + + /** + * @param {function(Node)} callable + * @return {JDOM} + */ + eachElems(callable) { for (const el of this.elem) { callable.call(el, el) } return this } + /** + * @return {JDOM|null} + */ first() { + return this.elem.length > 0 ? new JDOM(this.elem[0]) : null + } + + /** + * @return {Node|null} + */ + element() { return this.elem.length > 0 ? this.elem[0] : null } + /** + * @return {Node[]} + */ + elements() { + return this.elem + } + + /** + * @param {string|Number} text + * @return {JDOM} + */ setText(text) { - return this.each(el => { + return this.eachElems(el => { el.innerText = text }) } + /** + * @return {string|null} + */ getText() { - const el = this.first() + const el = this.element() return el ? el.innerText : null } - text(text = null) { - return text === null ? this.getText() : this.setText(text) + /** + * @param {string|undefined} text + * @return {string|null|JDOM} + */ + text(text = undefined) { + return text === undefined ? this.getText() : this.setText(text) } + /** + * @param {string} html + * @return {JDOM} + */ setHTML(html) { - return this.each(el => { + return this.eachElems(el => { el.innerHTML = html }) } + /** + * @return {string|null} + */ getHTML() { - const el = this.first() + const el = this.element() return el ? el.innerHTML : null } - html(html = null) { - if (html === null) + /** + * @param {string|undefined} html + * @return {JDOM|string|null} + */ + html(html = undefined) { + if (html === undefined) return this.getHTML() else return this.setHTML(html) } + /** + * + * @param {CSSPropertiesConfiguration} css + * @return {JDOM} + */ css(css) { - return this.each(el => { + return this.eachElems(el => { for (const [key, value] of Object.entries(css)) { el.style[key] = value } }) } + /** + * @param {CSSPropertiesConfiguration} css + * @return {JDOM} + */ style(css) { return this.css(css) } - + /** + * @param {string} name + * @return {HTMLAttributes} + */ getAttr(name) { - const el = this.first() + const el = this.element() return el ? el.getAttribute(name) : null } + /** + * @return {Object} + */ getAttributes() { - const el = this.first() + const el = this.element() const attribs = {} if (el) { const elAttributes = el.attributes @@ -96,18 +196,32 @@ class JDOM { return attribs } + /** + * @param {HTMLAttributes} name + * @param {string} val + * @return {JDOM} + */ setAttr(name, val) { - return this.each(el => { + return this.eachElems(el => { el.setAttribute(name, val) }) } + /** + * @param {HTMLAttributes} name + * @return {JDOM} + */ removeAttr(name) { - return this.each(el => { + return this.eachElems(el => { el.removeAttribute(name) }) } + /** + * @param {HTMLAttributes} name + * @param {string|null|undefined} val + * @return {JDOM|string|null} + */ attr(name, val = undefined) { if (typeof name === 'string') return val === undefined ? this.getAttr(name) : this.setAttr(name, val) @@ -118,32 +232,54 @@ class JDOM { return this; } + /** + * @return {string[]} + */ attrs() { return this.getAttributes() } + /** + * @param {string} name + * @return {boolean} + */ hasClass(name) { - const el = this.first() + const el = this.element() return el ? el.classList.contains(name) : false } + /** + * @param {...string} names + * @return {JDOM} + */ addClass(...names) { - return this.each(el => { + return this.eachElems(el => { for (let name of names) { el.classList.add(name) } }) } + /** + * @param {...string} names + * @return {JDOM} + */ addClasses(...names) { return this.addClass(...names) } + /** + * @return {string[]} + */ getClasses() { - const el = this.first() + const el = this.element() return el ? [...el.classList] : [] } + /** + * @param {...string} names + * @return {JDOM} + */ classes(...names) { if (names.length === 0) { return this.getClasses() @@ -152,14 +288,22 @@ class JDOM { } + /** + * @param {string} name + * @return {JDOM} + */ removeClass(name) { - return this.each(el => { + return this.eachElems(el => { el.classList.remove(name) }) } + /** + * @param {string} name + * @return {JDOM} + */ toggleClass(name) { - return this.each(el => { + return this.eachElems(el => { if (el.classList.contains(name)) { el.classList.remove(name) } else { @@ -168,17 +312,28 @@ class JDOM { }) } + /** + * @return {any} + */ getValue() { - const el = this.first() + const el = this.element() return el ? el.value : null } + /** + * @param {any} val + * @return {JDOM} + */ setValue(val) { - return this.each(el => { + return this.eachElems(el => { el.value = val }) } + /** + * @param {any} val + * @return {JDOM} + */ val(value = undefined) { if (value === undefined) { return this.getValue() @@ -187,72 +342,133 @@ class JDOM { } } + /** + * @param name + * @param value + * @return {*|null|JDOM} + */ setOrGetProperty(name, value = undefined) { if (value === undefined) { - const el = this.first() + const el = this.element() return el ? el[name] : null } else { - return this.each(el => { + return this.eachElems(el => { el[name] = value }) } } + /** + * @param {string} val + * @return {*|JDOM|null} + */ id(val = undefined) { return this.setOrGetProperty('value', val) } - append(node) { - if (typeof node === 'string') { - return this.each(el => { - el.insertAdjacentHTML('beforeend', node) - }) - } else if (node instanceof JDOM) { - return this.each(el => { - el.appendChild(node.first()) - }) - } else { - return this.each(el => { - el.appendChild(node) - }) + /** + * @param {Array} nodes + * @return {JDOM} + */ + append(...nodes) { + let parent = this + if (parent.element() === document) + parent = new JDOM(document.body) + + for (const node of nodes) { + if (typeof node === 'string') { + parent.eachElems(el => { + if (el instanceof Element) { + el.insertAdjacentHTML('beforeend', node) + } else { + console.warn('Parent is not type of Element. Appending html might not work.') + const templ = document.createElement('template') + templ.innerHTML = node + el.appendChild(templ) + } + }) + } else if (node instanceof JDOM) { + parent.eachElems(el => { + el.appendChild(node.element()) + }) + } else { + parent.eachElems(el => { + el.appendChild(node) + }) + } } + return this } + /** + * @param {string, JDOM, Node} node + * @return {JDOM} + */ prepend(node) { - if (typeof node === 'string') { - return this.each(el => { - el.insertAdjacentHTML('beforebegin', node) - }) - } else if (node instanceof JDOM) { - return this.each(el => { - el.prepend(node.first()) - }) - } else { - return this.each(el => { - el.prepend(node) - }) + let parent = this + if (parent.element() === document) + parent = new JDOM(document.body) + + for (const node of nodes) { + if (typeof node === 'string') { + parent.eachElems(el => { + if (el instanceof Element) { + el.insertAdjacentHTML('beforebegin', node) + } else { + console.warn('Parent is not type of Element. Prepending html might not work.') + const templ = document.createElement('template') + templ.innerHTML = node + el.prepend(templ) + } + }) + } else if (node instanceof JDOM) { + parent.eachElems(el => { + el.prepend(node.element()) + }) + } else { + parent.eachElems(el => { + el.prepend(node) + }) + } } + return this } + /** + * Is the element hidden by style.display === 'none'? + * @return {boolean} + */ hidden() { - const el = this.first() + const el = this.element() return el ? el.style.display === 'none' : false } + /** + * @return {boolean} + */ shown() { return !this.hidden() } + /** + * @return {JDOM} + */ show() { - return this.each(el => el.style.display = '') + return this.eachElems(el => el.style.display = '') } + /** + * @return {JDOM} + */ hide() { - return this.each(el => el.style.display = 'none') + return this.eachElems(el => el.style.display = 'none') } + /** + * @return {JDOM} + */ toggle() { - return this.each(el => { + return this.eachElems(el => { if (el.style.display === 'none') { el.style.display = '' } else { @@ -261,6 +477,12 @@ class JDOM { }) } + /** + * + * @param {CSSPropertiesConfiguration} css CSS-Styles + * @param {Number} duration + * @return {Promise} + */ animate(css={}, duration = 1000) { return new Promise(r => { this.css({ @@ -274,6 +496,10 @@ class JDOM { }) } + /** + * @param {Animation[]} animations + * @return {Promise} + */ async animator(animations) { for (const animation of animations) { await this.animate(animation.css, animation.duration || 1000) @@ -281,11 +507,13 @@ class JDOM { return this; } - /* - EVENTS - * */ + /** + * @param {EventListenerType} listener + * @param {function(Event)} callable + * @return {JDOM} + */ on(listener, callable) { - this.each(el => { + this.eachElems(el => { for (const listenerSplit of listener.split('|')) { el.addEventListener(listenerSplit, callable) } @@ -293,13 +521,22 @@ class JDOM { return this } + /** + * @param {EventListenerType} listener + * @param {function(Event)} callable + * @return {JDOM} + */ removeEvent(listener, callable) { - this.each(el => { + this.eachElems(el => { el.removeEvent(listener, callable) }) return this } + /** + * @param {Object} events + * @return {JDOM} + */ bind(events = {}) { for (const [listener, callable] of Object.entries(events)) { this.on(listener, callable) @@ -307,92 +544,196 @@ class JDOM { return this } - click(callable = null) { - if (callable === null) { - return this.each(el => { + /** + * @param {function(PointerEvent)|undefined} callable + * @return {JDOM} + */ + click(callable = undefined) { + if (callable === undefined) { + return this.eachElems(el => { el.click() }) } return this.on('click', callable) } + + /** + * @param {number} index + * @return {JDOM} + */ get(index) { - return this.elem[index] + return new JDOM(this.elem[index]) } + /** + * @return {number} + */ size() { return this.elem.length } + /** + * @return {Element[]} + */ toArray() { return [...this.elem] } + /** @param {function(PointerEvent)} func @return {JDOM} */ contextmenu(func) { return this.on('contextmenu', func); } + /** @param {function(Event)} func @return {JDOM} */ change(func) { return this.on('change', func); } + /** @param {function(MouseEvent)} func @return {JDOM} */ mouseover(func) { return this.on('mouseover', func); } + /** @param {function(KeyboardEvent)} func @return {JDOM} */ keypress(func) { return this.on('keypress', func); } + /** @param {function(KeyboardEvent)} func @return {JDOM} */ keyup(func) { return this.on('keyup', func); } + /** @param {function(KeyboardEvent)} func @return {JDOM} */ keydown(func) { return this.on('keydown', func); } + /** @param {function(MouseEvent)} func @return {JDOM} */ dblclick(func) { return this.on('dblclick', func); } + /** @param {function(Event)} func @return {JDOM} */ resize(func) { return this.on('resize', func); } - + /** @param {function(Event)} func @return {JDOM} */ timeupdate(func) { return this.on('timeupdate', func); } - touchcancle(func) { return this.on('touchcancle', func); } + /** @param {function(TouchEvent)} func @return {JDOM} */ + touchcancel(func) { return this.on('touchcancel', func); } + /** @param {function(TouchEvent)} func @return {JDOM} */ touchend(func) { return this.on('touchend', func); } + /** @param {function(TouchEvent)} func @return {JDOM} */ touchmove(func) { return this.on('touchmove', func); } + /** @param {function(TouchEvent)} func @return {JDOM} */ touchstart(func) { return this.on('touchstart', func); } + /** @param {function(DragEvent)} func @return {JDOM} */ drag(func) { return this.on('drag', func); } + /** @param {function(DragEvent)} func @return {JDOM} */ dragenter(func) { return this.on('dragenter', func); } + /** @param {function(DragEvent)} func @return {JDOM} */ dragleave(func) { return this.on('dragleave', func); } + /** @param {function(DragEvent)} func @return {JDOM} */ dragover(func) { return this.on('dragover', func); } + /** @param {function(DragEvent)} func @return {JDOM} */ dragend(func) { return this.on('dragend', func); } + /** @param {function(DragEvent)} func @return {JDOM} */ dragstart(func) { return this.on('dragstart', func); } + /** @param {function(DragEvent)} func @return {JDOM} */ drop(func) { return this.on('drop', func); } + /** @param {function(FocusEvent)} func @return {JDOM} */ focus(func) { return this.on('focus', func); } + /** @param {function(FocusEvent)} func @return {JDOM} */ focusout(func) { return this.on('focusout', func); } + /** @param {function(FocusEvent)} func @return {JDOM} */ focusin(func) { return this.on('focusin', func); } + /** @param {function(Event)} func @return {JDOM} */ invalid(func) { return this.on('invalid', func); } + /** @param {function(Event)} func @return {JDOM} */ popstate(func) { return this.on('popstate', func); } + /** @param {function(Event)} func @return {JDOM} */ volumechange(func) { return this.on('volumechange', func); } + /** @param {function(Event)} func @return {JDOM} */ unload(func) { return this.on('unload', func); } + /** @param {function(Event)} func @return {JDOM} */ offline(func) { return this.on('offline', func); } + /** @param {function(Event)} func @return {JDOM} */ online(func) { return this.on('online', func); } - focus(func) { return this.on('focus', func); } + + /** + * @return {JDOM} + */ remove() { - return this.each(el => el.remove()) + return this.eachElems(el => el.remove()) } + /** + * @param {function(Event)} func + * @return {JDOM} + */ ready(func) { this.on('DOMContentLoaded', func); return this; } - static new(tag = 'div') { + [Symbol.iterator]() { + return this.elem + } + + /** + * @param {HTMLTag} tag + * @param constructorArgs + * @return {JDOM} + */ + static new(tag = 'div', ...constructorArgs) { + // Custom Elements Standard requires dash + if (tag.includes('-') && window?.customElements) { + const customElement = window.customElements.get(tag) + if (customElement) { + return new JDOM(new customElement(...constructorArgs)) + } + } + + return new JDOM(document.createElement(tag)) } - static component(component, {shadowed = false} = {}) { + /** + * @param {function(JDOM, JDOMCustomHTMLElement)} component + * @param {Object} options + * @return {JDOMCustomHTMLElement} + */ + static component(component, {shadowed = false, style = undefined} = {}) { return class extends HTMLElement { - constructor() { + addStyle(style) { + const styleEl = document.createElement('style') + styleEl.textContent = style + this.el.appendChild(styleEl) + } + + constructor(...args) { super() - let el = this + this.args = args + + /** + * @type Element + */ + this.el = this + if (shadowed) { - el = this.attachShadow({mode: 'closed'}); + this.el = this.attachShadow({mode: 'closed'}); + } + + if (style !== undefined) { + this.addStyle(style) } - const $el = new JDOM(el) - component($el, $el) + const $el = new JDOM(this.el) + component.call(this, $el, this) } } } + /** + * @param {string} tag + * @param {Node|HTMLElement|JDOMCustomHTMLElement} component + */ static registerComponent(tag, component) { return window.customElements.define(tag, component) } + /** + * @param {function(Event)} callback + */ + static ready(callback) { + (new JDOM(document)).ready(callback) + } + + /** + * @param {string} html + * @return {JDOM} + */ static fromHTML(html) { return JDOM.new('template').html(html || '') } diff --git a/README.md b/README.md index bd98b9a..73adac2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# JDOM `2.0.2` +# JDOM `2.1.0` # A wrapper for query selector and html elements ## Install @@ -25,8 +25,8 @@ el.css({ background: '#000000' }) -el.each(el => { - console.log($(el).html()) +el.each($el => { + console.log(el.html()) }) el.text('Hello world') @@ -84,11 +84,16 @@ $('#app').append(