Skip to content

Instantly share code, notes, and snippets.

@cxmeel
Last active June 27, 2022 10:08
Show Gist options
  • Save cxmeel/fce8ecc9a55534f1d398d5ab7dee13d3 to your computer and use it in GitHub Desktop.
Save cxmeel/fce8ecc9a55534f1d398d5ab7dee13d3 to your computer and use it in GitHub Desktop.
ES6 Extended DOM Traversal

ES6 HTMLElement Extensions

This is a small library for extending HTMLElement methods.

Element.descendant()

Returns the first descendant of the element which matches the given selector. This is identical to Element.querySelector().

Syntax

<HTMLElement> HTMLElement.descendant(<String> selector)

Element.ancestor()

Returns the first ancestor of the element which matches the given selector. Opposite of Element.descendant().

Syntax

<HTMLElement> HTMLElement.ancestor(<String> selector)

Element.descendantOf()

Returns true if the element this is invoked on is a descendant of the given element.

Syntax

<Boolean> HTMLElement.descendantOf(<HTMLElement> ancestorNode)

Element.ancestorOf()

Returns true if the element this is invoked on is an ancestor of the given element. Opposite of Element.descendantOf().

Syntax

<Boolean> HTMLElement.ancestorOf(<HTMLElement> descendantNode)

Notes

Can also be called on document.

Element.delete()

Deletes the given element from the DOM. Identical to HTMLElement.parentElement.removeChild(HTMLElement).

Syntax

<void> HTMLElement.delete()

Notes

Can also be called on document.

Element.prepend()

Inserts the given element to the top/start of the desired parent element, before its first child. Identical to parentElement.insertAdjacentElement('AfterBegin', newChild).

Syntax

<HTMLElement> parentNode.prepend(<HTMLElement> newChild)

Notes

Can also be called on document.

Element.append()

Inserts the given element to the bottom/end of the desired parent element, after its last child. Identical to parentElement.insertAdjacentElement('BeforeEnd', newChild) and parentElement.appendChild(newChild).

Syntax

<HTMLElement> parentNode.append(<HTMLElement> newChild)

Notes

Can also be called on document.

Element.before()

Inserts the given element before the element this is invoked on, and placed within its parent. Identical to element.insertAdjacentElement('BeforeBegin', newSibling).

Syntax

<HTMLElement> element.before(<HTMLElement> newSibling)

Notes

Can also be called on document.

Element.after()

Inserts the given element after the element this is invoked on, and placed within its parent. Identical to element.insertAdjacentElement('AfterEnd', newSibling).

Syntax

<HTMLElement> element.after(<HTMLElement> newSibling)

Notes

Can also be called on document.

document.waitForChild()

Waits for the first element matching the given selector to be available in the DOM.

Syntax

<Promise<HTMLElement>> document.waitForChild(<string> selector[, <number> timeout])

Example

document.waitForChild('div', 3000).then(div => {
    div.innerHTML = 'Hello world!';
});

Notes

The timeout defaults to 60 seconds (60000 ms).

document.create()

Creates a new element with the given properties.

Syntax

