UNPKG

jeddy

Version:

Reactive UI building Framework

304 lines 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.changed = exports.createElement = exports.addEventListeners = exports.updateProps = exports.updateProp = exports.setProps = exports.removeProp = exports.setValue = exports.setProp = exports.isCustomProp = exports.extractEventName = exports.isEventProp = exports.removeBooleanProp = exports.setBooleanProp = exports.updateElement = exports.generateVTree = void 0; const Events_1 = require("./Events"); /** * Sets a boolean property for the DOM Elements like a checkbox * @param {HTMLElement} $target Parent element * @param {string} name Name of the property eg. checked * @param {boolean} value Value of the property eg. True or False * @return {void} Returns nothing */ function setBooleanProp($target, name, value) { if (value) { $target.setAttribute(name, name); $target[name] = true; } else { $target[name] = false; } } exports.setBooleanProp = setBooleanProp; /** * Removes a boolean property for the DOM Elements like a checkbox * @param {HTMLElement} $target Parent element * @param {string} name Name of the property eg. checked * @return {void} Returns nothing */ function removeBooleanProp($target, name) { $target.removeAttribute(name); $target[name] = false; } exports.removeBooleanProp = removeBooleanProp; /** * Check if a prop is an Event prop eg. onClick * @param {string} name Event Name eg. onClick * @return {boolean} true or false */ function isEventProp(name) { return /^on/.test(name); } exports.isEventProp = isEventProp; /** * Extracts the name of the event eg. onClick => click * @param {string} name Event Name eg. onClick * @return {string} Returns an event name eg.click */ function extractEventName(name) { return name.slice(2).toLowerCase(); } exports.extractEventName = extractEventName; /** * Checks if a prop is a custom prop eg. Events or key * @param {string} name Prop name * @return {boolean} true or false */ function isCustomProp(name) { return isEventProp(name) || name === 'key'; } exports.isCustomProp = isCustomProp; /** * Sets a value to the INPUT, SELECT and TEXTAREA * @param {HTMLElement} $target HTMLElement (INPUT, SELECT or TEXTAREA) * @param {string} value Value * @return {void} */ function setValue($target, value) { document.addEventListener('DOMContentLoaded', () => { $target.value = value; $target.setAttribute('value', value); }); $target.value = value; $target.setAttribute('value', value); } exports.setValue = setValue; /** * Sets prop(s) to an HTMLElement * @param {HTMLElement} $target HTMLElement * @param {string} name Name of the property * @param {string} value Value * @return {void} */ function setProp($target, name, value) { if (isCustomProp(name)) { return; } else if (name === 'className') { $target.setAttribute('class', value); } else if (typeof value === 'boolean') { setBooleanProp($target, name, value); } else { if (($target.nodeName == "INPUT" || $target.nodeName == "SELECT" || $target.nodeName == "TEXTAREA") && name == "value") { setValue($target, value); } else { $target.setAttribute(name, value); } } } exports.setProp = setProp; /** * Removes prop(s) to an HTMLElement * @param {HTMLElement} $target HTMLElement * @param {string} name Name of the property * @param {string} value Value * @return {void} */ function removeProp($target, name, value) { if (isCustomProp(name)) { return; } else if (name === 'class') { //Todo: Remove specific class & style $target.removeAttribute('class'); } else if (typeof value === 'boolean') { removeBooleanProp($target, name); } else { if (($target.nodeName == "INPUT" || $target.nodeName == "TEXTAREA") && name == "value") { $target.value = ''; } else { $target.removeAttribute(name); } } } exports.removeProp = removeProp; /** * Iterates throught props and sets them to an HTMLElement * @param {HTMLElement} $target HTMLElement * @param {object} props All props * @return {void} */ function setProps($target, props) { Object.keys(props).forEach(name => { setProp($target, name, props[name]); }); } exports.setProps = setProps; /** * Update a prop in case it has changed * @param {HTMLElement} $target HTMLElement * @param {string} name Prop name * @param {string} newVal newVal * @param {string} oldVal oldVal * @return {void} */ function updateProp($target, name, newVal, oldVal) { if (!newVal) { removeProp($target, name, oldVal); } else if (!oldVal || newVal !== oldVal || $target.nodeName == "INPUT" || $target.nodeName == "TEXTAREA") { setProp($target, name, newVal); } } exports.updateProp = updateProp; /** * Iterate through props and Update a prop in case it has changed * @param {HTMLElement} $target HTMLElement * @param {object} newProps newVal * @param {object} oldProps oldVal * @return {void} */ function updateProps($target, newProps, oldProps = {}) { const props = Object.assign({}, newProps, oldProps); Object.keys(props).forEach(name => { updateProp($target, name, newProps[name], oldProps[name]); }); } exports.updateProps = updateProps; /** * Checks if a prop is an event listener and adds it accordingly * @param {HTMLElement} $target HTMLElement * @param {object} props * @return {void} */ function addEventListeners($target, props) { Object.keys(props).forEach(name => { if (isEventProp(name)) { $target[name] = function _listener(e) { props[name](e); $target.removeEventListener(name, _listener); }; } }); } exports.addEventListeners = addEventListeners; /** * Converts a VTree into a DOM Element * @param {HTMLElement} $target HTMLElement * @param {object} node VTree * @return {HTMLElement | Text} HTMLElement | Text */ function createElement(node) { if (typeof node === 'string') { return document.createTextNode(node); } const $el = document.createElement(node.type); setProps($el, node.props); addEventListeners($el, node.props); node.children .map(createElement) .forEach($el.appendChild.bind($el)); return $el; } exports.createElement = createElement; /** * Checks if a node has changed (If true the node gets replaced) * eg. from 'button' to 'div' * or 'hello' to 'hell' * or 'div' to text * @param {Object|string} node1 The old node * @param {Object|string} node2 The new node * @return {boolean} */ function changed(node1, node2) { const A = typeof node1 !== typeof node2; const B = typeof node1 === 'string' && node1 !== node2; const C = node1.type !== node2.type; const Z = node1.props ? true : false; const W = Z ? node1.props.key ? true : false : false; const D = Z && W; return A || B || C || D; } exports.changed = changed; /** * Reconciles the real DOM to accomodate changes in the virtual DOM * @param {HTMLElement} $target HTMLElement * @param {object} newNode VTree * @param {object} oldNode VTree * @param {number} index Index of the current element * @return {void} */ function updateElement($parent, newNode, oldNode, index = 0) { if (!oldNode) { $parent.appendChild(createElement(newNode)); } else if (!newNode) { let times = ($parent.childNodes.length || 0) - index; while (times-- > 0) { if ($parent.lastChild) { $parent.removeChild($parent.lastChild); } } } else if (changed(newNode, oldNode)) { $parent.replaceChild(createElement(newNode), $parent.childNodes[index]); } else if (newNode.type) { updateProps($parent.childNodes[index], newNode.props, oldNode.props); addEventListeners($parent.childNodes[index], newNode.props); const newLength = newNode.children.length; const oldLength = oldNode.children.length; for (let i = 0; i < newLength || i < oldLength; i++) { updateElement($parent.childNodes[index], newNode.children[i], oldNode.children[i], i); } } } exports.updateElement = updateElement; /** * Converts an DOM Element into an VTree * @param {HTMLElement} node HTMLElement * @return {object} VTree */ function generateVTree(node) { let props = {}; node.getAttributeNames().forEach((name) => { if (node.type == "radio" && name == "checked") { props[name] = node.getAttribute(name) == "true"; } else { props[name] = node.getAttribute(name); } }); if (node.type == "select" && node.value) { [].slice.call(node.childNodes).forEach((child) => { if (child.value == node.value) { child.selected = true; child.setAttribute('selected', 'selected'); } }); } Events_1.default.forEach((e) => { if (node[e]) props[e] = node[e]; }); return { type: node.nodeName, props, children: [].slice.call(node.childNodes).map((node) => { if (node.nodeName == "#text") { return node.data; } return generateVTree(node); }) }; } exports.generateVTree = generateVTree; //# sourceMappingURL=Reconcile.js.map