spyne
Version:
Reactive Real-DOM Framework for Advanced Javascript applications
310 lines (284 loc) • 7.55 kB
JavaScript
import { baseCoreMixins } from '../utils/mixins/base-core-mixins.js'
import { DomElementTemplate } from './dom-element-template.js'
import { deepMerge } from '../utils/deep-merge.js'
import { is, defaultTo, pick, mapObjIndexed, forEachObjIndexed, pipe } from 'ramda'
class DomElement {
/**
* @module DomElement
* @type util
*
* @desc
* <p>This is the ViewStream rendering engine.</p>
* <p>This is the recommended process for creating HTMLElements that do not require the logic and overhead of a ViewStream instance.</p>
* <button class='modal-btn btn btn-blue-ref' data-type='modal-window' data-num=800 data-value='attributes'>View Attributes</button>
*
* @constructor
* @param {string} tagName the tagname for this dom element.
* @param {object} attributes any domElement attribute (except for class )
* @param {string|object} data string for text tags and json for templates
* @param {template} template
* @property {String} props.tagName - = 'div'; Default for tagName.
* @property {Object} props.attributes - = {}; This can be any valid HTML attribute for the given tagName.
* @property {String|Object} props.data = undefined; This is either a String for an element or JSON data object for a template.
* @property {String|HTML} props.template = undefined; If a template is defined, the DomElement will use it.
*
*/
constructor(props = {}, testMode = false) {
const checkDefault = (dflt, val) => defaultTo(dflt)(val)
props.tagName = checkDefault('div', props.tagName)
props.attributes = props.attributes !== undefined ? props.attributes : this.getDomAttributes(props)
props.attrs = this.updateAttrs(props.attributes)
props.fragment = document.createDocumentFragment()
this.testMode = testMode
this.props = props
this.addMixins()
}
setProp(key, val) {
this.props[key] = val
}
getProp(val) {
return this.props[val]
}
get el() {
return this.props.el
}
setElAttrs(el, params) {
const addAttributes = (val, key) => {
const addToDataset = (val, key) => { el.dataset[key] = val }
if (key === 'dataset') {
forEachObjIndexed(addToDataset, val)
} else {
el.setAttribute(key, val)
}
}
this.getProp('attrs').forEach(addAttributes)
return el
}
updateAttrs(params, m) {
const theMap = m !== undefined ? m : new Map()
const addAttributes = (val, key) => theMap.set(key, val)
mapObjIndexed(addAttributes, params)
return theMap
}
addTemplate(el) {
const template = this.getProp('template')
const addTmpl = (template) => {
let data = this.getProp('data')
data = is(Object, data) ? data : {}
const frag = new DomElementTemplate(template, data, { testMode: this?.testMode }).renderDocFrag()
const fragIsString = is(String, frag)
fragIsString ? el.innerHTML = frag : el.appendChild(frag)
return el
}
const doNothing = (el) => el
return template !== undefined ? addTmpl(template) : doNothing(el)
}
createElement(tagName = 'div') {
return document.createElement(tagName)
}
addContent(el) {
const text = (this.getProp('data'))
const isText = is(String, text)
if (isText === true) {
const txt = document.createTextNode(text)
el.appendChild(txt)
}
return el
}
execute() {
const el = pipe(
this.createElement.bind(this),
this.setElAttrs.bind(this),
this.addTemplate.bind(this),
this.addContent.bind(this)
)(this.getProp('tagName'))
// this.getProp('fragment').appendChild(el);
this.props.el = el
}
/**
* This method will render the HTML Element
* @returns {HTMLElement} HTMLElement
*/
render() {
this.execute()
this.props.template = undefined
this.props.data = undefined
this.props.attributes = undefined
return this.getProp('el')
}
renderToHTMLString() {
this.execute()
this.props.template = undefined
this.props.data = undefined
this.props.attributes = undefined
return this.getProp('el').outerHTML
}
returnIfDefined(obj, val) {
if (val !== undefined) {
const isObj = typeof (val) === 'undefined'
isObj === false ? obj[val] = val : obj[val] = deepMerge(obj[val], val)
}
}
updateprops(val) {
this.returnIfDefined(this.props, val)
return this
}
updatepropsAndRun(val) {
this.updateprops(val)
this.execute()
return this.getProp('fragment')
}
unmount() {
if (this.props !== undefined) {
this.getProp('el').remove()
this.props = undefined
this.gc()
}
}
updateTag(tagName = 'div') { this.updateprops(tagName) }
updateAttributes(attrs = {}) { this.updateprops(attrs) }
updateTemplate(template) { this.updateprops(template) }
updateData(data = {}) { this.updateprops(data) }
addTagAndRender(tagName = 'div') { this.updatepropsAndRun(tagName) }
addAttrsibutesAndRender(attrs = {}) { this.updatepropsAndRun(attrs) }
addTemplateAndRender(template) { this.updatepropsAndRun(template) }
addDataAndRender(data = {}) { this.updatepropsAndRun(data) }
// ==================================
// BASE CORE MIXINS
// ==================================
addMixins() {
const coreMixins = baseCoreMixins()
this.gc = coreMixins.gc.bind(this)
}
getDomAttributes(props) {
return pick(this.attributesArray, props)
}
get attributesArray() {
return [
'accept',
'accept-charset',
'accesskey',
'action',
'align',
'allow',
'alt',
'async',
'autocapitalize',
'autocomplete',
'autofocus',
'autoplay',
'bgcolor',
'border',
'buffered',
'challenge',
'charset',
'checked',
'cite',
'class',
'code',
'codebase',
'color',
'cols',
'colspan',
'content',
'contenteditable',
'contextmenu',
'controls',
'coords',
'crossorigin',
'csp',
'dataset',
'datetime',
'decoding',
'default',
'defer',
'dir',
'dirname',
'disabled',
'download',
'draggable',
'dropzone',
'enctype',
'for',
'form',
'formaction',
'headers',
'height',
'hidden',
'high',
'href',
'hreflang',
'http-equiv',
'icon',
'id',
'importance',
'integrity',
'ismap',
'itemprop',
'keytype',
'kind',
'label',
'lang',
'language',
'lazyload',
'list',
'loop',
'low',
'manifest',
'max',
'maxlength',
'minlength',
'media',
'method',
'min',
'multiple',
'muted',
'name',
'novalidate',
'open',
'optimum',
'pattern',
'ping',
'placeholder',
'poster',
'preload',
'radiogroup',
'readonly',
'referrerpolicy',
'rel',
'required',
'reversed',
'rows',
'rowspan',
'sandbox',
'scope',
'scoped',
'selected',
'shape',
'size',
'sizes',
'slot',
'span',
'spellcheck',
'src',
'srcdoc',
'srclang',
'srcset',
'start',
'step',
'style',
'summary',
'tabindex',
'target',
'title',
'translate',
'type',
'usemap',
'value',
'width',
'wrap'
]
}
}
const DomEl = DomElement
export { DomElement, DomEl }