UNPKG

@yagni-js/yagni-dom

Version:

Yet another functional library (DOM API related)

1,845 lines (1,749 loc) 85.9 kB
/** * * @module @yagni-js/yagni-dom * @version 2.2.0 * @author Yuri Egorov <ysegorov at gmail dot com> * @license Unlicense http://unlicense.org * */ 'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var yagni = require('@yagni-js/yagni'); /** * Takes an attribute `name` as an argument and returns **a new function**, * which then takes an Element `el` as an argument and returns value * of an attribute on the element. * Returns **`null`** if attribute doesn't exist. * * Uses `getAttribute` method of Element. * * @category Element * * @param {String} name attribute name to get value for * @returns {Function} to take `el` as an argument and return value of * an attribute on the element or `null` if attribute doesn't exist * * @see getProp * @see getData * * @example * * import {h, hText, getAttr} from '@yagni-js/yagni-dom'; * * const spec = h('a', {title: 'Foo'}, {}, [hText('Foo link')]); * const el = spec(); * * const getTitle = getAttr('title'); * const getHref = getAttr('href'); * * const title = getTitle(el); // => 'Foo' * const href = getHref(el); // => null * */ function getAttr(name) { return function _getAttr(el) { return el.getAttribute(name); }; } /** * Sets the value of an attribute on the element and returns element. * If value is not defined (is `undefined` or `null`) it will not be set. * * Uses `setAttribute` method of Element. * * @category Element * * @param {Element} el target element * @param {String} name attribute name * @param {String} value attribute value * @returns {Element} el * * @private * */ function setAttribute(el, name, value) { // NB. side effect const res = yagni.isDefined(value) ? el.setAttribute(name, value) : el; return el; } /** * Takes an attribute `name` and `value` as arguments and returns * **a new function**, which then takes an Element `el` as an argument and * sets the value of an attribute on the element. Returns `el`. * If value is not defined (is `undefined` or `null`) it will not be set. * * Uses `setAttribute` method of Element. * * @category Element * * @param {String} name attribute name * @param {String} value attribute value * @returns {Function} to take an Element `el` as an argument, set the value * of an attribute on the element and return `el` * * @see setProp * @see setData * * @example * * import {h, hText, getAttr, setAttr} from '@yagni-js/yagni-dom'; * * const spec = h('a', {title: 'Foo'}, {}, [hText('Foo link')]); * const el = spec(); * * const getHref = getAttr('href'); * const setHrefToTop = setAttr('href', '#top'); * * const before = getHref(el); // => null * * const el2 = setHrefToTop(el); // el2 is el * * const after = getHref(el2); // => '#top' * */ function setAttr(name, value) { return function _setAttr(el) { return setAttribute(el, name, value); }; } /** * Takes Element `el` as an argument and returns **a new function**, * which then takes an attribute `name` and `value` as arguments and sets * the value of an attribute on the element. Returns `el`. * If value is not defined (is `undefined` or `null`) it will not be set. * * Uses `setAttribute` method of Element. * * @category Element * * @param {Element} el target Element * @returns {Function} to take an attribute `name` and `value`, set the * value of an attribute on the element and return `el` * * @see setPropTo * @see setDataTo * * @example * * import {h, hText, getAttr, setAttrTo} from '@yagni-js/yagni-dom'; * * const spec = h('a', {title: 'Foo'}, {}, [hText('Foo link')]); * const el = spec(); * * const getHref = getAttr('href'); * const setElAttr = setAttrTo(el); * * const before = getHref(el); // => null * * const el2 = setElAttr('href', '#top'); // => el2 is el * * const after = getHref(el2); // => '#top' * */ function setAttrTo(el) { return function _setAttrTo(name, value) { return setAttribute(el, name, value); }; } /** * Takes an object `obj` of `(name, value)` pairs as an argument and * returns **a new function**, which then takes an Element `el` as an argument * and iteratively sets the value of an attribute on the element for * each `(name, value)` pair from `obj`. Returns `el`. * If value for a correspondent attribute is not defined * (is `undefined` or `null`) it will not be set. * * Object `obj` structure: * * {attr1: 'value1', attr2: 'value2', ...} * * Uses `setAttribute` method of Element. * * @function * @category Element * * @param {Object} obj source object of attribute `(name, value)` pairs * @returns {Function} to take an Element `el` as an argument, iteratively * set attributes values on the `el` and return `el` * * @see setProps * @see setDatas * * @example * * import {h, hText, getAttr, setAttrs} from '@yagni-js/yagni-dom'; * * const spec = h('a', {}, {}, [hText('Foo link')]); * const el = spec(); * * const getHref = getAttr('href'); * const getTitle = getAttr('title'); * const attrsSetter = setAttrs({title: 'Foo', href: '#top'}); * * const title1 = getTitle(el); // => null * const href1 = getHref(el); // => null * * const el2 = attrsSetter(el); * * const title2 = getTitle(el); // => 'Foo' * const href2 = getHref(el); // => '#top' * */ const setAttrs = yagni.reduceObj(setAttribute); /** * Takes an attribute `name` as an argument and returns **a new function**, * which then takes an Element `el` as an argument, removes an attribute * from the element and returns `el`. * * Uses `removeAttribute` method of Element. * * @category Element * * @param {String} name attribute name * @returns {Function} to take an Element `el` as an argument, remove an * attribute from the element and return `el` * * @example * * import {h, hText, getAttr, removeAttr} from '@yagni-js/yagni-dom'; * * const spec = h('a', {title: 'Foo', href: '#top'}, {}, [hText('Foo link')]); * * const removeTitle = remoteAttr('title'); * * const el = spec(); * // => <a href="#top" title="Foo">Foo link</a> * * const el2 = removeTitle(el); * // => <a href="#top">Foo link</a> * // el2 === el * */ function removeAttr(name) { return yagni.tap( function _removeAttr(el) { return el.removeAttribute(name); } ); } const doc = window.document; /** * Takes `tagName` as an argument and returns newly created HTML element * specified by `tagName`. * * Uses `document.createElement` method. * * @category Element * * @param {String} tagName * @returns {Element} newly created HTML element * * @see createElementNS * @see createSVGElement * @see createText * * @example * * import {createElement} from '@yagni-js/yagni-dom'; * * const div = createElement('div'); // => <div></div> * */ function createElement(tagName) { return doc.createElement(tagName); } /** * Takes `namespace` URI as an argument and returns **a new function**, * which then takes `tagName` as an argument and returns newly created HTML * element with the specified `namespace` URI and `tagName`. * * Uses `document.createElementNS` method. * * Important Namespace URIs: * * - [HTML] http://www.w3.org/1999/xhtml * - [SVG] http://www.w3.org/2000/svg * - [MathML] http://www.w3.org/1998/mathml * * @category Element * * @param {URI} namespace * @returns {Function} to take `tagName` as an argument and return newly * created HTML element with the specified `namespace` URI and `tagName` * * @see createElement * @see createSVGElement * @see createText * * @example * * import {createElementNS} from '@yagni-js/yagni-dom'; * * const createSVGEl = createElementNS('http://www.w3.org/2000/svg'); * * const svg = createSVGEl('svg'); * // => <svg xmlns="http://www.w3.org/2000/svg"></svg> * */ function createElementNS(namespace) { return function (tagName) { return doc.createElementNS(namespace, tagName); }; } /** * Takes `tagName` as an argument and returns newly created HTML element * with `http://www.w3.org/2000/svg` namespace URI and specified `tagName`. * * Uses `document.createElementNS` method. * * @function * @category Element * * @param {String} tagName * @returns {Element} newly created HTML element with * 'http://www.w3.org/2000/svg' namespace URI and specified `tagName` * * @see createElement * @see createElementNS * @see createText * * @example * * import {createSVGElement} from '@yagni-js/yagni-dom'; * * const svg = createSVGElement('svg'); * // => <svg xmlns="http://www.w3.org/2000/svg"></svg> * */ const createSVGElement = createElementNS('http://www.w3.org/2000/svg'); /** * Takes `text` as an argument and returns newly created Text node. * * Uses `document.createTextNode` method. * * @category Element * * @param {String} text text to be put to text node * @returns {TextNode} newly created Text node * * @see createElement * @see createElementNS * @see createSVGElement * * @example * * import {createText} from '@yagni-js/yagni-dom'; * * const text = createText('foo'); // => #text "foo" * */ function createText(text) { return doc.createTextNode(text); } /** * Takes some property `name` as an argument and returns **a new function**, * which then takes an Element `el` as an argument and returns value of the * specified property on the element. * * @function * @category Element * * @param {String} name property name to get value for * @returns {Function} to take Element `el` as an argument and return value of * the specified property on the element or `undefined` if property * doesn't exist * * @see getAttr * @see getData * * @example * * import {h, hText, getProp} from '@yagni-js/yagni-dom'; * * const spec = h('a', {}, {foo: 'Foo'}, [hText('Foo link')]); * const el = spec(); * * const getFooProp = getProp('foo'); * const getBazProp = getProp('baz'); * * const foo = getFooProp(el); // => 'Foo' * const baz = getBazProp(el); // => undefined * */ const getProp = yagni.pick; /** * Sets the value of a property on the element and returns element. * * @function * @category Element * * @param {Element} el target element * @param {String} name property name * @param {*} value property value * @returns {Element} el * * @private * */ const setProperty = yagni.mutate; /** * Takes some property `name` and `value` as arguments and returns * **a new function**, which then takes an Element `el` as an argument and * sets the value of a property on the element. Returns `el`. * * @category Element * * @param {String} name property name * @param {*} value property value * @returns {Function} to take an Element `el` as an argument, set the value * of a property on the element and return `el` * * @see setAttr * @see setData * * @example * * import {h, hText, getProp, setProp} from '@yagni-js/yagni-dom'; * * const spec = h('a', {}, {foo: 'Foo'}, [hText('Foo link')]); * const el = spec(); * * const getFooProp = getProp('foo'); * const setFooProp = setProp('foo', 'Baz'); * * const before = getFooProp(el); // => 'Foo' * * const el2 = setFooProp(el); // el2 is el * * const after = getFooProp(el); // => 'Baz' * */ function setProp(name, value) { return function _setProp(el) { return setProperty(el, name, value); }; } /** * Takes Element `el` as an argument and returns **a new function**, * which then takes some property `name` and `value` as arguments and * sets the value of a property on the element. Returns `el`. * * @category Element * * @param {Element} el target Element * @returns {Function} to take some property `name` and `value`, set the value * of a property on the element and return `el` * * @see setAttrTo * @see setDataTo * * @example * * import {h, hText, getProp, setPropTo} from '@yagni-js/yagni-dom'; * * const spec = h('a', {}, {foo: 'Foo'}, [hText('Foo link')]); * const el = spec(); * * const getFooProp = getProp('foo'); * const setElProp = setPropTo(el); * * const before = getFooProp(el); // => 'Foo' * * const el2 = setElProp('foo', 'Baz'); // el2 is el * * const after = getFooProp(el); // => 'Baz' * */ function setPropTo(el) { return function _setPropTo(name, value) { return setProperty(el, name, value); }; } /** * Takes an object `obj` of `(name, value)` pairs as an argument and * returns **a new function**, which then takes an Element `el` as an argument * and iteratively sets the value of a property on the element for * each `(name, value)` pair from `obj`. Returns `el`. * * Object `obj` structure: * * {prop1: 'value1', prop2: 'value2', ...} * * @function * @category Element * * @param {Object} obj source object of `(name, value)` pairs * @returns {Function} to take an Element `el` as an argument, iteratively * set properties values on the `el` and return `el` * * @see setAttrs * @see setDatas * * @example * * import {h, hText, getProp, setProps} from '@yagni-js/yagni-dom'; * * const spec = h('a', {}, {}, [hText('Foo link')]); * const el = spec(); * * const getFoo = getProp('foo'); * const getBaz = getProp('baz'); * const propsSetter = setProps({foo: 'Foo', baz: 42}); * * const foo1 = getFoo(el); // => undefined * const baz1 = getBaz(el); // => undefined * * const el2 = propsSetter(el); // el2 is el * * const foo2 = getFoo(el); // => 'Foo' * const baz2 = getBaz(el); // => 42 * */ const setProps = yagni.reduceObj(setProperty); /** * Takes an Element `el` as an argument and returns value of the `textContent` * property on the element. * * @function * @category Element * * @param {Element} el target element * @returns {String} value of the `textContent` property on the element * * @example * * import {h, hText, textContent} from '@yagni-js/yagni-dom'; * * const spec = h('a', {}, {}, [hText('Foo link')]); * const el = spec(); * * const text = textContent(el); // => 'Foo link' * */ const textContent = getProp('textContent'); /** * Takes css `selector` as an argument and returns * **a new function**, which then takes an Element `el` as an argument and * returns `true` if `el` matches specified `selector`, returns `false` * otherwise. * * Uses `matches` method of element. * * @category Element * * @param {String} selector css selector * @returns {Function} to take Element `el` as an argument and return `true` * if `el` matches `selector` or `false` otherwise * * @example * * import {createElement, addClass, matches} from '@yagni-js/yagni-dom'; * * const div = createElement('div'); * * const addFooClass = addClass('foo'); * const matchesFooClass = matches('.foo'); * const matchesBazClass = matches('.baz'); * * const el = addFooClass(div); // => el is div * * const res1 = matchesFooClass(el); // => true * const res2 = matchesBazClass(el); // => false * */ function matches(selector) { return function _matches(el) { return el.matches(selector); }; } /** * Takes css `selector` as an argument and returns * **a new function**, which then takes an Element `el` as an argument and * returns the closest ancestor of the `el` which matches the `selector`. * Returns `null` if there is no such an ancestor. * * Uses `closest` method of element. * * @category Element * * @param {String} selector css selector * @returns {Function} to take an Element `el` as an argument and return * closest ancestor of `el` which matches the `selector` or `null` if there is * no such an ancestor * * @example * * import {createElement, appendTo, addClass, closest} from '@yagni-js/yagni-dom'; * * const ul = createElement('ul'); * const li = createElement('li'); * const a = createElement('a'); * const span = createElement('span'); * * const addFooClass = addClass('foo'); * const closestFoo = closest('.foo'); * * const appendToUl = appendTo(ul); * const appendToLi = appendTo(li); * const appendToA = appendTo(a); * * const ul1 = appendToUl(li); // => ul1 is ul * const li1 = appendToLi(a); // => li1 is li * const a1 = appendToA(span); // => a1 is a * * const li2 = addFooClass(li); // => li2 is li * * const res1 = closestFoo(span); // => li * const res2 = closestFoo(a); // => li * const res3 = closestFoo(li); // => null * const res4 = closestFoo(ul); // => null * */ function closest(selector) { return function _closest(el) { return el.closest(selector); }; } const byIdRe = /^#([\w-]+)$/; const byClassRe = /^(\.[\w-]+)+$/; /** * Takes css `selector` as an argument and returns **a new function**, * which then takes an Element `el` as an argument and returns an element or * an array of elements within `el` that matches specified `selector`. * * Uses `getElementById` method of element for searches by element's `id`. * Uses `getElementsByClassName` method of element for searches by single or * multiple class names. * Uses `querySelectorAll` method of element for other cases. * * For searches by element id returns single element or `null` if there is * no match. * For other cases returns an array. * * @category Element * * @param {String} selector css selector * @returns {Function} to take an Element `el` as an argument and return * single element or an array of elements within `el` that matches `selector`, * or `null` if there are no matches * * @example * * import {createElement, appendTo, setAttr, query} from '@yagni-js/yagni-dom'; * * const ul = createElement('ul'); * const li = createElement('li'); * const a = createElement('a'); * const span = createElement('span'); * * const setFooBarClass = setAttr('class', 'foo bar'); * const setBazId = setAttr('id', 'baz'); * const queryFoo = query('.foo.bar'); * const queryBaz = query('#baz'); * * const appendToUl = appendTo(ul); * const appendToLi = appendTo(li); * const appendToA = appendTo(a); * * const ul1 = appendToUl(li); // => ul1 is ul * const li1 = appendToLi(a); // => li1 is li * const a1 = appendToA(span); // => a1 is a * * const li2 = setFooBarClass(li); // => li2 is li * const a2 = setBazId(a); // => a2 is a * * const res1 = queryFoo(ul); // => [li] * const res2 = queryFoo(li); // => [li] * const res3 = queryFoo(a); // => [] * const res4 = queryFoo(span); // => [] * * const res5 = queryBaz(ul); // => a * const res6 = queryBaz(a); // => a * const res7 = queryBaz(span); // => null * */ function query(selector) { const byId = byIdRe.test(selector); const byClass = byClassRe.test(selector); return function _query(el) { return byId ? doc.getElementById(selector.substr(1)) : yagni.toArray( byClass ? el.getElementsByClassName(selector.replace(/\./g, ' ').trim()) : el.querySelectorAll(selector)); }; } /** * Takes css `selector` as an argument and returns **a new function**, * which then takes an Element `el` as an argument and returns * first element within `el` that matches specified `selector`. * * Uses `query` and returns first element from an array of found elements. * For searches by element id returns `query` result as is. * * @category Element * * @param {String} selector css selector * @returns {Function} to take an Element `el` as an argument and return * first element within `el` that matches `selector` * * @example * * import {createElement, appendTo, queryFirst} from '@yagni-js/yagni-dom'; * * const ul = createElement('ul'); * const li1 = createElement('li'); * const li2 = createElement('li'); * const li3 = createElement('li'); * * const appendToUl = appendTo(ul); * const firstLi = queryFirst('li'); * const firstSpan = queryFirst('span'); * const firstById = queryFirst('#first'); * * const ul1 = appendToUl(li1); // => ul1 is ul * const ul2 = appendToUl(li2); // => ul2 is ul * const ul3 = appendToUl(li3); // => ul3 is ul * * const res1 = firstLi(ul); // => li1 * const res2 = firstSpan(li2); // => undefined * const res3 = firstById(li3); // => null * */ function queryFirst(selector) { return yagni.pipe([ query(selector), yagni.ifElse( yagni.isArray, yagni.first, yagni.identity ) ]); } /** * Takes an Element `el` and returns it's parent Element. Returns `null` * if `el` has no parent or parent is not an Element. * * @function * @category Element * * @param {Element} el source element * @returns {Element} parent Element if exists, `null` otherwise * * @example * * import {createElement, appendTo, parent} from '@yagni-js/yagni-dom'; * * const div = createElement('div'); * const p = createElement('p'); * * const appendToDiv = appendTo(div); * * const div2 = appendToDiv(p); // => div2 is div * * const res = parent(p); // => res is div * */ const parent = yagni.pick('parentElement'); /** * Takes an Element `el` and returns it's first child Element. Returns `null` * if there are no child elements. * * @function * @category Element * * @param {Element} el source element * @returns {Element} first child Element if exists, `null` otherwise * * @example * * import {createElement, appendTo, firstChild} from '@yagni-js/yagni-dom'; * * const ul = createElement('ul'); * const li1 = createElement('li'); * const li2 = createElement('li'); * const li3 = createElement('li'); * * const appendToList = appendTo(ul); * * const ul1 = appendToList(li1); // => ul1 is ul * const ul2 = appendToList(li2); // => ul2 is ul * const ul3 = appendToList(li3); // => ul3 is ul * * const res = firstChild(ul); // => li1 * */ const firstChild = yagni.pick('firstElementChild'); /** * Takes an Element `el` and returns it's last child Element. Returns `null` * if there are no child elements. * * @function * @category Element * * @param {Element} el source element * @returns {Element} last child Element if exists, `null` otherwise * * @example * * import {createElement, appendTo, lastChild} from '@yagni-js/yagni-dom'; * * const ul = createElement('ul'); * const li1 = createElement('li'); * const li2 = createElement('li'); * const li3 = createElement('li'); * * const appendToList = appendTo(ul); * * const ul1 = appendToList(li1); // => ul1 is ul * const ul2 = appendToList(li2); // => ul2 is ul * const ul3 = appendToList(li3); // => ul3 is ul * * const res = lastChild(ul); // => li3 * */ const lastChild = yagni.pick('lastElementChild'); /** * Takes an Element `el` and returns it's immediately following Element. * Returns `null` if `el` is the last element in the list. * * @function * @category Element * * @param {Element} el source element * @returns {Element} immediately following Element if exists, `null` otherwise * * @example * * import {createElement, appendTo, next} from '@yagni-js/yagni-dom'; * * const ul = createElement('ul'); * const li1 = createElement('li'); * const li2 = createElement('li'); * const li3 = createElement('li'); * * const appendToList = appendTo(ul); * * const ul1 = appendToList(li1); // => ul1 is ul * const ul2 = appendToList(li2); // => ul2 is ul * const ul3 = appendToList(li3); // => ul3 is ul * * const res = next(li1); // => li2 * const res = next(li2); // => li3 * const res = next(li3); // => null * */ const next = yagni.pick('nextElementSibling'); /** * Takes an Element `el` and returns it's immediately preceding Element. * Returns `null` if `el` is the first element in the list. * * @function * @category Element * * @param {Element} el source element * @returns {Element} immediately preceding Element if exists, `null` otherwise * * @example * * import {createElement, appendTo, prev} from '@yagni-js/yagni-dom'; * * const ul = createElement('ul'); * const li1 = createElement('li'); * const li2 = createElement('li'); * const li3 = createElement('li'); * * const appendToList = appendTo(ul); * * const ul1 = appendToList(li1); // => ul1 is ul * const ul2 = appendToList(li2); // => ul2 is ul * const ul3 = appendToList(li3); // => ul3 is ul * * const res = prev(li1); // => null * const res = prev(li2); // => li1 * const res = prev(li3); // => li2 * */ const prev = yagni.pick('previousElementSibling'); /** * Takes an Element `el` and returns **a new array**, containing all child * elements. * * @function * @category Element * * @param {Element} el source element * @returns {Array} array containing child elements * * @example * * import {createElement, appendTo, children} from '@yagni-js/yagni-dom'; * * const ul = createElement('ul'); * const li1 = createElement('li'); * const li2 = createElement('li'); * const li3 = createElement('li'); * * const appendToList = appendTo(ul); * * const ul1 = appendToList(li1); // => ul1 is ul * const ul2 = appendToList(li2); // => ul2 is ul * const ul3 = appendToList(li3); // => ul3 is ul * * const res = children(ul); // => [li1, li2, li3] * */ const children = yagni.pipe([ yagni.pick('children'), yagni.toArray ]); /** * Takes css `selector` to match against as an argument and returns * **a new function**, which then takes an Element `el` as an argument * and returns **a new array**, containing all matching siblings. * * @function * @category Element * * @param {Element} el source element * @returns {Array} array containing matching siblings * * @example * * import {createElement, appendTo, siblings} from '@yagni-js/yagni-dom'; * * const ul = createElement('ul'); * const li1 = createElement('li'); * const li2 = createElement('li'); * const li3 = createElement('li'); * * const appendToList = appendTo(ul); * const others = siblings('li'); * * const ul1 = appendToList(li1); // => ul1 is ul * const ul2 = appendToList(li2); // => ul2 is ul * const ul3 = appendToList(li3); // => ul3 is ul * * const res1 = others(li1); // => [li2, li3] * const res2 = others(li2); // => [li1, li3] * const res3 = others(li3); // => [li1, li2] * */ function siblings(selector) { const match = matches(selector); return function _siblings(el) { function matched(x) { return x !== el && match(x); } return yagni.pipe([ parent, children, yagni.filter(matched) ])(el); }; } // // alternative implementation for siblings() // // export function siblings(selector) { // return call( // pipe([ // fn2( // and, // pipe([ // equals, // not // ]), // pipe([ // always(selector), // matches // ]) // ), // filter // ]), // pipe([ // parent, // children // ]) // ); // } /** * Takes an Element `el` as an argument and returns **a new function**, * which then takes another Element `target` as an argument, appends * `el` to `target` as a last child and returns `target`. * * Uses `appendChild` method of `target` element. * * @category Tree mutation * * @param {Element} el element to be appended * @returns {Function} to take another Element `target` as an argument, * append `el` to `target` as a last child and return `target` * * @see appendTo * @see appendAfter * @see prepend * @see prependTo * * @example * * import {createElement, append} from '@yagni-js/yagni-dom'; * * const ul = createElement('ul'); * const li = createElement('li'); * * const appendLi = append(li); * * const res = appendLi(ul); * // => res is ul * // => <ul><li></li></ul> * */ function append(el) { return function _append(target) { // NB. unused assignment const child = target.appendChild(el); return target; }; } /** * Takes an Element `target` as an argument and returns **a new function**, * which then takes another Element `el` as an argument, appends * `el` to `target` as a last child and returns `target`. * * Uses `appendChild` method of `target` element. * * @category Tree mutation * * @param {Element} target element to append child to * @returns {Function} to take another Element `el` as an argument, * append `el` to `target` as a last child and return `target` * * @see append * @see appendAfter * @see prepend * @see prependTo * * @example * * import {createElement, appendTo} from '@yagni-js/yagni-dom'; * * const ul = createElement('ul'); * const li = createElement('li'); * * const appendToUl = appendTo(ul); * * const res = appendToUl(li); * // => res is ul * // => <ul><li></li></ul> * */ function appendTo(target) { return function _appendTo(el) { // NB. unused assignment const child = target.appendChild(el); return target; }; } /** * Takes an Element `target` as an argument and returns **a new function**, * which then takes another Element `el` as an argument, appends `el` after * `target` and returns `el`. * * Uses `insertBefore` method of parent element of `target`. * * @category Tree mutation * * @param {Element} target element to append after * @returns {Function} to take another Element `el` as an argument, * append `el` after `target` and return `el * * @see append * @see appendTo * @see prepend * @see prependTo * * @example * * import {createElement, appendTo, appendAfter} from '@yagni-js/yagni-dom'; * * const div = createElement('div'); * const ul = createElement('ul'); * const p = createElement('p'); * * const appendToDiv = appendTo(div); * const appendAfterUl = appendAfter(ul); * * const div1 = appendToDiv(ul); * // => div1 is div * // => <div><ul></ul></div> * * const res = appendAfterUl(p); * // => res is p * // => <div><ul></ul><p></p></div> * */ function appendAfter(target) { return function _appendAfter(el) { // NB. unused assignment const child = parent(target).insertBefore(el, next(target)); return el; }; } /** * Takes an Element `el` as an argument and returns **a new function**, * which then takes another Element `target` as an argument, appends * `el` to `target` as a first child and returns `target`. * * Uses `insertBefore` method of `target` element. * * @category Tree mutation * * @param {Element} el element to be prepended * @returns {Function} to take another Element `target` as an argument, * append `el` to `target` as a first child and return `target` * * @see append * @see appendTo * @see appendAfter * @see prependTo * * @example * * import {createElement, appendTo, prepend} from '@yagni-js/yagni-dom'; * * const div = createElement('div'); * const ul = createElement('ul'); * const p = createElement('p'); * * const appendToDiv = appendTo(div); * const prependP = prepend(p); * * const div1 = appendToDiv(ul); * // => div1 is div * // => <div><ul></ul></div> * * const div2 = prependP(div); * // => div2 is div * // => <div><p></p><ul></ul></div> * */ function prepend(el) { return function _prepend(target) { // NB. unused assignment const child = target.insertBefore(el, firstChild(target)); return target; }; } /** * Takes an Element `target` as an argument and returns **a new function**, * which then takes another Element `el` as an argument, appends * `el` to `target` as a first child and returns `target`. * * Uses `insertBefore` method of `target` element. * * @category Tree mutation * * @param {Element} target element to prepend to * @returns {Function} to take another Element `el` as an argument, * append `el` to `target` as a first child and return `target` * * @see append * @see appendTo * @see appendAfter * @see prepend * * @example * * import {createElement, prependTo} from '@yagni-js/yagni-dom'; * * const div = createElement('div'); * const ul = createElement('ul'); * const p = createElement('p'); * * const prependToDiv = prependTo(div); * * const div1 = prependToDiv(ul); * // => div1 is div * // => <div><ul></ul></div> * * const div2 = prependToDiv(div); * // => div2 is div * // => <div><p></p><ul></ul></div> * */ function prependTo(target) { return function _prependTo(el) { // NB. unused assignment const child = target.insertBefore(el, firstChild(target)); return target; }; } /** * Takes two Elements `el` and `child` as arguments, removes `child` from `el` * and returns `el`. * * Uses `removeChild` method of `el`. * * @category Tree mutation * * @param {Element} el parent element to remove child from * @param {Element} child child element to remove * @returns {Element} parent element `el` * * @see remove * @see removeChildren * @see replace * * @example * * import {createElement, appendTo, removeChild} from '@yagni-js/yagni-dom'; * * const ul = createElement('ul'); * const li = createElement('li'); * * const appendToUl = appendTo(ul); * * const ul1 = appendToUl(li); * // => ul1 is ul * // => <ul><li></li></ul> * * const ul2 = removeChild(ul, li); * // => ul2 is ul * // => <ul></ul> * */ function removeChild(el, child) { // NB. unused assignment const res = el.removeChild(child); return el; } /** * Takes an Element `el` as an argument and removes it from DOM tree. * Returns parent element if `el` has parent element, returns `el` if `el` * has no parent element. * * Uses `removeChild` method of parent element of `el`. * * @function * @category Tree mutation * * @param {Element} el element to remove * @returns {Element} parent element if `el` has parent or `el` otherwise * * @see removeChild * @see removeChildren * @see replace * * @example * * import {createElement, appendTo, remove} from '@yagni-js/yagni-dom'; * * const ul = createElement('ul'); * const li = createElement('li'); * * const appendToUl = appendTo(ul); * * const ul1 = appendToUl(li); * // => ul1 is ul * // => <ul><li></li></ul> * * const ul2 = remove(li); * // => ul2 is ul * // => <ul></ul> * */ const remove = yagni.pipe([ yagni.transform({ parent: parent, el: yagni.identity }), yagni.ifElse( yagni.pipe([yagni.pick('parent'), yagni.isNil]), yagni.pick('el'), yagni.fn2(removeChild, yagni.pick('parent'), yagni.pick('el')) ) ]); /** * Takes an Element `el` as an argument, removes all children elements and * returns `el`. * * Uses `removeChild` method of `el`. * * @category Tree mutation * * @param {Element} el element to remove children elements from * @returns {Element} `el` * * @see remove * @see removeChild * * @example * * import {createElement, appendTo, removeChildren} from '@yagni-js/yagni-dom'; * * const ul = createElement('ul'); * const li1 = createElement('li'); * const li2 = createElement('li'); * const li3 = createElement('li'); * * const appendToUl = appendTo(ul); * * const ul1 = appendToUl(li1); // => ul1 is ul * const ul2 = appendToUl(li2); // => ul2 is ul * const ul3 = appendToUl(li3); // => ul3 is ul * // => <ul><li></li><li></li><li></li></ul> * * const res = removeChildren(ul); * // => res is ul * // => <ul></ul> * */ function removeChildren(el) { const elements = children(el); return elements.reduce(removeChild, el); } /** * Takes an Element `oldEl` as an argument and returns **a new function**, * which then takes another Element `newEl` as an argument, replaces * `oldEl` with `newEl` and returns `newEl`. * * Uses `replaceChild` method of parent element of `oldEl`. * * @category Tree mutation * * @param {Element} oldEl element to be replaced * @returns {Function} to take another Element `newEl` as an argument, * replace `oldEl` with `newEl` and return `newEl` * * @see remove * @see removeChild * * @example * * import {createElement, appendTo, replace} from '@yagni-js/yagni-dom'; * * const div = createElement('div'); * const a = createElement('a'); * const span = createElement('span'); * * const appendToDiv = appendTo(div); * const replaceA = replace(a); * * const div1 = appendToDiv(a); * // => div1 is div * // => <div><a></a></div> * * const span1 = replaceA(span); * // => span1 is span * // => <div><span></span></div> * */ function replace(oldEl) { return function _replace(newEl) { // NB. unused assignment const res = parent(oldEl).replaceChild(newEl, oldEl); return newEl; }; } /** * Marker to skip child node creation if necessary. * * @private * */ const skipMarker = 'SKIP'; /** * Takes some value as an arguments, compares it to skip marker and returns * `true` if value equals to skip marker or `false` otherwise. * * @private * @function * * @param {*} smth some value to test equality to skip marker * @returns {Boolean} `true` if `smth` equals to skip marker or * `false` otherwise * */ const isSkipMarker = yagni.equals(skipMarker); /** * Function to always return skip marker on call. * * Is in use in `@yagni-js/yagni-parser` in html to `@yagni-js/yagni-dom` * conversion. * * @category Hyperscript * * @returns {String} skip marker to indicate child node should not be created * while creating children nodes * * @example * * import {h, hText, hSkip} from '@yagni-js/yagni-dom'; * * const div1 = h('div', {}, {}, [hSkip()]); * const div2 = h('div', {}, {}, [hText('')]); * * const el1 = div1(); // => el.firstChild is null * const el2 = div2(); // => el.firstChild is TextNode * */ const hSkip = yagni.always(skipMarker); /** * Takes an element `target` and a function `factory` as arguments, * calls function `factory`, which must return an element or a node, * and appends it to `target` as a child. * * @private * * @param {Element} target an element to append child to * @param {Function} factory function to call to create an element or a node * @returns {Element} target element * */ function createChild(target, factory) { const el = factory(); // NB. side effect - unused assignment const child = target.appendChild(el); return target } /** * Takes an array `children` as an argument and returns **a new function**, * which then takes an element `target` as an argument, creates from `children` * child elements, appends newly created nodes to `target` and returns `target` * back. * * An array `children` must contain functions to call to create dom elements. * * @private * * @param {Array} children array of functions to call to create dom elements * @returns {Function} to take an element `target` as an argument, create * child elements, append them to `target` and return `target` back * */ function createChildren(children) { return function _createChildren(target) { return children.reduce( function __createChildren(el, item) { return yagni.isArray(item) ? item.reduce(__createChildren, el) : ( isSkipMarker(item) ? el : createChild(el, item)); }, target ); }; } /** * Takes a string `tagName`, an object `attrs`, an object `props` and an * array `children` as arguments and returns a function, * which should be called later to create dom element. * * Uses `createElement`, `setAttrs` and `setProps` to create an element and * set its attributes and properties. * * @category Hyperscript * * @param {String} tagName tag name of dom element to create * @param {Object} attrs dom element attributes * @param {Object} props dom element properties * @param {Array} children an array of functions to call * to create child dom elements * @returns {Function} function to call to create dom element according to * passed in arguments * * @see hSVG * @see hText * @see createElement * @see setAttrs * @see setProps * * @example * * import {h, hText} from '@yagni-js/yagni-dom'; * * const list = h('ul', {'class': 'list', 'data-foo': 'baz', {}, [ * h('li', {'class': 'list-item'}, {}, [hText('Foo')]), * h('li', {'class': 'list-item'}, {}, [hText('Baz')]), * h('li', {'class': 'list-item'}, {}, [hText('Bar')]) * ]); * * const el = list(); * // => <ul class="list" data-foo="baz"> * // <li class="list-item">Foo</li> * // <li class="list-item">Baz</li> * // <li class="list-item">Bar</li> * // </ul> * */ function h(tagName, attrs, props, children) { return yagni.pipe([ yagni.lazy(createElement, tagName), setAttrs(attrs), setProps(props), createChildren(children) ]); } /** * Takes a string `tagName`, an object `attrs`, an object `props` and an * array `children` as arguments and returns a function, * which should be called later to create svg dom element. * * Uses `createSVGElement`, `setAttrs` and `setProps` to create an element and * set its attributes and properties. * * @category Hyperscript * * @param {String} tagName tag name of svg dom element to create * @param {Object} attrs svg dom element attributes * @param {Object} props svg dom element properties * @param {Array} children an array of functions to call * to create child svg dom elements * @returns {Function} function to call to create svg dom element according to * passed in arguments * * @see h * @see hText * @see createSVGElement * @see setAttrs * @see setProps * * @example * * import {hSVG} from '@yagni-js/yagni-dom'; * * const circle = hSVG('svg', {'viewBox': '0 0 24 24', 'class': 'icon'}, {}, [ * hSVG('circle', {cx: 12, cy: 12, r: 8}, {}, []) * ]); * * const el = circle(); * // => <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="icon"> * // <circle cx="12" cy="12" r="8"></circle> * // </svg> * */ function hSVG(tagName, attrs, props, children) { return yagni.pipe([ yagni.lazy(createSVGElement, tagName), setAttrs(attrs), setProps(props), createChildren(children) ]); } /** * Takes a string `text` as an argument and returns **a new function**, * which should be called later to create text node for passed in `text`. * * @category Hyperscript * * @param {String} text text for text node * @returns {Function} to call to create text node * * @see h * @see hSVG * * @example * * import {hText} from '@yagni-js/yagni-dom'; * * const text = 'Foo-Baz-Bar'; * * const textFactory = hText(text); * * const node = textFactory(); // => text node `Foo-Baz_bar' * */ function hText(text) { return yagni.lazy(createText, text); } /** * Takes an element `target` as an argument and returns **a new function**, * which then takes a function to call to create dom element, * appends newly created dom element to `target` as child and * returns `target` element back. * * Works only for dom elements, not for text or comment nodes. * * @category Hyperscript * * @param {Element} target target element to append newly created * dom element to * @returns {Function} to take a function to call to create dom element, * append newly created dom element to `target` and return `target` back * * @see h * @see hSVG * @see renderAfter * @see renderC * @see renderR * * @example * * import {h, render, createElement} from '@yagni-js/yagni-dom'; * * const div = createElement('div'); // => <div></div> * const renderToDiv = render(div); * * const p = h('p', {'class': 'story'}, {}, [ * 'Here goes the story...' * ]); * * const el = renderToDiv(p); * // => el is div * // => <div> * // <p class="story">Here goes the story...</p> * // </div> * * */ function render(target) { return function (factory) { const el = factory(); // NB. unused assignment const res = target.insertAdjacentElement('beforeend', el); return target; }; } /** * Takes an element `target` as an argument and returns **a new function**, * which then takes a function to call to create dom element, * appends newly created dom element after `target` element and * returns newly created dom element back. * * Works only for dom elements, not for text or comment nodes. * * @category Hyperscript * * @param {Element} target target element to append newly created * dom element after * @returns {Function} to take a function to call to create dom element, * append newly created dom element after `target` and return it back * * @see h * @see hSVG * @see render * @see renderC * @see renderR * @see appendAfter * * @example * * import {h, hText, renderAfter} from '@yagni-js/yagni-dom'; * * const list = h('ul', {'class': 'list', 'data-foo': 'baz', {}, [ * h('li', {'class': 'list-item'}, {}, [hText('Foo')]), * h('li', {'class': 'list-item'}, {}, [hText('Baz')]) * ]); * const item = h('li', {'class': 'list-item'}, {}, [hText('Bar')]); * * const el = list(); * // => <ul class="list" data-foo="baz"> * // <li class="list-item">Foo</li> * // <li class="list-item">Baz</li> * // </ul> * * const renderAfterBaz = renderAfter(el.lastChild); * * const res = renderAfterBaz(item); * // => res is item * * // => el has the following structure: * * // => <ul class="list" data-foo="baz"> * // <li class="list-item">Foo</li> * // <li class="list-item">Baz</li> * // <li class="list-item">Bar</li> * // </ul> * */ function renderAfter(target) { return function (factory) { const el = factory(); return target.insertAdjacentElement('afterend', el); }; } /** * Takes an element `target` as an argument and returns **a new function**, * which then takes a function to call to create dom element, * removes all children elements from `target`, appends newly created * dom element to `target` and returns `target` element back. * * Works only for dom elements, not for text or comment nodes. * * @category Hyperscript * * @param {Element} target element to remove all children elements from * and append newly created dom element to * @returns {Function} to take a function to call to create dom element, * remove all children elements from `target`, append newly created * dom element to `target` and return `target` element back * * @see h * @see hSVG * @see render * @see renderAfter * @see renderR * @see removeChildren * * @example * * import {h, hText, renderC} from '@yagni-js/yagni-dom'; * * const list = h('ul', {'class': 'list', 'data-foo': 'baz', {}, [ * h('li', {'class': 'list-item'}, {}, [hText('Foo')]), * h('li', {'class': 'list-item'}, {}, [hText('Baz')]) * ]); * const item = h('li', {'class': 'list-item'}, {}, [hText('Bar')]); * * const el = list(); * // => <ul class="list" data-foo="baz"> * // <li class="list-item">Foo</li> * // <li class="list-item">Baz</li> * // </ul> * * const clearListAndRender = renderC(el); * * const res = clearListAndRender(item); * // => res is el * * // => <ul class="list" data-foo="baz"> * // <li class="list-item">Bar</li> * // </ul> * */ function renderC(target) { return function (factory) { const el = factory(); // NB. unused assignment const cleaned = removeChildren(target); // NB. unused assignment const res = target.insertAdjacentElement('beforeend', el); return target; }; } /** * Takes an element `target` as an argument and returns **a new function**, * which then takes a function to call to create dom element or text node, * replaces `target` element with newly created dom element or text node * and returns newly created dom element or text node back. * * @category Hyperscript * * @param {Element} target element to replace * @returns {Function} to take a function to call to create dom element * or text node, replace `target` element with newly created * dom element or text node and return it back * * @see h * @see hSVG * @see render * @see renderAfter * @see renderC * @see replace * * @example * * import {h, hText, renderR} from '@yagni-js/yagni-dom'; * * const list = h('ul', {'class': 'list', 'data-foo': 'baz', {}, [ * h('li', {'class': 'list-item'}, {}, [hText('Foo')]), * h('li', {'class': 'list-item'}, {}, [hText('Baz')]) * ]); * const item = h('li', {'class': 'list-item'}, {}, [hText('Bar')]); * * const el = list(); * // => <ul class="list" data-foo="