You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
OneAuth/ui/vhtml/v.js

1129 lines
35 KiB
JavaScript

2 weeks ago
/*
* v.js
* Copyright (C) 2024 veypi <i@veypi.com>
*
* Distributed under terms of the GPL license.
*/
import vproxy from './vproxy.js'
import vget from './vget.js'
import vrouter from './vrouter.js'
import utils from './utils.js'
import setupVdev from './vdev.js'
(async function() {
const globalStyle = document.createElement('style')
globalStyle.innerHTML = `
[vref] {
display: block;
}
[vparsing] {
display: none;
-webkit-text-fill-color: transparent;
}
vslot, vrouter {
display: block;
}
`
if (document.head.firstChild) {
document.head.insertBefore(globalStyle, document.head.firstChild)
} else {
document.head.appendChild(globalStyle)
}
const DelayCache = []
const config = { attributes: false, childList: true, subtree: true, characterData: false }
const runVdelay = (d) => {
if (!d.isConnected) {
return
}
let delay = d.getAttribute('vdelay')
if (delay) {
let fc = DelayCache[delay]
if (fc) {
fc(d)
} else {
console.error('delay not found:', delay, d)
}
}
}
const callback = function(mutationsList, observer) {
mutationsList.forEach(function(mutation) {
for (let node of mutation.addedNodes) {
if (node.nodeType === 1) { // 元素节点
runVdelay(node)
node.querySelectorAll('*[vdelay]').forEach(runVdelay)
}
}
})
}
const observer = new MutationObserver(callback);
observer.observe(document.body, config);
function recordGet(code, data) {
code = `with (sandbox) { ${code} }`
const fn = new Function('sandbox', code);
let res = vproxy.Wrap({})
let keys = []
const proxy = new Proxy(data, {
// 拦截所有属性,防止到 Proxy 对象以外的作用域链查找。
has(target, key) {
return true;
},
get(target, key, receiver) {
if (key === Symbol.unscopables) {
return undefined;
}
let v = Reflect.get(target, key, receiver);
if (keys.indexOf(key) === -1) {
keys.push(key)
}
return v
},
set(target, key, newValue, receiver) {
return false
}
});
fn(proxy)
vproxy.Watch(() => {
keys.forEach(k => {
if (res[k] !== data[k]) {
res[k] = data[k]
}
})
})
return res
}
function findLastAccess(code, data) {
code = `with (sandbox) { ${code} }`
const fn = new Function('sandbox', code);
let res = {
data: null,
key: null,
}
const wrap = (tmp) => {
return new Proxy(tmp, {
// 拦截所有属性,防止到 Proxy 对象以外的作用域链查找。
has(target, key) {
return true;
},
get(target, key, receiver) {
if (key === Symbol.unscopables) {
return undefined;
}
let v = Reflect.get(target, key, receiver);
res.data = target
res.key = key
if (typeof v === 'function') {
console.warn('vhtml not support function with "v:" variables bind')
}
if (typeof v === 'object' && v) {
return wrap(v)
}
return v
},
set(target, key, newValue, receiver) {
// console.log('set', target, key, newValue)
// return Reflect.set(target, key, newValue, receiver);
return false
}
})
}
fn(wrap(data))
return res
}
const varRegex = /{{|}}/g;
const vforRegex = /^(\s*(\w+)\s+in\s+|\((\w+),\s*(\w+)\)\s+in\s+)([\w\?\$\.\[\]\(\)'"]+)$/
class vhtml {
/** @type {HTMLElement} */
app = null
scoped = null
vget = vget
vproxy = vproxy
__noproxy = true
$router = vrouter.$router
constructor(id) {
if (typeof id === 'string') {
this.app = document.getElementById(id)
} else if (id instanceof HTMLElement) {
this.app = id
} else {
this.app = document.body
}
if (!this.app) {
console.error(`Can't find element by id: ${id}`)
return
}
let init = async () => {
// vget.SetBaseFile(await vget.FetchFile(window.location.pathname))
let mainParser = await vget.FetchUI(window.location.pathname, {}, true)
this.scoped = mainParser.env?.scoped || ''
if (mainParser.env?.vdev && window.self !== window.top) {
setupVdev()
}
this.parseRef('root', this.app, {}, mainParser.env || {}, mainParser, true)
}
init()
}
/**
* @param{HTMLElement} dom
* @param {Object} scopedData
* */
async parseDom(dom, scopedData = {}, env) {
if (env instanceof HTMLElement) {
console.log(env)
throw new Error('env error')
}
let nodeName = dom.nodeName.toLowerCase()
if (dom.nodeType === 3) {
this.parseTextNode(dom, scopedData, env)
return
} else if (dom.nodeType === 8) {
// comment node
return
} else if (dom.nodeType !== 1) {
console.log('Other Node Type:', dom.nodeType, dom);
return
}
if (dom.hasAttribute('no-vhtml') || dom.vparsed) {
return
}
let vfortxt = dom.getAttribute('v-for')
if (vfortxt !== null) {
this.parseVfor(vfortxt, dom, scopedData, env)
return
}
if (nodeName.indexOf('-') !== -1) {
let url = '/' + nodeName.split('-').join('/')
let singleMode = dom.hasAttribute('single')
this.parseRef(url, dom, scopedData, env, null, singleMode)
dom.vparsed = true
return
}
if (dom.getAttribute(':vsrc')) {
let code = dom.getAttribute(':vsrc')
dom.removeAttribute(':vsrc')
let attrs = Array.from(dom.attributes).map(a => {
let res = { name: a.name, value: a.value }
return res
})
let oldChilds = Array.from(dom.childNodes)
vproxy.Watch(() => {
delete dom.vparsed
dom.setAttribute('vparsing', '')
let vsrc = vproxy.Run(code, scopedData, env)
if (!vsrc) {
return
}
Array.from(dom.attributes).forEach(a => { dom.removeAttribute(a.name) })
dom.innerHTML = ''
attrs.forEach(a => { dom.setAttribute(a.name, a.value) })
oldChilds.forEach(c => { dom.appendChild(c.cloneNode(true)) })
this.parseRef(vsrc, dom, scopedData, env, null, false)
dom.vparsed = true
})
return
}
if (dom.getAttribute('vsrc')) {
let singleMode = dom.hasAttribute('single')
this.parseRef(dom.getAttribute('vsrc'), dom, scopedData, env, null, singleMode)
dom.vparsed = true
return
}
if (nodeName === 'div' && dom.getAttribute('v-html')) {
let vhtmlCode = dom.getAttribute('v-html')
dom.removeAttribute('v-html')
dom.innerHTML = ''
this.parseAttrs(dom, scopedData, env)
dom.vparsed = true
vproxy.Watch(() => {
let innerHTML = vproxy.Run(vhtmlCode, scopedData, env)
dom.innerHTML = innerHTML
let childs = this.parseVif(Array.from(dom.childNodes), scopedData, env)
for (let n of childs) {
this.parseDom(n, scopedData, env)
}
})
return
}
if (nodeName === 'vslot') {
this.parseSlots(dom, scopedData, env)
dom.vparsed = true
return
}
if (nodeName === 'vrouter') {
this.parseAttrs(dom, scopedData, env)
vrouter.$router.ParseVrouter(this, dom, env)
return
}
this.parseAttrs(dom, scopedData, env)
let childs = this.parseVif(Array.from(dom.childNodes), scopedData, env)
for (let n of childs) {
this.parseDom(n, scopedData, env)
}
dom.vparsed = true
}
onMountedRun(dom, cb, once = true) {
if (once) {
if (dom.isConnected) {
cb(dom)
return
}
let did = DelayCache.push((dom) => {
dom.removeAttribute('vdelay')
cb(dom)
})
dom.setAttribute('vdelay', did - 1)
return
}
if (dom.isConnected) {
cb(dom)
}
let did = DelayCache.push(cb)
dom.setAttribute('vdelay', did - 1)
}
/**
* @param{string} name
* @param{HTMLElement} dom
* */
AllENVs = {}
async parseRef(vsrc, dom, data, env, target, singleMode = false) {
dom.setAttribute('vparsing', '')
let oldEnv = env
let vrefof = dom.getAttribute('vrefof')
let parentRef = dom.closest(`*[vref='${vrefof}']`)
if (parentRef) {
env = parentRef.$env
} else {
// console.log('parentRef not found:', vrefof,vsrc)
}
if (!target && vsrc) {
if (!vsrc.endsWith('.html')) {
vsrc = vsrc + '.html'
}
target = await vget.FetchUI(vsrc, env, dom.hasAttribute('scoped'))
}
env = Object.assign({}, env, target?.env || {})
// env = target.env
dom.$env = env
dom.$vsrc = vsrc
env.$router = vrouter.$router
env.$emit = (evt, ...args) => {
evt = evt.toLowerCase()
if (!dom.$vevent) {
return
}
let fc = dom.$vevent[evt]
if (fc && typeof fc === 'function') {
fc(...args)
}
}
let originData = await this.setupRef(dom, data, oldEnv, target, singleMode)
if (singleMode) {
this.parseAttrs(dom, originData, env, target?.customAttrs)
} else {
this.parseAttrs(dom, data, oldEnv, target?.customAttrs)
}
let childs = this.parseVif(Array.from(dom.childNodes), originData, env)
for (let n of childs) {
this.parseDom(n, originData, env)
}
dom.removeAttribute('vparsing')
this.mountRef(dom, originData, env, target)
}
/**
* @param {HTMLElement} dom
* @parm {Object} data
* @param {{heads:HTMLElement[],body:HTMLElement,scripts:HTMLElement[],setup:HTMLElement}} target
* */
async setupRef(dom, data, env, target, singleMode = false) {
let originData = vproxy.Wrap({})
if (target.setup) {
let s = target.setup.innerHTML
s = await vproxy.ParseImport(s, originData, dom.$env, dom.$vsrc)
await vproxy.AsyncRun(s, originData, dom.$env, {
$node: dom
})
}
dom.$refScope = data
dom.$refData = originData
if (singleMode) {
return originData
}
if (!dom.$refSlots) {
// dom 预定义好内容
let slots = vproxy.Wrap({})
dom.childNodes.forEach((n) => {
let nName = n.getAttribute ? n.getAttribute('vslot') : ''
nName = nName || ''
if (!slots[nName]) {
slots[nName] = []
}
slots[nName].push(n)
})
dom.$refSlots = slots
}
dom.innerHTML = ''
let now = target.body.cloneNode(true)
dom.append(...now.childNodes)
// 处理调用方attr 传参
Object.keys(originData).forEach(k => {
const localK = utils.CamelToKebabCase(k)
if (typeof originData[k] === 'boolean') {
if (dom.hasAttribute(k) || dom.hasAttribute(localK)) {
originData[k] = true
}
} else if (dom.hasAttribute(k)) {
originData[k] = dom.getAttribute(k)
dom.removeAttribute(k)
} else if (dom.hasAttribute(localK)) {
originData[k] = dom.getAttribute(localK)
dom.removeAttribute(localK)
}
if (dom.hasAttribute(':' + k) || dom.hasAttribute(':' + localK)) {
let val = dom.getAttribute(':' + k) || dom.getAttribute(':' + localK)
dom.removeAttribute(':' + k)
dom.removeAttribute(':' + localK)
// 此时数据还没有监听,删除旧对象,使得外部的对象直接绑定在内部$data上
delete originData[k]
if (val) {
vproxy.Watch(() => vproxy.Run(val, data, env), () => {
originData[k] = vproxy.Run(val, data, env)
}, { deep: true })
} else {
vproxy.Watch(() => data[k], () => {
originData[k] = data[k]
}, { deep: true })
}
}
if (dom.hasAttribute('v:' + k) || dom.hasAttribute('v:' + localK)) {
let val = dom.getAttribute('v:' + k) || dom.getAttribute('v:' + localK)
dom.removeAttribute('v:' + k)
dom.removeAttribute('v:' + localK)
if (!val) {
val = k
}
delete originData[k]
vproxy.Watch(() => findLastAccess(val, data), (args) => {
if (args && args.data && args.key) {
originData[k] = args.data[args.key]
} else {
console.warn('not found variables in:' + val)
}
})
vproxy.Watch(() => originData[k], () => {
let args = findLastAccess(val, data)
if (args && args.data && args.key) {
args.data[args.key] = originData[k]
} else {
console.warn('not found variables in:' + val)
}
})
}
})
// handle local component attr
let attrs = Array.from(now.attributes)
attrs = attrs.filter(a => {
if (this.parseAttr(dom, a.name, a.value, originData, env)) {
now.removeAttribute(a.name)
return false
}
return true
})
attrs.forEach((a) => {
if (a.name === 'class') {
dom.classList.add(...a.value.trim().split(/\s+/))
} else if (a.name === 'style') {
let styles = a.value.split(';')
for (let s of styles) {
let ss = s.split(':')
if (ss.length === 2 && !dom.style[ss[0]]) {
let skey = ss[0].trim()
if (skey.startsWith('--')) {
dom.style.setProperty(skey, ss[1].trim())
} else {
dom.style[skey] = ss[1].trim()
}
}
}
} else if (!dom.getAttribute(a.name)) {
dom.setAttribute(a.name, a.value)
}
})
return originData
}
async mountRef(dom, scopedData, env, target) {
for (let s of target.scripts) {
if (s.hasAttribute('active')) {
this.onMountedRun(dom, () => {
vproxy.AsyncRun(s.innerHTML, scopedData, env, { $node: dom, $watch: vproxy.Watch })
}, false)
} else {
vproxy.AsyncRun(s.innerHTML, scopedData, env, { $node: dom, $watch: vproxy.Watch })
}
}
}
parseAttrs(dom, data, env, attrs) {
if (dom.nodeName === 'A') {
this.parseAHref(dom, data, env)
}
Array.from(dom.attributes).forEach(a => {
if (this.parseAttr(dom, a.name, a.value, data, env)) {
dom.removeAttribute(a.name)
}
})
if (attrs) {
// just for body element
Object.keys(attrs).forEach(k => {
this.parseAttr(dom, k, attrs[k], dom.$refData, env)
})
}
if (dom.hasAttribute('v-show')) {
let code = dom.getAttribute('v-show')
let oldDisplay = dom.style.display
vproxy.Watch(() => {
let res = vproxy.Run(code, data, env)
if (res) {
dom.style.display = oldDisplay
} else {
dom.style.display = 'none'
}
})
}
}
parseAHref(dom, data, env) {
if (!dom.hasAttribute("href") && !dom.hasAttribute(":href")) {
return
}
if (dom.hasAttribute(":href")) {
let code = dom.getAttribute(":href")
dom.removeAttribute(":href")
vproxy.Watch(() => {
let href = vproxy.Run(code, data, env)
console.log(code, href, env)
if (!href || href.startsWith('#') || href.startsWith('http')) {
return
} else if (href.startsWith('@')) {
dom.setAttribute('href', href.slice(1))
} else if (href) {
let scoped = env?.scoped
if (scoped) {
href = scoped + href
}
dom.setAttribute('href', href)
}
})
}
const fc = (to) => {
let url = to?.fullPath
if (dom.getAttribute('href') === url) {
dom.setAttribute('active', '')
} else {
dom.removeAttribute('active')
}
}
fc(vrouter.$router.current)
vrouter.$router.onChange(fc)
}
/**
* @param {HTMLElement} dom
* */
parseAttr(dom, name, value, data, env) {
if (name.startsWith(':')) {
let attrName = name.slice(1)
if (attrName === 'class' || attrName === 'style') {
this.handleStyle(dom, attrName, value, data, env)
} else {
vproxy.Watch(() => {
let res
if (value) {
res = vproxy.Run(value, data, env)
} else {
res = data[attrName]
}
utils.SetAttr(dom, attrName, res)
})
}
return true
} else if (name.startsWith('@')) {
this.handleEvent(dom, name, value, data, env)
return true
} else if (name.indexOf('!') > -1) {
console.warn('! prefix is deprecated, use : instead:', name, value, dom)
} else if (name.startsWith('v:')) {
let args = findLastAccess(value, data)
if (args && args.data && args.key) {
let vkey = args.key
let vdata = args.data
return utils.BindInputDomValue(dom, vdata, vkey, vproxy.Watch)
} else {
console.warn('not found variables in:' + value)
}
} else if (name === 'vdom') {
let vbind = findLastAccess(value, data)
if (vbind && vbind.data && vbind.key) {
vbind.data[vbind.key] = dom
} else {
console.warn('not found variables in:' + value)
}
return true
}
return false
}
handleStyle(dom, attrName, value, data, env) {
let oldValue = ''
vproxy.Watch(() => {
let res = vproxy.Run(value, data, env)
if (typeof res === 'function') {
res = res()
}
if (attrName === 'class') {
if (oldValue) {
dom.classList.remove(...oldValue.split(/\s+/))
oldValue = ''
}
if (res instanceof Array) {
oldValue = ''
res.forEach(r => {
if (typeof r === 'string' && r.length) {
oldValue += ' ' + r
} else if (typeof r === 'object') {
for (let k in r) {
if (r[k]) {
oldValue += ' ' + k
}
}
}
})
} else if (typeof res === 'string' && res.length) {
oldValue = res.trim()
} else if (typeof res === 'object') {
oldValue = ''
for (let k in res) {
if (res[k]) {
oldValue += ' ' + k
}
}
} else if (res) {
console.warn('class value error:', res)
}
oldValue = oldValue.trim()
if (oldValue) {
dom.classList.add(...oldValue.split(/\s+/))
}
} else if (attrName === 'style') {
if (oldValue) {
if (typeof oldValue === 'object') {
for (let k in oldValue) {
if (k.startsWith('--')) {
dom.style.removeProperty(k)
} else {
dom.style[k] = ''
}
}
} else if (typeof oldValue === 'string') {
let styles = oldValue.split(';')
for (let s of styles) {
let ss = s.split(':')
if (ss.length === 2) {
if (ss[0].trim().startsWith('--')) {
dom.style.removeProperty(ss[0].trim())
} else {
dom.style[ss[0].trim()] = ''
}
}
}
}
}
if (typeof res === 'object') {
for (let k in res) {
if (k.startsWith('--')) {
dom.style.setProperty(k, res[k])
} else {
dom.style[k] = res[k]
}
}
} else if (typeof res === 'string') {
let styles = res.split(';')
for (let s of styles) {
let ss = s.split(':')
if (ss.length === 2) {
if (ss[0].trim().startsWith('--')) {
dom.style.setProperty(ss[0].trim(), ss[1].trim())
} else {
dom.style[ss[0].trim()] = ss[1].trim()
}
}
}
}
oldValue = res
}
})
}
handleEvent(dom, name, value, data, env) {
let actionName = name.slice(1).split('.')
let evtMap = { 'self': false, 'prevent': false, 'stop': false }
let evt = actionName[0]
if (evt === 'mounted') {
this.onMountedRun(dom, (d) => {
let cb = vproxy.Run(value, data, env)
if (typeof cb === 'function') {
(function() {
})();
cb(d)
}
}, false)
} else if (evt === 'outerclick') {
let func = (e) => {
let cb = vproxy.Run(value, data, env, { $event: e })
if (typeof cb === 'function') {
cb(e)
}
}
utils.AddClicker(dom, 'outer', func)
} else if (utils.EventsList.indexOf(evt) !== -1) {
if (evt === 'keydown' || evt === 'keyup' || evt === 'keypress') {
if (dom.tagName !== 'INPUT' && dom.tagName !== 'TEXTAREA') {
dom.setAttribute('tabindex', '0')
}
}
let func = (e) => {
let cb = vproxy.Run(value, data, env, { $event: e })
if (typeof cb === 'function') {
cb(e)
}
}
actionName.slice(1).forEach(k => {
if (k.startsWith('delay')) {
let delay = k.slice(5)
if (!delay) {
delay = 1000
} else if (delay.endsWith('ms')) {
delay = Number(delay.slice(0, -2))
} else if (delay.endsWith('s')) {
delay = Number(delay.slice(0, -1)) * 1000
} else {
delay = Number(delay)
}
if (isNaN(delay)) {
delay = 1000
}
func = (e) => {
let fc = dom['_' + evt]
if (fc && typeof fc === 'number') {
clearTimeout(fc)
}
dom['_' + evt] = setTimeout(() => {
let cb = vproxy.Run(value, data, env, { $event: e })
if (typeof cb === 'function') {
cb(e)
}
}, delay)
}
}
evtMap[k] = true
})
dom.addEventListener(evt, (e) => {
if (actionName.length > 1 && (evt === 'keydown' || evt == 'keyup' || evt == 'keypress')) {
let btn = actionName[1]
if (btn !== e.key?.toLowerCase()) {
return
}
}
if (evtMap['self'] && e.currentTarget !== e.target) {
return
}
if (evtMap['prevent']) {
e.preventDefault()
}
if (evtMap['stop']) {
e.stopPropagation()
}
func(e)
})
} else {
dom.$vevent = dom.$vevent || {}
dom.$vevent[evt] = (...arg) => {
let cb = vproxy.Run(value, data, env, {})
if (typeof cb === 'function') {
cb(...arg)
}
}
}
}
parseTextNode(dom, data, env) {
// text node
let txt = dom.nodeValue.trim()
if (!txt) {
return
}
let match
let nstart = 0
let start = -1;
let txtItems = []
while ((match = varRegex.exec(txt)) !== null) {
if (match[0] === '{{') {
start = match.index
} else if (match[0] === '}}' && start >= 0) {
if (nstart !== start) {
txtItems.push(txt.slice(nstart, start))
}
txtItems.push('')
let valStr = txt.slice(start + 2, match.index)
let valIdx = txtItems.length
start = -1
nstart = match.index + 2
vproxy.Watch(() => {
txtItems[valIdx - 1] = vproxy.Run(valStr, data, env)
if (typeof txtItems[valIdx - 1] === 'object') {
txtItems[valIdx - 1] = JSON.stringify(txtItems[valIdx - 1])
}
dom.nodeValue = txtItems.join('')
})
}
}
txtItems.push(txt.slice(nstart))
dom.nodeValue = txtItems.join('')
}
vforDomCache = {}
parseVfor(vfortxt, dom, data, env) {
dom.removeAttribute('v-for')
let matches = vforRegex.exec(vfortxt)
if (matches?.length === 6) {
let vforTag = document.createElement('div')
vforTag.style.display = 'none'
let vforTagID = vproxy.GenUniqueID()
this.vforDomCache[vforTagID] = {}
dom.parentNode.replaceChild(vforTag, dom)
vproxy.Watch(() => {
let value = matches[3] || matches[2]
let key = matches[4]
let iters = vproxy.Run(matches[5], data, env)
let cache = this.vforDomCache[vforTagID]
let rendereds = new Set()
if (typeof iters === 'function') {
iters = iters()
} else if (typeof iters === 'number') {
iters = Array.from({ length: iters }, (_, i) => i)
}
if (iters === undefined || iters === null) {
iters = []
}
// 访问长度,触发监听
let _ = iters.length
if (typeof iters === 'object') {
let keys = Object.keys(iters)
let items = []
for (let kid in keys) {
let k = keys[kid]
let vfk = ''
if (iters[k] && iters[k][vproxy.DataID]) {
vfk = iters[k][vproxy.DataID]
} else {
vfk = k + '.' + iters[k]
}
vfk = vforTagID + "." + vfk
rendereds.add(vfk)
items.push({ k, vfk, val: iters[k] })
}
// Cleanup removed nodes
for (let k of Object.keys(cache)) {
if (!rendereds.has(k)) {
if (cache[k] instanceof Array) {
cache[k].forEach(d => d.remove())
} else {
cache[k].remove()
}
delete cache[k]
}
}
// Update and Reorder (Backwards)
let refNode = vforTag
for (let i = items.length - 1; i >= 0; i--) {
let { k, vfk, val } = items[i]
let curDom = cache[vfk]
if (curDom) {
if (key) {
curDom.$vforData[key] = k === '0' ? 0 : (Number(k) || k)
}
if (curDom.isConnected) {
if (curDom.nextSibling !== refNode) {
vforTag.parentNode.insertBefore(curDom, refNode)
}
refNode = curDom
}
continue
}
let newDom = dom.cloneNode(true)
cache[vfk] = newDom
let tmpData = { [value]: val }
if (key) {
tmpData[key] = k === '0' ? 0 : (Number(k) || k)
}
tmpData = vproxy.Wrap(tmpData, data)
newDom.$vforData = tmpData
vforTag.parentNode.insertBefore(newDom, refNode)
let vif = dom.getAttribute('v-if')
if (!vif) {
this.parseDom(newDom, tmpData, env)
refNode = newDom
continue
}
newDom.removeAttribute('v-if')
let watchid = -1
watchid = vproxy.Watch(() => {
let dom = cache[vfk]
if (!dom) {
vproxy.Cancel(watchid)
return
}
let res = vproxy.Run(vif, tmpData, env)
if (res) {
if (!dom.vparsed) {
this.parseDom(dom, tmpData, env)
}
if (!dom.isConnected) {
let founded = false
let before = vforTag
for (let tmpvfk in cache) {
if (tmpvfk === vfk) {
founded = true
continue
}
if (founded && cache[tmpvfk].isConnected) {
before = cache[tmpvfk]
break
}
}
vforTag.parentNode.insertBefore(dom, before)
}
} else {
if (dom.isConnected) {
dom.remove()
} else {
this.onMountedRun(dom, (d) => {
dom.remove()
})
}
}
})
if (newDom.isConnected) {
refNode = newDom
}
}
} else {
console.error('vfor iter object error:', [matches, iters, vfortxt, data])
}
})
} else {
console.error('vfor error:', vfortxt)
}
}
parseVif(nodes, data, env) {
let ifCache = { now: document.createElement('div'), conds: [], doms: [] }
const handleIf = (cache) => {
let ifData = { now: cache.now, conds: cache.conds, doms: cache.doms }
let ifList = []
for (let cid in ifData.conds) {
let c = ifData.conds[cid]
if (c === '') {
c = 'true'
} else {
c = 'Boolean(' + c + ')'
}
ifList.push(c)
}
let ifFc = `let res = [${ifList.join(',')}]\n return res.indexOf(true)`
vproxy.Watch(() => {
let res = vproxy.Run(ifFc, data, env)
let tmpDom = ifData.doms[res]
if (!tmpDom) {
tmpDom = document.createElement('div')
tmpDom.style.display = 'none'
}
return tmpDom
}, (tmpDom) => {
if (!tmpDom) {
return
}
this.onMountedRun(ifData.now, (d) => {
d.replaceWith(tmpDom)
ifData.now = tmpDom
})
if (!tmpDom?.vparsed) {
this.parseDom(tmpDom, data, env)
}
})
}
let childs = nodes.filter(d => {
if (!d.getAttribute || d.getAttribute('v-for')) {
return true
}
if (d.getAttribute('v-if') !== null) {
if (ifCache.conds.length > 0) {
handleIf(ifCache)
ifCache = { now: document.createElement('div'), conds: [], doms: [] }
}
d.replaceWith(ifCache.now)
// dom.replaceChild(ifCache.now, d)
ifCache.conds.push(d.getAttribute('v-if'))
d.removeAttribute('v-if')
ifCache.doms.push(d)
return false
} else if (d.getAttribute('v-else-if') !== null) {
ifCache.conds.push(d.getAttribute('v-else-if'))
d.removeAttribute('v-else-if')
ifCache.doms.push(d)
d.remove()
return false
} else if (d.getAttribute('v-else') !== null) {
ifCache.conds.push('')
d.removeAttribute('v-else')
ifCache.doms.push(d)
d.remove()
return false
}
return true
})
if (ifCache.conds.length > 0) {
handleIf(ifCache)
}
return childs
}
/**
* @param {HTMLElement} dom
* */
parseSlots(dom, data, env) {
// 先插入dom后解析避免slot搭配v-for,v-if使用出现bug
let slotof = dom.getAttribute('vrefof')
let refDom = dom.closest(`*[vref='${slotof}']`)
if (!refDom) {
// TODO
this.onMountedRun(dom, (d) => {
this.parseSlots(d, data, env)
})
return
}
while (true) {
let tmp = refDom?.parentNode.closest('*[vref]')
if (!tmp) {
break
}
if (tmp.getAttribute('vref') === slotof) {
refDom = tmp
} else {
break
}
}
let sName = dom.getAttribute('name') || ''
if (dom.getAttribute(':name')) {
let nameVal = dom.getAttribute(':name')
dom.removeAttribute(':name')
sName = vproxy.Run(nameVal, data, env)
}
//
if (!dom.originContent) {
dom.$originContent = Array.from(dom.childNodes)
dom.innerHTML = ''
}
// slot模板
dom.$slotCache = {}
vproxy.Watch(() => {
let slots = refDom.$refSlots || {}
// slot数据域
let slotsData = refDom.$refScope || {}
let sNodes = slots[sName]
if (sNodes && sNodes.length > 0) {
let hashID = sNodes[0].hashID
if (!hashID) {
hashID = vproxy.GenUniqueID()
sNodes[0].hashID = hashID
} else if (dom.$slotCache[hashID]) {
dom.innerHTML = ''
dom.append(...dom.$slotCache[hashID])
return
}
dom.innerHTML = ''
sNodes = sNodes.map(n => n.cloneNode(true))
dom.append(...sNodes)
let tmpSlotsData = slotsData
if (dom.getAttribute('vbind') !== null) {
let vbindAttrs = dom.getAttribute('vbind').split(',').map(a => a.trim())
tmpSlotsData = vproxy.Wrap({})
vbindAttrs.forEach(a => {
if (data.hasOwnProperty(a)) {
tmpSlotsData[a] = data[a]
}
})
// tmpSlotsData = recordGet(dom.getAttribute('vbind'), data)
vproxy.SetDataRoot(tmpSlotsData, slotsData)
}
let sNodeVrefof = ''
sNodes.find(n => {
if (n.getAttribute && n.getAttribute('vrefof')) {
sNodeVrefof = n.getAttribute('vrefof')
return true
}
return false
})
let sNodesEnv = env
if (sNodeVrefof) {
let sNodeVref = dom.closest(`*[vref='${sNodeVrefof}']`)
sNodesEnv = sNodeVref?.$env || env
}
sNodes = this.parseVif(sNodes, tmpSlotsData, sNodesEnv)
sNodes.forEach((n) => this.parseDom(n, tmpSlotsData, sNodesEnv))
dom.$slotCache[hashID] = sNodes
} else {
dom.innerHTML = ''
dom.append(...dom.$originContent)
let parsed = false
dom.$originContent.forEach((n) => {
if (n.hasAttribute && n.vparsed) {
parsed = true
}
})
if (!parsed) {
dom.$originContent = this.parseVif(dom.$originContent, data, env)
dom.$originContent.forEach((n) => this.parseDom(n, data, env))
}
}
})
this.parseAttrs(dom, data, env)
return dom
}
}
if (window.$vhtml) {
console.error('vhtml already exists.')
} else {
window.$vhtml = new vhtml(document.body)
console.log('vhtml loaded.')
}
})();