diff --git a/scripts/dom.js b/scripts/dom.js new file mode 100644 index 0000000..01bf840 --- /dev/null +++ b/scripts/dom.js @@ -0,0 +1,143 @@ +'use strict'; + +/** + * dom.js + * A module that helps working with HTML elements in the DOM + */ + +const boundaryRegex = /\s+/g; + +/** + * Add a wrapping of utility methods around a Node + * + * @param {Node} el Node to wrap + * @return {Object} Utility-wrapped node + */ +const wrapNode = (el) => Object.freeze({ + node: el, + + // shortcuts + get: selector => wrapNode(el.querySelector(selector)), + all: selector => wrapEls(el.querySelectorAll(selector)), + + parent: () => wrapNode(el.parentNode), + children: () => wrapEls(el.childNodes), + + add: subEl => el.appendChild(unwrapNode(subEl)), + remove: () => el.parentNode.removeChild(el), + + getAttr: name => el.getAttribute(name), + setAttr: (name, val) => el.setAttribute(name, val), + + /** + * Add event listeners for all given events + * + * @param {string} events Whitespace-separated list of events + * @param {function} listener Listener to add + * @return {null} + */ + on: (events, listener) => { + events.trim().split(boundaryRegex).forEach( + event => el.addEventListener(event, listener) + ); + }, + + /** + * Remove event listeners for all given events + * + * @param {strings} events Whitespace-separated list of events + * @param {function} listener Listener to remove + * @return {null} + */ + off: (events, listener) => { + events.trim().split(boundaryRegex).forEach( + event => el.removeEventListener(event, listener) + ); + } +}); + +/** + * Remove a wrapping around a Node + * + * @param {Object|Node} el A node, wrapped or not + * @return {Node} Unwrapped node + */ +const unwrapNode = el => el instanceof Node ? el : el.node; + +/** + * Override the methods of an Array so that it + * can easily manipulate Nodes it contains + * + * @param {Array} list A list to be wrapped + * @return {Array} Expanded list + */ +const expandList = (list) => Object.assign(list, { + // shortcuts + on: function (events, listener) { + this.forEach(node => node.on(events, listener)); + }, + + off: function (events, listener) { + this.forEach(node => node.off(events, listener)); + }, + + /** + * Check whether this list includes given node + * + * @param {Node|Object} el Element to check + * @return {bool} True if this list contains `el` + */ + includes: function (el) { + const length = this.length; + el = unwrapNode(el); + + for (let i = 0; i < length; i += 1) { + if (el === unwrapNode(this[i])) { + return true; + } + } + + return false; + }, + + /** + * Filter nodes in the list, removing filtered out nodes + * + * @param {function} check A function that returns true to keep given el + * @return {Array} New list of nodes after filtering + */ + filter: function (check) { + const length = this.length, newList = []; + + for (let i = 0; i < length; i += 1) { + if (!check(this[i], i, this)) { + this[i].remove(); + } else { + newList.push(this[i]); + } + } + + return expandList(newList); + } +}); + +/** + * Turn a NodeList into a real Array of Nodes + * + * @param {NodeList} els List of nodes to wrap + * @return {Array} An array of nodes + */ +const wrapList = (els) => { + const result = [], length = els.length; + + for (let i = 0; i < length; i += 1) { + result[i] = wrapNode(els[i]); + } + + return expandList(result); +}; + +export const create = (name) => wrapNode(document.createElement(name)); +export const html = wrapNode(document.documentElement); +export const head = wrapNode(document.head); +export const body = wrapNode(document.body);