UNPKG

spyne

Version:

Reactive Real-DOM Framework for Advanced Javascript applications

328 lines (288 loc) 8.82 kB
import { head, compose, reject, split, isEmpty, lte, defaultTo, prop } from 'ramda' import { SpyneAppProperties } from '../utils/spyne-app-properties.js' function generateSpyneSelectorId(el) { const num = () => Math.random().toString(36).replace(/\d/gm, '').substring(1, 8) let vsid = `${num()}` if (el.dataset.vsid === undefined) { el.dataset.vsid = vsid } else { vsid = el.dataset.vsid } return `[data-vsid='${vsid}']` } function isDevMode() { return SpyneAppProperties.debug === true } function isNodeElement(el) { const nodeCheck = compose(lte(0), defaultTo(-1), prop('nodeType')) return nodeCheck(el) } function getElOrList(cxt, sel, verboseBool = false) { const list = getNodeListArray(cxt, sel, verboseBool) return list.length === 1 ? head(list) : list } function testSelectors(cxt, sel, verboseBool) { const el = document.querySelector(cxt) const elIsDomElement = compose(lte(0), defaultTo(-1), prop('nodeType')) if (el !== null && elIsDomElement(el) === false) { console.warn(`Spyne Warning: the el object is not a valid single element, ${el}`) return } if (sel !== undefined && String(sel).trim().length > 0) { function isValidSelector(selector) { try { const elements = document.querySelectorAll(selector) return elements.length > 0 // Returns true if the selector is valid and selects at least one element } catch (e) { return false // Catching an exception means the selector is invalid } } const isValidSelectorBool = isValidSelector(sel) if (verboseBool === true && isDevMode() === true && isValidSelectorBool === false) { console.warn(`Spyne Warning: the selector, ${sel} does not exist in this el, ${cxt}`) } } } function getNodeListArray(cxt, sel, verboseBool = false) { const selector = sel !== undefined ? `${cxt} ${sel}` : cxt let isParent = false const returnSelectorIfValid = (selector) => { let arr = [] try { arr = document.querySelectorAll(selector) } catch (e) { arr = [] } try { isParent = document.querySelector(sel) === document.querySelector(cxt) if (isParent === true) { arr = [document.querySelector(sel)] } } catch (e) { // return []; } return arr// document.querySelectorAll(selector); } const elArr = returnSelectorIfValid(selector) if (verboseBool === true) { const mainSel = isParent === true ? 'body' : cxt testSelectors(mainSel, sel, verboseBool) } return elArr } function setInlineCss(val, cxt, sel) { const arr = getNodeListArray(cxt, sel) const addInlineCss = item => { item.style.cssText = val } arr.forEach(addInlineCss) return this } /** * * @module ViewStreamSelector * @type util * * @param {String|El} cxt The main element * @param {String|El} The selector as a String * @returns {function(*=)} * @constructor */ function ViewStreamSelector(cxt, sel) { cxt = typeof (cxt) === 'string' ? cxt : generateSpyneSelectorId(cxt) testSelectors(cxt, sel, false) function selector(sel) { return ViewStreamSelector(cxt, sel) } /** * Convenience method to map through a NodeList. * * @param {Function} fn * @returns An array of elements */ selector.map = (fn) => Array.from(getNodeListArray(cxt, sel)).map(fn) /** * Convenience method to iterate through a NodeList * * @param {Function} fn * @returns An array of elements */ selector.forEach = (fn) => Array.from(getNodeListArray(cxt, sel)).map(fn) selector.getNodeListArray = () => getNodeListArray(cxt, sel) /** * * Adds the class to the Element or to the NodeList. * @param {String} c * * * @property {String} c - = undefined; The class to be added. * **/ selector.addClass = (c) => { const arr = getNodeListArray(cxt, sel) const addClass = item => item.classList.add(c) arr.forEach(addClass) return this } /** * * @param {String} c * @desc Removes the class to the Element or to the NodeList. */ selector.removeClass = (c) => { const arr = getNodeListArray(cxt, sel) const removeClass = item => { item.classList.remove(c) } arr.forEach(removeClass) return this } /** * * @param {String} c * @desc Sets the class to equal exactly the class string. */ selector.setClass = (c) => { const arr = getNodeListArray(cxt, sel) /** * NON IE CLEANER SOLUTION * const removeClass = item => item.classList = c; * arr.forEach(removeClass); */ const setTheClass = item => { const removeClassStrArr = compose(reject(isEmpty), split(' '))(item.className) const classStrArr = c.split(' ') const remover = s => item.classList.remove(s) const adder = sel => item.classList.add(sel) removeClassStrArr.forEach(remover) classStrArr.forEach(adder) } arr.forEach(setTheClass) return this } selector.unmount = () => { // console.log('unmounting selector ', this); } /** * * @param {String} c * @param {Boolean} bool Default is undefined. * @desc Sets the class based on the provided boolean or the toggles the class. * * @example * this.props.el$.toggleClass('myclass', true); * */ selector.toggleClass = (c, bool) => { const arr = getNodeListArray(cxt, sel) const toggleClass = item => { bool = bool !== undefined ? bool : !item.classList.contains(c) bool ? item.classList.add(c) : item.classList.remove(c) } arr.forEach(toggleClass) return this } selector.toggle = (c, bool) => { selector.toggleClass(c, bool) return this } /** * Attaches html to the Selector's element * @param htmlElement */ selector.appendChild = (htmlElement) => { if (selector.el.length !== 0) { selector.el.appendChild(htmlElement) } else { console.warn(`Spyne Warning: The selector, ${sel} does not appear to be valid!`) } return selector.el } /** * * Adds class with a delay of 1ms to allow css to register a transition. * @param c */ selector.addAnimClass = (c) => { const delayAddClass = () => { const arr = getNodeListArray(cxt, sel) const addClass = item => { try { item.classList.add(c) } catch (e) { } } arr.forEach(addClass) } requestAnimationFrame(delayAddClass) // window.setTimeout(delayAddClass, 1); } /** * * @param {String} c * @param {String|HTMLElement} elSel The selector for the element. * @desc Sets the class active HTMLElement from a NodeList. */ selector.setActiveItem = (c, elSel) => { const arr = getNodeListArray(cxt, sel) const currentEl = typeof (elSel) === 'string' ? getElOrList(cxt, elSel) : elSel const toggleBool = item => item.isEqualNode(currentEl) ? item.classList.add(c) : item.classList.remove(c) if (isNodeElement(currentEl) === true) { arr.forEach(toggleBool) } else if (isDevMode() === true) { // console.log("SEL IS ",elSel,c); console.warn(`Spyne Warning: The selector, ${elSel}, does not appear to be a valid item in setActiveItem: ${c}`) } return this } /** * * @function el * * @desc * getter for the selector * * @returns * The a single element or a NodeList from the selector */ /** * * @function length * * @desc * Determines the length of the NodeList * * @returns * The length of the selector as a NodeList */ /** * * @function exists * * @desc * Determines whether an the selected element exists * * @returns * Boolean */ Object.defineProperty(selector, 'el', { get: () => getElOrList(cxt, sel, true) }) Object.defineProperty(selector, 'els', { get: () => getNodeListArray(cxt, sel) }) Object.defineProperty(selector, 'len', { get: () => getNodeListArray(cxt, sel, false).length }) Object.defineProperty(selector, 'exists', { get: () => getNodeListArray(cxt, sel, false).length >= 1 }) Object.defineProperty(selector, 'exist', { get: () => getNodeListArray(cxt, sel, false).length >= 1 }) Object.defineProperty(selector, 'nodeList', { get: () => getNodeListArray(cxt, sel) }) Object.defineProperty(selector, 'arr', { get: () => { const el = getElOrList(cxt, sel, true) if (el === undefined) { return [] } else if (el.length === undefined) { return [el] } else { return Array.from(el) } } }) Object.defineProperty(selector, 'inline', { set: (val) => setInlineCss(val, cxt, sel), get: () => selector }) Object.defineProperty(selector, 'inlineCss', { set: (val) => setInlineCss(val, cxt, sel), get: () => selector }) return selector } export { ViewStreamSelector }