<HTMLElement> document.create(<String> HTMLTag)(<Object> properties[, <Object> options)

Example

let button = document.create('button')({
    class: 'btn btn-md btn-primary',
    textContent: 'Yoo-hoo!',
    onClick: function() {
        this.textContent = 'Big summer blowout!';
    }
});

document.body.append(button);

Notes

This method extends document.createElement(); it accepts both parameters. The options parameter is an optional second parameter of the second call ({properties[, options]}).

HTMLElement.prototype.descendant = HTMLElement.prototype.querySelector;
HTMLElement.prototype.ancestor = function(selector) {
if (typeof(selector) !== 'string') throw new Error(`Got ${typeof(selector)}, expected string.`);
const recurse = node => {
if (node === null || node === undefined) throw new Error(`Couldn't find ancestor element matching "${selector}".`);
if (node.matches(selector)) return node;
return recurse(node.parentElement);
};
return recurse(this);
}
HTMLElement.prototype.descendantOf = function(node) {
if (node instanceof HTMLElement === false) throw new Error(`Got ${node.constructor.name}, expected HTMLElement.`);
const recurse = currentNode => {
if (currentNode === null) return false;
if (currentNode.isSameNode(node)) return true;
return recurse(currentNode.parentElement);
};
return recurse(this);
}
HTMLElement.prototype.ancestorOf = function(node) {
if (node instanceof HTMLElement === false) throw new Error(`Got ${node.constructor.name}, expected HTMLElement.`);
return node.descendantOf(this);
}
HTMLElement.prototype.delete = function() { return this.parentElement.removeChild(this) }
HTMLElement.prototype.prepend = function(node) {
if (node instanceof HTMLElement === false) throw new Error(`Got ${node.constructor.name}, expected HTMLElement.`);
return this.insertAdjacentElement('afterBegin', node);
}
HTMLElement.prototype.append = function(node) {
if (node instanceof HTMLElement === false) throw new Error(`Got ${node.constructor.name}, expected HTMLElement.`);
return this.insertAdjacentElement('beforeEnd', node);
}
HTMLElement.prototype.before = function(node) {
if (node instanceof HTMLElement === false) throw new Error(`Got ${node.constructor.name}, expected HTMLElement.`);
return this.insertAdjacentElement('beforeBegin', node);
}
HTMLElement.prototype.after = function(node) {
if (node instanceof HTMLElement === false) throw new Error(`Got ${node.constructor.name}, expected HTMLElement.`);
return this.insertAdjacentElement('afterEnd', node);
}
HTMLElement.prototype.parent = function() {
return this.parentElement;
}
HTMLElement.prototype.childs = function() {
return Array.from(this.children);
}
HTMLElement.prototype.descendants = function() {
return Array.from(this.querySelectorAll('*'));
}
HTMLElement.prototype.clear = function() {
this.descendants().forEach(desc => desc.delete());
}
HTMLDocument.prototype.ancestorOf = function(node) { return this.documentElement.ancestorOf(node) }
HTMLDocument.prototype.delete = function(node) { return this.documentElement.delete(node) }
HTMLDocument.prototype.prepend = function(node) { return this.documentElement.prepend(node) }
HTMLDocument.prototype.append = function(node) { return this.documentElement.append(node) }
HTMLDocument.prototype.before = function(node) { return this.documentElement.before(node) }
HTMLDocument.prototype.after = function(node) { return this.documentElement.after(node) }
HTMLDocument.prototype.childs = function() { return this.documentElement.childs() }
HTMLDocument.prototype.descendants = function() { return this.documentElement.descendants() }
HTMLDocument.prototype.clear = function() { return this.documentElement.clear() }
HTMLDocument.prototype.waitForChild = function(selector, timeout = 6e4) {
if (typeof(selector) !== 'string') throw new Error(`"Selector" got ${typeof(selector)}, expected string.`);
if (typeof(timeout) !== 'number') throw new Error(`"Timeout" got ${typeof(timeout)}, expected number.`);
return new Promise((resolve, reject) => {
let resolved = false;
const observer = new MutationObserver(mutations => {
for (let mutation of mutations) {
if (resolved) break;
for (let node of Array.from(mutation.addedNodes)) {
if (resolved) break;
if (node.matches && node.matches(selector)) {
observer.disconnect();
resolved = true;
resolve(node);
}
}
}
});
observer.observe(this, { childList: true, subtree: true });
let childNode = this.querySelector(selector);
if (childNode) {
resolved = true;
return resolve(childNode);
}
setTimeout(() => {
if (resolved) return;
childNode = this.querySelector(selector);
if (childNode) { resolved = true; return resolve(childNode) }
reject();
}, timeout);
});
}
HTMLDocument.prototype.create = function(elementSelector = 'span') {
if (typeof(elementSelector) !== 'string') throw new Error(`"Selector" got ${typeof(elementSelector)}, expected string.`);
return function(attributes = {}, options) {
if (typeof(attributes) !== 'object') throw new Error(`"Attributes" got ${typeof(attributes)}, expected object.`);
let newElement = document.createElement(elementSelector, options);
for (let [ attribute, value ] of Object.entries(attributes)) {
if (typeof(newElement[attribute]) === 'function') {
newElement[attribute](...value);
} else if (typeof(newElement[attribute]) !== 'undefined') {
newElement[attribute] = value;
} else {
newElement.setAttribute(attribute, value);
}
}
return newElement;
}
}
HTMLElement.prototype.create = function(elementSelector = 'span') {
const __parent = this;
if (typeof(elementSelector) !== 'string') throw new Error(`"Selector" got ${typeof(elementSelector)}, expected string.`);
return function(attributes = {}, options) {
const newElement = document.create(elementSelector)(attributes, options);
__parent.append(newElement);
return newElement;
}
}
HTMLElement.prototype.descendant=HTMLElement.prototype.querySelector,HTMLElement.prototype.ancestor=function(e){if("string"!=typeof e)throw new Error(`Got ${typeof e}, expected string.`);const t=n=>{if(null==n)throw new Error(`Couldn't find ancestor element matching "${e}".`);return n.matches(e)?n:t(n.parentElement)};return t(this)},HTMLElement.prototype.descendantOf=function(e){if(e instanceof HTMLElement==!1)throw new Error(`Got ${e.constructor.name}, expected HTMLElement.`);const t=n=>null!==n&&(!!n.isSameNode(e)||t(n.parentElement));return t(this)},HTMLElement.prototype.ancestorOf=function(e){if(e instanceof HTMLElement==!1)throw new Error(`Got ${e.constructor.name}, expected HTMLElement.`);return e.descendantOf(this)},HTMLElement.prototype.delete=function(){return this.parentElement.removeChild(this)},HTMLElement.prototype.prepend=function(e){if(e instanceof HTMLElement==!1)throw new Error(`Got ${e.constructor.name}, expected HTMLElement.`);return this.insertAdjacentElement("afterBegin",e)},HTMLElement.prototype.append=function(e){if(e instanceof HTMLElement==!1)throw new Error(`Got ${e.constructor.name}, expected HTMLElement.`);return this.insertAdjacentElement("beforeEnd",e)},HTMLElement.prototype.before=function(e){if(e instanceof HTMLElement==!1)throw new Error(`Got ${e.constructor.name}, expected HTMLElement.`);return this.insertAdjacentElement("beforeBegin",e)},HTMLElement.prototype.after=function(e){if(e instanceof HTMLElement==!1)throw new Error(`Got ${e.constructor.name}, expected HTMLElement.`);return this.insertAdjacentElement("afterEnd",e)},HTMLElement.prototype.parent=function(){return this.parentElement},HTMLElement.prototype.childs=function(){return Array.from(this.children)},HTMLElement.prototype.descendants=function(){return Array.from(this.querySelectorAll("*"))},HTMLElement.prototype.clear=function(){this.descendants().forEach(e=>e.delete())},HTMLDocument.prototype.ancestorOf=function(e){return this.documentElement.ancestorOf(e)},HTMLDocument.prototype.delete=function(e){return this.documentElement.delete(e)},HTMLDocument.prototype.prepend=function(e){return this.documentElement.prepend(e)},HTMLDocument.prototype.append=function(e){return this.documentElement.append(e)},HTMLDocument.prototype.before=function(e){return this.documentElement.before(e)},HTMLDocument.prototype.after=function(e){return this.documentElement.after(e)},HTMLDocument.prototype.childs=function(){return this.documentElement.childs()},HTMLDocument.prototype.descendants=function(){return this.documentElement.descendants()},HTMLDocument.prototype.clear=function(){return this.documentElement.clear()},HTMLDocument.prototype.waitForChild=function(e,t=6e4){if("string"!=typeof e)throw new Error(`"Selector" got ${typeof e}, expected string.`);if("number"!=typeof t)throw new Error(`"Timeout" got ${typeof t}, expected number.`);return new Promise((n,r)=>{let o=!1;const c=new MutationObserver(t=>{for(let r of t){if(o)break;for(let t of Array.from(r.addedNodes)){if(o)break;t.matches&&t.matches(e)&&(c.disconnect(),o=!0,n(t))}}});c.observe(this,{childList:!0,subtree:!0});let i=this.querySelector(e);if(i)return o=!0,n(i);setTimeout(()=>{if(!o){if(i=this.querySelector(e))return o=!0,n(i);r()}},t)})},HTMLDocument.prototype.create=function(e="span"){if("string"!=typeof e)throw new Error(`"Selector" got ${typeof e}, expected string.`);return function(t={},n){if("object"!=typeof t)throw new Error(`"Attributes" got ${typeof t}, expected object.`);let r=document.createElement(e,n);for(let[e,n]of Object.entries(t))"function"==typeof r[e]?r[e](...n):void 0!==r[e]?r[e]=n:r.setAttribute(e,n);return r}},HTMLElement.prototype.create=function(e="span"){const t=this;if("string"!=typeof e)throw new Error(`"Selector" got ${typeof e}, expected string.`);return function(n={},r){const o=document.create(e)(n,r);return t.append(o),o}};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment